keycloak-api-rails 1.0.1 → 1.1.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +9 -2
- data/.github/workflows/release.yml +52 -0
- data/.gitignore +6 -1
- data/CHANGELOG.md +8 -1
- data/Dockerfile +0 -1
- data/README.md +76 -20
- data/Rakefile +6 -0
- data/keycloak-api-rails.gemspec +7 -1
- data/lib/keycloak-api-rails/testing.rb +118 -0
- data/lib/keycloak-api-rails/version.rb +1 -1
- data/lib/keycloak-api-rails.rb +8 -0
- data/spec/keycloak-api-rails/service_spec.rb +6 -0
- data/spec/keycloak-api-rails/testing_spec.rb +102 -0
- metadata +26 -12
- data/Gemfile.lock +0 -249
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f53bfe86f8f72911bac95192b9a21fc0cf1ec01868e00f2cdd8edfcc57811e3
|
|
4
|
+
data.tar.gz: 2ad644c2561e191e9dd858055a9442df69fff0af02d078360164f1d41283f633
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: da17e6a76b47460200ea397c58346dbdd43975f7aecec34e973b77ad15ebd12739d2c5d533064af24204d5be7bd4cc0f3c74b3df98e6d2623289a0d66f1398ad
|
|
7
|
+
data.tar.gz: 1f1f0ed249c2963fec160fdbdb6910a8387024f54d9956c35729fb5438037b541989046aee9dab2936dcc585d8f5e9ca6cd1b3247c20fb4dcea62ff7e2fae253
|
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
|
|
@@ -0,0 +1,52 @@
|
|
|
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
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
.bundle/
|
|
2
2
|
log/*.log
|
|
3
3
|
.byebug_history
|
|
4
|
-
*.gem
|
|
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/CHANGELOG.md
CHANGED
|
@@ -5,11 +5,18 @@ 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.1] - 2026-07-31
|
|
9
|
+
|
|
10
|
+
* New test helpers, in `keycloak-api-rails/testing`: `KeycloakApiRails::Testing.stub_public_keys!` validates the tokens forged by `keycloak_token` and `keycloak_auth_headers`, so that controller and request tests can be authenticated without a Keycloak server, and without the boilerplate the README used to describe. This file is not loaded by `keycloak-api-rails` and has to be required explicitly
|
|
11
|
+
* Publish the gem to RubyGems from Github Actions when a `v*` tag is pushed, using RubyGems' Trusted Publishing
|
|
12
|
+
|
|
13
|
+
## [1.1.0] - 2026-07-31
|
|
9
14
|
|
|
10
15
|
* Dependencies: Upgrade test dependency `timecop` to `0.9.11`
|
|
11
16
|
* Upgrade Docker image to Ruby 3.4
|
|
12
17
|
* Remove all usages of `ActiveSupport` from the library, replaced by plain Ruby
|
|
18
|
+
* 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)
|
|
19
|
+
* Test dependency `byebug` is no longer pinned to an exact version, since byebug 12 requires Ruby 3.1
|
|
13
20
|
|
|
14
21
|
## [1.0.0] - 2025-12-10
|
|
15
22
|
|
data/Dockerfile
CHANGED
|
@@ -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.1"
|
|
9
22
|
```
|
|
10
23
|
|
|
11
24
|
## Token validation
|
|
@@ -178,36 +191,60 @@ end
|
|
|
178
191
|
|
|
179
192
|
## Writing integration tests
|
|
180
193
|
|
|
181
|
-
If you want to write controller tests in your codebase and that Keycloak is configured for these controllers,
|
|
194
|
+
If you want to write controller or request tests in your codebase and that Keycloak is configured for these controllers,
|
|
195
|
+
this gem provides the helpers that forge valid tokens, so that no Keycloak server is required.
|
|
196
|
+
These helpers are not loaded by `keycloak-api-rails`: they have to be required explicitly, so that nothing they define ends up in a production process.
|
|
182
197
|
These lines are based on tests written using `rspec`.
|
|
183
198
|
|
|
184
|
-
* First, create a private key. This key should be created once per test suite for performance matters.
|
|
185
199
|
```ruby
|
|
186
|
-
|
|
187
|
-
|
|
200
|
+
# spec/rails_helper.rb
|
|
201
|
+
require "keycloak-api-rails/testing"
|
|
202
|
+
|
|
203
|
+
RSpec.configure do |config|
|
|
204
|
+
config.include KeycloakApiRails::Testing::Helpers
|
|
205
|
+
config.before(:suite) { KeycloakApiRails::Testing.stub_public_keys! }
|
|
188
206
|
end
|
|
189
207
|
```
|
|
190
|
-
|
|
208
|
+
|
|
209
|
+
`stub_public_keys!` makes the library validate the tokens forged by these helpers, instead of fetching public keys from a Keycloak server.
|
|
210
|
+
`server_url` and `realm_id` therefore do not have to be configured in the test environment. Requests can then be authenticated with:
|
|
211
|
+
|
|
191
212
|
```ruby
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
sub: user.keycloak_id,
|
|
197
|
-
}
|
|
198
|
-
token = JSON::JWT.new(claims)
|
|
199
|
-
token.kid = "default"
|
|
200
|
-
token.sign($private_key, :RS256).to_s
|
|
213
|
+
it "returns the current user" do
|
|
214
|
+
get "/me", headers: keycloak_auth_headers(sub: user.keycloak_id)
|
|
215
|
+
|
|
216
|
+
expect(response).to have_http_status(:ok)
|
|
201
217
|
end
|
|
202
218
|
```
|
|
203
|
-
|
|
219
|
+
|
|
220
|
+
`keycloak_auth_headers` returns an `Authorization` header, and `keycloak_token` returns the token alone.
|
|
221
|
+
Both accept the same optional parameters, one for each claim this library reads:
|
|
222
|
+
|
|
223
|
+
| Parameter | Claim | Default |
|
|
224
|
+
| --- | --- | --- |
|
|
225
|
+
| `sub` | `sub` | a random UUID |
|
|
226
|
+
| `email` | `email` | none |
|
|
227
|
+
| `locale` | `locale` | none |
|
|
228
|
+
| `authorized_party` | `azp` | none |
|
|
229
|
+
| `roles` | `realm_access.roles` | none |
|
|
230
|
+
| `resource_roles` | `resource_access` | none |
|
|
231
|
+
| `issued_at` | `iat` | now |
|
|
232
|
+
| `expires_at` | `exp` | one hour after `issued_at` |
|
|
233
|
+
| `claims` | any other claim | none |
|
|
234
|
+
|
|
235
|
+
For instance, to authenticate a request as a user having the realm role `admin`, the role `reader` on the client `a-client`,
|
|
236
|
+
and a `tenant_id` attribute declared in `config.custom_attributes`:
|
|
237
|
+
|
|
204
238
|
```ruby
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
239
|
+
headers = keycloak_auth_headers(sub: user.keycloak_id,
|
|
240
|
+
roles: ["admin"],
|
|
241
|
+
resource_roles: { "a-client" => ["reader"] },
|
|
242
|
+
claims: { "tenant_id" => tenant.id })
|
|
209
243
|
```
|
|
210
244
|
|
|
245
|
+
An expired token is forged by passing an `expires_at` in the past, which is how a `401` can be tested.
|
|
246
|
+
Assigning `KeycloakApiRails.public_key_resolver = nil` restores the regular resolver.
|
|
247
|
+
|
|
211
248
|
## How to execute library tests
|
|
212
249
|
|
|
213
250
|
From the `keycloak-rails-api` directory:
|
|
@@ -217,6 +254,25 @@ From the `keycloak-rails-api` directory:
|
|
|
217
254
|
$ docker run -v `pwd`:/usr/src/app/ keycloak-rails-api:test bundle exec rspec spec
|
|
218
255
|
```
|
|
219
256
|
|
|
257
|
+
## How to release a new version
|
|
258
|
+
|
|
259
|
+
Releases are published to [RubyGems](https://rubygems.org/gems/keycloak-api-rails) by GitHub Actions
|
|
260
|
+
(`.github/workflows/release.yml`), through [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/):
|
|
261
|
+
no API key is stored in this repository, the workflow exchanges a short-lived GitHub OIDC token for a
|
|
262
|
+
scoped RubyGems credential.
|
|
263
|
+
|
|
264
|
+
1. Update `KeycloakApiRails::VERSION` in `lib/keycloak-api-rails/version.rb` and the `CHANGELOG.md`
|
|
265
|
+
2. Commit and push these changes to `main`
|
|
266
|
+
3. Tag the commit and push the tag:
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
$ git tag -a v1.2.0 -m "Version 1.2.0"
|
|
270
|
+
$ git push origin v1.2.0
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The workflow then checks that the tag matches `KeycloakApiRails::VERSION`, runs the tests, builds the gem
|
|
274
|
+
and pushes it. It only publishes tags starting with `v`.
|
|
275
|
+
|
|
220
276
|
## Next developments
|
|
221
277
|
|
|
222
278
|
* Manage multiple realms
|
data/Rakefile
ADDED
data/keycloak-api-rails.gemspec
CHANGED
|
@@ -15,10 +15,16 @@ 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
24
|
spec.add_development_dependency "timecop", "0.9.11"
|
|
23
|
-
|
|
25
|
+
# Required by 'rake release', which builds and publishes the gem from the CI.
|
|
26
|
+
spec.add_development_dependency "rake", ">= 13.0"
|
|
27
|
+
# Not pinned to an exact version: byebug 12 requires Ruby >= 3.1, byebug 13 requires Ruby >= 3.2.
|
|
28
|
+
# Older Rubies resolve to byebug 11.
|
|
29
|
+
spec.add_development_dependency "byebug", ">= 11.1.3"
|
|
24
30
|
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Test helpers for the applications that authenticate their requests with this library.
|
|
2
|
+
#
|
|
3
|
+
# This file is *not* loaded by "keycloak-api-rails": it has to be required explicitly, so that
|
|
4
|
+
# nothing it defines can end up in a production process.
|
|
5
|
+
#
|
|
6
|
+
# # spec/rails_helper.rb
|
|
7
|
+
# require "keycloak-api-rails/testing"
|
|
8
|
+
#
|
|
9
|
+
# RSpec.configure do |config|
|
|
10
|
+
# config.include KeycloakApiRails::Testing::Helpers
|
|
11
|
+
# config.before(:suite) { KeycloakApiRails::Testing.stub_public_keys! }
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# Requests can then be authenticated without a running Keycloak, and without configuring
|
|
15
|
+
# 'server_url' or 'realm_id':
|
|
16
|
+
#
|
|
17
|
+
# get "/me", headers: keycloak_auth_headers(sub: user.keycloak_id, roles: ["admin"])
|
|
18
|
+
|
|
19
|
+
require_relative "../keycloak-api-rails"
|
|
20
|
+
require "securerandom"
|
|
21
|
+
|
|
22
|
+
module KeycloakApiRails
|
|
23
|
+
module Testing
|
|
24
|
+
|
|
25
|
+
KEY_SIZE = 2048
|
|
26
|
+
KEY_ID = "keycloak-api-rails-testing"
|
|
27
|
+
ALGORITHM = :RS256
|
|
28
|
+
DEFAULT_VALIDITY_IN_SECONDS = 3600
|
|
29
|
+
|
|
30
|
+
# Returns the public keys forged by this module, instead of fetching them from a Keycloak server.
|
|
31
|
+
class PublicKeyResolver
|
|
32
|
+
def initialize(public_keys)
|
|
33
|
+
@public_keys = public_keys
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def find_public_keys
|
|
37
|
+
@public_keys
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
# The RSA key pair used to sign the tokens forged by this module. It is generated once per
|
|
43
|
+
# process: generating a key is by far the slowest operation of a test suite that uses tokens.
|
|
44
|
+
def private_key
|
|
45
|
+
@private_key ||= OpenSSL::PKey::RSA.generate(KEY_SIZE)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def signing_key
|
|
49
|
+
@signing_key ||= JSON::JWK.new(private_key, kid: KEY_ID)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def public_keys
|
|
53
|
+
@public_keys ||= JSON::JWK::Set.new(JSON::JWK.new(private_key.public_key, kid: KEY_ID))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Makes the library validate the tokens forged by this module. Assigning
|
|
57
|
+
# 'KeycloakApiRails.public_key_resolver = nil' restores the regular resolver.
|
|
58
|
+
def stub_public_keys!
|
|
59
|
+
KeycloakApiRails.public_key_resolver = PublicKeyResolver.new(public_keys)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Forges a signed token, similar to the ones emitted by Keycloak. Every claim this library
|
|
63
|
+
# reads has a dedicated parameter; any other claim can be passed through 'claims', which is
|
|
64
|
+
# also how the attributes declared in 'config.custom_attributes' are set.
|
|
65
|
+
def token_for(sub: SecureRandom.uuid,
|
|
66
|
+
email: nil,
|
|
67
|
+
locale: nil,
|
|
68
|
+
authorized_party: nil,
|
|
69
|
+
roles: [],
|
|
70
|
+
resource_roles: {},
|
|
71
|
+
issued_at: Time.now,
|
|
72
|
+
expires_at: nil,
|
|
73
|
+
claims: {})
|
|
74
|
+
payload = {
|
|
75
|
+
"sub" => sub,
|
|
76
|
+
"email" => email,
|
|
77
|
+
"locale" => locale,
|
|
78
|
+
"azp" => authorized_party,
|
|
79
|
+
"iat" => issued_at.to_i,
|
|
80
|
+
"exp" => (expires_at || (issued_at.to_i + DEFAULT_VALIDITY_IN_SECONDS)).to_i
|
|
81
|
+
}.reject { |_, value| value.nil? }
|
|
82
|
+
|
|
83
|
+
payload["realm_access"] = { "roles" => roles.map(&:to_s) } unless roles.nil? || roles.empty?
|
|
84
|
+
payload["resource_access"] = build_resource_access(resource_roles) unless resource_roles.nil? || resource_roles.empty?
|
|
85
|
+
|
|
86
|
+
claims.each { |name, value| payload[name.to_s] = value }
|
|
87
|
+
|
|
88
|
+
JSON::JWT.new(payload).sign(signing_key, ALGORITHM).to_s
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The 'Authorization' header a client would send, e.g. { "Authorization" => "Bearer eyJ0..." }.
|
|
92
|
+
# It accepts the same parameters as '.token_for'.
|
|
93
|
+
def auth_headers_for(**options)
|
|
94
|
+
{ "Authorization" => "Bearer #{token_for(**options)}" }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def build_resource_access(resource_roles)
|
|
100
|
+
resource_roles.inject({}) do |resource_access, (name, roles)|
|
|
101
|
+
resource_access[name.to_s] = { "roles" => Array(roles).map(&:to_s) }
|
|
102
|
+
resource_access
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Meant to be included in a test suite, e.g. 'config.include KeycloakApiRails::Testing::Helpers'.
|
|
108
|
+
module Helpers
|
|
109
|
+
def keycloak_token(**options)
|
|
110
|
+
KeycloakApiRails::Testing.token_for(**options)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def keycloak_auth_headers(**options)
|
|
114
|
+
KeycloakApiRails::Testing.auth_headers_for(**options)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/keycloak-api-rails.rb
CHANGED
|
@@ -33,6 +33,14 @@ module KeycloakApiRails
|
|
|
33
33
|
@public_key_resolver ||= PublicKeyCachedResolver.from_configuration(http_client, config)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Mainly used by "keycloak-api-rails/testing" to validate tokens without a Keycloak server.
|
|
37
|
+
# Assigning nil restores the regular resolver. The memoized service is discarded, since it holds
|
|
38
|
+
# a reference to the resolver that is being replaced.
|
|
39
|
+
def self.public_key_resolver=(resolver)
|
|
40
|
+
@public_key_resolver = resolver
|
|
41
|
+
@service = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
36
44
|
def self.service
|
|
37
45
|
@service ||= KeycloakApiRails::Service.new(public_key_resolver)
|
|
38
46
|
end
|
|
@@ -205,6 +205,12 @@ RSpec.describe KeycloakApiRails::Service do
|
|
|
205
205
|
@result = service2.need_middleware_authentication?(method, path, headers)
|
|
206
206
|
end
|
|
207
207
|
|
|
208
|
+
# The configuration is global: leaving 'opt_in' enabled would disable the authentication
|
|
209
|
+
# of every example running afterwards.
|
|
210
|
+
after do
|
|
211
|
+
KeycloakApiRails.config.opt_in = false
|
|
212
|
+
end
|
|
213
|
+
|
|
208
214
|
it "should return false" do
|
|
209
215
|
expect(@result).to be false
|
|
210
216
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
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.1
|
|
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
|
|
@@ -66,20 +65,34 @@ dependencies:
|
|
|
66
65
|
- - '='
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
67
|
version: 0.9.11
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '13.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
69
82
|
- !ruby/object:Gem::Dependency
|
|
70
83
|
name: byebug
|
|
71
84
|
requirement: !ruby/object:Gem::Requirement
|
|
72
85
|
requirements:
|
|
73
|
-
- -
|
|
86
|
+
- - ">="
|
|
74
87
|
- !ruby/object:Gem::Version
|
|
75
|
-
version:
|
|
88
|
+
version: 11.1.3
|
|
76
89
|
type: :development
|
|
77
90
|
prerelease: false
|
|
78
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
92
|
requirements:
|
|
80
|
-
- -
|
|
93
|
+
- - ">="
|
|
81
94
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
95
|
+
version: 11.1.3
|
|
83
96
|
description: Rails middleware that validates Authorization token emitted by Keycloak
|
|
84
97
|
email:
|
|
85
98
|
- lorent.lempereur.dev@gmail.com
|
|
@@ -88,14 +101,15 @@ extensions: []
|
|
|
88
101
|
extra_rdoc_files: []
|
|
89
102
|
files:
|
|
90
103
|
- ".github/workflows/ci.yml"
|
|
104
|
+
- ".github/workflows/release.yml"
|
|
91
105
|
- ".gitignore"
|
|
92
106
|
- ".rspec"
|
|
93
107
|
- CHANGELOG.md
|
|
94
108
|
- Dockerfile
|
|
95
109
|
- Gemfile
|
|
96
|
-
- Gemfile.lock
|
|
97
110
|
- MIT-LICENSE
|
|
98
111
|
- README.md
|
|
112
|
+
- Rakefile
|
|
99
113
|
- keycloak-api-rails.gemspec
|
|
100
114
|
- lib/keycloak-api-rails.rb
|
|
101
115
|
- lib/keycloak-api-rails/authentication.rb
|
|
@@ -107,6 +121,7 @@ files:
|
|
|
107
121
|
- lib/keycloak-api-rails/public_key_resolver.rb
|
|
108
122
|
- lib/keycloak-api-rails/railtie.rb
|
|
109
123
|
- lib/keycloak-api-rails/service.rb
|
|
124
|
+
- lib/keycloak-api-rails/testing.rb
|
|
110
125
|
- lib/keycloak-api-rails/token_error.rb
|
|
111
126
|
- lib/keycloak-api-rails/version.rb
|
|
112
127
|
- spec/keycloak-api-rails/activesupport_free_spec.rb
|
|
@@ -115,6 +130,7 @@ files:
|
|
|
115
130
|
- spec/keycloak-api-rails/helper_spec.rb
|
|
116
131
|
- spec/keycloak-api-rails/public_key_cached_resolver_spec.rb
|
|
117
132
|
- spec/keycloak-api-rails/service_spec.rb
|
|
133
|
+
- spec/keycloak-api-rails/testing_spec.rb
|
|
118
134
|
- spec/spec_helper.rb
|
|
119
135
|
- spec/support/activesupport_free_probe.rb
|
|
120
136
|
- spec/support/public_key_cached_resolver_stub.rb
|
|
@@ -124,7 +140,6 @@ homepage: https://github.com/looorent/keycloak-api-rails
|
|
|
124
140
|
licenses:
|
|
125
141
|
- MIT
|
|
126
142
|
metadata: {}
|
|
127
|
-
post_install_message:
|
|
128
143
|
rdoc_options: []
|
|
129
144
|
require_paths:
|
|
130
145
|
- lib
|
|
@@ -132,15 +147,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
132
147
|
requirements:
|
|
133
148
|
- - ">="
|
|
134
149
|
- !ruby/object:Gem::Version
|
|
135
|
-
version: '
|
|
150
|
+
version: '2.7'
|
|
136
151
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
152
|
requirements:
|
|
138
153
|
- - ">="
|
|
139
154
|
- !ruby/object:Gem::Version
|
|
140
155
|
version: '0'
|
|
141
156
|
requirements: []
|
|
142
|
-
rubygems_version: 3.
|
|
143
|
-
signing_key:
|
|
157
|
+
rubygems_version: 3.6.9
|
|
144
158
|
specification_version: 4
|
|
145
159
|
summary: Rails middleware that validates Authorization token emitted by Keycloak
|
|
146
160
|
test_files: []
|
data/Gemfile.lock
DELETED
|
@@ -1,249 +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.19)
|
|
12
|
-
railties
|
|
13
|
-
actioncable (8.1.3.1)
|
|
14
|
-
actionpack (= 8.1.3.1)
|
|
15
|
-
activesupport (= 8.1.3.1)
|
|
16
|
-
nio4r (~> 2.0)
|
|
17
|
-
websocket-driver (>= 0.6.1)
|
|
18
|
-
zeitwerk (~> 2.6)
|
|
19
|
-
actionmailbox (8.1.3.1)
|
|
20
|
-
actionpack (= 8.1.3.1)
|
|
21
|
-
activejob (= 8.1.3.1)
|
|
22
|
-
activerecord (= 8.1.3.1)
|
|
23
|
-
activestorage (= 8.1.3.1)
|
|
24
|
-
activesupport (= 8.1.3.1)
|
|
25
|
-
mail (>= 2.8.0)
|
|
26
|
-
actionmailer (8.1.3.1)
|
|
27
|
-
actionpack (= 8.1.3.1)
|
|
28
|
-
actionview (= 8.1.3.1)
|
|
29
|
-
activejob (= 8.1.3.1)
|
|
30
|
-
activesupport (= 8.1.3.1)
|
|
31
|
-
mail (>= 2.8.0)
|
|
32
|
-
rails-dom-testing (~> 2.2)
|
|
33
|
-
actionpack (8.1.3.1)
|
|
34
|
-
actionview (= 8.1.3.1)
|
|
35
|
-
activesupport (= 8.1.3.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.3.1)
|
|
44
|
-
action_text-trix (~> 2.1.15)
|
|
45
|
-
actionpack (= 8.1.3.1)
|
|
46
|
-
activerecord (= 8.1.3.1)
|
|
47
|
-
activestorage (= 8.1.3.1)
|
|
48
|
-
activesupport (= 8.1.3.1)
|
|
49
|
-
globalid (>= 0.6.0)
|
|
50
|
-
nokogiri (>= 1.8.5)
|
|
51
|
-
actionview (8.1.3.1)
|
|
52
|
-
activesupport (= 8.1.3.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.3.1)
|
|
58
|
-
activesupport (= 8.1.3.1)
|
|
59
|
-
globalid (>= 0.3.6)
|
|
60
|
-
activemodel (8.1.3.1)
|
|
61
|
-
activesupport (= 8.1.3.1)
|
|
62
|
-
activerecord (8.1.3.1)
|
|
63
|
-
activemodel (= 8.1.3.1)
|
|
64
|
-
activesupport (= 8.1.3.1)
|
|
65
|
-
timeout (>= 0.4.0)
|
|
66
|
-
activestorage (8.1.3.1)
|
|
67
|
-
actionpack (= 8.1.3.1)
|
|
68
|
-
activejob (= 8.1.3.1)
|
|
69
|
-
activerecord (= 8.1.3.1)
|
|
70
|
-
activesupport (= 8.1.3.1)
|
|
71
|
-
marcel (~> 1.0)
|
|
72
|
-
activesupport (8.1.3.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 (4.1.2)
|
|
88
|
-
bindata (3.0.0)
|
|
89
|
-
builder (3.3.0)
|
|
90
|
-
byebug (12.0.0)
|
|
91
|
-
concurrent-ruby (1.3.8)
|
|
92
|
-
connection_pool (3.0.2)
|
|
93
|
-
crass (1.0.7)
|
|
94
|
-
date (3.5.1)
|
|
95
|
-
diff-lcs (1.6.2)
|
|
96
|
-
drb (2.2.3)
|
|
97
|
-
erb (6.0.6)
|
|
98
|
-
erubi (1.13.1)
|
|
99
|
-
faraday (2.14.3)
|
|
100
|
-
faraday-net_http (>= 2.0, < 3.5)
|
|
101
|
-
json
|
|
102
|
-
logger
|
|
103
|
-
faraday-follow_redirects (0.5.0)
|
|
104
|
-
faraday (>= 1, < 3)
|
|
105
|
-
faraday-net_http (3.4.4)
|
|
106
|
-
net-http (~> 0.5)
|
|
107
|
-
globalid (1.4.0)
|
|
108
|
-
activesupport (>= 6.1)
|
|
109
|
-
i18n (1.15.2)
|
|
110
|
-
concurrent-ruby (~> 1.0)
|
|
111
|
-
io-console (0.8.2)
|
|
112
|
-
irb (1.18.0)
|
|
113
|
-
pp (>= 0.6.0)
|
|
114
|
-
prism (>= 1.3.0)
|
|
115
|
-
rdoc (>= 4.0.0)
|
|
116
|
-
reline (>= 0.4.2)
|
|
117
|
-
json (2.21.1)
|
|
118
|
-
json-jwt (1.17.1)
|
|
119
|
-
activesupport (>= 4.2)
|
|
120
|
-
aes_key_wrap
|
|
121
|
-
base64
|
|
122
|
-
bindata
|
|
123
|
-
faraday (~> 2.0)
|
|
124
|
-
faraday-follow_redirects
|
|
125
|
-
logger (1.7.0)
|
|
126
|
-
loofah (2.25.2)
|
|
127
|
-
crass (~> 1.0.2)
|
|
128
|
-
nokogiri (>= 1.12.0)
|
|
129
|
-
mail (2.9.1)
|
|
130
|
-
logger
|
|
131
|
-
mini_mime (>= 0.1.1)
|
|
132
|
-
net-imap
|
|
133
|
-
net-pop
|
|
134
|
-
net-smtp
|
|
135
|
-
marcel (1.2.1)
|
|
136
|
-
mini_mime (1.1.5)
|
|
137
|
-
mini_portile2 (2.8.9)
|
|
138
|
-
minitest (6.0.6)
|
|
139
|
-
drb (~> 2.0)
|
|
140
|
-
prism (~> 1.5)
|
|
141
|
-
net-http (0.9.1)
|
|
142
|
-
uri (>= 0.11.1)
|
|
143
|
-
net-imap (0.6.6)
|
|
144
|
-
date
|
|
145
|
-
net-protocol
|
|
146
|
-
net-pop (0.1.2)
|
|
147
|
-
net-protocol
|
|
148
|
-
net-protocol (0.2.2)
|
|
149
|
-
timeout
|
|
150
|
-
net-smtp (0.5.1)
|
|
151
|
-
net-protocol
|
|
152
|
-
nio4r (2.7.5)
|
|
153
|
-
nokogiri (1.19.4)
|
|
154
|
-
mini_portile2 (~> 2.8.2)
|
|
155
|
-
racc (~> 1.4)
|
|
156
|
-
pp (0.6.4)
|
|
157
|
-
prettyprint
|
|
158
|
-
prettyprint (0.2.0)
|
|
159
|
-
prism (1.9.0)
|
|
160
|
-
racc (1.8.1)
|
|
161
|
-
rack (3.2.6)
|
|
162
|
-
rack-session (2.1.2)
|
|
163
|
-
base64 (>= 0.1.0)
|
|
164
|
-
rack (>= 3.0.0)
|
|
165
|
-
rack-test (2.2.0)
|
|
166
|
-
rack (>= 1.3)
|
|
167
|
-
rackup (2.3.1)
|
|
168
|
-
rack (>= 3)
|
|
169
|
-
rails (8.1.3.1)
|
|
170
|
-
actioncable (= 8.1.3.1)
|
|
171
|
-
actionmailbox (= 8.1.3.1)
|
|
172
|
-
actionmailer (= 8.1.3.1)
|
|
173
|
-
actionpack (= 8.1.3.1)
|
|
174
|
-
actiontext (= 8.1.3.1)
|
|
175
|
-
actionview (= 8.1.3.1)
|
|
176
|
-
activejob (= 8.1.3.1)
|
|
177
|
-
activemodel (= 8.1.3.1)
|
|
178
|
-
activerecord (= 8.1.3.1)
|
|
179
|
-
activestorage (= 8.1.3.1)
|
|
180
|
-
activesupport (= 8.1.3.1)
|
|
181
|
-
bundler (>= 1.15.0)
|
|
182
|
-
railties (= 8.1.3.1)
|
|
183
|
-
rails-dom-testing (2.3.0)
|
|
184
|
-
activesupport (>= 5.0.0)
|
|
185
|
-
minitest
|
|
186
|
-
nokogiri (>= 1.6)
|
|
187
|
-
rails-html-sanitizer (1.7.1)
|
|
188
|
-
loofah (~> 2.25, >= 2.25.2)
|
|
189
|
-
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)
|
|
190
|
-
railties (8.1.3.1)
|
|
191
|
-
actionpack (= 8.1.3.1)
|
|
192
|
-
activesupport (= 8.1.3.1)
|
|
193
|
-
irb (~> 1.13)
|
|
194
|
-
rackup (>= 1.0.0)
|
|
195
|
-
rake (>= 12.2)
|
|
196
|
-
thor (~> 1.0, >= 1.2.2)
|
|
197
|
-
tsort (>= 0.2)
|
|
198
|
-
zeitwerk (~> 2.6)
|
|
199
|
-
rake (13.4.2)
|
|
200
|
-
rbs (4.1.1)
|
|
201
|
-
logger
|
|
202
|
-
prism (>= 1.6.0)
|
|
203
|
-
tsort
|
|
204
|
-
rdoc (8.0.0)
|
|
205
|
-
erb
|
|
206
|
-
prism (>= 1.6.0)
|
|
207
|
-
rbs (>= 4.0.0)
|
|
208
|
-
tsort
|
|
209
|
-
reline (0.6.3)
|
|
210
|
-
io-console (~> 0.5)
|
|
211
|
-
rspec (3.13.2)
|
|
212
|
-
rspec-core (~> 3.13.0)
|
|
213
|
-
rspec-expectations (~> 3.13.0)
|
|
214
|
-
rspec-mocks (~> 3.13.0)
|
|
215
|
-
rspec-core (3.13.6)
|
|
216
|
-
rspec-support (~> 3.13.0)
|
|
217
|
-
rspec-expectations (3.13.5)
|
|
218
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
219
|
-
rspec-support (~> 3.13.0)
|
|
220
|
-
rspec-mocks (3.13.8)
|
|
221
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
222
|
-
rspec-support (~> 3.13.0)
|
|
223
|
-
rspec-support (3.13.7)
|
|
224
|
-
securerandom (0.4.1)
|
|
225
|
-
thor (1.5.0)
|
|
226
|
-
timecop (0.9.11)
|
|
227
|
-
timeout (0.6.1)
|
|
228
|
-
tsort (0.2.0)
|
|
229
|
-
tzinfo (2.0.6)
|
|
230
|
-
concurrent-ruby (~> 1.0)
|
|
231
|
-
uri (1.1.1)
|
|
232
|
-
useragent (0.16.11)
|
|
233
|
-
websocket-driver (0.8.2)
|
|
234
|
-
base64
|
|
235
|
-
websocket-extensions (>= 0.1.0)
|
|
236
|
-
websocket-extensions (0.1.5)
|
|
237
|
-
zeitwerk (2.8.2)
|
|
238
|
-
|
|
239
|
-
PLATFORMS
|
|
240
|
-
ruby
|
|
241
|
-
|
|
242
|
-
DEPENDENCIES
|
|
243
|
-
byebug (= 12.0.0)
|
|
244
|
-
keycloak-api-rails!
|
|
245
|
-
rspec (= 3.13.2)
|
|
246
|
-
timecop (= 0.9.11)
|
|
247
|
-
|
|
248
|
-
BUNDLED WITH
|
|
249
|
-
2.4.13
|