keycloak_oauth 0.1.6 → 1.0.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 +4 -4
- data/app/controllers/keycloak_oauth/callbacks_controller.rb +2 -3
- data/app/services/keycloak_oauth/authentication_service.rb +13 -41
- data/app/services/keycloak_oauth/authorizable_service.rb +56 -6
- data/app/services/keycloak_oauth/get_users_service.rb +42 -0
- data/app/services/keycloak_oauth/logout_service.rb +4 -4
- data/app/services/keycloak_oauth/post_token_service.rb +43 -0
- data/app/services/keycloak_oauth/post_users_service.rb +47 -0
- data/app/services/keycloak_oauth/put_execute_actions_email_service.rb +57 -0
- data/app/services/keycloak_oauth/user_info_retrieval_service.rb +3 -3
- data/lib/keycloak_oauth/connection.rb +3 -3
- data/lib/keycloak_oauth/endpoints.rb +8 -0
- data/lib/keycloak_oauth/version.rb +1 -1
- metadata +12 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c8198edc1a928cd020798264f22e0ebcbbc6a4905aa4ebad578be3404ca92c4
|
4
|
+
data.tar.gz: a4cfff847382aba3b6b2757ca2e53da0dc1d21334ccaf21db2bd7b12083c8816
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e477873732dd5931bb3769d69831c77dbd9c18329c1c9391361a80e17bf1cdbfbf00847563bdbf32f3fe613121e98c89c8965f31adcefe71736d8d7a9e0cea3
|
7
|
+
data.tar.gz: 68d870d6d136be6e0f170bc1f1361c9325558829d52371f5708fbcd6043ed2e1f38680918fb44d31cf0b399f982c54389ff05e8a7ea071c57bbedf9b314dd8a9
|
@@ -7,8 +7,7 @@ module KeycloakOauth
|
|
7
7
|
def oauth2
|
8
8
|
authentication_service = KeycloakOauth::AuthenticationService.new(
|
9
9
|
authentication_params: authentication_params,
|
10
|
-
session: session
|
11
|
-
redirect_uri: current_uri_without_params
|
10
|
+
session: session
|
12
11
|
)
|
13
12
|
authentication_service.authenticate
|
14
13
|
map_authenticatable_if_implemented(session)
|
@@ -19,7 +18,7 @@ module KeycloakOauth
|
|
19
18
|
private
|
20
19
|
|
21
20
|
def authentication_params
|
22
|
-
params.permit(:code)
|
21
|
+
params.permit(:code).merge({ redirect_uri: current_uri_without_params })
|
23
22
|
end
|
24
23
|
|
25
24
|
def map_authenticatable_if_implemented(request)
|
@@ -1,59 +1,31 @@
|
|
1
|
-
require 'net/http'
|
2
|
-
|
3
1
|
module KeycloakOauth
|
4
|
-
class AuthenticationError < StandardError; end
|
5
|
-
|
6
2
|
class AuthenticationService
|
7
|
-
GRANT_TYPE = 'authorization_code'.freeze
|
8
|
-
CONTENT_TYPE = 'application/x-www-form-urlencoded'.freeze
|
9
3
|
ACCESS_TOKEN_KEY = 'access_token'.freeze
|
10
4
|
REFRESH_TOKEN_KEY = 'refresh_token'.freeze
|
11
5
|
|
12
|
-
attr_reader :session
|
6
|
+
attr_reader :session, :authentication_params
|
13
7
|
|
14
|
-
def initialize(authentication_params:, session
|
15
|
-
@
|
8
|
+
def initialize(authentication_params:, session:)
|
9
|
+
@authentication_params = authentication_params
|
16
10
|
@session = session
|
17
|
-
@redirect_uri = redirect_uri
|
18
11
|
end
|
19
12
|
|
20
13
|
def authenticate
|
21
|
-
|
14
|
+
post_token_service = KeycloakOauth::PostTokenService.new(
|
15
|
+
connection: KeycloakOauth.connection,
|
16
|
+
request_params: authentication_params
|
17
|
+
)
|
18
|
+
post_token_service.perform
|
19
|
+
store_credentials(post_token_service)
|
22
20
|
end
|
23
21
|
|
24
22
|
private
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
def get_tokens
|
29
|
-
uri = URI.parse(KeycloakOauth.connection.authentication_endpoint)
|
30
|
-
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
31
|
-
request = Net::HTTP::Post.new(uri)
|
32
|
-
request.set_content_type(CONTENT_TYPE)
|
33
|
-
request.set_form_data(token_request_params)
|
34
|
-
http.request(request)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def token_request_params
|
39
|
-
{
|
40
|
-
client_id: KeycloakOauth.connection.client_id,
|
41
|
-
client_secret: KeycloakOauth.connection.client_secret,
|
42
|
-
grant_type: GRANT_TYPE,
|
43
|
-
code: code,
|
44
|
-
redirect_uri: redirect_uri
|
45
|
-
}
|
46
|
-
end
|
47
|
-
|
48
|
-
def store_credentials(http_response)
|
49
|
-
response_hash = JSON.parse(http_response.body)
|
24
|
+
def store_credentials(post_token_service)
|
25
|
+
response_hash = post_token_service.parsed_response_body
|
50
26
|
|
51
|
-
|
52
|
-
|
53
|
-
session[:refresh_token] = response_hash[REFRESH_TOKEN_KEY]
|
54
|
-
else
|
55
|
-
raise KeycloakOauth::AuthenticationError.new(response_hash)
|
56
|
-
end
|
27
|
+
session[:access_token] = response_hash[ACCESS_TOKEN_KEY]
|
28
|
+
session[:refresh_token] = response_hash[REFRESH_TOKEN_KEY]
|
57
29
|
end
|
58
30
|
end
|
59
31
|
end
|
@@ -2,23 +2,73 @@ require 'net/http'
|
|
2
2
|
|
3
3
|
module KeycloakOauth
|
4
4
|
class AuthorizableError < StandardError; end
|
5
|
+
class NotFoundError < StandardError; end
|
5
6
|
|
6
7
|
class AuthorizableService
|
7
|
-
HTTP_SUCCESS_CODES = [Net::HTTPOK, Net::HTTPNoContent]
|
8
|
-
|
8
|
+
HTTP_SUCCESS_CODES = [Net::HTTPOK, Net::HTTPNoContent, Net::HTTPCreated]
|
9
|
+
CONTENT_TYPE_X_WWW_FORM_URLENCODED = 'application/x-www-form-urlencoded'.freeze
|
10
|
+
CONTENT_TYPE_JSON = 'application/json'.freeze
|
9
11
|
AUTHORIZATION_HEADER = 'Authorization'.freeze
|
10
12
|
|
13
|
+
attr_reader :http_response, :parsed_response_body
|
14
|
+
|
15
|
+
def perform
|
16
|
+
@http_response = send_request
|
17
|
+
@parsed_response_body ||= parse_response_body(http_response)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.uri_with_supported_query_params(url, supported_params, given_params)
|
21
|
+
uri = URI.parse(url)
|
22
|
+
|
23
|
+
query_params = supported_params.inject({}) do |acc, query_param|
|
24
|
+
acc[query_param] = given_params[query_param] if given_params[query_param].present?
|
25
|
+
acc
|
26
|
+
end
|
27
|
+
|
28
|
+
log_unsupported_params(given_params.keys - supported_params)
|
29
|
+
|
30
|
+
uri.query = URI.encode_www_form(query_params) if query_params.values.any?
|
31
|
+
uri
|
32
|
+
end
|
33
|
+
|
11
34
|
private
|
12
35
|
|
13
|
-
def
|
14
|
-
|
36
|
+
def parse_response_body(http_response)
|
37
|
+
response_body = http_response.body.present? ? JSON.parse(http_response.body) : http_response.body
|
15
38
|
|
16
|
-
return
|
39
|
+
return response_body if HTTP_SUCCESS_CODES.include?(http_response.code_type)
|
17
40
|
|
18
41
|
# TODO: For now, we assume that the access token is always valid.
|
19
42
|
# We do not yet handle the case where a refresh token is passed in and
|
20
43
|
# used if the access token has expired.
|
21
|
-
raise KeycloakOauth::AuthorizableError.new(
|
44
|
+
raise KeycloakOauth::AuthorizableError.new(error_message_from(response_body))
|
45
|
+
end
|
46
|
+
|
47
|
+
def error_message_from(response)
|
48
|
+
# Keycloak sometimes sends back a hash containing the "errorMessage" key,
|
49
|
+
# other times it returns a hash containing the "error_description key" key
|
50
|
+
# and other times it returns an empty string. There could be more cases,
|
51
|
+
# but for the moment we are only handling these three.
|
52
|
+
case response.class.to_s
|
53
|
+
when 'Hash'
|
54
|
+
if response.has_key?('errorMessage')
|
55
|
+
return response['errorMessage']
|
56
|
+
elsif response.has_key?('error_description')
|
57
|
+
return response['error_description']
|
58
|
+
elsif response.has_key?('error')
|
59
|
+
return response['error']
|
60
|
+
end
|
61
|
+
when 'String'
|
62
|
+
return response
|
63
|
+
else
|
64
|
+
'Unexpected Keycloak error'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.log_unsupported_params(query_params)
|
69
|
+
query_params.each do |query_param|
|
70
|
+
Rails.logger.warn { "Unsupported query param was passed in: #{query_param}" }
|
71
|
+
end
|
22
72
|
end
|
23
73
|
end
|
24
74
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module KeycloakOauth
|
4
|
+
class GetUsersService < KeycloakOauth::AuthorizableService
|
5
|
+
SUPPORTED_QUERY_PARAMS = %i(briefRepresentation email first firstName lastName max search username)
|
6
|
+
|
7
|
+
attr_reader :connection, :options
|
8
|
+
|
9
|
+
def initialize(connection:, access_token:, refresh_token:, options: {})
|
10
|
+
@connection = connection
|
11
|
+
@access_token = access_token
|
12
|
+
@refresh_token = refresh_token
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def send_request
|
17
|
+
get_users
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :access_token, :refresh_token
|
23
|
+
|
24
|
+
def get_users
|
25
|
+
uri = build_uri
|
26
|
+
|
27
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
28
|
+
request = Net::HTTP::Get.new(uri)
|
29
|
+
request[AUTHORIZATION_HEADER] = "Bearer #{access_token}"
|
30
|
+
http.request(request)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def build_uri
|
35
|
+
self.class.uri_with_supported_query_params(
|
36
|
+
connection.users_endpoint,
|
37
|
+
SUPPORTED_QUERY_PARAMS,
|
38
|
+
options
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -6,8 +6,8 @@ module KeycloakOauth
|
|
6
6
|
@session = session
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
|
9
|
+
def send_request
|
10
|
+
post_logout
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
@@ -16,9 +16,9 @@ module KeycloakOauth
|
|
16
16
|
|
17
17
|
def post_logout
|
18
18
|
uri = URI.parse(KeycloakOauth.connection.logout_endpoint)
|
19
|
-
Net::HTTP.start(uri.host, uri.port) do |http|
|
19
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
20
20
|
request = Net::HTTP::Post.new(uri)
|
21
|
-
request.set_content_type(
|
21
|
+
request.set_content_type(CONTENT_TYPE_X_WWW_FORM_URLENCODED)
|
22
22
|
request.set_form_data(logout_request_params)
|
23
23
|
request[AUTHORIZATION_HEADER] = "Bearer #{access_token}"
|
24
24
|
http.request(request)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module KeycloakOauth
|
4
|
+
class PostTokenService < KeycloakOauth::AuthorizableService
|
5
|
+
DEFAULT_GRANT_TYPE = 'authorization_code'.freeze
|
6
|
+
|
7
|
+
attr_reader :request_params, :connection
|
8
|
+
|
9
|
+
def initialize(connection:, access_token: nil, refresh_token: nil, request_params:)
|
10
|
+
@connection = connection
|
11
|
+
@access_token = access_token
|
12
|
+
@refresh_token = refresh_token
|
13
|
+
@request_params = request_params
|
14
|
+
end
|
15
|
+
|
16
|
+
def send_request
|
17
|
+
post_token
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :code, :redirect_uri, :access_token, :refresh_token
|
23
|
+
|
24
|
+
def post_token
|
25
|
+
uri = URI.parse(connection.authentication_endpoint)
|
26
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
27
|
+
request = Net::HTTP::Post.new(uri)
|
28
|
+
request[AUTHORIZATION_HEADER] = "Bearer #{access_token}" if access_token.present?
|
29
|
+
request.set_content_type(CONTENT_TYPE_X_WWW_FORM_URLENCODED)
|
30
|
+
request.set_form_data(token_request_params)
|
31
|
+
http.request(request)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def token_request_params
|
36
|
+
{
|
37
|
+
client_id: connection.client_id,
|
38
|
+
client_secret: connection.client_secret,
|
39
|
+
grant_type: DEFAULT_GRANT_TYPE
|
40
|
+
}.merge(request_params)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module KeycloakOauth
|
4
|
+
class DuplicationError < StandardError; end
|
5
|
+
|
6
|
+
class PostUsersService < KeycloakOauth::AuthorizableService
|
7
|
+
attr_reader :request_params, :connection, :user_params
|
8
|
+
|
9
|
+
def initialize(connection:, access_token:, refresh_token:, user_params:)
|
10
|
+
@connection = connection
|
11
|
+
@access_token = access_token
|
12
|
+
@refresh_token = refresh_token
|
13
|
+
@user_params = user_params
|
14
|
+
end
|
15
|
+
|
16
|
+
def send_request
|
17
|
+
post_users
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_accessor :access_token, :refresh_token
|
23
|
+
|
24
|
+
def post_users
|
25
|
+
uri = URI.parse(connection.users_endpoint)
|
26
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
27
|
+
request = Net::HTTP::Post.new(uri)
|
28
|
+
request.set_content_type(CONTENT_TYPE_JSON)
|
29
|
+
request[AUTHORIZATION_HEADER] = "Bearer #{access_token}"
|
30
|
+
request.body = user_params.to_json
|
31
|
+
http.request(request)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_response_body(http_response)
|
36
|
+
super
|
37
|
+
rescue KeycloakOauth::AuthorizableError => exception
|
38
|
+
raise exception unless is_exception_a_duplication?(exception)
|
39
|
+
raise KeycloakOauth::DuplicationError.new(exception)
|
40
|
+
end
|
41
|
+
|
42
|
+
def is_exception_a_duplication?(exception)
|
43
|
+
exception.message == "User exists with same email" ||
|
44
|
+
exception.message == "User exists with same username"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module KeycloakOauth
|
4
|
+
class PutExecuteActionsEmailService < KeycloakOauth::AuthorizableService
|
5
|
+
SUPPORTED_QUERY_PARAMS = %i(client_id lifespan redirect_uri)
|
6
|
+
|
7
|
+
attr_reader :connection, :user_id, :actions, :options
|
8
|
+
|
9
|
+
def initialize(connection: KeycloakOauth.connection, access_token:, refresh_token:, user_id:, actions:, options: {})
|
10
|
+
@connection = connection
|
11
|
+
@access_token = access_token
|
12
|
+
@refresh_token = refresh_token
|
13
|
+
@user_id = user_id
|
14
|
+
@actions = actions
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_request
|
19
|
+
put_execute_actions_email
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_accessor :access_token, :refresh_token
|
25
|
+
|
26
|
+
def put_execute_actions_email
|
27
|
+
uri = build_uri
|
28
|
+
|
29
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
30
|
+
request = Net::HTTP::Put.new(uri)
|
31
|
+
request.set_content_type(CONTENT_TYPE_JSON)
|
32
|
+
request[AUTHORIZATION_HEADER] = "Bearer #{access_token}"
|
33
|
+
request.body = actions.to_json
|
34
|
+
http.request(request)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_uri
|
39
|
+
self.class.uri_with_supported_query_params(
|
40
|
+
connection.put_execute_actions_email_endpoint(user_id),
|
41
|
+
SUPPORTED_QUERY_PARAMS,
|
42
|
+
options
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_response_body(http_response)
|
47
|
+
super
|
48
|
+
rescue KeycloakOauth::AuthorizableError => exception
|
49
|
+
raise exception unless not_found_error?(exception)
|
50
|
+
raise KeycloakOauth::NotFoundError.new(exception)
|
51
|
+
end
|
52
|
+
|
53
|
+
def not_found_error?(exception)
|
54
|
+
exception.message == "User not found"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -9,8 +9,8 @@ module KeycloakOauth
|
|
9
9
|
@refresh_token = refresh_token
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
|
12
|
+
def send_request
|
13
|
+
get_user
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
@@ -21,7 +21,7 @@ module KeycloakOauth
|
|
21
21
|
uri = URI.parse(KeycloakOauth.connection.user_info_endpoint)
|
22
22
|
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
23
23
|
request = Net::HTTP::Get.new(uri)
|
24
|
-
request.set_content_type(
|
24
|
+
request.set_content_type(CONTENT_TYPE_X_WWW_FORM_URLENCODED)
|
25
25
|
request[AUTHORIZATION_HEADER] = "Bearer #{access_token}"
|
26
26
|
http.request(request)
|
27
27
|
end
|
@@ -19,13 +19,13 @@ module KeycloakOauth
|
|
19
19
|
access_token: access_token,
|
20
20
|
refresh_token: refresh_token
|
21
21
|
)
|
22
|
-
service.
|
23
|
-
service.
|
22
|
+
service.perform
|
23
|
+
service.parsed_response_body
|
24
24
|
end
|
25
25
|
|
26
26
|
def logout(session:)
|
27
27
|
service = KeycloakOauth::LogoutService.new(session)
|
28
|
-
service.
|
28
|
+
service.perform
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -20,5 +20,13 @@ module KeycloakOauth
|
|
20
20
|
def logout_endpoint
|
21
21
|
"#{auth_url}/realms/#{realm}/protocol/openid-connect/logout"
|
22
22
|
end
|
23
|
+
|
24
|
+
def users_endpoint
|
25
|
+
"#{auth_url}/admin/realms/#{realm}/users"
|
26
|
+
end
|
27
|
+
|
28
|
+
def put_execute_actions_email_endpoint(user_id)
|
29
|
+
"#{auth_url}/admin/realms/#{realm}/users/#{user_id}/execute-actions-email"
|
30
|
+
end
|
23
31
|
end
|
24
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: keycloak_oauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- simplificator
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '0'
|
61
|
-
description:
|
61
|
+
description:
|
62
62
|
email:
|
63
63
|
- dev@simplificator.com
|
64
64
|
executables: []
|
@@ -70,7 +70,11 @@ files:
|
|
70
70
|
- app/controllers/keycloak_oauth/callbacks_controller.rb
|
71
71
|
- app/services/keycloak_oauth/authentication_service.rb
|
72
72
|
- app/services/keycloak_oauth/authorizable_service.rb
|
73
|
+
- app/services/keycloak_oauth/get_users_service.rb
|
73
74
|
- app/services/keycloak_oauth/logout_service.rb
|
75
|
+
- app/services/keycloak_oauth/post_token_service.rb
|
76
|
+
- app/services/keycloak_oauth/post_users_service.rb
|
77
|
+
- app/services/keycloak_oauth/put_execute_actions_email_service.rb
|
74
78
|
- app/services/keycloak_oauth/user_info_retrieval_service.rb
|
75
79
|
- config/routes.rb
|
76
80
|
- lib/keycloak_oauth.rb
|
@@ -86,7 +90,7 @@ metadata:
|
|
86
90
|
homepage_uri: https://rubygems.org/gems/keycloak_oauth
|
87
91
|
source_code_uri: https://github.com/simplificator/keycloak_oauth
|
88
92
|
changelog_uri: https://github.com/simplificator/keycloak_oauth
|
89
|
-
post_install_message:
|
93
|
+
post_install_message:
|
90
94
|
rdoc_options: []
|
91
95
|
require_paths:
|
92
96
|
- lib
|
@@ -94,15 +98,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
98
|
requirements:
|
95
99
|
- - ">="
|
96
100
|
- !ruby/object:Gem::Version
|
97
|
-
version: 2.
|
101
|
+
version: 2.6.0
|
98
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
103
|
requirements:
|
100
104
|
- - ">="
|
101
105
|
- !ruby/object:Gem::Version
|
102
106
|
version: '0'
|
103
107
|
requirements: []
|
104
|
-
rubygems_version: 3.
|
105
|
-
signing_key:
|
108
|
+
rubygems_version: 3.1.6
|
109
|
+
signing_key:
|
106
110
|
specification_version: 4
|
107
111
|
summary: Implementing OAuth with Keycloak in Ruby
|
108
112
|
test_files: []
|