keycloak-api-rails 1.0.0 → 1.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 +4 -4
- data/.github/workflows/ci.yml +9 -2
- data/.gitignore +5 -1
- data/CHANGELOG.md +9 -1
- data/Dockerfile +1 -2
- data/README.md +21 -8
- data/keycloak-api-rails.gemspec +6 -2
- data/lib/keycloak-api-rails/authentication.rb +3 -5
- data/lib/keycloak-api-rails/configuration.rb +9 -10
- data/lib/keycloak-api-rails/helper.rb +2 -2
- data/lib/keycloak-api-rails/public_key_cached_resolver.rb +1 -1
- data/lib/keycloak-api-rails/service.rb +2 -2
- data/lib/keycloak-api-rails/version.rb +1 -1
- data/spec/keycloak-api-rails/activesupport_free_spec.rb +18 -0
- data/spec/keycloak-api-rails/authentication_spec.rb +38 -0
- data/spec/keycloak-api-rails/configuration_spec.rb +101 -0
- data/spec/keycloak-api-rails/helper_spec.rb +61 -0
- data/spec/keycloak-api-rails/service_spec.rb +23 -1
- data/spec/support/activesupport_free_probe.rb +131 -0
- metadata +13 -14
- data/Gemfile.lock +0 -244
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb67f66191b532371ed77f5677c4959a3d01cd8d4fa270dd984a119d796077b0
|
|
4
|
+
data.tar.gz: 64f29e21b50fb2d6f46d2a136c299aa9864c78aea977c9e4f7dc6a4927a28dff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2db5967b4c4b7e678763d015dd05567103cafb42bce12f0018c241df45400f8c4538309a59dd7526994bc14cf587aef3b7c51040d89ca2599c91c409b6acf8a5
|
|
7
|
+
data.tar.gz: 847fc92280c17a54d7f10976197b4ea0b37fea4217ee7ef9dd6d7089399b261831a07d3d56d07ffce2827aaf86c4332da680b3eaa044a3c35d0cb2347f123043
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -11,11 +11,18 @@ permissions:
|
|
|
11
11
|
|
|
12
12
|
jobs:
|
|
13
13
|
test:
|
|
14
|
-
|
|
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
|
|
15
19
|
|
|
16
20
|
strategy:
|
|
21
|
+
fail-fast: false
|
|
17
22
|
matrix:
|
|
18
|
-
|
|
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']
|
|
19
26
|
|
|
20
27
|
steps:
|
|
21
28
|
- uses: actions/checkout@v4
|
data/.gitignore
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
.bundle/
|
|
2
2
|
log/*.log
|
|
3
3
|
.byebug_history
|
|
4
|
-
*.gem
|
|
4
|
+
*.gem
|
|
5
|
+
# This gem supports several Ruby versions, each of them resolving a different set of
|
|
6
|
+
# dependencies (Ruby 2.7 caps Rails to 7.1, Ruby 3.1 to 7.2, Ruby 3.2+ resolves Rails 8.1).
|
|
7
|
+
# A single committed lockfile cannot satisfy all of them.
|
|
8
|
+
Gemfile.lock
|
data/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [1.
|
|
8
|
+
## [1.1.0] - 2026-07-31
|
|
9
|
+
|
|
10
|
+
* Dependencies: Upgrade test dependency `timecop` to `0.9.11`
|
|
11
|
+
* Upgrade Docker image to Ruby 3.4
|
|
12
|
+
* Remove all usages of `ActiveSupport` from the library, replaced by plain Ruby
|
|
13
|
+
* Support Ruby 2.7 up to 4.0: declare `required_ruby_version >= 2.7` and stop committing `Gemfile.lock`, so that each Ruby version resolves the dependencies it supports (Rails 7.1 on Ruby 2.7 and 3.0, Rails 7.2 on Ruby 3.1, Rails 8.1 on Ruby 3.2 and above)
|
|
14
|
+
* Test dependency `byebug` is no longer pinned to an exact version, since byebug 12 requires Ruby 3.1
|
|
15
|
+
|
|
16
|
+
## [1.0.0] - 2025-12-10
|
|
9
17
|
|
|
10
18
|
* *[BREAKING CHANGE!]* Rename top-level module from `Keycloak` to `KeycloakApiRails` to avoid conflicts with other gems. (thanks to @eilers)
|
|
11
19
|
* *Migration: Please do a global find and replace of `Keycloak.` to `KeycloakApiRails.` in your application.*
|
data/Dockerfile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM ruby:3.
|
|
1
|
+
FROM ruby:3.4.10-slim-bookworm
|
|
2
2
|
|
|
3
3
|
RUN apt-get update -qq && apt-get install -y build-essential git ruby-dev && apt-get clean && \
|
|
4
4
|
mkdir -p /usr/src/app/lib/keycloak-api-rails
|
|
@@ -6,7 +6,6 @@ RUN apt-get update -qq && apt-get install -y build-essential git ruby-dev && apt
|
|
|
6
6
|
WORKDIR /usr/src/app
|
|
7
7
|
|
|
8
8
|
COPY Gemfile /usr/src/app/
|
|
9
|
-
COPY Gemfile.lock /usr/src/app/
|
|
10
9
|
COPY keycloak-api-rails.gemspec /usr/src/app/
|
|
11
10
|
COPY lib/keycloak-api-rails/version.rb /usr/src/app/lib/keycloak-api-rails/
|
|
12
11
|
RUN bundle install
|
data/README.md
CHANGED
|
@@ -2,10 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
This gem validates Keycloak JWT token for Ruby On Rails APIs.
|
|
4
4
|
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
* Ruby `>= 2.7`
|
|
8
|
+
* Rails `>= 4.2`
|
|
9
|
+
|
|
10
|
+
Every push is tested against Ruby 2.7, 3.0, 3.1, 3.2, 3.3, 3.4 and 4.0. Each of them installs the
|
|
11
|
+
most recent dependencies it supports, so the test suite runs against Rails 7.1 (Ruby 2.7 and 3.0),
|
|
12
|
+
Rails 7.2 (Ruby 3.1) and Rails 8.1 (Ruby 3.2 and above).
|
|
13
|
+
|
|
14
|
+
Ruby 2.7 and 3.0 reached their end of life and no longer receive security fixes. They are supported
|
|
15
|
+
here so that applications running on Rails 7.0 and 7.1 can use this gem, but upgrading Ruby remains
|
|
16
|
+
the recommended path.
|
|
17
|
+
|
|
5
18
|
## Install
|
|
6
19
|
|
|
7
20
|
```ruby
|
|
8
|
-
gem "keycloak-api-rails", "1.
|
|
21
|
+
gem "keycloak-api-rails", "1.1.0"
|
|
9
22
|
```
|
|
10
23
|
|
|
11
24
|
## Token validation
|
|
@@ -63,7 +76,7 @@ All options have a default value. However, all of them can be changed in your in
|
|
|
63
76
|
Create a `keycloak.rb` file in your Rails `config/initializers` folder. For instance:
|
|
64
77
|
|
|
65
78
|
```
|
|
66
|
-
|
|
79
|
+
KeycloakApiRails.configure do |config|
|
|
67
80
|
config.server_url = ENV["KEYCLOAK_SERVER_URL"]
|
|
68
81
|
config.realm_id = ENV["KEYCLOAK_REALM_ID"]
|
|
69
82
|
config.logger = Rails.logger
|
|
@@ -77,7 +90,7 @@ end
|
|
|
77
90
|
Or using opt-in configuration:
|
|
78
91
|
|
|
79
92
|
```ruby
|
|
80
|
-
|
|
93
|
+
KeycloakApiRails.configure do |config|
|
|
81
94
|
config.server_url = ENV["KEYCLOAK_SERVER_URL"]
|
|
82
95
|
config.realm_id = ENV["KEYCLOAK_REALM_ID"]
|
|
83
96
|
config.logger = Rails.logger
|
|
@@ -163,14 +176,14 @@ This should output `https://api.pouet.io/api/more-pouets?authorizationToken=myTo
|
|
|
163
176
|
|
|
164
177
|
### Accessing Keycloak Service
|
|
165
178
|
|
|
166
|
-
A lazy-loaded service KeycloakApiRails::Service can be accessed using `
|
|
179
|
+
A lazy-loaded service KeycloakApiRails::Service can be accessed using `KeycloakApiRails.service`.
|
|
167
180
|
For instance, to read a provided token:
|
|
168
181
|
```ruby
|
|
169
182
|
class RenderTokenController < ApplicationController
|
|
170
183
|
def show
|
|
171
184
|
uri = request.env["REQUEST_URI"]
|
|
172
185
|
headers = request.env
|
|
173
|
-
token =
|
|
186
|
+
token = KeycloakApiRails.service.read_token(uri, headers)
|
|
174
187
|
render json: { token: token }, status: :ok
|
|
175
188
|
end
|
|
176
189
|
end
|
|
@@ -200,10 +213,10 @@ let(:jwt) do
|
|
|
200
213
|
token.sign($private_key, :RS256).to_s
|
|
201
214
|
end
|
|
202
215
|
```
|
|
203
|
-
* Finally, in the same `shared_context`, stub `
|
|
216
|
+
* Finally, in the same `shared_context`, stub `KeycloakApiRails.public_key_resolver` to use a valid public key that is able to validate `jwt`:
|
|
204
217
|
```ruby
|
|
205
218
|
before(:each) do
|
|
206
|
-
public_key_resolver =
|
|
219
|
+
public_key_resolver = KeycloakApiRails.public_key_resolver
|
|
207
220
|
allow(public_key_resolver).to receive(:find_public_keys) { JSON::JWK::Set.new(JSON::JWK.new($private_key, kid: "default")) }
|
|
208
221
|
end
|
|
209
222
|
```
|
|
@@ -216,7 +229,7 @@ From the `keycloak-rails-api` directory:
|
|
|
216
229
|
$ docker build . -t keycloak-rails-api:test
|
|
217
230
|
$ docker run -v `pwd`:/usr/src/app/ keycloak-rails-api:test bundle exec rspec spec
|
|
218
231
|
```
|
|
219
|
-
|
|
232
|
+
|
|
220
233
|
## Next developments
|
|
221
234
|
|
|
222
235
|
* Manage multiple realms
|
data/keycloak-api-rails.gemspec
CHANGED
|
@@ -15,10 +15,14 @@ Gem::Specification.new do |spec|
|
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0")
|
|
16
16
|
spec.require_paths = ["lib"]
|
|
17
17
|
|
|
18
|
+
spec.required_ruby_version = ">= 2.7"
|
|
19
|
+
|
|
18
20
|
spec.add_dependency "rails", ">= 4.2"
|
|
19
21
|
spec.add_dependency "json-jwt", ">= 1.11.0"
|
|
20
22
|
|
|
21
23
|
spec.add_development_dependency "rspec", "3.13.2"
|
|
22
|
-
spec.add_development_dependency "timecop", "0.9.
|
|
23
|
-
|
|
24
|
+
spec.add_development_dependency "timecop", "0.9.11"
|
|
25
|
+
# Not pinned to an exact version: byebug 12 requires Ruby >= 3.1, byebug 13 requires Ruby >= 3.2.
|
|
26
|
+
# Older Rubies resolve to byebug 11.
|
|
27
|
+
spec.add_development_dependency "byebug", ">= 11.1.3"
|
|
24
28
|
end
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
module KeycloakApiRails
|
|
2
2
|
module Authentication
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if respond_to?(:helper_method)
|
|
7
|
-
helper_method :keycloak_authenticate
|
|
3
|
+
def self.included(base)
|
|
4
|
+
if base.respond_to?(:helper_method)
|
|
5
|
+
base.helper_method :keycloak_authenticate
|
|
8
6
|
end
|
|
9
7
|
end
|
|
10
8
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
module KeycloakApiRails
|
|
2
2
|
class Configuration
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
@@ -83,11 +83,11 @@ module KeycloakApiRails
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def self.read_token_from_query_string(uri)
|
|
86
|
-
if uri.
|
|
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&.
|
|
90
|
+
query_string_token&.at(1)
|
|
91
91
|
else
|
|
92
92
|
""
|
|
93
93
|
end
|
|
@@ -24,7 +24,7 @@ module KeycloakApiRails
|
|
|
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
|
|
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
|
|
@@ -53,8 +53,8 @@ module KeycloakApiRails
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def expired?(token)
|
|
56
|
-
token_expiration = Time.at(token["exp"])
|
|
57
|
-
token_expiration < Time.now + @token_expiration_tolerance_in_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
|
|
@@ -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
|
|
@@ -17,6 +17,44 @@ describe KeycloakApiRails::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
|
|
@@ -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,4 +1,65 @@
|
|
|
1
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" }
|
|
@@ -80,9 +80,31 @@ RSpec.describe KeycloakApiRails::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
|
|
|
@@ -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
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: keycloak-api-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lorent Lempereur
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rails
|
|
@@ -58,28 +57,28 @@ dependencies:
|
|
|
58
57
|
requirements:
|
|
59
58
|
- - '='
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: 0.9.
|
|
60
|
+
version: 0.9.11
|
|
62
61
|
type: :development
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
65
|
- - '='
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 0.9.
|
|
67
|
+
version: 0.9.11
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
69
|
name: byebug
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
72
71
|
requirements:
|
|
73
|
-
- -
|
|
72
|
+
- - ">="
|
|
74
73
|
- !ruby/object:Gem::Version
|
|
75
|
-
version:
|
|
74
|
+
version: 11.1.3
|
|
76
75
|
type: :development
|
|
77
76
|
prerelease: false
|
|
78
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
78
|
requirements:
|
|
80
|
-
- -
|
|
79
|
+
- - ">="
|
|
81
80
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
81
|
+
version: 11.1.3
|
|
83
82
|
description: Rails middleware that validates Authorization token emitted by Keycloak
|
|
84
83
|
email:
|
|
85
84
|
- lorent.lempereur.dev@gmail.com
|
|
@@ -93,7 +92,6 @@ files:
|
|
|
93
92
|
- CHANGELOG.md
|
|
94
93
|
- Dockerfile
|
|
95
94
|
- Gemfile
|
|
96
|
-
- Gemfile.lock
|
|
97
95
|
- MIT-LICENSE
|
|
98
96
|
- README.md
|
|
99
97
|
- keycloak-api-rails.gemspec
|
|
@@ -109,11 +107,14 @@ files:
|
|
|
109
107
|
- lib/keycloak-api-rails/service.rb
|
|
110
108
|
- lib/keycloak-api-rails/token_error.rb
|
|
111
109
|
- lib/keycloak-api-rails/version.rb
|
|
110
|
+
- spec/keycloak-api-rails/activesupport_free_spec.rb
|
|
112
111
|
- spec/keycloak-api-rails/authentication_spec.rb
|
|
112
|
+
- spec/keycloak-api-rails/configuration_spec.rb
|
|
113
113
|
- spec/keycloak-api-rails/helper_spec.rb
|
|
114
114
|
- spec/keycloak-api-rails/public_key_cached_resolver_spec.rb
|
|
115
115
|
- spec/keycloak-api-rails/service_spec.rb
|
|
116
116
|
- spec/spec_helper.rb
|
|
117
|
+
- spec/support/activesupport_free_probe.rb
|
|
117
118
|
- spec/support/public_key_cached_resolver_stub.rb
|
|
118
119
|
- spec/support/public_key_resolver_stub.rb
|
|
119
120
|
- spec/support/rails_helper.rb
|
|
@@ -121,7 +122,6 @@ homepage: https://github.com/looorent/keycloak-api-rails
|
|
|
121
122
|
licenses:
|
|
122
123
|
- MIT
|
|
123
124
|
metadata: {}
|
|
124
|
-
post_install_message:
|
|
125
125
|
rdoc_options: []
|
|
126
126
|
require_paths:
|
|
127
127
|
- lib
|
|
@@ -129,15 +129,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
129
129
|
requirements:
|
|
130
130
|
- - ">="
|
|
131
131
|
- !ruby/object:Gem::Version
|
|
132
|
-
version: '
|
|
132
|
+
version: '2.7'
|
|
133
133
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
134
|
requirements:
|
|
135
135
|
- - ">="
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
137
|
version: '0'
|
|
138
138
|
requirements: []
|
|
139
|
-
rubygems_version:
|
|
140
|
-
signing_key:
|
|
139
|
+
rubygems_version: 4.0.17
|
|
141
140
|
specification_version: 4
|
|
142
141
|
summary: Rails middleware that validates Authorization token emitted by Keycloak
|
|
143
142
|
test_files: []
|
data/Gemfile.lock
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
keycloak-api-rails (1.0.0)
|
|
5
|
-
json-jwt (>= 1.11.0)
|
|
6
|
-
rails (>= 4.2)
|
|
7
|
-
|
|
8
|
-
GEM
|
|
9
|
-
remote: https://rubygems.org/
|
|
10
|
-
specs:
|
|
11
|
-
action_text-trix (2.1.15)
|
|
12
|
-
railties
|
|
13
|
-
actioncable (8.1.1)
|
|
14
|
-
actionpack (= 8.1.1)
|
|
15
|
-
activesupport (= 8.1.1)
|
|
16
|
-
nio4r (~> 2.0)
|
|
17
|
-
websocket-driver (>= 0.6.1)
|
|
18
|
-
zeitwerk (~> 2.6)
|
|
19
|
-
actionmailbox (8.1.1)
|
|
20
|
-
actionpack (= 8.1.1)
|
|
21
|
-
activejob (= 8.1.1)
|
|
22
|
-
activerecord (= 8.1.1)
|
|
23
|
-
activestorage (= 8.1.1)
|
|
24
|
-
activesupport (= 8.1.1)
|
|
25
|
-
mail (>= 2.8.0)
|
|
26
|
-
actionmailer (8.1.1)
|
|
27
|
-
actionpack (= 8.1.1)
|
|
28
|
-
actionview (= 8.1.1)
|
|
29
|
-
activejob (= 8.1.1)
|
|
30
|
-
activesupport (= 8.1.1)
|
|
31
|
-
mail (>= 2.8.0)
|
|
32
|
-
rails-dom-testing (~> 2.2)
|
|
33
|
-
actionpack (8.1.1)
|
|
34
|
-
actionview (= 8.1.1)
|
|
35
|
-
activesupport (= 8.1.1)
|
|
36
|
-
nokogiri (>= 1.8.5)
|
|
37
|
-
rack (>= 2.2.4)
|
|
38
|
-
rack-session (>= 1.0.1)
|
|
39
|
-
rack-test (>= 0.6.3)
|
|
40
|
-
rails-dom-testing (~> 2.2)
|
|
41
|
-
rails-html-sanitizer (~> 1.6)
|
|
42
|
-
useragent (~> 0.16)
|
|
43
|
-
actiontext (8.1.1)
|
|
44
|
-
action_text-trix (~> 2.1.15)
|
|
45
|
-
actionpack (= 8.1.1)
|
|
46
|
-
activerecord (= 8.1.1)
|
|
47
|
-
activestorage (= 8.1.1)
|
|
48
|
-
activesupport (= 8.1.1)
|
|
49
|
-
globalid (>= 0.6.0)
|
|
50
|
-
nokogiri (>= 1.8.5)
|
|
51
|
-
actionview (8.1.1)
|
|
52
|
-
activesupport (= 8.1.1)
|
|
53
|
-
builder (~> 3.1)
|
|
54
|
-
erubi (~> 1.11)
|
|
55
|
-
rails-dom-testing (~> 2.2)
|
|
56
|
-
rails-html-sanitizer (~> 1.6)
|
|
57
|
-
activejob (8.1.1)
|
|
58
|
-
activesupport (= 8.1.1)
|
|
59
|
-
globalid (>= 0.3.6)
|
|
60
|
-
activemodel (8.1.1)
|
|
61
|
-
activesupport (= 8.1.1)
|
|
62
|
-
activerecord (8.1.1)
|
|
63
|
-
activemodel (= 8.1.1)
|
|
64
|
-
activesupport (= 8.1.1)
|
|
65
|
-
timeout (>= 0.4.0)
|
|
66
|
-
activestorage (8.1.1)
|
|
67
|
-
actionpack (= 8.1.1)
|
|
68
|
-
activejob (= 8.1.1)
|
|
69
|
-
activerecord (= 8.1.1)
|
|
70
|
-
activesupport (= 8.1.1)
|
|
71
|
-
marcel (~> 1.0)
|
|
72
|
-
activesupport (8.1.1)
|
|
73
|
-
base64
|
|
74
|
-
bigdecimal
|
|
75
|
-
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
76
|
-
connection_pool (>= 2.2.5)
|
|
77
|
-
drb
|
|
78
|
-
i18n (>= 1.6, < 2)
|
|
79
|
-
json
|
|
80
|
-
logger (>= 1.4.2)
|
|
81
|
-
minitest (>= 5.1)
|
|
82
|
-
securerandom (>= 0.3)
|
|
83
|
-
tzinfo (~> 2.0, >= 2.0.5)
|
|
84
|
-
uri (>= 0.13.1)
|
|
85
|
-
aes_key_wrap (1.1.0)
|
|
86
|
-
base64 (0.3.0)
|
|
87
|
-
bigdecimal (3.3.1)
|
|
88
|
-
bindata (2.5.1)
|
|
89
|
-
builder (3.3.0)
|
|
90
|
-
byebug (12.0.0)
|
|
91
|
-
concurrent-ruby (1.3.5)
|
|
92
|
-
connection_pool (3.0.2)
|
|
93
|
-
crass (1.0.6)
|
|
94
|
-
date (3.5.1)
|
|
95
|
-
diff-lcs (1.6.2)
|
|
96
|
-
drb (2.2.3)
|
|
97
|
-
erb (6.0.0)
|
|
98
|
-
erubi (1.13.1)
|
|
99
|
-
faraday (2.14.0)
|
|
100
|
-
faraday-net_http (>= 2.0, < 3.5)
|
|
101
|
-
json
|
|
102
|
-
logger
|
|
103
|
-
faraday-follow_redirects (0.4.0)
|
|
104
|
-
faraday (>= 1, < 3)
|
|
105
|
-
faraday-net_http (3.4.2)
|
|
106
|
-
net-http (~> 0.5)
|
|
107
|
-
globalid (1.3.0)
|
|
108
|
-
activesupport (>= 6.1)
|
|
109
|
-
i18n (1.14.7)
|
|
110
|
-
concurrent-ruby (~> 1.0)
|
|
111
|
-
io-console (0.8.1)
|
|
112
|
-
irb (1.15.3)
|
|
113
|
-
pp (>= 0.6.0)
|
|
114
|
-
rdoc (>= 4.0.0)
|
|
115
|
-
reline (>= 0.4.2)
|
|
116
|
-
json (2.17.1)
|
|
117
|
-
json-jwt (1.17.0)
|
|
118
|
-
activesupport (>= 4.2)
|
|
119
|
-
aes_key_wrap
|
|
120
|
-
base64
|
|
121
|
-
bindata
|
|
122
|
-
faraday (~> 2.0)
|
|
123
|
-
faraday-follow_redirects
|
|
124
|
-
logger (1.7.0)
|
|
125
|
-
loofah (2.24.1)
|
|
126
|
-
crass (~> 1.0.2)
|
|
127
|
-
nokogiri (>= 1.12.0)
|
|
128
|
-
mail (2.9.0)
|
|
129
|
-
logger
|
|
130
|
-
mini_mime (>= 0.1.1)
|
|
131
|
-
net-imap
|
|
132
|
-
net-pop
|
|
133
|
-
net-smtp
|
|
134
|
-
marcel (1.1.0)
|
|
135
|
-
mini_mime (1.1.5)
|
|
136
|
-
mini_portile2 (2.8.9)
|
|
137
|
-
minitest (5.26.2)
|
|
138
|
-
net-http (0.8.0)
|
|
139
|
-
uri (>= 0.11.1)
|
|
140
|
-
net-imap (0.5.12)
|
|
141
|
-
date
|
|
142
|
-
net-protocol
|
|
143
|
-
net-pop (0.1.2)
|
|
144
|
-
net-protocol
|
|
145
|
-
net-protocol (0.2.2)
|
|
146
|
-
timeout
|
|
147
|
-
net-smtp (0.5.1)
|
|
148
|
-
net-protocol
|
|
149
|
-
nio4r (2.7.5)
|
|
150
|
-
nokogiri (1.18.10)
|
|
151
|
-
mini_portile2 (~> 2.8.2)
|
|
152
|
-
racc (~> 1.4)
|
|
153
|
-
pp (0.6.3)
|
|
154
|
-
prettyprint
|
|
155
|
-
prettyprint (0.2.0)
|
|
156
|
-
psych (5.3.0)
|
|
157
|
-
date
|
|
158
|
-
stringio
|
|
159
|
-
racc (1.8.1)
|
|
160
|
-
rack (3.2.4)
|
|
161
|
-
rack-session (2.1.1)
|
|
162
|
-
base64 (>= 0.1.0)
|
|
163
|
-
rack (>= 3.0.0)
|
|
164
|
-
rack-test (2.2.0)
|
|
165
|
-
rack (>= 1.3)
|
|
166
|
-
rackup (2.3.1)
|
|
167
|
-
rack (>= 3)
|
|
168
|
-
rails (8.1.1)
|
|
169
|
-
actioncable (= 8.1.1)
|
|
170
|
-
actionmailbox (= 8.1.1)
|
|
171
|
-
actionmailer (= 8.1.1)
|
|
172
|
-
actionpack (= 8.1.1)
|
|
173
|
-
actiontext (= 8.1.1)
|
|
174
|
-
actionview (= 8.1.1)
|
|
175
|
-
activejob (= 8.1.1)
|
|
176
|
-
activemodel (= 8.1.1)
|
|
177
|
-
activerecord (= 8.1.1)
|
|
178
|
-
activestorage (= 8.1.1)
|
|
179
|
-
activesupport (= 8.1.1)
|
|
180
|
-
bundler (>= 1.15.0)
|
|
181
|
-
railties (= 8.1.1)
|
|
182
|
-
rails-dom-testing (2.3.0)
|
|
183
|
-
activesupport (>= 5.0.0)
|
|
184
|
-
minitest
|
|
185
|
-
nokogiri (>= 1.6)
|
|
186
|
-
rails-html-sanitizer (1.6.2)
|
|
187
|
-
loofah (~> 2.21)
|
|
188
|
-
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
189
|
-
railties (8.1.1)
|
|
190
|
-
actionpack (= 8.1.1)
|
|
191
|
-
activesupport (= 8.1.1)
|
|
192
|
-
irb (~> 1.13)
|
|
193
|
-
rackup (>= 1.0.0)
|
|
194
|
-
rake (>= 12.2)
|
|
195
|
-
thor (~> 1.0, >= 1.2.2)
|
|
196
|
-
tsort (>= 0.2)
|
|
197
|
-
zeitwerk (~> 2.6)
|
|
198
|
-
rake (13.3.1)
|
|
199
|
-
rdoc (6.17.0)
|
|
200
|
-
erb
|
|
201
|
-
psych (>= 4.0.0)
|
|
202
|
-
tsort
|
|
203
|
-
reline (0.6.3)
|
|
204
|
-
io-console (~> 0.5)
|
|
205
|
-
rspec (3.13.2)
|
|
206
|
-
rspec-core (~> 3.13.0)
|
|
207
|
-
rspec-expectations (~> 3.13.0)
|
|
208
|
-
rspec-mocks (~> 3.13.0)
|
|
209
|
-
rspec-core (3.13.6)
|
|
210
|
-
rspec-support (~> 3.13.0)
|
|
211
|
-
rspec-expectations (3.13.5)
|
|
212
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
213
|
-
rspec-support (~> 3.13.0)
|
|
214
|
-
rspec-mocks (3.13.7)
|
|
215
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
216
|
-
rspec-support (~> 3.13.0)
|
|
217
|
-
rspec-support (3.13.6)
|
|
218
|
-
securerandom (0.4.1)
|
|
219
|
-
stringio (3.1.9)
|
|
220
|
-
thor (1.4.0)
|
|
221
|
-
timecop (0.9.10)
|
|
222
|
-
timeout (0.5.0)
|
|
223
|
-
tsort (0.2.0)
|
|
224
|
-
tzinfo (2.0.6)
|
|
225
|
-
concurrent-ruby (~> 1.0)
|
|
226
|
-
uri (1.1.1)
|
|
227
|
-
useragent (0.16.11)
|
|
228
|
-
websocket-driver (0.8.0)
|
|
229
|
-
base64
|
|
230
|
-
websocket-extensions (>= 0.1.0)
|
|
231
|
-
websocket-extensions (0.1.5)
|
|
232
|
-
zeitwerk (2.7.3)
|
|
233
|
-
|
|
234
|
-
PLATFORMS
|
|
235
|
-
ruby
|
|
236
|
-
|
|
237
|
-
DEPENDENCIES
|
|
238
|
-
byebug (= 12.0.0)
|
|
239
|
-
keycloak-api-rails!
|
|
240
|
-
rspec (= 3.13.2)
|
|
241
|
-
timecop (= 0.9.10)
|
|
242
|
-
|
|
243
|
-
BUNDLED WITH
|
|
244
|
-
2.4.13
|