keycloak-api-rails 1.1.1 → 2.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/CHANGELOG.md +60 -0
- data/MIT-LICENSE +1 -1
- data/README.md +51 -21
- data/keycloak-api-rails.gemspec +16 -6
- data/lib/keycloak-api-rails/authentication.rb +3 -11
- data/lib/keycloak-api-rails/configuration.rb +84 -0
- data/lib/keycloak-api-rails/helper.rb +31 -4
- data/lib/keycloak-api-rails/http_client.rb +54 -19
- data/lib/keycloak-api-rails/middleware.rb +17 -20
- data/lib/keycloak-api-rails/public_key_cached_resolver.rb +26 -7
- data/lib/keycloak-api-rails/railtie.rb +16 -1
- data/lib/keycloak-api-rails/service.rb +77 -28
- data/lib/keycloak-api-rails/testing.rb +5 -3
- data/lib/keycloak-api-rails/token_error.rb +41 -23
- data/lib/keycloak-api-rails/version.rb +1 -1
- data/lib/keycloak-api-rails.rb +21 -6
- metadata +39 -23
- data/.github/workflows/ci.yml +0 -35
- data/.github/workflows/release.yml +0 -52
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/Dockerfile +0 -13
- data/Gemfile +0 -3
- data/Rakefile +0 -6
- data/spec/keycloak-api-rails/activesupport_free_spec.rb +0 -18
- data/spec/keycloak-api-rails/authentication_spec.rb +0 -70
- data/spec/keycloak-api-rails/configuration_spec.rb +0 -101
- data/spec/keycloak-api-rails/helper_spec.rb +0 -94
- data/spec/keycloak-api-rails/public_key_cached_resolver_spec.rb +0 -80
- data/spec/keycloak-api-rails/service_spec.rb +0 -281
- data/spec/keycloak-api-rails/testing_spec.rb +0 -102
- data/spec/spec_helper.rb +0 -14
- data/spec/support/activesupport_free_probe.rb +0 -131
- data/spec/support/public_key_cached_resolver_stub.rb +0 -11
- data/spec/support/public_key_resolver_stub.rb +0 -7
- data/spec/support/rails_helper.rb +0 -4
|
@@ -1,30 +1,49 @@
|
|
|
1
1
|
module KeycloakApiRails
|
|
2
2
|
class PublicKeyCachedResolver
|
|
3
|
+
FAILED_REFRESH_RETRY_DELAY_IN_SECONDS = 10
|
|
4
|
+
|
|
3
5
|
attr_reader :cached_public_key_retrieved_at
|
|
4
6
|
|
|
5
|
-
def initialize(http_client, realm_id, public_key_cache_ttl)
|
|
7
|
+
def initialize(http_client, realm_id, public_key_cache_ttl, logger = nil)
|
|
6
8
|
@resolver = PublicKeyResolver.new(http_client, realm_id)
|
|
7
9
|
@public_key_cache_ttl = public_key_cache_ttl
|
|
10
|
+
@logger = logger
|
|
8
11
|
@cached_public_keys = nil
|
|
9
12
|
@cached_public_key_retrieved_at = nil
|
|
13
|
+
@last_refresh_failure_at = nil
|
|
14
|
+
@mutex = Mutex.new
|
|
10
15
|
end
|
|
11
16
|
|
|
12
17
|
def self.from_configuration(http_client, configuration)
|
|
13
|
-
|
|
18
|
+
new(http_client, configuration.realm_id, configuration.public_key_cache_ttl, configuration.logger)
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
def find_public_keys
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@
|
|
22
|
+
@mutex.synchronize do
|
|
23
|
+
refresh_public_keys if public_keys_are_outdated?
|
|
24
|
+
@cached_public_keys
|
|
20
25
|
end
|
|
21
|
-
@cached_public_keys
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
private
|
|
25
29
|
|
|
30
|
+
def refresh_public_keys
|
|
31
|
+
@cached_public_keys = @resolver.find_public_keys
|
|
32
|
+
@cached_public_key_retrieved_at = Time.now
|
|
33
|
+
@last_refresh_failure_at = nil
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
raise if @cached_public_keys.nil?
|
|
36
|
+
|
|
37
|
+
@last_refresh_failure_at = Time.now
|
|
38
|
+
@logger&.warn("KeycloakApiRails: could not refresh the public keys (#{e.class}: #{e.message}). Keeping the ones retrieved at #{@cached_public_key_retrieved_at}.")
|
|
39
|
+
end
|
|
40
|
+
|
|
26
41
|
def public_keys_are_outdated?
|
|
27
|
-
@cached_public_keys.nil? ||
|
|
42
|
+
@cached_public_keys.nil? ||
|
|
43
|
+
@cached_public_key_retrieved_at.nil? ||
|
|
44
|
+
(Time.now > @cached_public_key_retrieved_at + @public_key_cache_ttl &&
|
|
45
|
+
(@last_refresh_failure_at.nil? ||
|
|
46
|
+
Time.now > @last_refresh_failure_at + FAILED_REFRESH_RETRY_DELAY_IN_SECONDS))
|
|
28
47
|
end
|
|
29
48
|
end
|
|
30
49
|
end
|
|
@@ -5,5 +5,20 @@ module KeycloakApiRails
|
|
|
5
5
|
initializer("keycloak.insert_middleware") do |app|
|
|
6
6
|
app.config.middleware.use(KeycloakApiRails::Middleware)
|
|
7
7
|
end
|
|
8
|
+
|
|
9
|
+
# Runs once every initializer has run, config/initializers/keycloak.rb included, so that a
|
|
10
|
+
# misconfiguration fails at boot instead of on the first request reaching the middleware.
|
|
11
|
+
config.after_initialize do
|
|
12
|
+
keycloak_configuration = KeycloakApiRails.config
|
|
13
|
+
keycloak_configuration.validate!
|
|
14
|
+
|
|
15
|
+
unless keycloak_configuration.server_configured?
|
|
16
|
+
keycloak_configuration.logger.warn(
|
|
17
|
+
"KeycloakApiRails: 'server_url' and 'realm_id' are not both configured. No token can be " \
|
|
18
|
+
"verified until they are, unless the public key resolver is replaced -- as " \
|
|
19
|
+
"\"keycloak-api-rails/testing\" does."
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
8
23
|
end
|
|
9
|
-
end
|
|
24
|
+
end
|
|
@@ -1,38 +1,43 @@
|
|
|
1
1
|
module KeycloakApiRails
|
|
2
|
+
class MissingPublicKeysError < StandardError; end
|
|
3
|
+
|
|
2
4
|
class Service
|
|
3
|
-
|
|
5
|
+
|
|
4
6
|
def initialize(key_resolver)
|
|
7
|
+
configuration = KeycloakApiRails.config
|
|
5
8
|
@key_resolver = key_resolver
|
|
6
|
-
@skip_paths =
|
|
7
|
-
@opt_in =
|
|
8
|
-
@
|
|
9
|
-
@
|
|
9
|
+
@skip_paths = normalize_skip_paths(configuration.skip_paths)
|
|
10
|
+
@opt_in = configuration.opt_in
|
|
11
|
+
@token_expiration_tolerance_in_seconds = configuration.token_expiration_tolerance_in_seconds
|
|
12
|
+
@expected_audiences = Array(configuration.expected_audience).map(&:to_s)
|
|
13
|
+
@expected_token_type = configuration.expected_token_type
|
|
14
|
+
@verify_not_before = configuration.verify_not_before
|
|
15
|
+
@allow_token_in_query_string = configuration.allow_token_in_query_string
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
def decode_and_verify(token)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
decoded_token
|
|
20
|
-
else
|
|
21
|
-
raise TokenError.expired(token)
|
|
22
|
-
end
|
|
23
|
-
else
|
|
24
|
-
raise TokenError.no_token(token)
|
|
19
|
+
raise TokenError.no_token(token) if token.nil? || token.empty?
|
|
20
|
+
|
|
21
|
+
public_keys = @key_resolver.find_public_keys
|
|
22
|
+
|
|
23
|
+
if public_keys.nil?
|
|
24
|
+
raise MissingPublicKeysError, "No Keycloak public key is available to verify the token"
|
|
25
25
|
end
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
rescue JSON::JWT::InvalidFormat
|
|
31
|
-
raise TokenError.invalid_format(token, e)
|
|
26
|
+
|
|
27
|
+
decoded_token = decode(token, public_keys)
|
|
28
|
+
verify_claims!(token, decoded_token)
|
|
29
|
+
decoded_token
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
def read_token(uri, headers)
|
|
35
|
-
|
|
33
|
+
header_token = Helper.read_token_from_headers(headers)
|
|
34
|
+
if !header_token.empty?
|
|
35
|
+
header_token
|
|
36
|
+
elsif @allow_token_in_query_string
|
|
37
|
+
Helper.read_token_from_query_string(uri).to_s
|
|
38
|
+
else
|
|
39
|
+
""
|
|
40
|
+
end
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
def need_middleware_authentication?(method, path, headers)
|
|
@@ -41,14 +46,39 @@ module KeycloakApiRails
|
|
|
41
46
|
|
|
42
47
|
private
|
|
43
48
|
|
|
49
|
+
def decode(token, public_keys)
|
|
50
|
+
decoded_token = JSON::JWT.decode(token, public_keys)
|
|
51
|
+
decoded_token.verify!(public_keys)
|
|
52
|
+
decoded_token
|
|
53
|
+
rescue JSON::JWT::VerificationFailed, JSON::JWK::Set::KidNotFound => e
|
|
54
|
+
raise TokenError.verification_failed(token, e)
|
|
55
|
+
rescue JSON::JWT::InvalidFormat => e
|
|
56
|
+
raise TokenError.invalid_format(token, e)
|
|
57
|
+
rescue StandardError => e
|
|
58
|
+
raise TokenError.unknown(token, e)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def verify_claims!(token, decoded_token)
|
|
62
|
+
raise TokenError.missing_claim(token, "exp") unless decoded_token.key?("exp")
|
|
63
|
+
raise TokenError.expired(token) if expired?(decoded_token)
|
|
64
|
+
raise TokenError.not_yet_valid(token) if not_yet_valid?(decoded_token)
|
|
65
|
+
raise TokenError.invalid_audience(token) unless audience_valid?(decoded_token)
|
|
66
|
+
raise TokenError.invalid_token_type(token) unless token_type_valid?(decoded_token)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def normalize_skip_paths(skip_paths)
|
|
70
|
+
(skip_paths || {}).each_with_object({}) do |(method, paths), normalized|
|
|
71
|
+
normalized[method.to_s.downcase.to_sym] = Array(paths)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
44
75
|
def should_skip?(method, path)
|
|
45
|
-
|
|
46
|
-
skip_paths
|
|
47
|
-
!skip_paths.nil? && !skip_paths.empty? && !skip_paths.find_index { |skip_path| skip_path.match(path) }.nil?
|
|
76
|
+
skip_paths = @skip_paths[method&.to_s&.downcase&.to_sym]
|
|
77
|
+
!skip_paths.nil? && skip_paths.any? { |skip_path| skip_path.match(path) }
|
|
48
78
|
end
|
|
49
79
|
|
|
50
80
|
def is_preflight?(method, headers)
|
|
51
|
-
method_symbol = method&.downcase&.to_sym
|
|
81
|
+
method_symbol = method&.to_s&.downcase&.to_sym
|
|
52
82
|
method_symbol == :options && !headers["HTTP_ACCESS_CONTROL_REQUEST_METHOD"].nil?
|
|
53
83
|
end
|
|
54
84
|
|
|
@@ -56,5 +86,24 @@ module KeycloakApiRails
|
|
|
56
86
|
token_expiration = Time.at(token["exp"])
|
|
57
87
|
token_expiration < Time.now + @token_expiration_tolerance_in_seconds
|
|
58
88
|
end
|
|
89
|
+
|
|
90
|
+
def not_yet_valid?(token)
|
|
91
|
+
return false unless @verify_not_before
|
|
92
|
+
|
|
93
|
+
not_before = token["nbf"]
|
|
94
|
+
!not_before.nil? && Time.at(not_before) > Time.now
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def audience_valid?(token)
|
|
98
|
+
return true if @expected_audiences.empty?
|
|
99
|
+
|
|
100
|
+
Array(token["aud"]).any? { |audience| @expected_audiences.include?(audience.to_s) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def token_type_valid?(token)
|
|
104
|
+
return true if @expected_token_type.nil?
|
|
105
|
+
|
|
106
|
+
token["typ"].to_s.casecmp?(@expected_token_type)
|
|
107
|
+
end
|
|
59
108
|
end
|
|
60
109
|
end
|
|
@@ -38,19 +38,21 @@ module KeycloakApiRails
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
MONITOR = Monitor.new
|
|
42
|
+
|
|
41
43
|
class << self
|
|
42
44
|
# The RSA key pair used to sign the tokens forged by this module. It is generated once per
|
|
43
45
|
# process: generating a key is by far the slowest operation of a test suite that uses tokens.
|
|
44
46
|
def private_key
|
|
45
|
-
@private_key ||= OpenSSL::PKey::RSA.generate(KEY_SIZE)
|
|
47
|
+
MONITOR.synchronize { @private_key ||= OpenSSL::PKey::RSA.generate(KEY_SIZE) }
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def signing_key
|
|
49
|
-
@signing_key ||= JSON::JWK.new(private_key, kid: KEY_ID)
|
|
51
|
+
MONITOR.synchronize { @signing_key ||= JSON::JWK.new(private_key, kid: KEY_ID) }
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
def public_keys
|
|
53
|
-
@public_keys ||= JSON::JWK::Set.new(JSON::JWK.new(private_key.public_key, kid: KEY_ID))
|
|
55
|
+
MONITOR.synchronize { @public_keys ||= JSON::JWK::Set.new(JSON::JWK.new(private_key.public_key, kid: KEY_ID)) }
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
# Makes the library validate the tokens forged by this module. Assigning
|
|
@@ -1,30 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def initialize(token, reason, message, original_error)
|
|
5
|
-
super(message)
|
|
6
|
-
@token = token
|
|
7
|
-
@reason = reason
|
|
8
|
-
@original_error = original_error
|
|
9
|
-
end
|
|
1
|
+
module KeycloakApiRails
|
|
2
|
+
class TokenError < StandardError
|
|
3
|
+
attr_reader :token, :reason, :original_error
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
def initialize(token, reason, message, original_error = nil)
|
|
6
|
+
super(message)
|
|
7
|
+
@token = token
|
|
8
|
+
@reason = reason
|
|
9
|
+
@original_error = original_error
|
|
10
|
+
end
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
def self.verification_failed(token, original_error)
|
|
13
|
+
new(token, :verification_failed, "Failed to verify JWT token", original_error)
|
|
14
|
+
end
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
def self.invalid_format(token, original_error)
|
|
17
|
+
new(token, :invalid_format, "Wrong JWT Format", original_error)
|
|
18
|
+
end
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
def self.no_token(token)
|
|
21
|
+
new(token, :no_token, "No JWT token provided")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.expired(token)
|
|
25
|
+
new(token, :expired, "JWT token is expired")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.not_yet_valid(token)
|
|
29
|
+
new(token, :not_yet_valid, "JWT token is not valid yet")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.invalid_audience(token)
|
|
33
|
+
new(token, :invalid_audience, "JWT token has been issued for another audience")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.invalid_token_type(token)
|
|
37
|
+
new(token, :invalid_token_type, "JWT token is not of the expected type")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.missing_claim(token, claim)
|
|
41
|
+
new(token, :missing_claim, "JWT token does not carry the mandatory claim '#{claim}'")
|
|
42
|
+
end
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
def self.unknown(token, original_error)
|
|
45
|
+
new(token, :unknown, "Failed to read JWT token", original_error)
|
|
46
|
+
end
|
|
29
47
|
end
|
|
30
48
|
end
|
data/lib/keycloak-api-rails.rb
CHANGED
|
@@ -2,6 +2,7 @@ require "logger"
|
|
|
2
2
|
require "json/jwt"
|
|
3
3
|
require "uri"
|
|
4
4
|
require "date"
|
|
5
|
+
require "monitor"
|
|
5
6
|
require "net/http"
|
|
6
7
|
|
|
7
8
|
require_relative "keycloak-api-rails/authentication"
|
|
@@ -17,8 +18,13 @@ require_relative "keycloak-api-rails/railtie" if defined?(Rails)
|
|
|
17
18
|
|
|
18
19
|
module KeycloakApiRails
|
|
19
20
|
|
|
21
|
+
# These objects are memoized lazily, on the first request each process serves -- which several
|
|
22
|
+
# threads of a threaded server reach at the same time. A Monitor rather than a Mutex: the
|
|
23
|
+
# memoizations nest, 'service' needing 'public_key_resolver', which needs 'http_client'.
|
|
24
|
+
MONITOR = Monitor.new
|
|
25
|
+
|
|
20
26
|
def self.configure
|
|
21
|
-
yield @configuration ||= KeycloakApiRails::Configuration.new
|
|
27
|
+
MONITOR.synchronize { yield @configuration ||= KeycloakApiRails::Configuration.new }
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
def self.config
|
|
@@ -26,23 +32,25 @@ module KeycloakApiRails
|
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def self.http_client
|
|
29
|
-
@http_client ||= KeycloakApiRails::HTTPClient.new(config, logger)
|
|
35
|
+
MONITOR.synchronize { @http_client ||= KeycloakApiRails::HTTPClient.new(config, logger) }
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def self.public_key_resolver
|
|
33
|
-
@public_key_resolver ||= PublicKeyCachedResolver.from_configuration(http_client, config)
|
|
39
|
+
MONITOR.synchronize { @public_key_resolver ||= PublicKeyCachedResolver.from_configuration(http_client, config) }
|
|
34
40
|
end
|
|
35
41
|
|
|
36
42
|
# Mainly used by "keycloak-api-rails/testing" to validate tokens without a Keycloak server.
|
|
37
43
|
# Assigning nil restores the regular resolver. The memoized service is discarded, since it holds
|
|
38
44
|
# a reference to the resolver that is being replaced.
|
|
39
45
|
def self.public_key_resolver=(resolver)
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
MONITOR.synchronize do
|
|
47
|
+
@public_key_resolver = resolver
|
|
48
|
+
@service = nil
|
|
49
|
+
end
|
|
42
50
|
end
|
|
43
51
|
|
|
44
52
|
def self.service
|
|
45
|
-
@service ||= KeycloakApiRails::Service.new(public_key_resolver)
|
|
53
|
+
MONITOR.synchronize { @service ||= KeycloakApiRails::Service.new(public_key_resolver) }
|
|
46
54
|
end
|
|
47
55
|
|
|
48
56
|
def self.logger
|
|
@@ -59,6 +67,13 @@ module KeycloakApiRails
|
|
|
59
67
|
config.token_expiration_tolerance_in_seconds = 10
|
|
60
68
|
config.public_key_cache_ttl = 86400
|
|
61
69
|
config.custom_attributes = []
|
|
70
|
+
config.ca_certificate_file = nil
|
|
71
|
+
config.expected_audience = nil
|
|
72
|
+
config.expected_token_type = nil
|
|
73
|
+
config.verify_not_before = false
|
|
74
|
+
config.allow_token_in_query_string = false
|
|
75
|
+
config.http_open_timeout = 5
|
|
76
|
+
config.http_read_timeout = 5
|
|
62
77
|
end
|
|
63
78
|
end
|
|
64
79
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: keycloak-api-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lorent Lempereur
|
|
@@ -10,7 +10,7 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: railties
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
@@ -65,6 +65,34 @@ dependencies:
|
|
|
65
65
|
- - '='
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: 0.9.11
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rails
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '4.2'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '4.2'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rack
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
68
96
|
- !ruby/object:Gem::Dependency
|
|
69
97
|
name: rake
|
|
70
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -93,23 +121,19 @@ dependencies:
|
|
|
93
121
|
- - ">="
|
|
94
122
|
- !ruby/object:Gem::Version
|
|
95
123
|
version: 11.1.3
|
|
96
|
-
description:
|
|
124
|
+
description: Rack middleware that validates the Keycloak JWT access token carried
|
|
125
|
+
by every request of a Ruby on Rails API. It verifies the token signature against
|
|
126
|
+
the realm public keys, exposes the authenticated user, its roles and the custom
|
|
127
|
+
attributes of the token to the controllers, and lets some routes opt out of authentication.
|
|
97
128
|
email:
|
|
98
129
|
- lorent.lempereur.dev@gmail.com
|
|
99
130
|
executables: []
|
|
100
131
|
extensions: []
|
|
101
132
|
extra_rdoc_files: []
|
|
102
133
|
files:
|
|
103
|
-
- ".github/workflows/ci.yml"
|
|
104
|
-
- ".github/workflows/release.yml"
|
|
105
|
-
- ".gitignore"
|
|
106
|
-
- ".rspec"
|
|
107
134
|
- CHANGELOG.md
|
|
108
|
-
- Dockerfile
|
|
109
|
-
- Gemfile
|
|
110
135
|
- MIT-LICENSE
|
|
111
136
|
- README.md
|
|
112
|
-
- Rakefile
|
|
113
137
|
- keycloak-api-rails.gemspec
|
|
114
138
|
- lib/keycloak-api-rails.rb
|
|
115
139
|
- lib/keycloak-api-rails/authentication.rb
|
|
@@ -124,22 +148,14 @@ files:
|
|
|
124
148
|
- lib/keycloak-api-rails/testing.rb
|
|
125
149
|
- lib/keycloak-api-rails/token_error.rb
|
|
126
150
|
- lib/keycloak-api-rails/version.rb
|
|
127
|
-
- spec/keycloak-api-rails/activesupport_free_spec.rb
|
|
128
|
-
- spec/keycloak-api-rails/authentication_spec.rb
|
|
129
|
-
- spec/keycloak-api-rails/configuration_spec.rb
|
|
130
|
-
- spec/keycloak-api-rails/helper_spec.rb
|
|
131
|
-
- spec/keycloak-api-rails/public_key_cached_resolver_spec.rb
|
|
132
|
-
- spec/keycloak-api-rails/service_spec.rb
|
|
133
|
-
- spec/keycloak-api-rails/testing_spec.rb
|
|
134
|
-
- spec/spec_helper.rb
|
|
135
|
-
- spec/support/activesupport_free_probe.rb
|
|
136
|
-
- spec/support/public_key_cached_resolver_stub.rb
|
|
137
|
-
- spec/support/public_key_resolver_stub.rb
|
|
138
|
-
- spec/support/rails_helper.rb
|
|
139
151
|
homepage: https://github.com/looorent/keycloak-api-rails
|
|
140
152
|
licenses:
|
|
141
153
|
- MIT
|
|
142
|
-
metadata:
|
|
154
|
+
metadata:
|
|
155
|
+
source_code_uri: https://github.com/looorent/keycloak-api-rails
|
|
156
|
+
changelog_uri: https://github.com/looorent/keycloak-api-rails/blob/main/CHANGELOG.md
|
|
157
|
+
bug_tracker_uri: https://github.com/looorent/keycloak-api-rails/issues
|
|
158
|
+
rubygems_mfa_required: 'true'
|
|
143
159
|
rdoc_options: []
|
|
144
160
|
require_paths:
|
|
145
161
|
- lib
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
name: Ruby
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [ "main" ]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [ "main" ]
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: read
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
test:
|
|
14
|
-
name: Ruby ${{ matrix.ruby-version }}
|
|
15
|
-
# Pinned rather than 'ubuntu-latest': ruby/setup-ruby has no prebuilt Rubies for
|
|
16
|
-
# ubuntu-26.04 yet, so the oldest versions of the matrix would break once the
|
|
17
|
-
# 'latest' alias moves on.
|
|
18
|
-
runs-on: ubuntu-24.04
|
|
19
|
-
|
|
20
|
-
strategy:
|
|
21
|
-
fail-fast: false
|
|
22
|
-
matrix:
|
|
23
|
-
# Each version resolves the newest dependencies it supports: Rails 7.1 on Ruby 2.7
|
|
24
|
-
# and 3.0, Rails 7.2 on Ruby 3.1, Rails 8.1 on Ruby 3.2 and above.
|
|
25
|
-
ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0']
|
|
26
|
-
|
|
27
|
-
steps:
|
|
28
|
-
- uses: actions/checkout@v4
|
|
29
|
-
- name: Set up Ruby
|
|
30
|
-
uses: ruby/setup-ruby@v1
|
|
31
|
-
with:
|
|
32
|
-
ruby-version: ${{ matrix.ruby-version }}
|
|
33
|
-
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
34
|
-
- name: Run tests
|
|
35
|
-
run: bundle exec rspec
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
# Publishing is triggered by pushing a version tag, e.g.:
|
|
4
|
-
# git tag -a v1.2.0 -m "Version 1.2.0" && git push origin v1.2.0
|
|
5
|
-
#
|
|
6
|
-
# No RubyGems API key is stored in this repository: the gem is pushed through
|
|
7
|
-
# RubyGems' Trusted Publishing, which exchanges a short-lived GitHub OIDC token
|
|
8
|
-
# for a scoped RubyGems credential. This requires a trusted publisher declared on
|
|
9
|
-
# https://rubygems.org/gems/keycloak-api-rails pointing to this repository, this
|
|
10
|
-
# workflow file name and the 'release' environment.
|
|
11
|
-
on:
|
|
12
|
-
push:
|
|
13
|
-
tags: [ "v*" ]
|
|
14
|
-
|
|
15
|
-
permissions:
|
|
16
|
-
contents: read
|
|
17
|
-
|
|
18
|
-
jobs:
|
|
19
|
-
release:
|
|
20
|
-
name: Push gem to RubyGems.org
|
|
21
|
-
# Pinned rather than 'ubuntu-latest', for the same reason as in ci.yml.
|
|
22
|
-
runs-on: ubuntu-24.04
|
|
23
|
-
|
|
24
|
-
environment: release
|
|
25
|
-
|
|
26
|
-
permissions:
|
|
27
|
-
contents: write # 'rake release' pushes the version tag when it does not exist yet
|
|
28
|
-
id-token: write # mandatory to request the OIDC token used by trusted publishing
|
|
29
|
-
|
|
30
|
-
steps:
|
|
31
|
-
- uses: actions/checkout@v4
|
|
32
|
-
|
|
33
|
-
- name: Set up Ruby
|
|
34
|
-
uses: ruby/setup-ruby@v1
|
|
35
|
-
with:
|
|
36
|
-
ruby-version: '3.4'
|
|
37
|
-
bundler-cache: true
|
|
38
|
-
|
|
39
|
-
- name: Check that the tag matches KeycloakApiRails::VERSION
|
|
40
|
-
run: |
|
|
41
|
-
tag="${GITHUB_REF_NAME#v}"
|
|
42
|
-
version="$(ruby -Ilib -r keycloak-api-rails/version -e 'print KeycloakApiRails::VERSION')"
|
|
43
|
-
if [ "$tag" != "$version" ]; then
|
|
44
|
-
echo "::error::Tag '${GITHUB_REF_NAME}' does not match KeycloakApiRails::VERSION '${version}'"
|
|
45
|
-
exit 1
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
- name: Run tests
|
|
49
|
-
run: bundle exec rspec
|
|
50
|
-
|
|
51
|
-
# Builds the gem, pushes it to RubyGems.org and waits for it to be available.
|
|
52
|
-
- uses: rubygems/release-gem@v1
|
data/.gitignore
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
.bundle/
|
|
2
|
-
log/*.log
|
|
3
|
-
.byebug_history
|
|
4
|
-
*.gem
|
|
5
|
-
pkg/
|
|
6
|
-
# This gem supports several Ruby versions, each of them resolving a different set of
|
|
7
|
-
# dependencies (Ruby 2.7 caps Rails to 7.1, Ruby 3.1 to 7.2, Ruby 3.2+ resolves Rails 8.1).
|
|
8
|
-
# A single committed lockfile cannot satisfy all of them.
|
|
9
|
-
Gemfile.lock
|
data/.rspec
DELETED
data/Dockerfile
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
FROM ruby:3.4.10-slim-bookworm
|
|
2
|
-
|
|
3
|
-
RUN apt-get update -qq && apt-get install -y build-essential git ruby-dev && apt-get clean && \
|
|
4
|
-
mkdir -p /usr/src/app/lib/keycloak-api-rails
|
|
5
|
-
|
|
6
|
-
WORKDIR /usr/src/app
|
|
7
|
-
|
|
8
|
-
COPY Gemfile /usr/src/app/
|
|
9
|
-
COPY keycloak-api-rails.gemspec /usr/src/app/
|
|
10
|
-
COPY lib/keycloak-api-rails/version.rb /usr/src/app/lib/keycloak-api-rails/
|
|
11
|
-
RUN bundle install
|
|
12
|
-
COPY . /usr/src/app
|
|
13
|
-
RUN bundle install
|
data/Gemfile
DELETED
data/Rakefile
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
require "rbconfig"
|
|
2
|
-
|
|
3
|
-
RSpec.describe "The library without ActiveSupport" do
|
|
4
|
-
|
|
5
|
-
let(:probe) { File.expand_path("../../support/activesupport_free_probe.rb", __FILE__) }
|
|
6
|
-
|
|
7
|
-
# RUBYOPT is cleared so that nothing set up by bundler is preloaded in the probe process.
|
|
8
|
-
def run_probe
|
|
9
|
-
output = IO.popen({ "RUBYOPT" => nil }, [RbConfig.ruby, probe], err: [:child, :out], &:read)
|
|
10
|
-
[$?.exitstatus, output]
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it "loads and behaves the same way when ActiveSupport is not available" do
|
|
14
|
-
status, output = run_probe
|
|
15
|
-
|
|
16
|
-
expect(status).to eq(0), "The library relies on ActiveSupport:\n#{output}"
|
|
17
|
-
end
|
|
18
|
-
end
|