keycloak_ruby 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe23b854fa6a62cf06242aab5d164dbcc9174e14c925dc296f9ebd1537bd1403
4
- data.tar.gz: 4ce5b1e227d240df6a9322346fccca4404f9f3519accd04989da8fde6c2ad6e2
3
+ metadata.gz: 9b5e24bb870789d3bfef9291dbb90ad2ad87a33e8786320fd2dbe0c759da7f8c
4
+ data.tar.gz: ceebf27600e81da2c16ffb1b82b6cba7e1a0d0176489c138fbf80ecab0aa3069
5
5
  SHA512:
6
- metadata.gz: dfb814621225d471d3121cd7a0f1e4543dea709f1e8251d58550bf6789e0f2a71d7d1882b2b1d90cfc2ec62730615f1a3c40e8cfb6839d54d9ae81385eea0108
7
- data.tar.gz: ecf788e5417fc9f1f22f175fbc454a936552bf64776930cbc32076863db3b08031180b62f75168f5ebc7274fe6810a0bff5c30d51beda0764e51cafba5fc2f93
6
+ metadata.gz: '054193e51f510f205ed667fd34a292ff89761fab3af75b77304d7aafa27680b9f08d0d0ba6d068471d417459812de443587f0689cd0efec9b98dfdecb5ddf109'
7
+ data.tar.gz: d9d7060295a053e533fe105fbb157bc1e0ff267bc2b78d9a17654149816dc76a3cb9fb37314233276c8f7b8f48abedc89ed3a0de36ec745d6a34e95f04c77834
data/.reek.yml ADDED
@@ -0,0 +1,9 @@
1
+ detectors:
2
+ UncommunicativeVariableName:
3
+ accept:
4
+ - e
5
+ TooManyStatements:
6
+ enabled: true
7
+ exclude:
8
+ - initialize
9
+ max_statements: 8
data/.rspec_status CHANGED
@@ -1,68 +1,72 @@
1
1
  example_id | status | run_time |
2
2
  -------------------------------------------------------- | ------ | --------------- |
3
- ./spec/keycloak_ruby/client_spec.rb[1:1:1:1] | passed | 0.01112 seconds |
4
- ./spec/keycloak_ruby/client_spec.rb[1:1:2:1] | passed | 0.00164 seconds |
5
- ./spec/keycloak_ruby/client_spec.rb[1:2:1:1] | passed | 0.00447 seconds |
6
- ./spec/keycloak_ruby/client_spec.rb[1:2:2:1] | passed | 0.00116 seconds |
7
- ./spec/keycloak_ruby/client_spec.rb[1:3:1] | passed | 0.00283 seconds |
8
- ./spec/keycloak_ruby/client_spec.rb[1:3:2] | passed | 0.00209 seconds |
9
- ./spec/keycloak_ruby/client_spec.rb[1:4:1:1] | passed | 0.00114 seconds |
10
- ./spec/keycloak_ruby/client_spec.rb[1:4:2:1] | passed | 0.00102 seconds |
11
- ./spec/keycloak_ruby/client_spec.rb[1:5:1] | passed | 0.00116 seconds |
12
- ./spec/keycloak_ruby/client_spec.rb[1:6:1:1] | passed | 0.00173 seconds |
13
- ./spec/keycloak_ruby/client_spec.rb[1:6:2:1] | passed | 0.00092 seconds |
14
- ./spec/keycloak_ruby/config_spec.rb[1:1:1:1] | passed | 0.00052 seconds |
15
- ./spec/keycloak_ruby/config_spec.rb[1:1:1:2] | passed | 0.00045 seconds |
16
- ./spec/keycloak_ruby/config_spec.rb[1:1:1:3] | passed | 0.00048 seconds |
17
- ./spec/keycloak_ruby/config_spec.rb[1:1:1:4] | passed | 0.00038 seconds |
18
- ./spec/keycloak_ruby/config_spec.rb[1:1:1:5] | passed | 0.00039 seconds |
19
- ./spec/keycloak_ruby/config_spec.rb[1:1:2:1] | passed | 0.00006 seconds |
20
- ./spec/keycloak_ruby/config_spec.rb[1:2:1] | passed | 0.00044 seconds |
21
- ./spec/keycloak_ruby/config_spec.rb[1:2:2] | passed | 0.0004 seconds |
22
- ./spec/keycloak_ruby/config_spec.rb[1:2:3] | passed | 0.00026 seconds |
23
- ./spec/keycloak_ruby/config_spec.rb[1:2:4] | passed | 0.00025 seconds |
24
- ./spec/keycloak_ruby/config_spec.rb[1:3:1] | passed | 0.00024 seconds |
25
- ./spec/keycloak_ruby/config_spec.rb[1:3:2] | passed | 0.00024 seconds |
26
- ./spec/keycloak_ruby/config_spec.rb[1:3:3] | passed | 0.00027 seconds |
27
- ./spec/keycloak_ruby/config_spec.rb[1:3:4] | passed | 0.00024 seconds |
28
- ./spec/keycloak_ruby/config_spec.rb[1:4:1:1] | passed | 0.00379 seconds |
29
- ./spec/keycloak_ruby/config_spec.rb[1:4:2:1] | passed | 0.00042 seconds |
30
- ./spec/keycloak_ruby/config_spec.rb[1:4:2:2] | passed | 0.00029 seconds |
31
- ./spec/keycloak_ruby/request_performer_spec.rb[1:1:1:1] | passed | 0.00099 seconds |
32
- ./spec/keycloak_ruby/request_performer_spec.rb[1:1:1:2] | passed | 0.0008 seconds |
33
- ./spec/keycloak_ruby/request_performer_spec.rb[1:1:2:1] | passed | 0.00067 seconds |
34
- ./spec/keycloak_ruby/request_performer_spec.rb[1:1:3:1] | passed | 0.00058 seconds |
35
- ./spec/keycloak_ruby/request_performer_spec.rb[1:1:4:1] | passed | 0.0006 seconds |
36
- ./spec/keycloak_ruby/response_validator_spec.rb[1:1:1:1] | passed | 0.00065 seconds |
37
- ./spec/keycloak_ruby/response_validator_spec.rb[1:1:2:1] | passed | 0.00017 seconds |
38
- ./spec/keycloak_ruby/response_validator_spec.rb[1:1:3:1] | passed | 0.00021 seconds |
39
- ./spec/keycloak_ruby/response_validator_spec.rb[1:1:4:1] | passed | 0.00009 seconds |
40
- ./spec/keycloak_ruby/response_validator_spec.rb[1:1:5:1] | passed | 0.00008 seconds |
41
- ./spec/keycloak_ruby/response_validator_spec.rb[1:1:6:1] | passed | 0.00011 seconds |
42
- ./spec/keycloak_ruby/response_validator_spec.rb[1:2:1:1] | passed | 0.0001 seconds |
43
- ./spec/keycloak_ruby/response_validator_spec.rb[1:2:2:1] | passed | 0.00012 seconds |
44
- ./spec/keycloak_ruby/response_validator_spec.rb[1:2:3:1] | passed | 0.0001 seconds |
45
- ./spec/keycloak_ruby/response_validator_spec.rb[1:2:4:1] | passed | 0.00009 seconds |
46
- ./spec/keycloak_ruby/response_validator_spec.rb[1:2:5:1] | passed | 0.00011 seconds |
47
- ./spec/keycloak_ruby/response_validator_spec.rb[1:2:6:1] | passed | 0.00016 seconds |
48
- ./spec/keycloak_ruby/response_validator_spec.rb[1:2:7:1] | passed | 0.00011 seconds |
49
- ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:1:1] | passed | 0.00085 seconds |
50
- ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:1:2] | passed | 0.00077 seconds |
51
- ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:2:1] | passed | 0.00093 seconds |
52
- ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:3:1] | passed | 0.00056 seconds |
53
- ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:4:1] | passed | 0.00072 seconds |
54
- ./spec/keycloak_ruby/token_service_spec.rb[1:1:1:1] | passed | 0.00696 seconds |
55
- ./spec/keycloak_ruby/token_service_spec.rb[1:1:2:1] | passed | 0.00129 seconds |
56
- ./spec/keycloak_ruby/token_service_spec.rb[1:1:2:2] | passed | 0.00826 seconds |
57
- ./spec/keycloak_ruby/token_service_spec.rb[1:2:1] | passed | 0.00072 seconds |
58
- ./spec/keycloak_ruby/token_service_spec.rb[1:2:2] | passed | 0.00044 seconds |
59
- ./spec/keycloak_ruby/token_service_spec.rb[1:2:3] | passed | 0.0004 seconds |
60
- ./spec/keycloak_ruby/token_service_spec.rb[1:3:1] | passed | 0.00056 seconds |
61
- ./spec/keycloak_ruby/token_service_spec.rb[1:3:2] | passed | 0.00083 seconds |
62
- ./spec/keycloak_ruby/token_service_spec.rb[1:3:3] | passed | 0.00038 seconds |
63
- ./spec/keycloak_ruby/token_service_spec.rb[1:4:1:1] | passed | 0.00057 seconds |
64
- ./spec/keycloak_ruby/token_service_spec.rb[1:4:2:1] | passed | 0.00072 seconds |
65
- ./spec/keycloak_ruby/token_service_spec.rb[1:4:3:1] | passed | 0.00056 seconds |
66
- ./spec/keycloak_ruby/token_service_spec.rb[1:4:3:2] | passed | 0.00055 seconds |
67
- ./spec/keycloak_ruby/token_service_spec.rb[1:5:1] | passed | 0.0007 seconds |
68
- ./spec/keycloak_ruby_spec.rb[1:1] | passed | 0.00005 seconds |
3
+ ./spec/keycloak_ruby/client_spec.rb[1:1:1:1] | passed | 0.0256 seconds |
4
+ ./spec/keycloak_ruby/client_spec.rb[1:1:2:1] | passed | 0.00494 seconds |
5
+ ./spec/keycloak_ruby/client_spec.rb[1:2:1:1] | passed | 0.01574 seconds |
6
+ ./spec/keycloak_ruby/client_spec.rb[1:2:2:1] | passed | 0.00294 seconds |
7
+ ./spec/keycloak_ruby/client_spec.rb[1:3:1] | passed | 0.00867 seconds |
8
+ ./spec/keycloak_ruby/client_spec.rb[1:3:2] | passed | 0.00564 seconds |
9
+ ./spec/keycloak_ruby/client_spec.rb[1:4:1:1] | passed | 0.00271 seconds |
10
+ ./spec/keycloak_ruby/client_spec.rb[1:4:2:1] | passed | 0.00267 seconds |
11
+ ./spec/keycloak_ruby/client_spec.rb[1:5:1] | passed | 0.00413 seconds |
12
+ ./spec/keycloak_ruby/client_spec.rb[1:6:1:1] | passed | 0.00466 seconds |
13
+ ./spec/keycloak_ruby/client_spec.rb[1:6:2:1] | passed | 0.00228 seconds |
14
+ ./spec/keycloak_ruby/config_spec.rb[1:1:1:1] | passed | 0.00122 seconds |
15
+ ./spec/keycloak_ruby/config_spec.rb[1:1:1:2] | passed | 0.00104 seconds |
16
+ ./spec/keycloak_ruby/config_spec.rb[1:1:1:3] | passed | 0.0009 seconds |
17
+ ./spec/keycloak_ruby/config_spec.rb[1:1:1:4] | passed | 0.00113 seconds |
18
+ ./spec/keycloak_ruby/config_spec.rb[1:1:1:5] | passed | 0.00111 seconds |
19
+ ./spec/keycloak_ruby/config_spec.rb[1:1:2:1] | passed | 0.00025 seconds |
20
+ ./spec/keycloak_ruby/config_spec.rb[1:2:1] | passed | 0.00117 seconds |
21
+ ./spec/keycloak_ruby/config_spec.rb[1:2:2] | passed | 0.00122 seconds |
22
+ ./spec/keycloak_ruby/config_spec.rb[1:2:3] | passed | 0.00097 seconds |
23
+ ./spec/keycloak_ruby/config_spec.rb[1:2:4] | passed | 0.00092 seconds |
24
+ ./spec/keycloak_ruby/config_spec.rb[1:3:1] | passed | 0.00076 seconds |
25
+ ./spec/keycloak_ruby/config_spec.rb[1:3:2] | passed | 0.00078 seconds |
26
+ ./spec/keycloak_ruby/config_spec.rb[1:3:3] | passed | 0.00059 seconds |
27
+ ./spec/keycloak_ruby/config_spec.rb[1:3:4] | passed | 0.00073 seconds |
28
+ ./spec/keycloak_ruby/config_spec.rb[1:4:1:1] | passed | 0.01004 seconds |
29
+ ./spec/keycloak_ruby/config_spec.rb[1:4:2:1] | passed | 0.0012 seconds |
30
+ ./spec/keycloak_ruby/config_spec.rb[1:4:2:2] | passed | 0.00094 seconds |
31
+ ./spec/keycloak_ruby/request_performer_spec.rb[1:1:1:1] | passed | 0.00226 seconds |
32
+ ./spec/keycloak_ruby/request_performer_spec.rb[1:1:1:2] | passed | 0.00168 seconds |
33
+ ./spec/keycloak_ruby/request_performer_spec.rb[1:1:2:1] | passed | 0.00181 seconds |
34
+ ./spec/keycloak_ruby/request_performer_spec.rb[1:1:3:1] | passed | 0.00152 seconds |
35
+ ./spec/keycloak_ruby/request_performer_spec.rb[1:1:4:1] | passed | 0.00155 seconds |
36
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:1:1:1] | passed | 0.00203 seconds |
37
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:1:2:1] | passed | 0.00056 seconds |
38
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:1:3:1] | passed | 0.00053 seconds |
39
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:1:4:1] | passed | 0.00034 seconds |
40
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:1:5:1] | passed | 0.00028 seconds |
41
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:1:6:1] | passed | 0.00033 seconds |
42
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:2:1:1] | passed | 0.00032 seconds |
43
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:2:2:1] | passed | 0.00042 seconds |
44
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:2:3:1] | passed | 0.00034 seconds |
45
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:2:4:1] | passed | 0.00044 seconds |
46
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:2:5:1] | passed | 0.00042 seconds |
47
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:2:6:1] | passed | 0.00035 seconds |
48
+ ./spec/keycloak_ruby/response_validator_spec.rb[1:2:7:1] | passed | 0.00032 seconds |
49
+ ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:1:1] | passed | 0.00247 seconds |
50
+ ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:1:2] | passed | 0.00199 seconds |
51
+ ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:2:1] | passed | 0.00237 seconds |
52
+ ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:3:1] | passed | 0.0017 seconds |
53
+ ./spec/keycloak_ruby/token_refresher_spec.rb[1:1:4:1] | passed | 0.0022 seconds |
54
+ ./spec/keycloak_ruby/token_service_spec.rb[1:1:1:1] | passed | 0.01982 seconds |
55
+ ./spec/keycloak_ruby/token_service_spec.rb[1:1:2:1] | passed | 0.00324 seconds |
56
+ ./spec/keycloak_ruby/token_service_spec.rb[1:1:2:2] | passed | 0.0209 seconds |
57
+ ./spec/keycloak_ruby/token_service_spec.rb[1:2:1] | passed | 0.00223 seconds |
58
+ ./spec/keycloak_ruby/token_service_spec.rb[1:2:2] | passed | 0.00208 seconds |
59
+ ./spec/keycloak_ruby/token_service_spec.rb[1:2:3] | passed | 0.00168 seconds |
60
+ ./spec/keycloak_ruby/token_service_spec.rb[1:3:1] | passed | 0.00144 seconds |
61
+ ./spec/keycloak_ruby/token_service_spec.rb[1:3:2] | passed | 0.00124 seconds |
62
+ ./spec/keycloak_ruby/token_service_spec.rb[1:3:3] | passed | 0.0013 seconds |
63
+ ./spec/keycloak_ruby/token_service_spec.rb[1:4:1:1] | passed | 0.00157 seconds |
64
+ ./spec/keycloak_ruby/token_service_spec.rb[1:4:2:1] | passed | 0.00238 seconds |
65
+ ./spec/keycloak_ruby/token_service_spec.rb[1:4:3:1] | passed | 0.00333 seconds |
66
+ ./spec/keycloak_ruby/token_service_spec.rb[1:4:3:2] | passed | 0.0019 seconds |
67
+ ./spec/keycloak_ruby/token_service_spec.rb[1:5:1] | passed | 0.00224 seconds |
68
+ ./spec/keycloak_ruby_spec.rb[1:1] | passed | 0.00021 seconds |
69
+ ./spec/keycloak_ruby_spec.rb[1:2] | passed | 0.01876 seconds |
70
+ ./spec/keycloak_ruby_spec.rb[1:3:1] | passed | 0.00099 seconds |
71
+ ./spec/keycloak_ruby_spec.rb[1:3:2] | passed | 0.00095 seconds |
72
+ ./spec/keycloak_ruby_spec.rb[1:3:3] | passed | 0.00024 seconds |
data/Rakefile CHANGED
@@ -8,7 +8,9 @@ RSpec::Core::RakeTask.new(:spec) do |task|
8
8
  end
9
9
 
10
10
  require "rubocop/rake_task"
11
-
12
11
  RuboCop::RakeTask.new
13
12
 
14
- task default: %i[spec rubocop]
13
+ require "reek/rake/task"
14
+ Reek::Rake::Task.new
15
+
16
+ task default: %i[spec rubocop reek]
@@ -9,6 +9,8 @@ module KeycloakRuby
9
9
 
10
10
  included do
11
11
  before_action :authenticate_user!
12
+
13
+ helper_method :current_user, :user_signed_in?
12
14
  end
13
15
 
14
16
  def keycloak_jwt_service
@@ -22,5 +24,9 @@ module KeycloakRuby
22
24
  def current_user
23
25
  @current_user ||= keycloak_jwt_service.find_user
24
26
  end
27
+
28
+ def user_signed_in?
29
+ current_user.present?
30
+ end
25
31
  end
26
32
  end
@@ -44,7 +44,7 @@ module KeycloakRuby
44
44
  # @raise [KeycloakRuby::Errors::UserCreationError] If user creation fails.
45
45
  # @return [Hash] The newly created user's data.
46
46
  def create_user(user_attrs = {})
47
- user_data = build_user_data(user_attrs)
47
+ user_data = self.class.build_user_data(user_attrs)
48
48
 
49
49
  response = http_request(
50
50
  http_method: :post,
@@ -112,19 +112,7 @@ module KeycloakRuby
112
112
  update_redirect_uris_for(client_record["id"], redirect_uris)
113
113
  end
114
114
 
115
- private
116
-
117
- def build_auth_body(username, password)
118
- {
119
- client_id: @config.oauth_client_id,
120
- client_secret: @config.oauth_client_secret,
121
- username: username,
122
- password: password,
123
- grant_type: "password"
124
- }
125
- end
126
-
127
- def build_user_data(attrs)
115
+ def self.build_user_data(attrs)
128
116
  { username: attrs.fetch(:username), email: attrs.fetch(:email), enabled: true,
129
117
  credentials: [
130
118
  {
@@ -135,13 +123,7 @@ module KeycloakRuby
135
123
  ] }
136
124
  end
137
125
 
138
- # Builds RequestParams and passes them to the RequestPerformer.
139
- def http_request(options = {})
140
- params = build_request_params(options)
141
- @request_performer.call(params)
142
- end
143
-
144
- def build_request_params(opts)
126
+ def self.build_request_params(opts)
145
127
  RequestParams.new(
146
128
  http_method: opts.fetch(:http_method),
147
129
  url: opts.fetch(:url),
@@ -153,6 +135,24 @@ module KeycloakRuby
153
135
  )
154
136
  end
155
137
 
138
+ private
139
+
140
+ def build_auth_body(username, password)
141
+ {
142
+ client_id: @config.oauth_client_id,
143
+ client_secret: @config.oauth_client_secret,
144
+ username: username,
145
+ password: password,
146
+ grant_type: "password"
147
+ }
148
+ end
149
+
150
+ # Builds RequestParams and passes them to the RequestPerformer.
151
+ def http_request(options = {})
152
+ params = self.class.build_request_params(options)
153
+ @request_performer.call(params)
154
+ end
155
+
156
156
  # Retrieves client details by its "clientId".
157
157
  #
158
158
  # @param client_id [String] The "clientId" in Keycloak.
@@ -39,6 +39,10 @@ module KeycloakRuby
39
39
  oauth_client_id
40
40
  oauth_client_secret
41
41
  ].freeze
42
+
43
+ # Default environment
44
+ DEFAULT_ENV = "development"
45
+
42
46
  # :reek:Attribute
43
47
  attr_accessor :keycloak_url,
44
48
  :app_host,
@@ -91,7 +95,6 @@ module KeycloakRuby
91
95
  private
92
96
 
93
97
  # Loads configuration from YAML file if it exists
94
- # :reek:ManualDispatch
95
98
  def load_config
96
99
  return unless File.exist?(@config_path)
97
100
 
@@ -102,16 +105,31 @@ module KeycloakRuby
102
105
 
103
106
  def load_yaml_file
104
107
  YAML.safe_load(ERB.new(File.read(@config_path)).result, aliases: true)
108
+ rescue Errno::ENOENT, Psych::SyntaxError => e
109
+ raise Errors::ConfigurationError, "Failed to load YAML from #{@config_path}: #{e.message}"
105
110
  end
106
111
 
112
+ # Determines current environment
113
+ #
114
+ # @return [String] Current environment name
115
+ # @api private
107
116
  def current_env
108
- defined?(Rails) ? Rails.env : ENV["APP_ENV"] || "development"
117
+ @current_env ||= (defined?(Rails) && Rails.env) || ENV["APP_ENV"] || DEFAULT_ENV
109
118
  end
110
119
 
120
+ # Applies configuration from hash
121
+ #
122
+ # @param config_hash [Hash] Configuration key-value pairs
123
+ # @api private
111
124
  def apply_config(config_hash)
112
125
  config_hash.each do |key, value|
113
126
  setter = :"#{key}="
114
- public_send(setter, value) if respond_to?(setter)
127
+ begin
128
+ public_send(setter, value)
129
+ rescue NoMethodError
130
+ # Silently ignore unknown configuration keys
131
+ next
132
+ end
115
133
  end
116
134
  end
117
135
  end
@@ -21,27 +21,21 @@ module KeycloakRuby
21
21
  # @raise [request_params.error_class] If the response code is not in success_codes
22
22
  # or HTTParty raises an error.
23
23
  def call(request_params)
24
- # To reduce FeatureEnvy, extract local variables
25
- http_method = request_params.http_method
26
- url = request_params.url
27
- headers = request_params.headers
28
- body = request_params.body
29
-
30
- response = HTTParty.send(http_method, url, headers: headers, body: body)
24
+ request_options = request_params.to_h.slice(:headers, :body)
25
+ response = HTTParty.send(request_params.http_method, request_params.url, **request_options)
31
26
  verify_response!(response, request_params)
32
27
  response
33
28
  rescue HTTParty::Error => e
34
- KeycloakRuby.logger.error("#{request_params.error_message} (HTTParty error): #{e.message}")
35
- raise request_params.error_class, e.message
29
+ message = e.message
30
+ KeycloakRuby.logger.error("#{request_params.error_message} (HTTParty error): #{message}")
31
+ raise request_params.error_class, message
36
32
  end
37
33
 
38
34
  private
39
35
 
40
36
  # Safe validation: returns true/false
41
- def verify_response(response, request_params)
42
- code = response.code
43
- success_codes = request_params.success_codes
44
-
37
+ # :reek:UtilityFunction
38
+ def verify_response(code, success_codes)
45
39
  case success_codes
46
40
  when Range
47
41
  success_codes.cover?(code)
@@ -54,12 +48,13 @@ module KeycloakRuby
54
48
 
55
49
  # Bang version that raises an error on invalid response
56
50
  def verify_response!(response, request_params)
57
- return if verify_response(response, request_params)
51
+ code = response.code
52
+ return if verify_response(code, request_params.success_codes)
58
53
 
59
- code = response.code
60
- error_message = request_params.error_message
61
- KeycloakRuby.logger.error("#{error_message}: #{code} => #{response.body}")
62
- raise request_params.error_class, "#{error_message}: #{code} => #{response.body}"
54
+ message = request_params.error_message
55
+ body = response.body
56
+ KeycloakRuby.logger.error("#{message}: #{code} => #{body}")
57
+ raise request_params.error_class, "#{message}: #{code} => #{body}"
63
58
  end
64
59
  end
65
60
  end
@@ -85,15 +85,16 @@ module KeycloakRuby
85
85
  # Raises appropriate validation error based on failure reason
86
86
  # @raise [KeycloakRuby::Errors::TokenRefreshFailed]
87
87
  def raise_validation_error # rubocop:disable Metrics/MethodLength
88
+ error_description = @data["error_description"]
88
89
  if !valid_http_status?
89
90
  raise Errors::TokenRefreshFailed,
90
91
  "Keycloak API request failed with status #{@response.code}: #{extract_error_message}"
91
92
  elsif invalid_grant?
92
93
  raise Errors::TokenRefreshFailed,
93
- "Invalid grant: #{@data["error_description"] || "Refresh token invalid or expired"}"
94
+ "Invalid grant: #{error_description || "Refresh token invalid or expired"}"
94
95
  elsif error_present?
95
96
  raise Errors::TokenRefreshFailed,
96
- "Keycloak error: #{@data["error"]} - #{@data["error_description"]}"
97
+ "Keycloak error: #{@data["error"]} - #{error_description}"
97
98
  else
98
99
  raise Errors::TokenRefreshFailed,
99
100
  "Invalid response: access token missing from response"
@@ -103,10 +104,12 @@ module KeycloakRuby
103
104
  # Extracts error message from response
104
105
  # @return [String]
105
106
  def extract_error_message
106
- if @data["error_description"]
107
- @data["error_description"]
108
- elsif @response.body.length < 100 # Prevent huge error messages
109
- @response.body
107
+ error_description = @data["error_description"]
108
+ response_body = @response.body
109
+ if error_description
110
+ error_description
111
+ elsif response_body.length < 100 # Prevent huge error messages
112
+ response_body
110
113
  else
111
114
  "See response body for details"
112
115
  end
@@ -56,32 +56,31 @@ module KeycloakRuby
56
56
  private
57
57
 
58
58
  def mock_keycloak_login(user, use_capybara: true)
59
- OmniAuth.config.test_mode = true
59
+ config = OmniAuth.config
60
+ config.test_mode = true
60
61
  token_data = generate_fake_tokens(user)
61
- OmniAuth.config.mock_auth[:keycloak] = token_data
62
+ config.mock_auth[:keycloak] = token_data
62
63
 
63
- if use_capybara
64
- capybara_login
65
- else
66
- store_session(token_data.credentials)
67
- end
64
+ use_capybara ? capybara_login : store_session(token_data.credentials)
68
65
  end
69
66
 
70
67
  def capybara_login
71
68
  visit "/login"
72
- click_on I18n.t("user.login") if page.has_button? I18n.t("user.login")
69
+ translated_login_link = I18n.t("user.login")
70
+ click_on translated_login_link if page.has_button? translated_login_link
73
71
  end
74
72
 
75
73
  def generate_fake_tokens(user)
76
74
  email = user.email
77
- token_payload = { "email" => email, "exp" => 2.hours.from_now.to_i }
75
+ expired_time = 2.hours.from_now.to_i
76
+ token_payload = { "email" => email, "exp" => expired_time }
78
77
 
79
78
  OmniAuth::AuthHash.new(provider: "keycloak", uid: "uid-#{email}", info: { email: email },
80
79
  credentials: OmniAuth::AuthHash.new(
81
80
  token: JWT.encode(token_payload, nil, "none"),
82
81
  refresh_token: "fake-refresh-#{email}",
83
82
  id_token: "fake-id-#{email}",
84
- expires_at: 2.hours.from_now.to_i
83
+ expires_at: expired_time
85
84
  ))
86
85
  end
87
86
 
@@ -118,6 +117,7 @@ if defined?(RSpec)
118
117
  end
119
118
  elsif defined?(Minitest)
120
119
  module Minitest
120
+ # Include helpers in minitest module
121
121
  class Test
122
122
  include KeycloakRuby::Testing::KeycloakHelpers
123
123
  end
@@ -20,8 +20,12 @@ module KeycloakRuby
20
20
  end
21
21
 
22
22
  # Store token
23
+ # :reek:DuplicateMethodCall
24
+ # :reek:FeatureEnvy
23
25
  def store_tokens(data)
24
- @session[:access_token] = extract_access_token(data)
26
+ # It's necessary, because omniauth return request.env["omniauth.auth"] as 'token', not 'access_token'
27
+
28
+ @session[:access_token] = data["token"] || data["access_token"]
25
29
  @session[:refresh_token] = data["refresh_token"] if data["refresh_token"]
26
30
  @session[:id_token] = data["id_token"] if data["id_token"]
27
31
  end
@@ -33,9 +37,6 @@ module KeycloakRuby
33
37
  private
34
38
 
35
39
  # It's necessary, because omniauth return request.env["omniauth.auth"] as 'token', not 'access_token'
36
- def extract_access_token(data)
37
- data["token"] || data["access_token"]
38
- end
39
40
 
40
41
  # Gets current token or attempts refresh if expired
41
42
  # @return [Hash, nil] Decoded token claims
@@ -52,6 +53,7 @@ module KeycloakRuby
52
53
 
53
54
  # Decodes JWT token
54
55
  # @raise [Errors::TokenExpired, Errors::TokenInvalid]
56
+ # :reek:DuplicateMethodCall
55
57
  def decode_token(token)
56
58
  options = jwt_decode_options
57
59
 
@@ -57,8 +57,9 @@ module KeycloakRuby
57
57
  #
58
58
  # @raise [KeycloakRuby::Errors::Error] if any request to Keycloak fails
59
59
  def find_or_create(user_attrs = {})
60
- users = find(user_attrs[:email])
61
- user = users.detect { |u| u["email"].casecmp?(user_attrs[:email]) }
60
+ user_email = user_attrs[:email]
61
+ users = find(user_email)
62
+ user = users.detect { |user| user["email"].casecmp?(user_email) }
62
63
 
63
64
  if user
64
65
  { user_data: user, created: false }
@@ -5,8 +5,8 @@ module KeycloakRuby
5
5
  # The current version of the KeycloakRuby gem as a string.
6
6
  # Follows Semantic Versioning 2.0 (https://semver.org)
7
7
  # @example
8
- # KeycloakRuby::VERSION # => "0.1.2"
9
- VERSION = "0.1.2"
8
+ # KeycloakRuby::VERSION # => "0.1.3"
9
+ VERSION = "0.1.3"
10
10
 
11
11
  # Provides version information and comparison methods for the KeycloakRuby gem.
12
12
  # This module follows Semantic Versioning 2.0 guidelines.
data/lib/keycloak_ruby.rb CHANGED
@@ -6,7 +6,8 @@ require "omniauth_openid_connect"
6
6
  require "httparty"
7
7
  require "jwt"
8
8
  require "zeitwerk"
9
-
9
+ require "omniauth/rails_csrf_protection/version"
10
+ require "omniauth/rails_csrf_protection/railtie" if defined?(Rails)
10
11
  loader = Zeitwerk::Loader.for_gem
11
12
  loader.ignore(
12
13
  "#{__dir__}/generators",
@@ -24,15 +25,11 @@ module KeycloakRuby
24
25
  # Defaults to Rails.logger if available, or a standard Logger.
25
26
  #
26
27
  # @return [Logger]
28
+ # :reek:Attribute
27
29
  attr_writer :logger
28
30
 
29
31
  def logger
30
- @logger ||= if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
31
- Rails.logger
32
- else
33
- require "logger"
34
- Logger.new($stdout).tap { |log| log.level = Logger::INFO }
35
- end
32
+ @logger ||= resolve_logger
36
33
  end
37
34
 
38
35
  # Returns the singleton configuration object. The configuration is
@@ -52,6 +49,31 @@ module KeycloakRuby
52
49
  yield config
53
50
  config.validate!
54
51
  end
52
+
53
+ private
54
+
55
+ def resolve_logger
56
+ if rails_defined? && rails_logger
57
+ rails_logger
58
+ else
59
+ default_logger
60
+ end
61
+ end
62
+
63
+ def rails_defined?
64
+ defined?(Rails)
65
+ end
66
+
67
+ def rails_logger
68
+ Rails.logger if rails_defined?
69
+ rescue NoMethodError
70
+ nil
71
+ end
72
+
73
+ def default_logger
74
+ require "logger"
75
+ Logger.new($stdout).tap { |log| log.level = Logger::INFO }
76
+ end
55
77
  end
56
78
  # Load test helpers only in test environment
57
79
  if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test" || defined?(RSpec) || defined?(Minitest)
@@ -1,7 +1,19 @@
1
1
  # Module for interacting with Keycloak
2
2
  module KeycloakRuby
3
+ self.@logger: untyped
4
+
3
5
  self.@config: untyped
4
6
 
7
+ # Logger used throughout the gem
8
+ #
9
+ # Defaults to Rails.logger if available, or a standard Logger.
10
+ #
11
+ # @return [Logger]
12
+ # :reek:Attribute
13
+ attr_writer self.logger: untyped
14
+
15
+ def self.logger: () -> untyped
16
+
5
17
  # Returns the singleton configuration object. The configuration is
6
18
  # initialized on first access and validated immediately.
7
19
  #
@@ -15,642 +27,13 @@ module KeycloakRuby
15
27
  # @raise [ConfigurationError] if configuration is invalid
16
28
  def self.configure: () { (untyped) -> untyped } -> untyped
17
29
 
18
- VERSION: untyped
19
- end
20
-
21
- # lib/keycloak_ruby/user.rb
22
- module KeycloakRuby
23
- # User-related operations for interacting with Keycloak.
24
- # This class provides a simple interface for creating, deleting, and finding users in Keycloak.
25
- class User
26
- self.@client: untyped
27
-
28
- # Creates a new user in Keycloak.
29
- #
30
- # @param user_attrs [Hash] A hash of user attributes (e.g., :username, :email, :password, :temporary).
31
- # @return [Hash] The created user's data.
32
- # @raise [KeycloakRuby::Error] If the user creation fails.
33
- def self.create: (?::Hash[untyped, untyped] user_attrs) -> untyped
34
-
35
- # Deletes users from Keycloak based on a search string.
36
- #
37
- # @param search_string [String] A string to search for users (e.g., username, email, etc.).
38
- # @return [void]
39
- # @raise [KeycloakRuby::Error] If any user deletion fails.
40
- def self.delete: (untyped search_string) -> untyped
41
-
42
- # Deletes a user from Keycloak by ID.
43
- #
44
- # @param user_id [String] The ID of the user to delete.
45
- # @return [void]
46
- # @raise [KeycloakRuby::Error] If the deletion fails.
47
- def self.delete_by_id: (untyped user_id) -> untyped
48
-
49
- # Finds users in Keycloak based on a search string.
50
- #
51
- # @param search_string [String] A string to search for users (e.g., username, email, etc.).
52
- # @return [Array<Hash>] An array of user objects (hashes) matching the search criteria.
53
- # @raise [KeycloakRuby::Error] If the search fails.
54
- def self.find: (untyped search_string) -> untyped
55
-
56
- private
57
-
58
- # Provides a singleton instance of the KeycloakRuby::Client.
59
- #
60
- # @return [KeycloakRuby::Client] The client instance used for making API requests.
61
- def self.client: () -> untyped
62
- end
63
- end
64
-
65
- module KeycloakRuby
66
- # Responsible for performing HTTP requests with HTTParty
67
- # and validating the response. This class helps to reduce
68
- # FeatureEnvy and keep the Client code cleaner.
69
- # :reek:FeatureEnvy
70
- class RequestPerformer
71
- @config: untyped
72
-
73
- def initialize: (untyped config) -> void
74
-
75
- # Executes an HTTP request and verifies the response code.
76
- #
77
- # @param request_params [KeycloakRuby::RequestParams] - an object containing
78
- # :http_method, :url, :headers, :body, :success_codes, :error_class, :error_message
79
- #
80
- # @return [HTTParty::Response] The HTTParty response object on success.
81
- # @raise [request_params.error_class] If the response code is not in success_codes
82
- # or HTTParty raises an error.
83
- def call: (untyped request_params) -> untyped
84
-
85
- private
86
-
87
- # Safe validation: returns true/false
88
- def verify_response: (untyped response, untyped request_params) -> untyped
89
-
90
- # Bang version that raises an error on invalid response
91
- def verify_response!: (untyped response, untyped request_params) -> (nil | untyped)
92
- end
93
- end
94
-
95
- # keycloak_ruby/testing/keycloak_helpers.rb
96
- # :reek:UtilityFunction :reek:ControlParameter :reek:ManualDispatch :reek:BooleanParameter :reek:LongParameterList
97
- module KeycloakRuby
98
- module Testing
99
- # Helper module for tests with Keycloak
100
- module KeycloakHelpers
101
- self.@keycloak_users: untyped
102
-
103
- # Combines both sign-in approaches with automatic detection of test type
104
- def sign_in: (untyped user, ?test_type: untyped) -> untyped
105
-
106
- # Мокирует авторизацию в request-тестах, подставляя указанного пользователя в current_user.
107
- # Нужно, так как в request-тестах нет прямого доступа к сессиям и внешним сервисам авторизации.
108
- def mock_token_service: (untyped user) -> untyped
109
-
110
- def create_keycloak_user: (username: untyped, email: untyped, password: untyped, temporary: untyped) -> untyped
111
-
112
- # Delete all users from Keycloak
113
- def self.delete_all_keycloak_users: () -> untyped
114
-
115
- def self.track_keycloak_user: (untyped user_id) -> untyped
116
-
117
- def self.cleanup_keycloak_users: () -> (nil | untyped)
118
-
119
- private
120
-
121
- def mock_keycloak_login: (untyped user, ?use_capybara: bool) -> untyped
122
-
123
- def capybara_login: () -> untyped
124
-
125
- def generate_fake_tokens: (untyped user) -> untyped
126
-
127
- def store_session: (untyped credentials) -> untyped
128
-
129
- def rspec_auto_detect_test_type: () -> (:request | :feature | :controller)
130
-
131
- def auto_detect_test_type: () -> (untyped | :feature)
132
- end
133
- end
134
- end
135
-
136
- module Minitest
137
- class Test
138
- include KeycloakRuby::Testing::KeycloakHelpers
139
- end
140
- end
141
-
142
- module KeycloakRuby
143
- # Validates Keycloak API responses with both safe and strict modes
144
- #
145
- # Provides two validation approaches:
146
- # 1. Safe validation (#validate) - returns boolean
147
- # 2. Strict validation (#validate!) - raises detailed exceptions
148
- #
149
- # @example Safe validation
150
- # validator = ResponseValidator.new(response)
151
- # if validator.validate
152
- # # proceed with valid response
153
- # else
154
- # # handle invalid response
155
- # end
156
- #
157
- # @example Strict validation
158
- # begin
159
- # data = ResponseValidator.new(response).validate!
160
- # # use validated data
161
- # rescue KeycloakRuby::Errors::TokenRefreshFailed => e
162
- # # handle error
163
- # end
164
- class ResponseValidator
165
- @response: untyped
166
-
167
- @data: untyped
168
-
169
- # Initialize with the HTTP response
170
- # @param response [HTTP::Response] The raw HTTP response from Keycloak
171
- def initialize: (untyped response) -> void
172
-
173
- # Safe validation - returns boolean instead of raising exceptions
174
- # @return [Boolean] true if response is valid, false otherwise
175
- def validate: () -> (false | untyped)
176
-
177
- # Strict validation - raises exceptions for invalid responses
178
- # @return [Hash] Parsed response data if valid
179
- # @raise [KeycloakRuby::Errors::TokenRefreshFailed] if validation fails
180
- def validate!: () -> untyped
181
-
182
- private
183
-
184
- # Parses JSON response body, returns empty hash on failure
185
- # @return [Hash]
186
- def parse_response_body: () -> untyped
187
-
188
- # Checks if HTTP status indicates success
189
- # @return [Boolean]
190
- def valid_http_status?: () -> untyped
191
-
192
- # Checks for OAuth2 "invalid_grant" error
193
- # @return [Boolean]
194
- def invalid_grant?: () -> untyped
195
-
196
- # Checks for any error in response
197
- # @return [Boolean]
198
- def error_present?: () -> untyped
199
-
200
- # Verifies access token presence
201
- # @return [Boolean]
202
- def access_token_present?: () -> untyped
203
-
204
- # Raises appropriate validation error based on failure reason
205
- # @raise [KeycloakRuby::Errors::TokenRefreshFailed]
206
- def raise_validation_error: () -> untyped
207
-
208
- # Extracts error message from response
209
- # @return [String]
210
- def extract_error_message: () -> (untyped | untyped | "See response body for details")
211
- end
212
- end
213
-
214
- # lib/keycloak_ruby/config.rb
215
- module KeycloakRuby
216
- # Configuration class for Keycloak Ruby gem.
217
- #
218
- # Handles loading and validation of Keycloak configuration from either:
219
- # - A YAML file (default: config/keycloak.yml)
220
- # - Direct attribute assignment
221
- #
222
- # == Example YAML Configuration
223
- #
224
- # development:
225
- # keycloak_url: "https://keycloak.example.com"
226
- # app_host: "http://localhost:3000"
227
- # realm: "my-realm"
228
- # oauth_client_id: "my-client"
229
- # oauth_client_secret: "secret"
230
- #
231
- # == Example Programmatic Configuration
232
- #
233
- # config = KeycloakRuby::Config.new
234
- # config.keycloak_url = "https://keycloak.example.com"
235
- # config.realm = "my-realm"
236
- # # ... etc
237
- # :reek:MissingSafeMethod
238
- class Config
239
- @config_path: untyped
240
-
241
- # Default path to configuration file (Rails.root/config/keycloak.yml)
242
- DEFAULT_CONFIG_PATH: untyped
243
-
244
- REQUIRED_ATTRIBUTES: ::Array[:keycloak_url | :app_host | :realm | :oauth_client_id | :oauth_client_secret]
245
-
246
- # :reek:Attribute
247
- attr_accessor keycloak_url: untyped
248
-
249
- # :reek:Attribute
250
- attr_accessor app_host: untyped
251
-
252
- # :reek:Attribute
253
- attr_accessor realm: untyped
254
-
255
- # :reek:Attribute
256
- attr_accessor admin_client_id: untyped
257
-
258
- # :reek:Attribute
259
- attr_accessor admin_client_secret: untyped
260
-
261
- # :reek:Attribute
262
- attr_accessor oauth_client_id: untyped
263
-
264
- # :reek:Attribute
265
- attr_accessor oauth_client_secret: untyped
266
-
267
- attr_reader config_path: untyped
268
-
269
- # Initialize configuration, optionally loading from YAML file
270
- #
271
- # @param config_path [String] Path to YAML config file (default: config/keycloak.yml)
272
- #
273
- #
274
- def initialize: (?untyped config_path) -> void
275
-
276
- def validate!: () -> untyped
277
-
278
- def realm_url: () -> ::String
279
-
280
- def redirect_url: () -> ::String
281
-
282
- def logout_url: () -> ::String
283
-
284
- def token_url: () -> ::String
285
-
286
- private
287
-
288
- # Loads configuration from YAML file if it exists
289
- # :reek:ManualDispatch
290
- def load_config: () -> (nil | untyped)
291
-
292
- def load_yaml_file: () -> untyped
293
-
294
- def current_env: () -> untyped
295
-
296
- def apply_config: (untyped config_hash) -> untyped
297
- end
298
- end
299
-
300
- module KeycloakRuby
301
- # Include test methods
302
- module Testing
303
- def self.included: (untyped base) -> untyped
304
- end
305
- end
306
-
307
- # lib/keycloak_ruby/request_params.rb
308
- module KeycloakRuby
309
- # A small, typed struct for request parameters
310
- RequestParams: untyped
311
- end
312
-
313
- # lib/keycloak_ruby/errors.rb
314
- module KeycloakRuby
315
- # Namespace for all KeycloakRuby specific errors
316
- # Follows a hierarchical structure for better error handling
317
- module Errors
318
- # Base error class for all KeycloakRuby errors
319
- # All custom errors inherit from this class
320
- class Error < StandardError
321
- end
322
-
323
- # Raised when there's an issue with gem configuration
324
- class ConfigurationError < Error
325
- end
326
-
327
- # Base class for authentication failures
328
- class AuthenticationError < Error
329
- end
330
-
331
- # Raised when user credentials are invalid
332
- class InvalidCredentials < AuthenticationError
333
- end
334
-
335
- # Raised when user account is not found
336
- class UserNotFound < AuthenticationError
337
- end
338
-
339
- # Raised when account is temporarily locked
340
- class AccountLocked < AuthenticationError
341
- end
342
-
343
- # Raised when user creation fails
344
- class UserCreationError < Error
345
- end
346
-
347
- # Raised when user update fails
348
- class UserUpdateError < Error
349
- end
350
-
351
- # Raised when user deletion fails
352
- class UserDeletionError < Error
353
- end
354
-
355
- # Base class for all token-related errors
356
- class TokenError < Error
357
- end
358
-
359
- # Raised when token has expired +
360
- class TokenExpired < TokenError
361
- end
362
-
363
- # Raised when token is invalid (malformed, wrong signature, etc.)
364
- class TokenInvalid < TokenError
365
- end
366
-
367
- # Raised when token refresh fails
368
- class TokenRefreshFailed < TokenError
369
- end
370
-
371
- # Raised when token verification fails
372
- class TokenVerificationFailed < TokenError
373
- end
374
-
375
- # Raised when API request fails
376
- class APIError < Error
377
- end
378
-
379
- # Raised when receiving 4xx responses from Keycloak
380
- class ClientError < APIError
381
- end
382
-
383
- # Raised when receiving 5xx responses from Keycloak
384
- class ServerError < APIError
385
- end
386
-
387
- # Raised when connection to Keycloak fails
388
- class ConnectionError < APIError
389
- end
390
- end
391
- end
392
-
393
- # lib/keycloak_ruby/token_service.rb
394
- # :reek:FeatureEnvy
395
- module KeycloakRuby
396
- # Service for check and refresh jwt tokens
397
- class TokenService
398
- @session: untyped
399
-
400
- @config: untyped
401
-
402
- @refresh_mutex: untyped
403
-
404
- @fetch_jwks: untyped
405
-
406
- @issuer_url: untyped
407
-
408
- def initialize: (untyped session, ?untyped config) -> void
409
-
410
- # Finds user by token claims
411
- # @return [User, nil]
412
- def find_user: () -> (nil | untyped)
413
-
414
- # Store token
415
- def store_tokens: (untyped data) -> untyped
416
-
417
- def clear_tokens: () -> untyped
418
-
419
- private
420
-
421
- # It's necessary, because omniauth return request.env["omniauth.auth"] as 'token', not 'access_token'
422
- def extract_access_token: (untyped data) -> untyped
423
-
424
- # Gets current token or attempts refresh if expired
425
- # @return [Hash, nil] Decoded token claims
426
- def current_token: () -> untyped
427
-
428
- # Decodes JWT token
429
- # @raise [Errors::TokenExpired, Errors::TokenInvalid]
430
- def decode_token: (untyped token) -> untyped
431
-
432
- def fetch_jwks: () -> untyped
433
-
434
- # Attempts to refresh the current token
435
- def refresh_current_token: () -> untyped
436
-
437
- # JWT decoding options with JWKS
438
- def jwt_decode_options: () -> { algorithms: ::Array["RS256"], verify_iss: true, iss: untyped, aud: untyped, verify_expiration: true, jwks: untyped }
439
-
440
- # Constructs issuer URL from configuration
441
- def issuer_url: () -> untyped
442
- end
443
- end
444
-
445
- # lib/keycloak_ruby/token_refresher.rb`
446
- module KeycloakRuby
447
- # Handles OAuth2 refresh token flow with Keycloak
448
- #
449
- # Responsible for:
450
- # - Executing refresh token requests
451
- # - Validating responses
452
- # - Managing refresh failures
453
- #
454
- # @example Basic usage
455
- # refresher = TokenRefresher.new(session, config)
456
- # new_tokens = refresher.call
457
- #
458
- # @example With error handling
459
- # begin
460
- # refresher.call
461
- # rescue KeycloakRuby::Errors::TokenRefreshFailed => e
462
- # # Handle token refresh failure (e.g., redirect to login)
463
- # end
464
- class TokenRefresher
465
- @session: untyped
466
-
467
- @config: untyped
468
-
469
- def initialize: (untyped session, untyped config) -> void
470
-
471
- # Main entry point - refreshes the token
472
- # @return [Hash] New token data if successful
473
- # @raise [KeycloakRuby::Errors::TokenRefreshFailed] if refresh fails
474
- def call: () -> untyped
475
-
476
- private
477
-
478
- def refresh_token_flow: () -> untyped
479
-
480
- def request_refresh: () -> untyped
481
-
482
- def validate_response: (untyped response) -> untyped
483
-
484
- def handle_http_error: (untyped exception) -> untyped
485
-
486
- # :reek:FeatureEnvy
487
- def handle_failed_validation: (untyped response) -> untyped
488
-
489
- def refresh_params: () -> { grant_type: "refresh_token", client_id: untyped, client_secret: untyped, refresh_token: untyped }
490
-
491
- def headers: () -> { "Content-Type" => "application/x-www-form-urlencoded", "Accept" => "application/json", "User-Agent" => ::String }
492
-
493
- def log_refresh_attempt: () -> untyped
494
-
495
- def log_successful_refresh: () -> untyped
496
- end
497
- end
498
-
499
- # lib/keycloak_ruby/client.rb
500
- module KeycloakRuby
501
- # Client for interacting with Keycloak (create, delete, and find users, etc.).
502
- # rubocop:disable Metrics/ClassLength
503
- # rubocop:disable Metrics/MethodLength
504
- # :reek:TooManyMethods
505
- class Client
506
- @config: untyped
507
-
508
- @request_performer: untyped
509
-
510
- def initialize: (?untyped config) -> void
511
-
512
- # Authenticates a user with Keycloak and returns token data upon success.
513
- #
514
- # @param username [String] The user's username or email.
515
- # @param password [String] The user's password.
516
- # @return [Hash] The token data (access_token, refresh_token, id_token, etc.).
517
- # @raise [KeycloakRuby::Errors::InvalidCredentials] If authentication fails.
518
- def authenticate_user: (username: untyped, password: untyped) -> untyped
519
-
520
- # Creates a user in Keycloak and returns the newly created user's data.
521
- #
522
- # @param user_attrs [Hash] A hash of user attributes. Must contain:
523
- # :username, :email, :password, :temporary
524
- # @option user_attrs [String] :username The username for the new user.
525
- # @option user_attrs [String] :email The user's email.
526
- # @option user_attrs [String] :password The initial password.
527
- # @option user_attrs [Boolean] :temporary Whether to force a password update on first login.
528
- #
529
- # @raise [KeycloakRuby::Errors::UserCreationError] If user creation fails.
530
- # @return [Hash] The newly created user's data.
531
- def create_user: (?::Hash[untyped, untyped] user_attrs) -> untyped
532
-
533
- # Deletes all users that match the provided search string (e.g., username, email).
534
- #
535
- # @param search_string [String] The search criteria for finding users in Keycloak.
536
- # @raise [KeycloakRuby::Errors::UserDeletionError] If any user deletion fails.
537
- def delete_users: (untyped search_string) -> untyped
538
-
539
- # Deletes a single user by ID in Keycloak.
540
- #
541
- # @param user_id [String] The ID of the user to delete.
542
- # @raise [KeycloakRuby::Errors::UserDeletionError] If the deletion fails.
543
- def delete_user_by_id: (untyped user_id) -> untyped
544
-
545
- # Finds all users in Keycloak that match the given search string.
546
- #
547
- # @param search [String] The search query (e.g., part of username or email).
548
- # @return [Array<Hash>] An array of user objects.
549
- # @raise [KeycloakRuby::Errors::APIError] If the request fails.
550
- def find_users: (untyped search) -> untyped
551
-
552
- # Updates the redirect URIs for a specific client in Keycloak.
553
- #
554
- # @param client_id [String] The client ID in Keycloak.
555
- # @param redirect_uris [Array<String>] A list of valid redirect URIs for this client.
556
- # @raise [KeycloakRuby::Errors::ConnectionError] If the update request fails.
557
- def update_client_redirect_uris: (client_id: untyped, redirect_uris: untyped) -> untyped
558
-
559
- private
560
-
561
- def build_auth_body: (untyped username, untyped password) -> { client_id: untyped, client_secret: untyped, username: untyped, password: untyped, grant_type: "password" }
562
-
563
- def build_user_data: (untyped attrs) -> { username: untyped, email: untyped, enabled: true, credentials: ::Array[{ type: "password", value: untyped, temporary: untyped }] }
564
-
565
- # Builds RequestParams and passes them to the RequestPerformer.
566
- def http_request: (?::Hash[untyped, untyped] options) -> untyped
567
-
568
- def build_request_params: (untyped opts) -> untyped
569
-
570
- # Retrieves client details by its "clientId".
571
- #
572
- # @param client_id [String] The "clientId" in Keycloak.
573
- # @return [Hash] The client details.
574
- # @raise [KeycloakRuby::Errors::ClientError] If no matching client is found or the request fails.
575
- def find_client_by_id: (untyped client_id) -> untyped
576
-
577
- # Performs a PUT request to update the redirect URIs for a given client in Keycloak.
578
- #
579
- # @param client_id [String] The internal Keycloak client ID.
580
- # @param redirect_uris [Array<String>] List of valid redirect URIs for this client.
581
- # @raise [KeycloakRuby::Errors::ConnectionError] If the update request fails.
582
- def update_redirect_uris_for: (untyped client_id, untyped redirect_uris) -> untyped
583
-
584
- # Fetches a user by ID from Keycloak.
585
- #
586
- # @param user_id [String] The user's ID in Keycloak.
587
- # @return [Hash] The Keycloak user data.
588
- # @raise [KeycloakRuby::Errors::UserNotFound] If the user cannot be found or the request fails.
589
- def fetch_user: (untyped user_id) -> untyped
590
-
591
- # Retrieves the admin token used to authenticate calls to the Keycloak Admin API.
592
- #
593
- # @return [String] The admin access token.
594
- # @raise [KeycloakRuby::Errors::TokenVerificationFailed] If the token request fails.
595
- def admin_token: () -> untyped
596
-
597
- # Returns a set of default headers for requests requiring the admin token.
598
- #
599
- # @return [Hash] A headers Hash including Authorization and Content-Type.
600
- def default_headers: () -> { "Authorization" => ::String, "Content-Type" => "application/json" }
601
- end
602
- end
603
-
604
- # lib/keycloak_ruby/version.rb
605
- # Module for interacting with Keycloak
606
- module KeycloakRuby
607
- # Version module following Semantic Versioning 2.0 guidelines
608
- # Provides detailed version information and helper methods
609
- #
610
- # @example Getting version information
611
- # KeycloakRuby::Version::VERSION # => "0.1.0"
612
- # KeycloakRuby::Version.to_a # => [0, 1, 0]
613
- # KeycloakRuby::Version.to_h # => { major: 0, minor: 1, patch: 0, pre: nil }
614
- # KeycloakRuby.version # => "0.1.0"
615
- #
616
- # @example Checking version
617
- # KeycloakRuby::Version >= '0.1.0' # => true
618
- # Module for work with Version
619
- module Version
620
- # Major version number (incompatible API changes)
621
- MAJOR: 0
622
-
623
- # Minor version number (backwards-compatible functionality)
624
- MINOR: 1
625
-
626
- # Patch version number (backwards-compatible bug fixes)
627
- PATCH: 0
628
-
629
- # Pre-release version (nil for stable releases)
630
- PRE: nil
631
-
632
- # Full version string
633
- VERSION: untyped
634
-
635
- # Returns version components as an array
636
- # @return [Array<Integer, Integer, Integer, String|nil>]
637
- def self.to_a: () -> ::Array[untyped]
30
+ private
638
31
 
639
- # Returns version components as a hash
640
- # @return [Hash<Symbol, Integer|String|nil>]
641
- def self.to_h: () -> { major: untyped, minor: untyped, patch: untyped, pre: untyped }
32
+ def self.resolve_logger: () -> untyped
642
33
 
643
- # Compares version with another version string
644
- # @param version_string [String] version to compare with (e.g., "1.2.3")
645
- # @return [Boolean]
646
- def self.>=: (untyped version_string) -> untyped
34
+ def self.rails_defined?: () -> untyped
647
35
 
648
- # Returns the full version string
649
- # @return [String]
650
- def self.to_s: () -> untyped
651
- end
36
+ def self.rails_logger: () -> untyped
652
37
 
653
- # Returns the current gem version
654
- # @return [String]
655
- def self.version: () -> untyped
38
+ def self.default_logger: () -> untyped
656
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keycloak_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Arkhipov
8
8
  - Georgy Shcherbakov
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-26 00:00:00.000000000 Z
11
+ date: 2025-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: omniauth-rails_csrf_protection
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.0.2
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.0.2
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: zeitwerk
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -102,6 +116,7 @@ executables: []
102
116
  extensions: []
103
117
  extra_rdoc_files: []
104
118
  files:
119
+ - ".reek.yml"
105
120
  - ".rspec_status"
106
121
  - ".rubocop.yml"
107
122
  - ".ruby-version"