keycloak-ruby 1.0
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 +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +55 -0
- data/LICENSE.txt +21 -0
- data/README.md +499 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/keycloak.gemspec +39 -0
- data/lib/generators/initializer_generator.rb +7 -0
- data/lib/generators/keycloak.rb +12 -0
- data/lib/keycloak.rb +912 -0
- data/lib/keycloak/exceptions.rb +7 -0
- data/lib/keycloak/token.rb +11 -0
- data/lib/keycloak/version.rb +3 -0
- metadata +147 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "keycloak"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/keycloak.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "keycloak/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "keycloak-ruby"
|
8
|
+
spec.version = Keycloak::VERSION
|
9
|
+
spec.authors = ["Orkhan Maharramli", "Guilherme Portugues"]
|
10
|
+
spec.email = ["orkhan.maharramli@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Add authentication to applications and secure services with Keycloak}
|
13
|
+
#spec.description = %q{TODO: Write a longer description or delete this line.}
|
14
|
+
spec.homepage = "https://github.com/orkhan/keycloak-ruby.git"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
# if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata["allowed_push_host"] = "https://github.com/orkhan/keycloak-ruby.git"
|
21
|
+
# else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
# "public gem pushes."
|
24
|
+
# end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
34
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
35
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
36
|
+
spec.add_runtime_dependency "rest-client"
|
37
|
+
spec.add_runtime_dependency "jwt"
|
38
|
+
spec.add_runtime_dependency "json"
|
39
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Set proxy to connect in keycloak server
|
2
|
+
Keycloak.proxy = ''
|
3
|
+
# If true, then all request exception will explode in application (this is the default value)
|
4
|
+
Keycloak.generate_request_exception = true
|
5
|
+
# controller that manage the user session
|
6
|
+
Keycloak.keycloak_controller = 'session'
|
7
|
+
# relm name (only if the installation file is not present)
|
8
|
+
Keycloak.realm = ''
|
9
|
+
# relm url (only if the installation file is not present)
|
10
|
+
Keycloak.auth_server_url = ''
|
11
|
+
# Installation file
|
12
|
+
Keycloak.installation_file = 'keycloak.json'
|
data/lib/keycloak.rb
ADDED
@@ -0,0 +1,912 @@
|
|
1
|
+
require 'keycloak/exceptions'
|
2
|
+
require 'keycloak/token'
|
3
|
+
require 'keycloak/version'
|
4
|
+
require 'rest-client'
|
5
|
+
require 'json'
|
6
|
+
require 'jwt'
|
7
|
+
require 'base64'
|
8
|
+
require 'uri'
|
9
|
+
|
10
|
+
module Keycloak
|
11
|
+
KEYCLOAK_JSON_FILE = 'keycloak.json'
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :proxy, :generate_request_exception, :keycloak_controller,
|
15
|
+
:proc_cookie_token, :proc_external_attributes,
|
16
|
+
:realm, :auth_server_url, :installation_file
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.explode_exception
|
20
|
+
if Keycloak.generate_request_exception == nil
|
21
|
+
Keycloak.generate_request_exception = false
|
22
|
+
end
|
23
|
+
Keycloak.generate_request_exception
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.installation_file
|
27
|
+
@installation_file ||= KEYCLOAK_JSON_FILE
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.installation_file=(file = nil)
|
31
|
+
raise InstallationFileNotFound unless file.instance_of?(String) && File.exists?(file)
|
32
|
+
@installation_file = file || KEYCLOAK_JSON_FILE
|
33
|
+
end
|
34
|
+
|
35
|
+
module Client
|
36
|
+
class << self
|
37
|
+
attr_accessor :realm, :auth_server_url, :client_id, :secret, :configuration, :public_key
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.get_token(user, password)
|
41
|
+
setup_module
|
42
|
+
|
43
|
+
payload = { 'client_id' => @client_id,
|
44
|
+
'client_secret' => @secret,
|
45
|
+
'username' => user,
|
46
|
+
'password' => password,
|
47
|
+
'grant_type' => 'password' }
|
48
|
+
|
49
|
+
res = mount_request_token(payload)
|
50
|
+
json = JSON.parse(res)
|
51
|
+
Keycloak::Token.new(
|
52
|
+
access_token: json["access_token"],
|
53
|
+
expires_in: json["expires_in"],
|
54
|
+
refresh_expires_in: json["refresh_expires_in"],
|
55
|
+
refresh_token: json["refresh_token"],
|
56
|
+
token_type: json["token_type"],
|
57
|
+
not_before_policy: json["not-before-policy"],
|
58
|
+
session_state: json["session_state"],
|
59
|
+
scope: json["scope"]
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.get_token_by_code(code, redirect_uri)
|
64
|
+
verify_setup
|
65
|
+
|
66
|
+
payload = { 'client_id' => @client_id,
|
67
|
+
'client_secret' => @secret,
|
68
|
+
'code' => code,
|
69
|
+
'grant_type' => 'authorization_code',
|
70
|
+
'redirect_uri' => redirect_uri }
|
71
|
+
|
72
|
+
mount_request_token(payload)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.get_token_by_exchange(issuer, issuer_token)
|
76
|
+
setup_module
|
77
|
+
|
78
|
+
payload = { 'client_id' => @client_id, 'client_secret' => @secret, 'audience' => @client_id, 'grant_type' => 'urn:ietf:params:oauth:grant-type:token-exchange', 'subject_token_type' => 'urn:ietf:params:oauth:token-type:access_token', 'subject_issuer' => issuer, 'subject_token' => issuer_token }
|
79
|
+
header = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
80
|
+
_request = -> do
|
81
|
+
RestClient.post(@configuration['token_endpoint'], payload, header){|response, request, result|
|
82
|
+
# case response.code
|
83
|
+
# when 200
|
84
|
+
# response.body
|
85
|
+
# else
|
86
|
+
# response.return!
|
87
|
+
# end
|
88
|
+
response.body
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
exec_request _request
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.get_userinfo_issuer(access_token = '')
|
96
|
+
verify_setup
|
97
|
+
|
98
|
+
access_token = self.token["access_token"] if access_token.empty?
|
99
|
+
payload = { 'access_token' => access_token }
|
100
|
+
header = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
101
|
+
_request = -> do
|
102
|
+
RestClient.post(@configuration['userinfo_endpoint'], payload, header){ |response, request, result|
|
103
|
+
response.body
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
exec_request _request
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.get_token_by_refresh_token(refresh_token = '')
|
111
|
+
verify_setup
|
112
|
+
|
113
|
+
refresh_token = self.token['refresh_token'] if refresh_token.empty?
|
114
|
+
|
115
|
+
payload = { 'client_id' => @client_id,
|
116
|
+
'client_secret' => @secret,
|
117
|
+
'refresh_token' => refresh_token,
|
118
|
+
'grant_type' => 'refresh_token' }
|
119
|
+
|
120
|
+
mount_request_token(payload)
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.get_token_by_client_credentials(client_id = '', secret = '')
|
124
|
+
setup_module
|
125
|
+
|
126
|
+
client_id = @client_id if client_id.empty?
|
127
|
+
secret = @secret if secret.empty?
|
128
|
+
|
129
|
+
payload = { 'client_id' => client_id,
|
130
|
+
'client_secret' => secret,
|
131
|
+
'grant_type' => 'client_credentials' }
|
132
|
+
|
133
|
+
mount_request_token(payload)
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.get_token_introspection(token = '', client_id = '', secret = '')
|
137
|
+
verify_setup
|
138
|
+
|
139
|
+
token = self.token["access_token"] if token.empty?
|
140
|
+
payload = { 'token' => token }
|
141
|
+
|
142
|
+
client_id = @client_id if client_id.empty?
|
143
|
+
secret = @secret if secret.empty?
|
144
|
+
|
145
|
+
authorization = Base64.strict_encode64("#{client_id}:#{secret}")
|
146
|
+
authorization = "Basic #{authorization}"
|
147
|
+
|
148
|
+
header = {'Content-Type' => 'application/x-www-form-urlencoded',
|
149
|
+
'authorization' => authorization}
|
150
|
+
|
151
|
+
_request = -> do
|
152
|
+
RestClient.post(@configuration['token_introspection_endpoint'], payload, header){|response, request, result|
|
153
|
+
case response.code
|
154
|
+
when 200..399
|
155
|
+
response.body
|
156
|
+
else
|
157
|
+
response.return!
|
158
|
+
end
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
exec_request _request
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.url_login_redirect(redirect_uri, response_type = 'code')
|
166
|
+
verify_setup
|
167
|
+
|
168
|
+
p = URI.encode_www_form({ response_type: response_type, client_id: @client_id, redirect_uri: redirect_uri })
|
169
|
+
"#{@configuration['authorization_endpoint']}?#{p}"
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.logout(redirect_uri = '', refresh_token = '')
|
173
|
+
verify_setup
|
174
|
+
|
175
|
+
if self.token || !refresh_token.empty?
|
176
|
+
|
177
|
+
refresh_token = self.token['refresh_token'] if refresh_token.empty?
|
178
|
+
|
179
|
+
payload = { 'client_id' => @client_id,
|
180
|
+
'client_secret' => @secret,
|
181
|
+
'refresh_token' => refresh_token
|
182
|
+
}
|
183
|
+
|
184
|
+
header = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
185
|
+
|
186
|
+
if redirect_uri.empty?
|
187
|
+
final_url = @configuration['end_session_endpoint']
|
188
|
+
else
|
189
|
+
final_url = "#{@configuration['end_session_endpoint']}?#{URI.encode_www_form({ redirect_uri: redirect_uri })}"
|
190
|
+
end
|
191
|
+
|
192
|
+
_request = -> do
|
193
|
+
RestClient.post(final_url, payload, header){ |response, request, result|
|
194
|
+
case response.code
|
195
|
+
when 200..399
|
196
|
+
true
|
197
|
+
else
|
198
|
+
response.return!
|
199
|
+
end
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
exec_request _request
|
204
|
+
else
|
205
|
+
true
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.get_userinfo(access_token = '')
|
210
|
+
verify_setup
|
211
|
+
|
212
|
+
access_token = self.token["access_token"] if access_token.empty?
|
213
|
+
|
214
|
+
payload = { 'access_token' => access_token }
|
215
|
+
|
216
|
+
header = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
217
|
+
|
218
|
+
_request = -> do
|
219
|
+
RestClient.post(@configuration['userinfo_endpoint'], payload, header){ |response, request, result|
|
220
|
+
case response.code
|
221
|
+
when 200
|
222
|
+
response.body
|
223
|
+
else
|
224
|
+
response.return!
|
225
|
+
end
|
226
|
+
}
|
227
|
+
end
|
228
|
+
|
229
|
+
exec_request _request
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.url_user_account
|
233
|
+
verify_setup
|
234
|
+
|
235
|
+
"#{@auth_server_url}/realms/#{@realm}/account"
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.has_role?(user_role, access_token = '')
|
239
|
+
verify_setup
|
240
|
+
|
241
|
+
if user_signed_in?(access_token)
|
242
|
+
dt = decoded_access_token(access_token)[0]
|
243
|
+
dt = dt["resource_access"][@client_id]
|
244
|
+
if dt != nil
|
245
|
+
dt["roles"].each do |role|
|
246
|
+
return true if role.to_s == user_role.to_s
|
247
|
+
end
|
248
|
+
false
|
249
|
+
else
|
250
|
+
false
|
251
|
+
end
|
252
|
+
else
|
253
|
+
false
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.user_signed_in?(access_token = '')
|
258
|
+
verify_setup
|
259
|
+
|
260
|
+
begin
|
261
|
+
JSON(get_token_introspection(access_token))['active'] === true
|
262
|
+
rescue => e
|
263
|
+
if e.class < Keycloak::KeycloakException
|
264
|
+
raise
|
265
|
+
else
|
266
|
+
false
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.get_attribute(attributeName, access_token = '')
|
272
|
+
verify_setup
|
273
|
+
|
274
|
+
attr = decoded_access_token(access_token)[0]
|
275
|
+
attr[attributeName]
|
276
|
+
end
|
277
|
+
|
278
|
+
def self.token
|
279
|
+
if !Keycloak.proc_cookie_token.nil?
|
280
|
+
JSON Keycloak.proc_cookie_token.call
|
281
|
+
else
|
282
|
+
raise Keycloak::ProcCookieTokenNotDefined
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def self.external_attributes
|
287
|
+
if !Keycloak.proc_external_attributes.nil?
|
288
|
+
Keycloak.proc_external_attributes.call
|
289
|
+
else
|
290
|
+
raise Keycloak::ProcExternalAttributesNotDefined
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.decoded_access_token(access_token = '')
|
295
|
+
access_token = self.token["access_token"] if access_token.empty?
|
296
|
+
JWT.decode access_token, @public_key, false, { :algorithm => 'RS256' }
|
297
|
+
end
|
298
|
+
|
299
|
+
def self.decoded_refresh_token(refresh_token = '')
|
300
|
+
refresh_token = self.token["access_token"] if refresh_token.empty?
|
301
|
+
JWT.decode refresh_token, @public_key, false, { :algorithm => 'RS256' }
|
302
|
+
end
|
303
|
+
|
304
|
+
private
|
305
|
+
|
306
|
+
KEYCLOACK_CONTROLLER_DEFAULT = 'session'
|
307
|
+
|
308
|
+
def self.get_installation
|
309
|
+
if File.exists?(Keycloak.installation_file)
|
310
|
+
installation = JSON File.read(Keycloak.installation_file)
|
311
|
+
@realm = installation["realm"]
|
312
|
+
@client_id = installation["resource"]
|
313
|
+
@secret = installation["credentials"]["secret"]
|
314
|
+
@public_key = installation["realm-public-key"]
|
315
|
+
@auth_server_url = installation["auth-server-url"]
|
316
|
+
openid_configuration
|
317
|
+
else
|
318
|
+
if Keycloak.realm.empty? || Keycloak.auth_server_url.empty?
|
319
|
+
raise "#{Keycloak.installation_file} and relm settings not found."
|
320
|
+
else
|
321
|
+
@realm = Keycloak.realm
|
322
|
+
@auth_server_url = Keycloak.auth_server_url
|
323
|
+
openid_configuration
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def self.verify_setup
|
329
|
+
get_installation if @configuration.nil?
|
330
|
+
end
|
331
|
+
|
332
|
+
def self.setup_module
|
333
|
+
Keycloak.proxy ||= ''
|
334
|
+
Keycloak.keycloak_controller ||= KEYCLOACK_CONTROLLER_DEFAULT
|
335
|
+
get_installation
|
336
|
+
end
|
337
|
+
|
338
|
+
def self.exec_request(proc_request)
|
339
|
+
if Keycloak.explode_exception
|
340
|
+
proc_request.call
|
341
|
+
else
|
342
|
+
begin
|
343
|
+
proc_request.call
|
344
|
+
rescue RestClient::ExceptionWithResponse => err
|
345
|
+
err.response
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def self.openid_configuration
|
351
|
+
RestClient.proxy = Keycloak.proxy unless Keycloak.proxy.empty?
|
352
|
+
config_url = "#{@auth_server_url}/realms/#{@realm}/.well-known/openid-configuration"
|
353
|
+
_request = -> do
|
354
|
+
RestClient.get config_url
|
355
|
+
end
|
356
|
+
response = exec_request _request
|
357
|
+
if response.code == 200
|
358
|
+
@configuration = JSON response.body
|
359
|
+
else
|
360
|
+
response.return!
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def self.mount_request_token(payload)
|
365
|
+
header = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
366
|
+
|
367
|
+
_request = -> do
|
368
|
+
RestClient.post(@configuration['token_endpoint'], payload, header){|response, request, result|
|
369
|
+
case response.code
|
370
|
+
when 200
|
371
|
+
response.body
|
372
|
+
else
|
373
|
+
response.return!
|
374
|
+
end
|
375
|
+
}
|
376
|
+
end
|
377
|
+
|
378
|
+
exec_request _request
|
379
|
+
end
|
380
|
+
|
381
|
+
def self.decoded_id_token(idToken = '')
|
382
|
+
tk = self.token
|
383
|
+
idToken = tk["id_token"] if idToken.empty?
|
384
|
+
if idToken
|
385
|
+
@decoded_id_token = JWT.decode idToken, @public_key, false, { :algorithm => 'RS256' }
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|
390
|
+
|
391
|
+
# Os recursos desse module (admin) serão utilizadas apenas por usuários que possuem as roles do client realm-management
|
392
|
+
module Admin
|
393
|
+
class << self
|
394
|
+
end
|
395
|
+
|
396
|
+
def self.get_users(query_parameters = nil, access_token = nil)
|
397
|
+
generic_get("users/", query_parameters, access_token)
|
398
|
+
end
|
399
|
+
|
400
|
+
def self.create_user(user_representation, access_token = nil)
|
401
|
+
generic_post("users/", nil, user_representation, access_token)
|
402
|
+
end
|
403
|
+
|
404
|
+
def self.count_users(access_token = nil)
|
405
|
+
generic_get("users/count/", nil, access_token)
|
406
|
+
end
|
407
|
+
|
408
|
+
def self.get_user(id, access_token = nil)
|
409
|
+
generic_get("users/#{id}", nil, access_token)
|
410
|
+
end
|
411
|
+
|
412
|
+
def self.update_user(id, user_representation, access_token = nil)
|
413
|
+
generic_put("users/#{id}", nil, user_representation, access_token)
|
414
|
+
end
|
415
|
+
|
416
|
+
def self.delete_user(id, access_token = nil)
|
417
|
+
generic_delete("users/#{id}", nil, nil, access_token)
|
418
|
+
end
|
419
|
+
|
420
|
+
def self.revoke_consent_user(id, client_id = nil, access_token = nil)
|
421
|
+
if client_id.nil?
|
422
|
+
client_id = Keycloak::Client.client_id
|
423
|
+
end
|
424
|
+
generic_delete("users/#{id}/consents/#{client_id}", nil, nil, access_token)
|
425
|
+
end
|
426
|
+
|
427
|
+
def self.update_account_email(id, actions, redirect_uri = '', client_id = nil, access_token = nil)
|
428
|
+
if client_id.nil?
|
429
|
+
client_id = Keycloak::Client.client_id
|
430
|
+
end
|
431
|
+
generic_put("users/#{id}/execute-actions-email", {:redirect_uri => redirect_uri, :client_id => client_id}, actions, access_token)
|
432
|
+
end
|
433
|
+
|
434
|
+
def self.get_role_mappings(id, access_token = nil)
|
435
|
+
generic_get("users/#{id}/role-mappings", nil, access_token)
|
436
|
+
end
|
437
|
+
|
438
|
+
def self.get_clients(query_parameters = nil, access_token = nil)
|
439
|
+
generic_get("clients/", query_parameters, access_token)
|
440
|
+
end
|
441
|
+
|
442
|
+
def self.get_all_roles_client(id, access_token = nil)
|
443
|
+
generic_get("clients/#{id}/roles", nil, access_token)
|
444
|
+
end
|
445
|
+
|
446
|
+
def self.get_roles_client_by_name(id, role_name, access_token = nil)
|
447
|
+
generic_get("clients/#{id}/roles/#{role_name}", nil, access_token)
|
448
|
+
end
|
449
|
+
|
450
|
+
def self.add_client_level_roles_to_user(id, client, role_representation, access_token = nil)
|
451
|
+
generic_post("users/#{id}/role-mappings/clients/#{client}", nil, role_representation, access_token)
|
452
|
+
end
|
453
|
+
|
454
|
+
def self.delete_client_level_roles_from_user(id, client, role_representation, access_token = nil)
|
455
|
+
generic_delete("users/#{id}/role-mappings/clients/#{client}", nil, role_representation, access_token)
|
456
|
+
end
|
457
|
+
|
458
|
+
def self.get_client_level_role_for_user_and_app(id, client, access_token = nil)
|
459
|
+
generic_get("users/#{id}/role-mappings/clients/#{client}", nil, access_token)
|
460
|
+
end
|
461
|
+
|
462
|
+
def self.update_effective_user_roles(id, client_id, roles_names, access_token = nil)
|
463
|
+
client = JSON get_clients({ clientId: client_id }, access_token)
|
464
|
+
|
465
|
+
user_roles = JSON get_client_level_role_for_user_and_app(id, client[0]['id'], access_token)
|
466
|
+
|
467
|
+
roles = Array.new
|
468
|
+
# Include new role
|
469
|
+
roles_names.each do |r|
|
470
|
+
if r && !r.empty?
|
471
|
+
found = false
|
472
|
+
user_roles.each do |ur|
|
473
|
+
found = ur['name'] == r
|
474
|
+
break if found
|
475
|
+
found = false
|
476
|
+
end
|
477
|
+
if !found
|
478
|
+
role = JSON get_roles_client_by_name(client[0]['id'], r, access_token)
|
479
|
+
roles.push(role)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
garbage_roles = Array.new
|
485
|
+
# Exclude old role
|
486
|
+
user_roles.each do |ur|
|
487
|
+
found = false
|
488
|
+
roles_names.each do |r|
|
489
|
+
if r && !r.empty?
|
490
|
+
found = ur['name'] == r
|
491
|
+
break if found
|
492
|
+
found = false
|
493
|
+
end
|
494
|
+
end
|
495
|
+
if !found
|
496
|
+
garbage_roles.push(ur)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
if garbage_roles.count > 0
|
501
|
+
delete_client_level_roles_from_user(id, client[0]['id'], garbage_roles, access_token)
|
502
|
+
end
|
503
|
+
|
504
|
+
if roles.count > 0
|
505
|
+
add_client_level_roles_to_user(id, client[0]['id'], roles, access_token)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def self.reset_password(id, credential_representation, access_token = nil)
|
510
|
+
generic_put("users/#{id}/reset-password", nil, credential_representation, access_token)
|
511
|
+
end
|
512
|
+
|
513
|
+
def self.get_effective_client_level_role_composite_user(id, client, access_token = nil)
|
514
|
+
generic_get("users/#{id}/role-mappings/clients/#{client}/composite", nil, access_token)
|
515
|
+
end
|
516
|
+
|
517
|
+
# Generics methods
|
518
|
+
|
519
|
+
def self.generic_get(service, query_parameters = nil, access_token = nil)
|
520
|
+
Keycloak.generic_request(effective_access_token(access_token), full_url(service), query_parameters, nil, 'GET')
|
521
|
+
end
|
522
|
+
|
523
|
+
def self.generic_post(service, query_parameters, body_parameter, access_token = nil)
|
524
|
+
Keycloak.generic_request(effective_access_token(access_token), full_url(service), query_parameters, body_parameter, 'POST')
|
525
|
+
end
|
526
|
+
|
527
|
+
def self.generic_put(service, query_parameters, body_parameter, access_token = nil)
|
528
|
+
Keycloak.generic_request(effective_access_token(access_token), full_url(service), query_parameters, body_parameter, 'PUT')
|
529
|
+
end
|
530
|
+
|
531
|
+
def self.generic_delete(service, query_parameters = nil, body_parameter = nil, access_token = nil)
|
532
|
+
Keycloak.generic_request(effective_access_token(access_token), full_url(service), query_parameters, body_parameter, 'DELETE')
|
533
|
+
end
|
534
|
+
|
535
|
+
private
|
536
|
+
|
537
|
+
def self.effective_access_token(access_token)
|
538
|
+
if access_token.blank?
|
539
|
+
Keycloak::Client.token['access_token']
|
540
|
+
else
|
541
|
+
access_token
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def self.base_url
|
546
|
+
Keycloak::Client.auth_server_url + "/admin/realms/#{Keycloak::Client.realm}/"
|
547
|
+
end
|
548
|
+
|
549
|
+
def self.full_url(service)
|
550
|
+
base_url + service
|
551
|
+
end
|
552
|
+
|
553
|
+
end
|
554
|
+
|
555
|
+
module Internal
|
556
|
+
include Keycloak::Admin
|
557
|
+
|
558
|
+
class << self
|
559
|
+
end
|
560
|
+
|
561
|
+
def self.get_users(query_parameters = nil)
|
562
|
+
proc = lambda {|token|
|
563
|
+
Keycloak::Admin.get_users(query_parameters, token["access_token"])
|
564
|
+
}
|
565
|
+
|
566
|
+
default_call(proc)
|
567
|
+
end
|
568
|
+
|
569
|
+
def self.change_password(user_id, redirect_uri = '')
|
570
|
+
proc = lambda {|token|
|
571
|
+
Keycloak.generic_request(token["access_token"],
|
572
|
+
Keycloak::Admin.full_url("users/#{user_id}/execute-actions-email"),
|
573
|
+
{:redirect_uri => redirect_uri, :client_id => Keycloak::Client.client_id},
|
574
|
+
['UPDATE_PASSWORD'],
|
575
|
+
'PUT')
|
576
|
+
}
|
577
|
+
|
578
|
+
default_call(proc)
|
579
|
+
end
|
580
|
+
|
581
|
+
def self.forgot_password(user_login, redirect_uri = '')
|
582
|
+
user = get_user_info(user_login, true)
|
583
|
+
change_password(user['id'], redirect_uri)
|
584
|
+
end
|
585
|
+
|
586
|
+
def self.get_logged_user_info
|
587
|
+
proc = lambda {|token|
|
588
|
+
userinfo = JSON Keycloak::Client.get_userinfo
|
589
|
+
Keycloak.generic_request(token["access_token"],
|
590
|
+
Keycloak::Admin.full_url("users/#{userinfo['sub']}"),
|
591
|
+
nil, nil, 'GET')
|
592
|
+
}
|
593
|
+
|
594
|
+
default_call(proc)
|
595
|
+
end
|
596
|
+
|
597
|
+
def self.get_user_info(user_login, whole_word = false)
|
598
|
+
proc = lambda { |token|
|
599
|
+
if user_login.index('@').nil?
|
600
|
+
search = {:username => user_login}
|
601
|
+
else
|
602
|
+
search = {:email => user_login}
|
603
|
+
end
|
604
|
+
users = JSON Keycloak.generic_request(token["access_token"],
|
605
|
+
Keycloak::Admin.full_url("users/"),
|
606
|
+
search, nil, 'GET')
|
607
|
+
users[0]
|
608
|
+
if users.count.zero?
|
609
|
+
raise Keycloak::UserLoginNotFound
|
610
|
+
else
|
611
|
+
efective_index = -1
|
612
|
+
users.each_with_index do |user, i|
|
613
|
+
if whole_word
|
614
|
+
efective_index = i if user_login == user['username'] || user_login == user['email']
|
615
|
+
else
|
616
|
+
efective_index = 0
|
617
|
+
end
|
618
|
+
break if efective_index >= 0
|
619
|
+
end
|
620
|
+
|
621
|
+
if efective_index >= 0
|
622
|
+
if whole_word
|
623
|
+
users[efective_index]
|
624
|
+
else
|
625
|
+
users
|
626
|
+
end
|
627
|
+
else
|
628
|
+
raise Keycloak::UserLoginNotFound
|
629
|
+
end
|
630
|
+
end
|
631
|
+
}
|
632
|
+
|
633
|
+
default_call(proc)
|
634
|
+
end
|
635
|
+
|
636
|
+
def self.exists_name_or_email(value, user_id = '')
|
637
|
+
begin
|
638
|
+
usuario = Keycloak::Internal.get_user_info(value, true)
|
639
|
+
if user_id.empty? || user_id != usuario['id']
|
640
|
+
usuario.present?
|
641
|
+
else
|
642
|
+
false
|
643
|
+
end
|
644
|
+
rescue StandardError
|
645
|
+
false
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
def self.logged_federation_user?
|
650
|
+
info = get_logged_user_info
|
651
|
+
info['federationLink'] != nil
|
652
|
+
end
|
653
|
+
|
654
|
+
def self.create_simple_user(username, password, email, first_name, last_name, realm_roles_names, client_roles_names, proc = nil)
|
655
|
+
begin
|
656
|
+
username.downcase!
|
657
|
+
user = get_user_info(username, true)
|
658
|
+
newUser = false
|
659
|
+
rescue Keycloak::UserLoginNotFound
|
660
|
+
newUser = true
|
661
|
+
rescue
|
662
|
+
raise
|
663
|
+
end
|
664
|
+
|
665
|
+
proc_default = lambda { |token|
|
666
|
+
user_representation = { username: username,
|
667
|
+
email: email,
|
668
|
+
firstName: first_name,
|
669
|
+
lastName: last_name,
|
670
|
+
enabled: true }
|
671
|
+
|
672
|
+
if !newUser || Keycloak.generic_request(token["access_token"],
|
673
|
+
Keycloak::Admin.full_url("users/"),
|
674
|
+
nil, user_representation, 'POST')
|
675
|
+
|
676
|
+
user = get_user_info(username, true) if newUser
|
677
|
+
|
678
|
+
credential_representation = { type: "password",
|
679
|
+
temporary: false,
|
680
|
+
value: password }
|
681
|
+
|
682
|
+
if user['federationLink'] != nil || Keycloak.generic_request(token["access_token"],
|
683
|
+
Keycloak::Admin.full_url("users/#{user['id']}/reset-password"),
|
684
|
+
nil, credential_representation, 'PUT')
|
685
|
+
|
686
|
+
client = JSON Keycloak.generic_request(token["access_token"],
|
687
|
+
Keycloak::Admin.full_url("clients/"),
|
688
|
+
{ clientId: Keycloak::Client.client_id }, nil, 'GET')
|
689
|
+
|
690
|
+
if client_roles_names.count > 0
|
691
|
+
roles = []
|
692
|
+
client_roles_names.each do |r|
|
693
|
+
if r.present?
|
694
|
+
role = JSON Keycloak.generic_request(token["access_token"],
|
695
|
+
Keycloak::Admin.full_url("clients/#{client[0]['id']}/roles/#{r}"),
|
696
|
+
nil, nil, 'GET')
|
697
|
+
roles.push(role)
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
if roles.count > 0
|
702
|
+
Keycloak.generic_request(token["access_token"],
|
703
|
+
Keycloak::Admin.full_url("users/#{user['id']}/role-mappings/clients/#{client[0]['id']}"),
|
704
|
+
nil, roles, 'POST')
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
if realm_roles_names.count > 0
|
709
|
+
roles = []
|
710
|
+
realm_roles_names.each do |r|
|
711
|
+
if r.present?
|
712
|
+
role = JSON Keycloak.generic_request(token["access_token"],
|
713
|
+
Keycloak::Admin.full_url("roles/#{r}"),
|
714
|
+
nil, nil, 'GET')
|
715
|
+
roles.push(role)
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
if roles.count > 0
|
720
|
+
Keycloak.generic_request(token["access_token"],
|
721
|
+
Keycloak::Admin.full_url("users/#{user['id']}/role-mappings/realm"),
|
722
|
+
nil, roles, 'POST')
|
723
|
+
end
|
724
|
+
else
|
725
|
+
true
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
729
|
+
}
|
730
|
+
|
731
|
+
if default_call(proc_default)
|
732
|
+
proc.call user unless proc.nil?
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
def self.create_starter_user(username, password, email, client_roles_names, proc = nil)
|
737
|
+
Keycloak::Internal.create_simple_user(username, password, email, '', '', [], client_roles_names, proc)
|
738
|
+
end
|
739
|
+
|
740
|
+
def self.get_client_roles
|
741
|
+
proc = lambda {|token|
|
742
|
+
client = JSON Keycloak::Admin.get_clients({ clientId: Keycloak::Client.client_id }, token["access_token"])
|
743
|
+
|
744
|
+
Keycloak.generic_request(token["access_token"],
|
745
|
+
Keycloak::Admin.full_url("clients/#{client[0]['id']}/roles"),
|
746
|
+
nil, nil, 'GET')
|
747
|
+
}
|
748
|
+
|
749
|
+
default_call(proc)
|
750
|
+
end
|
751
|
+
|
752
|
+
def self.get_client_user_roles(user_id)
|
753
|
+
proc = lambda {|token|
|
754
|
+
client = JSON Keycloak::Admin.get_clients({ clientId: Keycloak::Client.client_id }, token["access_token"])
|
755
|
+
Keycloak::Admin.get_effective_client_level_role_composite_user(user_id, client[0]['id'], token["access_token"])
|
756
|
+
}
|
757
|
+
|
758
|
+
default_call(proc)
|
759
|
+
end
|
760
|
+
|
761
|
+
def self.has_role?(user_id, user_role)
|
762
|
+
roles = JSON get_client_user_roles(user_id)
|
763
|
+
if !roles.nil?
|
764
|
+
roles.each do |role|
|
765
|
+
return true if role['name'].to_s == user_role.to_s
|
766
|
+
end
|
767
|
+
false
|
768
|
+
else
|
769
|
+
false
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
protected
|
774
|
+
|
775
|
+
def self.default_call(proc)
|
776
|
+
begin
|
777
|
+
tk = nil
|
778
|
+
resp = nil
|
779
|
+
|
780
|
+
Keycloak::Client.get_installation
|
781
|
+
|
782
|
+
payload = { 'client_id' => Keycloak::Client.client_id,
|
783
|
+
'client_secret' => Keycloak::Client.secret,
|
784
|
+
'grant_type' => 'client_credentials' }
|
785
|
+
|
786
|
+
header = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
787
|
+
|
788
|
+
_request = -> do
|
789
|
+
RestClient.post(Keycloak::Client.configuration['token_endpoint'], payload, header){|response, request, result|
|
790
|
+
case response.code
|
791
|
+
when 200..399
|
792
|
+
tk = JSON response.body
|
793
|
+
resp = proc.call(tk)
|
794
|
+
else
|
795
|
+
response.return!
|
796
|
+
end
|
797
|
+
}
|
798
|
+
end
|
799
|
+
|
800
|
+
Keycloak::Client.exec_request _request
|
801
|
+
ensure
|
802
|
+
if tk
|
803
|
+
payload = { 'client_id' => Keycloak::Client.client_id,
|
804
|
+
'client_secret' => Keycloak::Client.secret,
|
805
|
+
'refresh_token' => tk["refresh_token"] }
|
806
|
+
|
807
|
+
header = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
808
|
+
_request = -> do
|
809
|
+
RestClient.post(Keycloak::Client.configuration['end_session_endpoint'], payload, header){|response, request, result|
|
810
|
+
case response.code
|
811
|
+
when 200..399
|
812
|
+
resp if resp.nil?
|
813
|
+
else
|
814
|
+
response.return!
|
815
|
+
end
|
816
|
+
}
|
817
|
+
end
|
818
|
+
Keycloak::Client.exec_request _request
|
819
|
+
end
|
820
|
+
end
|
821
|
+
end
|
822
|
+
|
823
|
+
end
|
824
|
+
|
825
|
+
private
|
826
|
+
|
827
|
+
def self.generic_request(access_token, uri, query_parameters, body_parameter, method)
|
828
|
+
Keycloak::Client.verify_setup
|
829
|
+
final_url = uri
|
830
|
+
|
831
|
+
header = {'Content-Type' => 'application/x-www-form-urlencoded',
|
832
|
+
'Authorization' => "Bearer #{access_token}"}
|
833
|
+
|
834
|
+
if query_parameters
|
835
|
+
parameters = URI.encode_www_form(query_parameters)
|
836
|
+
final_url = final_url << '?' << parameters
|
837
|
+
end
|
838
|
+
|
839
|
+
case method.upcase
|
840
|
+
when 'GET'
|
841
|
+
_request = -> do
|
842
|
+
RestClient.get(final_url, header){|response, request, result|
|
843
|
+
rescue_response(response)
|
844
|
+
}
|
845
|
+
end
|
846
|
+
when 'POST', 'PUT'
|
847
|
+
header["Content-Type"] = 'application/json'
|
848
|
+
parameters = JSON.generate body_parameter
|
849
|
+
_request = -> do
|
850
|
+
case method.upcase
|
851
|
+
when 'POST'
|
852
|
+
RestClient.post(final_url, parameters, header){|response, request, result|
|
853
|
+
rescue_response(response)
|
854
|
+
}
|
855
|
+
else
|
856
|
+
RestClient.put(final_url, parameters, header){|response, request, result|
|
857
|
+
rescue_response(response)
|
858
|
+
}
|
859
|
+
end
|
860
|
+
end
|
861
|
+
when 'DELETE'
|
862
|
+
_request = -> do
|
863
|
+
if body_parameter
|
864
|
+
header["Content-Type"] = 'application/json'
|
865
|
+
parameters = JSON.generate body_parameter
|
866
|
+
RestClient::Request.execute(method: :delete, url: final_url,
|
867
|
+
payload: parameters, headers: header) { |response, request, result|
|
868
|
+
rescue_response(response)
|
869
|
+
}
|
870
|
+
else
|
871
|
+
RestClient.delete(final_url, header) { |response, request, result|
|
872
|
+
rescue_response(response)
|
873
|
+
}
|
874
|
+
end
|
875
|
+
end
|
876
|
+
else
|
877
|
+
raise
|
878
|
+
end
|
879
|
+
|
880
|
+
_request.call
|
881
|
+
|
882
|
+
end
|
883
|
+
|
884
|
+
def self.rescue_response(response)
|
885
|
+
case response.code
|
886
|
+
when 200..399
|
887
|
+
if response.body.empty?
|
888
|
+
true
|
889
|
+
else
|
890
|
+
response.body
|
891
|
+
end
|
892
|
+
when 400..499
|
893
|
+
begin
|
894
|
+
response.return!
|
895
|
+
rescue RestClient::ExceptionWithResponse => err
|
896
|
+
raise ActionController::RoutingError.new(err.response)
|
897
|
+
end
|
898
|
+
else
|
899
|
+
if Keycloak.explode_exception
|
900
|
+
response.return!
|
901
|
+
else
|
902
|
+
begin
|
903
|
+
response.return!
|
904
|
+
rescue RestClient::ExceptionWithResponse => err
|
905
|
+
err.response
|
906
|
+
rescue StandardError => e
|
907
|
+
e.message
|
908
|
+
end
|
909
|
+
end
|
910
|
+
end
|
911
|
+
end
|
912
|
+
end
|