keycloak-api-rails 0.12.4 → 1.0.1

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.
@@ -1,14 +1,13 @@
1
- module Keycloak
1
+ module KeycloakApiRails
2
2
  class Configuration
3
- include ActiveSupport::Configurable
4
- config_accessor :server_url
5
- config_accessor :realm_id
6
- config_accessor :skip_paths
7
- config_accessor :opt_in
8
- config_accessor :token_expiration_tolerance_in_seconds
9
- config_accessor :public_key_cache_ttl
10
- config_accessor :custom_attributes
11
- config_accessor :logger
12
- config_accessor :ca_certificate_file
3
+ attr_accessor :server_url
4
+ attr_accessor :realm_id
5
+ attr_accessor :skip_paths
6
+ attr_accessor :opt_in
7
+ attr_accessor :token_expiration_tolerance_in_seconds
8
+ attr_accessor :public_key_cache_ttl
9
+ attr_accessor :custom_attributes
10
+ attr_accessor :logger
11
+ attr_accessor :ca_certificate_file
13
12
  end
14
13
  end
@@ -1,4 +1,4 @@
1
- module Keycloak
1
+ module KeycloakApiRails
2
2
  class Helper
3
3
 
4
4
  CURRENT_USER_ID_KEY = "keycloak:keycloak_id"
@@ -83,11 +83,11 @@ module Keycloak
83
83
  end
84
84
 
85
85
  def self.read_token_from_query_string(uri)
86
- if uri.present?
86
+ if uri && !uri.strip.empty?
87
87
  parsed_uri = URI.parse(uri)
88
88
  query = URI.decode_www_form(parsed_uri.query || "")
89
89
  query_string_token = query.detect { |param| param.first == QUERY_STRING_TOKEN_KEY }
90
- query_string_token&.second
90
+ query_string_token&.at(1)
91
91
  else
92
92
  ""
93
93
  end
@@ -1,4 +1,4 @@
1
- module Keycloak
1
+ module KeycloakApiRails
2
2
  class HTTPClient
3
3
  def initialize(configuration, logger)
4
4
  @server_url = configuration.server_url
@@ -20,7 +20,7 @@ module Keycloak
20
20
  response.value
21
21
  JSON.parse(response.body)
22
22
  rescue
23
- @logger.error("Keycloak responded with an error when calling '#{path}'. Status #{response.code}. Payload: #{response.body}")
23
+ @logger.error("KeycloakApiRails responded with an error when calling '#{path}'. Status #{response.code}. Payload: #{response.body}")
24
24
  end
25
25
  end
26
26
  end
@@ -1,4 +1,4 @@
1
- module Keycloak
1
+ module KeycloakApiRails
2
2
 
3
3
  class Middleware
4
4
  def initialize(app)
@@ -41,15 +41,15 @@ module Keycloak
41
41
  end
42
42
 
43
43
  def service
44
- Keycloak.service
44
+ KeycloakApiRails.service
45
45
  end
46
46
 
47
47
  def logger
48
- Keycloak.logger
48
+ KeycloakApiRails.logger
49
49
  end
50
50
 
51
51
  def config
52
- Keycloak.config
52
+ KeycloakApiRails.config
53
53
  end
54
54
  end
55
55
  end
@@ -1,4 +1,4 @@
1
- module Keycloak
1
+ module KeycloakApiRails
2
2
  class PublicKeyCachedResolver
3
3
  attr_reader :cached_public_key_retrieved_at
4
4
 
@@ -24,7 +24,7 @@ module Keycloak
24
24
  private
25
25
 
26
26
  def public_keys_are_outdated?
27
- @cached_public_keys.nil? || @cached_public_key_retrieved_at.nil? || Time.now > (@cached_public_key_retrieved_at + @public_key_cache_ttl.seconds)
27
+ @cached_public_keys.nil? || @cached_public_key_retrieved_at.nil? || Time.now > (@cached_public_key_retrieved_at + @public_key_cache_ttl)
28
28
  end
29
29
  end
30
30
  end
@@ -1,4 +1,4 @@
1
- module Keycloak
1
+ module KeycloakApiRails
2
2
  class PublicKeyResolver
3
3
  def initialize(http_client, realm_id)
4
4
  @realm_id = realm_id
@@ -1,9 +1,9 @@
1
- module Keycloak
1
+ module KeycloakApiRails
2
2
  class Railtie < Rails::Railtie
3
3
  railtie_name :keycloak_api_rails
4
4
 
5
5
  initializer("keycloak.insert_middleware") do |app|
6
- app.config.middleware.use(Keycloak::Middleware)
6
+ app.config.middleware.use(KeycloakApiRails::Middleware)
7
7
  end
8
8
  end
9
9
  end
@@ -1,12 +1,12 @@
1
- module Keycloak
1
+ module KeycloakApiRails
2
2
  class Service
3
3
 
4
4
  def initialize(key_resolver)
5
5
  @key_resolver = key_resolver
6
- @skip_paths = Keycloak.config.skip_paths
7
- @opt_in = Keycloak.config.opt_in
8
- @logger = Keycloak.config.logger
9
- @token_expiration_tolerance_in_seconds = Keycloak.config.token_expiration_tolerance_in_seconds
6
+ @skip_paths = KeycloakApiRails.config.skip_paths
7
+ @opt_in = KeycloakApiRails.config.opt_in
8
+ @logger = KeycloakApiRails.config.logger
9
+ @token_expiration_tolerance_in_seconds = KeycloakApiRails.config.token_expiration_tolerance_in_seconds
10
10
  end
11
11
 
12
12
  def decode_and_verify(token)
@@ -53,8 +53,8 @@ module Keycloak
53
53
  end
54
54
 
55
55
  def expired?(token)
56
- token_expiration = Time.at(token["exp"]).to_datetime
57
- token_expiration < Time.now + @token_expiration_tolerance_in_seconds.seconds
56
+ token_expiration = Time.at(token["exp"])
57
+ token_expiration < Time.now + @token_expiration_tolerance_in_seconds
58
58
  end
59
59
  end
60
60
  end
@@ -1,3 +1,3 @@
1
- module Keycloak
2
- VERSION = "0.12.4"
1
+ module KeycloakApiRails
2
+ VERSION = "1.0.1"
3
3
  end
@@ -15,10 +15,10 @@ require_relative "keycloak-api-rails/service"
15
15
  require_relative "keycloak-api-rails/middleware"
16
16
  require_relative "keycloak-api-rails/railtie" if defined?(Rails)
17
17
 
18
- module Keycloak
18
+ module KeycloakApiRails
19
19
 
20
20
  def self.configure
21
- yield @configuration ||= Keycloak::Configuration.new
21
+ yield @configuration ||= KeycloakApiRails::Configuration.new
22
22
  end
23
23
 
24
24
  def self.config
@@ -26,7 +26,7 @@ module Keycloak
26
26
  end
27
27
 
28
28
  def self.http_client
29
- @http_client ||= Keycloak::HTTPClient.new(config, logger)
29
+ @http_client ||= KeycloakApiRails::HTTPClient.new(config, logger)
30
30
  end
31
31
 
32
32
  def self.public_key_resolver
@@ -34,7 +34,7 @@ module Keycloak
34
34
  end
35
35
 
36
36
  def self.service
37
- @service ||= Keycloak::Service.new(public_key_resolver)
37
+ @service ||= KeycloakApiRails::Service.new(public_key_resolver)
38
38
  end
39
39
 
40
40
  def self.logger
@@ -0,0 +1,18 @@
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
@@ -1,10 +1,10 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Keycloak::Authentication do
3
+ describe KeycloakApiRails::Authentication do
4
4
  class ExampleController < ActionController::Base
5
- include Keycloak::Authentication
5
+ include KeycloakApiRails::Authentication
6
6
  # Mark protected methods public so they may be called in tests
7
- public(*Keycloak::Authentication.protected_instance_methods)
7
+ public(*KeycloakApiRails::Authentication.protected_instance_methods)
8
8
  end
9
9
 
10
10
  let(:controller) { ExampleController.new }
@@ -17,6 +17,44 @@ describe Keycloak::Authentication do
17
17
  }
18
18
  end
19
19
 
20
+ describe "when included" do
21
+ it "declares keycloak_authenticate as a helper method when the base class supports helpers" do
22
+ declared_helper_methods = []
23
+ base = Class.new do
24
+ define_singleton_method(:helper_method) { |*names| declared_helper_methods.concat(names) }
25
+ end
26
+
27
+ base.include(KeycloakApiRails::Authentication)
28
+
29
+ expect(declared_helper_methods).to eq [:keycloak_authenticate]
30
+ end
31
+
32
+ it "declares the helper method on a real controller" do
33
+ expect(ExampleController._helper_methods).to include :keycloak_authenticate
34
+ end
35
+
36
+ it "does not fail when the base class does not support helpers" do
37
+ expect {
38
+ Class.new.include(KeycloakApiRails::Authentication)
39
+ }.to_not raise_error
40
+ end
41
+
42
+ it "exposes its methods as protected instance methods" do
43
+ expect(KeycloakApiRails::Authentication.protected_instance_methods).to match_array %i[
44
+ keycloak_authenticate
45
+ authentication_failed
46
+ authentication_succeeded
47
+ ]
48
+ end
49
+
50
+ it "does not add any public method to the including class" do
51
+ base = Class.new.include(KeycloakApiRails::Authentication)
52
+
53
+ expect(base.public_instance_methods).to_not include :keycloak_authenticate
54
+ expect(base.protected_instance_methods).to include :keycloak_authenticate
55
+ end
56
+ end
57
+
20
58
  describe "#keycloak_authenticate" do
21
59
  before do
22
60
  # Mock request object because we aren't using real request spec
@@ -24,7 +62,7 @@ describe Keycloak::Authentication do
24
62
  end
25
63
 
26
64
  it "it authenticates with request header" do
27
- expect_any_instance_of(Keycloak::Service).to receive(:decode_and_verify).with(token).and_return("A User")
65
+ expect_any_instance_of(KeycloakApiRails::Service).to receive(:decode_and_verify).with(token).and_return("A User")
28
66
  expect(controller).to receive(:authentication_succeeded)
29
67
  controller.keycloak_authenticate
30
68
  end
@@ -0,0 +1,101 @@
1
+ RSpec.describe KeycloakApiRails::Configuration do
2
+
3
+ # Every attribute that used to be declared through ActiveSupport's `config_accessor`.
4
+ let(:attribute_names) do
5
+ %i[
6
+ server_url
7
+ realm_id
8
+ skip_paths
9
+ opt_in
10
+ token_expiration_tolerance_in_seconds
11
+ public_key_cache_ttl
12
+ custom_attributes
13
+ logger
14
+ ca_certificate_file
15
+ ]
16
+ end
17
+
18
+ describe "attributes" do
19
+ let(:configuration) { KeycloakApiRails::Configuration.new }
20
+
21
+ it "exposes a reader and a writer for each attribute" do
22
+ attribute_names.each do |name|
23
+ expect(configuration).to respond_to name
24
+ expect(configuration).to respond_to "#{name}="
25
+ end
26
+ end
27
+
28
+ it "returns nil for each attribute that has not been assigned" do
29
+ attribute_names.each do |name|
30
+ expect(configuration.public_send(name)).to be_nil
31
+ end
32
+ end
33
+
34
+ it "returns the value that has been assigned" do
35
+ attribute_names.each do |name|
36
+ configuration.public_send("#{name}=", "a value for #{name}")
37
+ expect(configuration.public_send(name)).to eq "a value for #{name}"
38
+ end
39
+ end
40
+
41
+ it "does not respond to an unknown attribute" do
42
+ expect(configuration).to_not respond_to :not_a_configuration_attribute
43
+ expect {
44
+ configuration.not_a_configuration_attribute
45
+ }.to raise_error NoMethodError
46
+ end
47
+ end
48
+
49
+ describe "#configure" do
50
+ after(:each) do
51
+ KeycloakApiRails.load_configuration
52
+ end
53
+
54
+ it "yields the configuration" do
55
+ expect { |block| KeycloakApiRails.configure(&block) }.to yield_with_args KeycloakApiRails::Configuration
56
+ end
57
+
58
+ it "keeps the values assigned within the block" do
59
+ KeycloakApiRails.configure do |config|
60
+ config.server_url = "http://keycloak:8080"
61
+ config.realm_id = "a-realm"
62
+ config.ca_certificate_file = "/etc/ssl/a-certificate.pem"
63
+ end
64
+
65
+ expect(KeycloakApiRails.config.server_url).to eq "http://keycloak:8080"
66
+ expect(KeycloakApiRails.config.realm_id).to eq "a-realm"
67
+ expect(KeycloakApiRails.config.ca_certificate_file).to eq "/etc/ssl/a-certificate.pem"
68
+ end
69
+
70
+ it "always returns the same configuration instance" do
71
+ expect(KeycloakApiRails.config).to be KeycloakApiRails.config
72
+ end
73
+ end
74
+
75
+ describe "#load_configuration" do
76
+ before(:each) do
77
+ KeycloakApiRails.load_configuration
78
+ end
79
+
80
+ after(:each) do
81
+ KeycloakApiRails.load_configuration
82
+ end
83
+
84
+ it "applies the documented default values" do
85
+ config = KeycloakApiRails.config
86
+
87
+ expect(config.server_url).to be_nil
88
+ expect(config.realm_id).to be_nil
89
+ expect(config.logger).to be_a ::Logger
90
+ expect(config.skip_paths).to eq({})
91
+ expect(config.opt_in).to be false
92
+ expect(config.token_expiration_tolerance_in_seconds).to eq 10
93
+ expect(config.public_key_cache_ttl).to eq 86400
94
+ expect(config.custom_attributes).to eq []
95
+ end
96
+
97
+ it "exposes the logger through KeycloakApiRails.logger" do
98
+ expect(KeycloakApiRails.logger).to be KeycloakApiRails.config.logger
99
+ end
100
+ end
101
+ end
@@ -1,11 +1,72 @@
1
- RSpec.describe Keycloak::Helper do
1
+ RSpec.describe KeycloakApiRails::Helper do
2
+ describe "#read_token_from_query_string" do
3
+
4
+ subject { KeycloakApiRails::Helper.read_token_from_query_string(uri) }
5
+
6
+ context "when the uri is nil" do
7
+ let(:uri) { nil }
8
+ it "returns an empty token" do
9
+ expect(subject).to eq ""
10
+ end
11
+ end
12
+
13
+ context "when the uri is empty" do
14
+ let(:uri) { "" }
15
+ it "returns an empty token" do
16
+ expect(subject).to eq ""
17
+ end
18
+ end
19
+
20
+ context "when the uri only contains whitespaces" do
21
+ let(:uri) { " " }
22
+ it "returns an empty token" do
23
+ expect(subject).to eq ""
24
+ end
25
+ end
26
+
27
+ context "when the uri has no query string" do
28
+ let(:uri) { "http://www.an-url.io/health" }
29
+ it "returns no token" do
30
+ expect(subject).to be_nil
31
+ end
32
+ end
33
+
34
+ context "when the uri has a query string but no token" do
35
+ let(:uri) { "http://www.an-url.io/health?firstName=ouioui" }
36
+ it "returns no token" do
37
+ expect(subject).to be_nil
38
+ end
39
+ end
40
+
41
+ context "when the uri has a token in the query string" do
42
+ let(:uri) { "http://www.an-url.io/health?authorizationToken=aToken" }
43
+ it "returns the token" do
44
+ expect(subject).to eq "aToken"
45
+ end
46
+ end
47
+
48
+ context "when the uri has a token among other query string parameters" do
49
+ let(:uri) { "http://www.an-url.io/health?firstName=ouioui&authorizationToken=aToken&lastName=nonnon" }
50
+ it "returns the token" do
51
+ expect(subject).to eq "aToken"
52
+ end
53
+ end
54
+
55
+ context "when the uri has an empty token in the query string" do
56
+ let(:uri) { "http://www.an-url.io/health?authorizationToken=" }
57
+ it "returns an empty token" do
58
+ expect(subject).to eq ""
59
+ end
60
+ end
61
+ end
62
+
2
63
  describe "#create_url_with_token" do
3
64
 
4
65
  let(:uri) { "http://www.an-url.io" }
5
66
  let(:token) { "aToken" }
6
67
 
7
68
  before(:each) do
8
- @url_with_token = Keycloak::Helper.create_url_with_token(uri, token)
69
+ @url_with_token = KeycloakApiRails::Helper.create_url_with_token(uri, token)
9
70
  end
10
71
 
11
72
  context "when the uri has no query string yet" do
@@ -1,12 +1,12 @@
1
- RSpec.describe Keycloak::Service do
1
+ RSpec.describe KeycloakApiRails::Service do
2
2
 
3
3
  let(:public_key_cache_ttl) { 86400 }
4
4
  let(:server_url) { "whatever:8080" }
5
5
  let(:realm_id) { "pouet" }
6
- let!(:resolver) { Keycloak::PublicKeyCachedResolver.new(server_url, realm_id, public_key_cache_ttl) }
6
+ let!(:resolver) { KeycloakApiRails::PublicKeyCachedResolver.new(server_url, realm_id, public_key_cache_ttl) }
7
7
 
8
8
  before(:each) do
9
- resolver.instance_variable_set(:@resolver, Keycloak::PublicKeyResolverStub.new)
9
+ resolver.instance_variable_set(:@resolver, KeycloakApiRails::PublicKeyResolverStub.new)
10
10
  now = Time.local(2018, 1, 9, 12, 0, 0)
11
11
  Timecop.freeze(now)
12
12
  end
@@ -1,9 +1,9 @@
1
- RSpec.describe Keycloak::Service do
1
+ RSpec.describe KeycloakApiRails::Service do
2
2
 
3
3
  let!(:private_key) { OpenSSL::PKey::RSA.generate(2048) }
4
4
  let!(:public_key) { private_key.public_key }
5
- let!(:key_resolver) { Keycloak::PublicKeyCachedResolverStub.new(public_key) }
6
- let!(:service) { Keycloak::Service.new(key_resolver) }
5
+ let!(:key_resolver) { KeycloakApiRails::PublicKeyCachedResolverStub.new(public_key) }
6
+ let!(:service) { KeycloakApiRails::Service.new(key_resolver) }
7
7
 
8
8
  before(:each) do
9
9
  now = Time.local(2018, 1, 9, 12, 0, 0)
@@ -17,7 +17,7 @@ RSpec.describe Keycloak::Service do
17
17
  describe "#decode_and_verify" do
18
18
  def create_token(private_key, expiration_date, algorithm)
19
19
  claim = {
20
- iss: "Keycloak",
20
+ iss: "KeycloakApiRails",
21
21
  exp: expiration_date,
22
22
  nbf: Time.local(2018, 1, 1, 0, 0, 0)
23
23
  }
@@ -80,9 +80,31 @@ RSpec.describe Keycloak::Service do
80
80
  end
81
81
  end
82
82
 
83
+ # The service reads `token_expiration_tolerance_in_seconds` when it is instantiated:
84
+ # a token expiring within that tolerance is already considered as expired.
85
+ context "and token expires within the expiration tolerance" do
86
+ let(:expiration_date) { Time.now + 5 }
87
+
88
+ it "should raise an error :expiration_date" do
89
+ expect(KeycloakApiRails.config.token_expiration_tolerance_in_seconds).to eq 10
90
+ expect {
91
+ service.decode_and_verify(token)
92
+ }.to raise_error(TokenError, "JWT token is expired")
93
+ end
94
+ end
95
+
96
+ context "and token expires just after the expiration tolerance" do
97
+ let(:expiration_date) { Time.now + 30 }
98
+
99
+ it "should return a not-nil decoded token" do
100
+ expect(KeycloakApiRails.config.token_expiration_tolerance_in_seconds).to eq 10
101
+ expect(service.decode_and_verify(token)).to_not be_nil
102
+ end
103
+ end
104
+
83
105
  context "and token is not expired" do
84
106
  let(:expiration_date) { Time.now + 2.days }
85
-
107
+
86
108
  context "and token is encrypted using RS256" do
87
109
  let(:algorithm) { :RS256 }
88
110
 
@@ -111,7 +133,7 @@ RSpec.describe Keycloak::Service do
111
133
 
112
134
 
113
135
  before(:each) do
114
- Keycloak.config.skip_paths = {
136
+ KeycloakApiRails.config.skip_paths = {
115
137
  post: [/^\/skip/],
116
138
  get: [/^\/skip/]
117
139
  }
@@ -178,8 +200,8 @@ RSpec.describe Keycloak::Service do
178
200
 
179
201
  context "when configured as opt_in" do
180
202
  before do
181
- Keycloak.config.opt_in = true
182
- service2 = Keycloak::Service.new(key_resolver)
203
+ KeycloakApiRails.config.opt_in = true
204
+ service2 = KeycloakApiRails::Service.new(key_resolver)
183
205
  @result = service2.need_middleware_authentication?(method, path, headers)
184
206
  end
185
207
 
@@ -0,0 +1,131 @@
1
+ # Loads the library and exercises every code path that used to rely on ActiveSupport,
2
+ # in a process where ActiveSupport is *not* loaded.
3
+ #
4
+ # The regular test suite loads `rails/all`, which makes ActiveSupport available everywhere and
5
+ # therefore cannot detect that the library depends on it again. This probe is executed in a
6
+ # separate process by spec/keycloak-api-rails/activesupport_free_spec.rb.
7
+ #
8
+ # The library files are required one by one on purpose: `lib/keycloak-api-rails.rb` pulls in
9
+ # `json/jwt`, which depends on ActiveSupport. The point of this probe is that *our* code does not
10
+ # use ActiveSupport, not that it never gets loaded by a third party.
11
+
12
+ require "uri"
13
+ require "date"
14
+ require "logger"
15
+
16
+ LIB = File.expand_path("../../../lib/keycloak-api-rails", __FILE__)
17
+
18
+ # Minimal stand-in for the parts of lib/keycloak-api-rails.rb the required files call at runtime.
19
+ module KeycloakApiRails
20
+ class << self
21
+ attr_accessor :config
22
+ end
23
+ end
24
+
25
+ require "#{LIB}/configuration"
26
+ require "#{LIB}/helper"
27
+ require "#{LIB}/authentication"
28
+ require "#{LIB}/public_key_resolver"
29
+ require "#{LIB}/public_key_cached_resolver"
30
+ require "#{LIB}/service"
31
+
32
+ $failures = []
33
+
34
+ def assert(description)
35
+ $failures << description unless yield
36
+ rescue => e
37
+ $failures << "#{description} (raised #{e.class}: #{e.message})"
38
+ end
39
+
40
+ assert("ActiveSupport is not loaded by the library") { !defined?(ActiveSupport) }
41
+
42
+ ### Configuration -- used to be ActiveSupport::Configurable
43
+ configuration = KeycloakApiRails::Configuration.new
44
+ KeycloakApiRails.config = configuration
45
+
46
+ %i[
47
+ server_url realm_id skip_paths opt_in token_expiration_tolerance_in_seconds
48
+ public_key_cache_ttl custom_attributes logger ca_certificate_file
49
+ ].each do |name|
50
+ assert("Configuration##{name} is nil until it is assigned") { configuration.public_send(name).nil? }
51
+ configuration.public_send("#{name}=", "a value for #{name}")
52
+ assert("Configuration##{name} returns what was assigned") { configuration.public_send(name) == "a value for #{name}" }
53
+ end
54
+
55
+ ### Authentication -- used to be ActiveSupport::Concern
56
+ declared_helper_methods = []
57
+ controller_with_helpers = Class.new do
58
+ define_singleton_method(:helper_method) { |*names| declared_helper_methods.concat(names) }
59
+ end
60
+ controller_with_helpers.include(KeycloakApiRails::Authentication)
61
+ assert("Authentication declares keycloak_authenticate as a helper method") do
62
+ declared_helper_methods == [:keycloak_authenticate]
63
+ end
64
+ assert("Authentication can be included in a class without helper_method") do
65
+ Class.new.include(KeycloakApiRails::Authentication)
66
+ true
67
+ end
68
+ assert("Authentication keeps its methods protected") do
69
+ KeycloakApiRails::Authentication.protected_instance_methods.sort ==
70
+ %i[authentication_failed authentication_succeeded keycloak_authenticate]
71
+ end
72
+
73
+ ### Helper#read_token_from_query_string -- used to be `present?` and `second`
74
+ {
75
+ nil => "",
76
+ "" => "",
77
+ " " => "",
78
+ "http://www.an-url.io/health" => nil,
79
+ "http://www.an-url.io/health?firstName=ouioui" => nil,
80
+ "http://www.an-url.io/health?authorizationToken=aToken" => "aToken",
81
+ "http://www.an-url.io/health?a=1&authorizationToken=aToken&b=2" => "aToken",
82
+ }.each do |uri, expected|
83
+ assert("Helper.read_token_from_query_string(#{uri.inspect}) returns #{expected.inspect}") do
84
+ KeycloakApiRails::Helper.read_token_from_query_string(uri) == expected
85
+ end
86
+ end
87
+
88
+ ### Service#expired? -- used to be `to_datetime` and `seconds`
89
+ configuration.token_expiration_tolerance_in_seconds = 10
90
+ configuration.skip_paths = {}
91
+ configuration.opt_in = false
92
+ configuration.logger = ::Logger.new(File::NULL)
93
+ service = KeycloakApiRails::Service.new(nil)
94
+ now = Time.now
95
+
96
+ {
97
+ "expiring in one hour" => [(now + 3600).to_i, false],
98
+ "expired two days ago" => [(now - 172800).to_i, true],
99
+ "expiring within the tolerance (5s < 10s)" => [(now + 5).to_i, true],
100
+ "expiring after the tolerance (30s > 10s)" => [(now + 30).to_i, false],
101
+ }.each do |description, (exp, expected)|
102
+ assert("Service#expired? returns #{expected} for a token #{description}") do
103
+ service.send(:expired?, { "exp" => exp }) == expected
104
+ end
105
+ end
106
+
107
+ ### PublicKeyCachedResolver -- used to be `seconds`
108
+ ttl = 86400
109
+ resolver = KeycloakApiRails::PublicKeyCachedResolver.new(nil, "a-realm", ttl)
110
+ assert("PublicKeyCachedResolver considers an empty cache as outdated") do
111
+ resolver.send(:public_keys_are_outdated?) == true
112
+ end
113
+ resolver.instance_variable_set(:@cached_public_keys, "a-public-key")
114
+ resolver.instance_variable_set(:@cached_public_key_retrieved_at, Time.now - (ttl - 10))
115
+ assert("PublicKeyCachedResolver keeps a cache that is within its TTL") do
116
+ resolver.send(:public_keys_are_outdated?) == false
117
+ end
118
+ resolver.instance_variable_set(:@cached_public_key_retrieved_at, Time.now - (ttl + 10))
119
+ assert("PublicKeyCachedResolver invalidates a cache that outlived its TTL") do
120
+ resolver.send(:public_keys_are_outdated?) == true
121
+ end
122
+
123
+ assert("ActiveSupport has not been loaded while exercising the library") { !defined?(ActiveSupport) }
124
+
125
+ if $failures.empty?
126
+ puts "The library behaves correctly without ActiveSupport"
127
+ exit 0
128
+ else
129
+ $failures.each { |failure| puts "FAILED: #{failure}" }
130
+ exit 1
131
+ end