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,102 +0,0 @@
|
|
|
1
|
-
require_relative "../../lib/keycloak-api-rails/testing"
|
|
2
|
-
|
|
3
|
-
RSpec.describe KeycloakApiRails::Testing do
|
|
4
|
-
include KeycloakApiRails::Testing::Helpers
|
|
5
|
-
|
|
6
|
-
# The tokens are validated by the real middleware and the real service, so that these examples
|
|
7
|
-
# fail if what this module forges stops being what the library expects.
|
|
8
|
-
let(:authenticated_env) { {} }
|
|
9
|
-
let(:app) do
|
|
10
|
-
downstream = lambda do |env|
|
|
11
|
-
authenticated_env.replace(env)
|
|
12
|
-
[200, {}, ["OK"]]
|
|
13
|
-
end
|
|
14
|
-
KeycloakApiRails::Middleware.new(downstream)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def get(headers)
|
|
18
|
-
app.call({
|
|
19
|
-
"REQUEST_METHOD" => "GET",
|
|
20
|
-
"PATH_INFO" => "/me",
|
|
21
|
-
"REQUEST_URI" => "http://example.org/me",
|
|
22
|
-
"HTTP_AUTHORIZATION" => headers["Authorization"]
|
|
23
|
-
})
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
before(:each) do
|
|
27
|
-
# The middleware only authenticates a request when the configuration tells it to, and the
|
|
28
|
-
# configuration is global: these examples cannot rely on what the other ones leave behind.
|
|
29
|
-
KeycloakApiRails.config.opt_in = false
|
|
30
|
-
KeycloakApiRails.config.skip_paths = {}
|
|
31
|
-
KeycloakApiRails::Testing.stub_public_keys!
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
after(:each) do
|
|
35
|
-
KeycloakApiRails.public_key_resolver = nil
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
describe ".stub_public_keys!" do
|
|
39
|
-
it "should validate the tokens forged by this module" do
|
|
40
|
-
status, _headers, _body = get(keycloak_auth_headers)
|
|
41
|
-
|
|
42
|
-
expect(status).to eq(200)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
it "should not require a Keycloak server to be configured" do
|
|
46
|
-
expect(KeycloakApiRails.config.server_url).to be_nil
|
|
47
|
-
expect(KeycloakApiRails.config.realm_id).to be_nil
|
|
48
|
-
|
|
49
|
-
status, _headers, _body = get(keycloak_auth_headers)
|
|
50
|
-
|
|
51
|
-
expect(status).to eq(200)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
describe ".token_for" do
|
|
56
|
-
it "should authenticate the given user" do
|
|
57
|
-
get(keycloak_auth_headers(sub: "a-keycloak-id"))
|
|
58
|
-
|
|
59
|
-
expect(KeycloakApiRails::Helper.current_user_id(authenticated_env)).to eq("a-keycloak-id")
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
it "should generate a different user for each token" do
|
|
63
|
-
first_token = JSON::JWT.decode(keycloak_token, KeycloakApiRails::Testing.public_keys)
|
|
64
|
-
second_token = JSON::JWT.decode(keycloak_token, KeycloakApiRails::Testing.public_keys)
|
|
65
|
-
|
|
66
|
-
expect(first_token["sub"]).not_to eq(second_token["sub"])
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it "should expose every claim read by the library" do
|
|
70
|
-
get(keycloak_auth_headers(sub: "a-keycloak-id",
|
|
71
|
-
email: "an-email@keycloak.io",
|
|
72
|
-
locale: "fr",
|
|
73
|
-
authorized_party: "a-client",
|
|
74
|
-
roles: ["admin", "user"],
|
|
75
|
-
resource_roles: { "a-client" => ["reader"] }))
|
|
76
|
-
|
|
77
|
-
expect(KeycloakApiRails::Helper.current_user_id(authenticated_env)).to eq("a-keycloak-id")
|
|
78
|
-
expect(KeycloakApiRails::Helper.current_user_email(authenticated_env)).to eq("an-email@keycloak.io")
|
|
79
|
-
expect(KeycloakApiRails::Helper.current_user_locale(authenticated_env)).to eq("fr")
|
|
80
|
-
expect(KeycloakApiRails::Helper.current_authorized_party(authenticated_env)).to eq("a-client")
|
|
81
|
-
expect(KeycloakApiRails::Helper.current_user_roles(authenticated_env)).to eq(["admin", "user"])
|
|
82
|
-
expect(KeycloakApiRails::Helper.current_resource_roles(authenticated_env)).to eq({ "a-client" => ["reader"] })
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
it "should expose the claims declared as custom attributes" do
|
|
86
|
-
KeycloakApiRails.config.custom_attributes = ["tenant_id"]
|
|
87
|
-
|
|
88
|
-
get(keycloak_auth_headers(claims: { "tenant_id" => "a-tenant" }))
|
|
89
|
-
|
|
90
|
-
expect(KeycloakApiRails::Helper.current_user_custom_attributes(authenticated_env)).to eq({ "tenant_id" => "a-tenant" })
|
|
91
|
-
ensure
|
|
92
|
-
KeycloakApiRails.config.custom_attributes = []
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
it "should forge a token that is expired when 'expires_at' is in the past" do
|
|
96
|
-
status, _headers, body = get(keycloak_auth_headers(issued_at: Time.now - 7200, expires_at: Time.now - 3600))
|
|
97
|
-
|
|
98
|
-
expect(status).to eq(401)
|
|
99
|
-
expect(body.first).to include("JWT token is expired")
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
data/spec/spec_helper.rb
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
require_relative "../lib/keycloak-api-rails"
|
|
2
|
-
require_relative "support/rails_helper"
|
|
3
|
-
require_relative "support/public_key_cached_resolver_stub"
|
|
4
|
-
require_relative "support/public_key_resolver_stub"
|
|
5
|
-
require "timecop"
|
|
6
|
-
require "byebug"
|
|
7
|
-
|
|
8
|
-
RSpec.configure do |config|
|
|
9
|
-
config.include RailsHelper
|
|
10
|
-
|
|
11
|
-
config.expect_with :rspec do |expectations|
|
|
12
|
-
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,131 +0,0 @@
|
|
|
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
|