keycloak-admin 2.0.1 → 2.0.2
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 +13 -0
- data/README.md +40 -4
- data/keycloak-admin.gemspec +14 -6
- data/lib/keycloak-admin/client/client.rb +22 -10
- data/lib/keycloak-admin/client/client_authz_permission_client.rb +1 -1
- data/lib/keycloak-admin/client/client_authz_policy_client.rb +1 -1
- data/lib/keycloak-admin/client/client_authz_resource_client.rb +1 -1
- data/lib/keycloak-admin/client/client_authz_scope_client.rb +1 -1
- data/lib/keycloak-admin/client/group_client.rb +2 -5
- data/lib/keycloak-admin/client/organization_client.rb +7 -7
- data/lib/keycloak-admin/client/user_client.rb +8 -4
- data/lib/keycloak-admin/error.rb +62 -0
- data/lib/keycloak-admin/version.rb +1 -1
- data/lib/keycloak-admin.rb +1 -0
- metadata +21 -56
- data/.github/workflows/Dockerfile +0 -24
- data/.github/workflows/ci.yml +0 -94
- data/.github/workflows/release.yml +0 -112
- data/.gitignore +0 -10
- data/.rspec +0 -2
- data/Dockerfile +0 -13
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -54
- data/Rakefile +0 -6
- data/bin/console +0 -9
- data/spec/client/attack_detection_client_spec.rb +0 -102
- data/spec/client/client_authz_permission_client_spec.rb +0 -170
- data/spec/client/client_authz_policy_client_spec.rb +0 -170
- data/spec/client/client_authz_resource_client_spec.rb +0 -150
- data/spec/client/client_authz_scope_client_spec.rb +0 -134
- data/spec/client/client_client_spec.rb +0 -133
- data/spec/client/client_role_mappings_client_spec.rb +0 -83
- data/spec/client/client_scope_client_spec.rb +0 -220
- data/spec/client/client_scope_protocol_mapper_client_spec.rb +0 -230
- data/spec/client/client_spec.rb +0 -101
- data/spec/client/configurable_token_client_spec.rb +0 -34
- data/spec/client/group_client_spec.rb +0 -328
- data/spec/client/identity_provider_client_spec.rb +0 -92
- data/spec/client/organization_client_spec.rb +0 -595
- data/spec/client/realm_client_spec.rb +0 -155
- data/spec/client/resource_spec.rb +0 -223
- data/spec/client/response_spec.rb +0 -68
- data/spec/client/role_client_spec.rb +0 -79
- data/spec/client/role_mapper_client_spec.rb +0 -113
- data/spec/client/token_client_spec.rb +0 -68
- data/spec/client/user_client_spec.rb +0 -418
- data/spec/configuration_spec.rb +0 -150
- data/spec/gemspec_spec.rb +0 -25
- data/spec/integration/client_authorization_spec.rb +0 -93
- data/spec/representation/attack_detection_representation_spec.rb +0 -16
- data/spec/representation/client_authz_permission_representation_spec.rb +0 -52
- data/spec/representation/client_authz_policy_representation_spec.rb +0 -47
- data/spec/representation/client_authz_resource_representation_spec.rb +0 -33
- data/spec/representation/client_authz_scope_representation_spec.rb +0 -19
- data/spec/representation/client_representation_spec.rb +0 -119
- data/spec/representation/client_scope_representation_spec.rb +0 -125
- data/spec/representation/credential_representation_spec.rb +0 -68
- data/spec/representation/group_representation_spec.rb +0 -22
- data/spec/representation/identity_provider_mapper_representation_spec.rb +0 -24
- data/spec/representation/identity_provider_representation_spec.rb +0 -113
- data/spec/representation/impersonation_representation_spec.rb +0 -163
- data/spec/representation/organization_representation_spec.rb +0 -64
- data/spec/representation/protocol_mapper_representation_spec.rb +0 -57
- data/spec/representation/role_representation_spec.rb +0 -37
- data/spec/representation/session_representation_spec.rb +0 -16
- data/spec/representation/user_representation_spec.rb +0 -15
- data/spec/resource/group_resource_spec.rb +0 -14
- data/spec/resource/user_resource_spec.rb +0 -14
- data/spec/spec_helper.rb +0 -36
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9bc43409191a911c85de270b70021cbe48d0372844c5a2a4d87409f250d6b646
|
|
4
|
+
data.tar.gz: eee1d04d6026a04da64769c02cbe49ec9f84d186a1fafefc45476925979333c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96fccdf9c111acf6cfc67d6d05f7fd3b7e87140292d63df5dd9fdfebb228c30595416ae23ee8e465e06be1b54e3be857d8a64f599a447d642082086054805eb4
|
|
7
|
+
data.tar.gz: c49b8b8e7b2a13ae05eb69c47c3a0ee4c935a4e8a689fbf8863d01f843f95ee2506460e2ccf852223586266ae3591ee9cefff83819d087410b2fe53ccfadf0c1
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ 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
|
+
## [2.0.2] - 2026-08-01
|
|
9
|
+
|
|
10
|
+
* [Breaking] HTTP failures now raise a `KeycloakAdmin::ApiError` subclass instead of a bare `RuntimeError`.
|
|
11
|
+
* Messages are unchanged, so code matching on the text keeps working.
|
|
12
|
+
* Code rescuing `RuntimeError` explicitly must be updated.
|
|
13
|
+
* A create call answering something other than `201 Created` now raises `KeycloakAdmin::UnexpectedResponseError`.
|
|
14
|
+
* [Feature] A `401` answer now drops the cached access token and replays the request once with a freshly fetched one, so a token revoked before its advertised expiry no longer fails every call until it lapses.
|
|
15
|
+
* [Fix] `GroupClient#members` called ActiveSupport's `Object#try` and raised `NoMethodError` outside a Rails application.
|
|
16
|
+
* [Fix] `UserClient#execute_actions_email` (and `#forgot_password`) called ActiveSupport's `Numeric#seconds` and raised `NoMethodError` outside a Rails application whenever a `lifespan` was passed.
|
|
17
|
+
* [Fix] `ClientAuthzPolicyClient#find_by` opened a second `?` in its URL, which folded `name`, `type`, `first` and `max` into the value of `permission`; Keycloak silently returned an unfiltered list.
|
|
18
|
+
* [Fix] Query parameters are now percent-encoded everywhere they are built. Search terms, and the `redirect_uri` of `UserClient#execute_actions_email`, containing a space, `&` or `=` used to break the URL or forge extra parameters.
|
|
19
|
+
* [Fix] `OrganizationClient#build` raised `NameError` instead of the intended `ArgumentError` when `domains` was not an `Array`.
|
|
20
|
+
|
|
8
21
|
## [2.0.1] - 2026-07-31
|
|
9
22
|
|
|
10
23
|
* [Feature] Requests are now logged through `config.logger` (method, URL, and response status) via Faraday's `:logger` middleware.
|
data/README.md
CHANGED
|
@@ -16,7 +16,7 @@ This gem *does not* require Rails.
|
|
|
16
16
|
For example, using `bundle`, add this line to your Gemfile.
|
|
17
17
|
|
|
18
18
|
```ruby
|
|
19
|
-
gem "keycloak-admin", "2.0.
|
|
19
|
+
gem "keycloak-admin", "2.0.2"
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## Login
|
|
@@ -109,6 +109,42 @@ All options have a default value. However, all of them can be changed in your in
|
|
|
109
109
|
| `faraday_options` | `{}`| Hash | Optional | Options to pass to `Faraday.new` (e.g. `request:`, `ssl:`, `proxy:`) | `{ request: { timeout: 5 } }` |
|
|
110
110
|
|
|
111
111
|
|
|
112
|
+
## Error handling
|
|
113
|
+
|
|
114
|
+
Every HTTP failure raises a `KeycloakAdmin::ApiError` carrying the `status`, `body` and `headers`
|
|
115
|
+
of the response, so you never have to parse the message. Rescue as narrowly or as broadly as you need:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
begin
|
|
119
|
+
KeycloakAdmin.realm("a-realm").users.get(user_id)
|
|
120
|
+
rescue KeycloakAdmin::NotFoundError => e # exactly 404
|
|
121
|
+
nil
|
|
122
|
+
rescue KeycloakAdmin::ClientError => e # any 4xx
|
|
123
|
+
logger.warn("Rejected with #{e.status}: #{e.body}")
|
|
124
|
+
raise
|
|
125
|
+
rescue KeycloakAdmin::ServerError => e # any 5xx
|
|
126
|
+
raise
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
| Class | Raised on |
|
|
131
|
+
| ---- | ---- |
|
|
132
|
+
| `KeycloakAdmin::Error` | Base class of every error this gem raises |
|
|
133
|
+
| `KeycloakAdmin::ApiError` | Any non-2xx answer; parent of the two families below |
|
|
134
|
+
| `KeycloakAdmin::ClientError` | Any 4xx |
|
|
135
|
+
| `KeycloakAdmin::BadRequestError` | 400 |
|
|
136
|
+
| `KeycloakAdmin::UnauthorizedError` | 401 |
|
|
137
|
+
| `KeycloakAdmin::ForbiddenError` | 403 |
|
|
138
|
+
| `KeycloakAdmin::NotFoundError` | 404 |
|
|
139
|
+
| `KeycloakAdmin::ConflictError` | 409 |
|
|
140
|
+
| `KeycloakAdmin::ServerError` | Any 5xx |
|
|
141
|
+
| `KeycloakAdmin::UnexpectedResponseError` | A create call answered something other than `201 Created` |
|
|
142
|
+
|
|
143
|
+
`Faraday::TimeoutError` and the other connection-level Faraday errors are left untouched and propagate as-is, since no response ever came back to describe.
|
|
144
|
+
|
|
145
|
+
A `401` is handled before it reaches you: the cached access token is dropped and the request is replayed once with a freshly fetched one, so a token revoked before its advertised expiry does not fail every call until it lapses `KeycloakAdmin::UnauthorizedError` is raised only if the replay is
|
|
146
|
+
refused too.
|
|
147
|
+
|
|
112
148
|
## Use Cases
|
|
113
149
|
|
|
114
150
|
### Supported features
|
|
@@ -348,7 +384,7 @@ KeycloakAdmin.realm("a_realm").groups.list
|
|
|
348
384
|
|
|
349
385
|
Returns an array of `KeycloakAdmin::GroupRepresentation`.
|
|
350
386
|
|
|
351
|
-
According to [the documentation](https://www.keycloak.org/docs-api/22.0.
|
|
387
|
+
According to [the documentation](https://www.keycloak.org/docs-api/22.0.2/rest-api/index.html#_groups):
|
|
352
388
|
* When providing a `String` parameter, this produces an arbitrary search string
|
|
353
389
|
* When providing a `Hash`, you can specify other fields (_e.g_ q, max, first)
|
|
354
390
|
|
|
@@ -898,6 +934,6 @@ scoped RubyGems credential.
|
|
|
898
934
|
3. Tag the commit and push the tag:
|
|
899
935
|
|
|
900
936
|
```
|
|
901
|
-
$ git tag -a v2.0.
|
|
902
|
-
$ git push origin v2.0.
|
|
937
|
+
$ git tag -a v2.0.2 -m "Version 2.0.2"
|
|
938
|
+
$ git push origin v2.0.2
|
|
903
939
|
```
|
data/keycloak-admin.gemspec
CHANGED
|
@@ -12,15 +12,23 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = "Keycloak Admin REST API client written in Ruby"
|
|
13
13
|
spec.license = "MIT"
|
|
14
14
|
|
|
15
|
-
spec.files
|
|
15
|
+
spec.files = Dir.glob(["lib/**/*.rb", "CHANGELOG.md", "MIT-LICENSE", "README.md", "keycloak-admin.gemspec"]).sort
|
|
16
16
|
spec.require_paths = ["lib"]
|
|
17
17
|
|
|
18
|
-
spec.required_ruby_version =
|
|
18
|
+
spec.required_ruby_version = ">= 3.1"
|
|
19
|
+
|
|
20
|
+
spec.metadata = {
|
|
21
|
+
"source_code_uri" => spec.homepage,
|
|
22
|
+
"changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
|
|
23
|
+
"bug_tracker_uri" => "#{spec.homepage}/issues",
|
|
24
|
+
"rubygems_mfa_required" => "true"
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
spec.add_dependency "http-cookie", "~> 1.0", ">= 1.0.3"
|
|
21
28
|
spec.add_dependency "faraday", "~> 2.0"
|
|
22
29
|
spec.add_dependency "base64"
|
|
23
|
-
spec.add_development_dependency "rspec",
|
|
24
|
-
spec.add_development_dependency "byebug",
|
|
25
|
-
spec.add_development_dependency "rake",
|
|
26
|
-
|
|
30
|
+
spec.add_development_dependency "rspec", "3.13.2"
|
|
31
|
+
spec.add_development_dependency "byebug", "12.0.0"
|
|
32
|
+
spec.add_development_dependency "rake", ">= 13.0"
|
|
33
|
+
spec.add_development_dependency "webmock", "~> 3.0"
|
|
34
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
|
|
1
3
|
module KeycloakAdmin
|
|
2
4
|
class Client
|
|
3
5
|
|
|
@@ -25,16 +27,24 @@ module KeycloakAdmin
|
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def execute_http
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
replayed = false
|
|
31
|
+
begin
|
|
32
|
+
yield
|
|
33
|
+
rescue Faraday::TimeoutError
|
|
34
|
+
raise
|
|
35
|
+
rescue Faraday::ClientError, Faraday::ServerError => e
|
|
36
|
+
if !replayed && e.response && e.response[:status] == 401 && !@configuration.cached_token.nil?
|
|
37
|
+
@configuration.clear_cached_token!
|
|
38
|
+
replayed = true
|
|
39
|
+
retry
|
|
40
|
+
end
|
|
41
|
+
raise ApiError.from_response(e.response)
|
|
42
|
+
end
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
def created_id(response)
|
|
36
46
|
unless response.status == 201
|
|
37
|
-
raise
|
|
47
|
+
raise UnexpectedResponseError.new(response.status, response.reason_phrase)
|
|
38
48
|
end
|
|
39
49
|
(_head, _separator, id) = response.headers[:location].rpartition("/")
|
|
40
50
|
id
|
|
@@ -52,6 +62,12 @@ module KeycloakAdmin
|
|
|
52
62
|
|
|
53
63
|
private
|
|
54
64
|
|
|
65
|
+
def build_query(parameters)
|
|
66
|
+
parameters.map do |name, value|
|
|
67
|
+
"#{URI.encode_uri_component(name.to_s)}=#{URI.encode_uri_component(value.to_s)}"
|
|
68
|
+
end.join("&")
|
|
69
|
+
end
|
|
70
|
+
|
|
55
71
|
def fetch_token
|
|
56
72
|
KeycloakAdmin.create_client(@configuration, @configuration.client_realm_name).token.get
|
|
57
73
|
end
|
|
@@ -59,9 +75,5 @@ module KeycloakAdmin
|
|
|
59
75
|
def resource(url)
|
|
60
76
|
Resource.new(url, @configuration.faraday_options, @configuration.logger)
|
|
61
77
|
end
|
|
62
|
-
|
|
63
|
-
def http_error(response)
|
|
64
|
-
raise "Keycloak: The request failed with response code #{response[:status]} and message: #{response[:body]}"
|
|
65
|
-
end
|
|
66
78
|
end
|
|
67
79
|
end
|
|
@@ -20,7 +20,7 @@ module KeycloakAdmin
|
|
|
20
20
|
|
|
21
21
|
def find_by(name, resource_param, scope = nil)
|
|
22
22
|
response = execute_http do
|
|
23
|
-
url = "#{authz_permission_url(@client_id)}
|
|
23
|
+
url = "#{authz_permission_url(@client_id)}?#{build_query(name: name, resource: resource_param, type: @type, scope: scope, deep: true, first: 0, max: 100)}"
|
|
24
24
|
resource(url).get(headers)
|
|
25
25
|
end
|
|
26
26
|
JSON.parse(response).map { |role_as_hash| ClientAuthzPermissionRepresentation.from_hash(role_as_hash) }
|
|
@@ -33,7 +33,7 @@ module KeycloakAdmin
|
|
|
33
33
|
|
|
34
34
|
def find_by(name, type)
|
|
35
35
|
response = execute_http do
|
|
36
|
-
url = "#{authz_policy_url(@client_id, @type)}
|
|
36
|
+
url = "#{authz_policy_url(@client_id, @type)}&#{build_query(name: name, type: type, first: 0, max: 11)}"
|
|
37
37
|
resource(url).get(headers)
|
|
38
38
|
end
|
|
39
39
|
JSON.parse(response).map { |role_as_hash| ClientAuthzPolicyRepresentation.from_hash(role_as_hash) }
|
|
@@ -47,7 +47,7 @@ module KeycloakAdmin
|
|
|
47
47
|
|
|
48
48
|
def find_by(name, type, uris, owner, scope)
|
|
49
49
|
response = execute_http do
|
|
50
|
-
url = "#{authz_resources_url(@client_id)}
|
|
50
|
+
url = "#{authz_resources_url(@client_id)}?#{build_query(name: name, type: type, uris: uris, owner: owner, scope: scope, deep: true, first: 0, max: 100)}"
|
|
51
51
|
resource(url).get(headers)
|
|
52
52
|
end
|
|
53
53
|
JSON.parse(response).map { |role_as_hash| ClientAuthzResourceRepresentation.from_hash(role_as_hash) }
|
|
@@ -35,7 +35,7 @@ module KeycloakAdmin
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def search(name)
|
|
38
|
-
url = "#{authz_scopes_url(@client_id)}
|
|
38
|
+
url = "#{authz_scopes_url(@client_id)}?#{build_query(first: 0, max: 11, deep: false, name: name)}"
|
|
39
39
|
response = execute_http do
|
|
40
40
|
resource(url).get(headers)
|
|
41
41
|
end
|
|
@@ -75,11 +75,8 @@ module KeycloakAdmin
|
|
|
75
75
|
|
|
76
76
|
def members(group_id, first=0, max=100)
|
|
77
77
|
url = "#{groups_url(group_id)}/members"
|
|
78
|
-
query = {first: first
|
|
79
|
-
unless query.empty?
|
|
80
|
-
query_string = query.to_a.map { |e| "#{e[0]}=#{e[1]}" }.join("&")
|
|
81
|
-
url = "#{url}?#{query_string}"
|
|
82
|
-
end
|
|
78
|
+
query = {first: first&.to_i, max: max&.to_i}.compact
|
|
79
|
+
url = "#{url}?#{build_query(query)}" unless query.empty?
|
|
83
80
|
response = execute_http do
|
|
84
81
|
resource(url).get(headers)
|
|
85
82
|
end
|
|
@@ -170,24 +170,24 @@ module KeycloakAdmin
|
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
def count_url(exact, query, search)
|
|
173
|
-
query_parameters = {exact: exact, q: query, search: search}.compact
|
|
173
|
+
query_parameters = build_query({exact: exact, q: query, search: search}.compact)
|
|
174
174
|
"#{organizations_url}/count?#{query_parameters}"
|
|
175
175
|
end
|
|
176
176
|
|
|
177
177
|
def organizations_url_with_parameters(brief_representation, exact, first, max, query, search)
|
|
178
|
-
query_parameters = {
|
|
178
|
+
query_parameters = build_query({
|
|
179
179
|
briefRepresentation: brief_representation,
|
|
180
180
|
exact: exact,
|
|
181
181
|
first: first,
|
|
182
182
|
max: max,
|
|
183
183
|
q: query,
|
|
184
184
|
search: search
|
|
185
|
-
}.compact
|
|
185
|
+
}.compact)
|
|
186
186
|
"#{organizations_url}?#{query_parameters}"
|
|
187
187
|
end
|
|
188
188
|
|
|
189
189
|
def associated_with_member_url(member_id, brief_representation=true)
|
|
190
|
-
"#{organizations_url}/members/#{member_id}/organizations
|
|
190
|
+
"#{organizations_url}/members/#{member_id}/organizations?#{build_query(briefRepresentation: brief_representation)}"
|
|
191
191
|
end
|
|
192
192
|
|
|
193
193
|
def members_count_url(organization_id)
|
|
@@ -212,19 +212,19 @@ module KeycloakAdmin
|
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
def members_url_with_query_parameters(organization_id, exact, first, max, membership_type, search)
|
|
215
|
-
query_parameters = {
|
|
215
|
+
query_parameters = build_query({
|
|
216
216
|
exact: exact,
|
|
217
217
|
first: first,
|
|
218
218
|
max: max,
|
|
219
219
|
membershipType: membership_type,
|
|
220
220
|
search: search
|
|
221
|
-
}.compact
|
|
221
|
+
}.compact)
|
|
222
222
|
"#{organization_url(organization_id)}/members?#{query_parameters}"
|
|
223
223
|
end
|
|
224
224
|
|
|
225
225
|
def build(name, alias_name, enabled, description, redirect_url=nil, domains=[], attributes={})
|
|
226
226
|
unless domains.is_a?(Array)
|
|
227
|
-
raise ArgumentError.new("domains must be an Array, got #{
|
|
227
|
+
raise ArgumentError.new("domains must be an Array, got #{domains.class}")
|
|
228
228
|
end
|
|
229
229
|
|
|
230
230
|
unless domains.all? { |domain| domain.is_a?(KeycloakAdmin::OrganizationDomainRepresentation) }
|
|
@@ -141,10 +141,14 @@ module KeycloakAdmin
|
|
|
141
141
|
def execute_actions_email(user_id, actions=[], lifespan=nil, redirect_uri=nil, client_id=nil)
|
|
142
142
|
raise ArgumentError.new("client_id must be defined") if client_id.nil? && !redirect_uri.nil?
|
|
143
143
|
execute_http do
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
query = {}
|
|
145
|
+
query[:client_id] = client_id unless client_id.nil?
|
|
146
|
+
query[:redirect_uri] = redirect_uri unless redirect_uri.nil?
|
|
147
|
+
query[:lifespan] = lifespan.to_i unless lifespan.nil?
|
|
148
|
+
|
|
149
|
+
url = execute_actions_email_url(user_id)
|
|
150
|
+
url = "#{url}?#{build_query(query)}" unless query.empty?
|
|
151
|
+
Resource.put(url, create_payload(actions), headers, @configuration.logger)
|
|
148
152
|
end
|
|
149
153
|
user_id
|
|
150
154
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module KeycloakAdmin
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
|
|
4
|
+
# Raised when Keycloak answers with a non-2xx status.
|
|
5
|
+
#
|
|
6
|
+
# Callers can rescue the precise failure (KeycloakAdmin::NotFoundError), a whole family
|
|
7
|
+
# (KeycloakAdmin::ClientError for any 4xx) or every HTTP failure (KeycloakAdmin::ApiError),
|
|
8
|
+
# and read #status/#body off the exception rather than parsing the message.
|
|
9
|
+
class ApiError < Error
|
|
10
|
+
attr_reader :status, :body, :headers
|
|
11
|
+
|
|
12
|
+
def initialize(status, body, headers = {})
|
|
13
|
+
@status = status
|
|
14
|
+
@body = body
|
|
15
|
+
@headers = headers || {}
|
|
16
|
+
super("Keycloak: The request failed with response code #{status} and message: #{body}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.from_response(response)
|
|
20
|
+
response ||= {}
|
|
21
|
+
status = response[:status]
|
|
22
|
+
error_class_for(status).new(status, response[:body], response[:headers])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.error_class_for(status)
|
|
26
|
+
case status
|
|
27
|
+
when 400 then BadRequestError
|
|
28
|
+
when 401 then UnauthorizedError
|
|
29
|
+
when 403 then ForbiddenError
|
|
30
|
+
when 404 then NotFoundError
|
|
31
|
+
when 409 then ConflictError
|
|
32
|
+
when 400..499 then ClientError
|
|
33
|
+
when 500..599 then ServerError
|
|
34
|
+
else ApiError
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Any 4xx: the request itself was rejected.
|
|
40
|
+
class ClientError < ApiError; end
|
|
41
|
+
|
|
42
|
+
class BadRequestError < ClientError; end
|
|
43
|
+
class UnauthorizedError < ClientError; end
|
|
44
|
+
class ForbiddenError < ClientError; end
|
|
45
|
+
class NotFoundError < ClientError; end
|
|
46
|
+
class ConflictError < ClientError; end
|
|
47
|
+
|
|
48
|
+
# Any 5xx: Keycloak accepted the request but failed to serve it.
|
|
49
|
+
class ServerError < ApiError; end
|
|
50
|
+
|
|
51
|
+
# Raised when a create call succeeds at the HTTP level but answers something other than
|
|
52
|
+
# 201 Created, leaving no Location header to read the new resource's id from.
|
|
53
|
+
class UnexpectedResponseError < Error
|
|
54
|
+
attr_reader :status, :reason_phrase
|
|
55
|
+
|
|
56
|
+
def initialize(status, reason_phrase)
|
|
57
|
+
@status = status
|
|
58
|
+
@reason_phrase = reason_phrase
|
|
59
|
+
super("Create method returned status #{reason_phrase} (Code: #{status}); expected status: Created (201)")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/keycloak-admin.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: keycloak-admin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lorent Lempereur
|
|
@@ -99,6 +99,20 @@ dependencies:
|
|
|
99
99
|
- - ">="
|
|
100
100
|
- !ruby/object:Gem::Version
|
|
101
101
|
version: '13.0'
|
|
102
|
+
- !ruby/object:Gem::Dependency
|
|
103
|
+
name: webmock
|
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - "~>"
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '3.0'
|
|
109
|
+
type: :development
|
|
110
|
+
prerelease: false
|
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - "~>"
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '3.0'
|
|
102
116
|
description: Keycloak Admin REST API client written in Ruby
|
|
103
117
|
email:
|
|
104
118
|
- lorent.lempereur.dev@gmail.com
|
|
@@ -106,19 +120,9 @@ executables: []
|
|
|
106
120
|
extensions: []
|
|
107
121
|
extra_rdoc_files: []
|
|
108
122
|
files:
|
|
109
|
-
- ".github/workflows/Dockerfile"
|
|
110
|
-
- ".github/workflows/ci.yml"
|
|
111
|
-
- ".github/workflows/release.yml"
|
|
112
|
-
- ".gitignore"
|
|
113
|
-
- ".rspec"
|
|
114
123
|
- CHANGELOG.md
|
|
115
|
-
- Dockerfile
|
|
116
|
-
- Gemfile
|
|
117
|
-
- Gemfile.lock
|
|
118
124
|
- MIT-LICENSE
|
|
119
125
|
- README.md
|
|
120
|
-
- Rakefile
|
|
121
|
-
- bin/console
|
|
122
126
|
- keycloak-admin.gemspec
|
|
123
127
|
- lib/keycloak-admin.rb
|
|
124
128
|
- lib/keycloak-admin/client/attack_detection_client.rb
|
|
@@ -144,6 +148,7 @@ files:
|
|
|
144
148
|
- lib/keycloak-admin/client/token_client.rb
|
|
145
149
|
- lib/keycloak-admin/client/user_client.rb
|
|
146
150
|
- lib/keycloak-admin/configuration.rb
|
|
151
|
+
- lib/keycloak-admin/error.rb
|
|
147
152
|
- lib/keycloak-admin/representation/attack_detection_representation.rb
|
|
148
153
|
- lib/keycloak-admin/representation/camel_json.rb
|
|
149
154
|
- lib/keycloak-admin/representation/client_authz_permission_representation.rb
|
|
@@ -174,54 +179,14 @@ files:
|
|
|
174
179
|
- lib/keycloak-admin/resource/group_resource.rb
|
|
175
180
|
- lib/keycloak-admin/resource/user_resource.rb
|
|
176
181
|
- lib/keycloak-admin/version.rb
|
|
177
|
-
- spec/client/attack_detection_client_spec.rb
|
|
178
|
-
- spec/client/client_authz_permission_client_spec.rb
|
|
179
|
-
- spec/client/client_authz_policy_client_spec.rb
|
|
180
|
-
- spec/client/client_authz_resource_client_spec.rb
|
|
181
|
-
- spec/client/client_authz_scope_client_spec.rb
|
|
182
|
-
- spec/client/client_client_spec.rb
|
|
183
|
-
- spec/client/client_role_mappings_client_spec.rb
|
|
184
|
-
- spec/client/client_scope_client_spec.rb
|
|
185
|
-
- spec/client/client_scope_protocol_mapper_client_spec.rb
|
|
186
|
-
- spec/client/client_spec.rb
|
|
187
|
-
- spec/client/configurable_token_client_spec.rb
|
|
188
|
-
- spec/client/group_client_spec.rb
|
|
189
|
-
- spec/client/identity_provider_client_spec.rb
|
|
190
|
-
- spec/client/organization_client_spec.rb
|
|
191
|
-
- spec/client/realm_client_spec.rb
|
|
192
|
-
- spec/client/resource_spec.rb
|
|
193
|
-
- spec/client/response_spec.rb
|
|
194
|
-
- spec/client/role_client_spec.rb
|
|
195
|
-
- spec/client/role_mapper_client_spec.rb
|
|
196
|
-
- spec/client/token_client_spec.rb
|
|
197
|
-
- spec/client/user_client_spec.rb
|
|
198
|
-
- spec/configuration_spec.rb
|
|
199
|
-
- spec/gemspec_spec.rb
|
|
200
|
-
- spec/integration/client_authorization_spec.rb
|
|
201
|
-
- spec/representation/attack_detection_representation_spec.rb
|
|
202
|
-
- spec/representation/client_authz_permission_representation_spec.rb
|
|
203
|
-
- spec/representation/client_authz_policy_representation_spec.rb
|
|
204
|
-
- spec/representation/client_authz_resource_representation_spec.rb
|
|
205
|
-
- spec/representation/client_authz_scope_representation_spec.rb
|
|
206
|
-
- spec/representation/client_representation_spec.rb
|
|
207
|
-
- spec/representation/client_scope_representation_spec.rb
|
|
208
|
-
- spec/representation/credential_representation_spec.rb
|
|
209
|
-
- spec/representation/group_representation_spec.rb
|
|
210
|
-
- spec/representation/identity_provider_mapper_representation_spec.rb
|
|
211
|
-
- spec/representation/identity_provider_representation_spec.rb
|
|
212
|
-
- spec/representation/impersonation_representation_spec.rb
|
|
213
|
-
- spec/representation/organization_representation_spec.rb
|
|
214
|
-
- spec/representation/protocol_mapper_representation_spec.rb
|
|
215
|
-
- spec/representation/role_representation_spec.rb
|
|
216
|
-
- spec/representation/session_representation_spec.rb
|
|
217
|
-
- spec/representation/user_representation_spec.rb
|
|
218
|
-
- spec/resource/group_resource_spec.rb
|
|
219
|
-
- spec/resource/user_resource_spec.rb
|
|
220
|
-
- spec/spec_helper.rb
|
|
221
182
|
homepage: https://github.com/looorent/keycloak-admin-ruby
|
|
222
183
|
licenses:
|
|
223
184
|
- MIT
|
|
224
|
-
metadata:
|
|
185
|
+
metadata:
|
|
186
|
+
source_code_uri: https://github.com/looorent/keycloak-admin-ruby
|
|
187
|
+
changelog_uri: https://github.com/looorent/keycloak-admin-ruby/blob/main/CHANGELOG.md
|
|
188
|
+
bug_tracker_uri: https://github.com/looorent/keycloak-admin-ruby/issues
|
|
189
|
+
rubygems_mfa_required: 'true'
|
|
225
190
|
rdoc_options: []
|
|
226
191
|
require_paths:
|
|
227
192
|
- lib
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
### Dockerfile for: tillawy/keycloak-github-actions
|
|
2
|
-
##
|
|
3
|
-
## To build & push
|
|
4
|
-
# docker buildx build . --platform linux/amd64 -t tillawy/keycloak-github-actions:25.0.1
|
|
5
|
-
# docker push tillawy/keycloak-github-actions:25.0.1
|
|
6
|
-
#
|
|
7
|
-
## To Run Locally
|
|
8
|
-
# docker run \
|
|
9
|
-
# --rm \
|
|
10
|
-
# -p 8080:8080 \
|
|
11
|
-
# -e KEYCLOAK_ADMIN="admin" \
|
|
12
|
-
# -e KEYCLOAK_ADMIN_PASSWORD="admin" \
|
|
13
|
-
# -e KC_HOSTNAME="http://localhost:8080" \
|
|
14
|
-
# -e KC_HOSTNAME_ADMIN="http://localhost:8080" \
|
|
15
|
-
# -e KC_HTTP_ENABLED="true" \
|
|
16
|
-
# -e KC_DB="dev-file" \
|
|
17
|
-
# tillawy/keycloak-github-actions:25.0.1
|
|
18
|
-
|
|
19
|
-
FROM quay.io/keycloak/keycloak:25.0.1
|
|
20
|
-
|
|
21
|
-
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
|
|
22
|
-
|
|
23
|
-
CMD ["start-dev", "--hostname=http://localhost:8080" , "--hostname-admin=http://localhost:8080" , "--http-enabled=true", "--verbose"]
|
|
24
|
-
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# This workflow uses actions that are not certified by GitHub.
|
|
2
|
-
# They are provided by a third-party and are governed by
|
|
3
|
-
# separate terms of service, privacy policy, and support
|
|
4
|
-
# documentation.
|
|
5
|
-
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
|
6
|
-
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
|
7
|
-
|
|
8
|
-
name: Ruby
|
|
9
|
-
|
|
10
|
-
on:
|
|
11
|
-
push:
|
|
12
|
-
branches: [ "main" ]
|
|
13
|
-
pull_request:
|
|
14
|
-
branches: [ "main" ]
|
|
15
|
-
|
|
16
|
-
permissions:
|
|
17
|
-
contents: read
|
|
18
|
-
|
|
19
|
-
jobs:
|
|
20
|
-
test:
|
|
21
|
-
name: Ruby ${{ matrix.ruby-version }}
|
|
22
|
-
runs-on: ubuntu-24.04
|
|
23
|
-
|
|
24
|
-
strategy:
|
|
25
|
-
fail-fast: false
|
|
26
|
-
matrix:
|
|
27
|
-
ruby-version: ['3.1', '3.2', '3.3', '3.4']
|
|
28
|
-
|
|
29
|
-
steps:
|
|
30
|
-
- name: Start Keycloak
|
|
31
|
-
run: |
|
|
32
|
-
docker run -d --name keycloak -p 8080:8080 \
|
|
33
|
-
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
|
|
34
|
-
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
|
|
35
|
-
-e KC_HOSTNAME=http://localhost:8080 \
|
|
36
|
-
-e KC_HOSTNAME_ADMIN=http://localhost:8080 \
|
|
37
|
-
-e KC_HTTP_ENABLED=true \
|
|
38
|
-
-e KC_DB=dev-file \
|
|
39
|
-
quay.io/keycloak/keycloak:26.7.0 \
|
|
40
|
-
start-dev
|
|
41
|
-
|
|
42
|
-
- name: Wait for Keycloak to be ready
|
|
43
|
-
run: |
|
|
44
|
-
for i in $(seq 1 30); do
|
|
45
|
-
if curl --silent --fail http://localhost:8080/realms/master > /dev/null; then
|
|
46
|
-
echo "Keycloak is ready"
|
|
47
|
-
exit 0
|
|
48
|
-
fi
|
|
49
|
-
sleep 5
|
|
50
|
-
done
|
|
51
|
-
echo "Keycloak did not become ready in time"
|
|
52
|
-
docker logs keycloak
|
|
53
|
-
exit 1
|
|
54
|
-
|
|
55
|
-
- name: create realm
|
|
56
|
-
run: |
|
|
57
|
-
TOKEN=$(curl --silent --location --request POST "http://localhost:8080/realms/master/protocol/openid-connect/token" \
|
|
58
|
-
--header 'Content-Type: application/x-www-form-urlencoded' \
|
|
59
|
-
--data-urlencode 'grant_type=password' \
|
|
60
|
-
--data-urlencode 'username=admin' \
|
|
61
|
-
--data-urlencode 'password=admin' \
|
|
62
|
-
--data-urlencode 'client_id=admin-cli' | jq -r '.access_token')
|
|
63
|
-
|
|
64
|
-
curl --silent --show-error -L -X POST "http://localhost:8080/admin/realms" \
|
|
65
|
-
--header "Content-Type: application/json" \
|
|
66
|
-
--header "Authorization: Bearer ${TOKEN}" \
|
|
67
|
-
--data '{"realm":"dummy","enabled":true}'
|
|
68
|
-
|
|
69
|
-
curl --silent --show-error --request POST 'http://localhost:8080/admin/realms/dummy/clients' \
|
|
70
|
-
--header 'Authorization: Bearer '$TOKEN \
|
|
71
|
-
--header 'Content-Type: application/json' \
|
|
72
|
-
--data-raw '{
|
|
73
|
-
"clientId":"dummy-client",
|
|
74
|
-
"enabled":true,
|
|
75
|
-
"consentRequired": false,
|
|
76
|
-
"attributes":{},
|
|
77
|
-
"serviceAccountsEnabled": true,
|
|
78
|
-
"protocol":"openid-connect",
|
|
79
|
-
"publicClient":false,
|
|
80
|
-
"authorizationServicesEnabled": true,
|
|
81
|
-
"clientAuthenticatorType":"client-secret",
|
|
82
|
-
"redirectUris":["http://localhost:8180/demo"]
|
|
83
|
-
}'
|
|
84
|
-
|
|
85
|
-
- uses: actions/checkout@v6
|
|
86
|
-
- name: Set up Ruby
|
|
87
|
-
uses: ruby/setup-ruby@v1
|
|
88
|
-
with:
|
|
89
|
-
ruby-version: ${{ matrix.ruby-version }}
|
|
90
|
-
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
91
|
-
- name: Run tests
|
|
92
|
-
run: bundle exec rspec
|
|
93
|
-
env:
|
|
94
|
-
GITHUB_ACTIONS: true
|