keycloak-admin 1.0.24 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6bc5dc390440dedbf6a682c53717e334e4f174b31e43b7990dae87bed2e0bbf
4
- data.tar.gz: b37367aaa9726b48a266b55baf7fcebf772f302c18f215ad0e694d6e60421608
3
+ metadata.gz: bf6847f9dc60316780255644c15320d06a998331df2283e30a007c4bea951ba1
4
+ data.tar.gz: 4af207ec29032148c58ff23194f4ade85aec556718bb4896c00ec29fe95000a9
5
5
  SHA512:
6
- metadata.gz: 869a2108bbffcdc242a438a20b6d8c06fa907354e2d0648cac91920d08ba070b2bf9cc35d710f1a1fcb40691176b84b515372af540bdc7383d4835217e9ce989
7
- data.tar.gz: ed68ed6de159d1d0bf87c298b49fab7fc94cc1d5f563ef25a2b56e922807ddb98b9fba69f189bc7ccc843380595afce42cc202e14b53f07fcc97f7c141145e43
6
+ metadata.gz: 6e03b3d8ae4f5eac52399fefcbea427d34a5afc5e08475365a8166faa5031eebb7b87cff23393e16e91e2df0f50263023b29262addff9550f79ef7bb5c739637
7
+ data.tar.gz: 1c91e21ee8d74ba5ca05f773d20acdc11e3a6c8fd8ab3e8098cb75c7d44ed1c7763e3954b094012a5fe9000c1ff4cb048355e7a3a1f134b8911fc349b03919d9
data/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ 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.1.1] - 2024-01-21
9
+
10
+ * Add/List realm-role/s to a group, Allow role-names with spaces, List groups assigned to role (thanks to @LiquidMagical
11
+ )
12
+
13
+ ## [1.1.0] - 2023-10-03
14
+
15
+ * Search for groups with parameters (thanks to @@tlloydthwaites)
16
+ * Get client by ID, Find client by Client ID, Update Client (thanks to @gee-forr)
17
+
8
18
  ## [1.0.24] - 2023-06-07
9
19
 
10
20
  * Revert the modifications on the feature 'Update a User' introduced in `1.0.22`. This implementation had breaking changes such as not being able to update several attributes (`first_name`, `email`, etc).
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- keycloak-admin (1.0.24)
4
+ keycloak-admin (1.1.1)
5
5
  http-cookie (~> 1.0, >= 1.0.3)
6
6
  rest-client (~> 2.0)
7
7
 
@@ -10,14 +10,13 @@ GEM
10
10
  specs:
11
11
  byebug (11.1.3)
12
12
  diff-lcs (1.5.0)
13
- domain_name (0.5.20190701)
14
- unf (>= 0.0.5, < 1.0.0)
13
+ domain_name (0.6.20240107)
15
14
  http-accept (1.7.0)
16
15
  http-cookie (1.0.5)
17
16
  domain_name (~> 0.5)
18
- mime-types (3.4.1)
17
+ mime-types (3.5.2)
19
18
  mime-types-data (~> 3.2015)
20
- mime-types-data (3.2023.0218.1)
19
+ mime-types-data (3.2023.1205)
21
20
  netrc (0.11.0)
22
21
  rest-client (2.1.0)
23
22
  http-accept (>= 1.7.0, < 2.0)
@@ -33,13 +32,10 @@ GEM
33
32
  rspec-expectations (3.12.3)
34
33
  diff-lcs (>= 1.2.0, < 2.0)
35
34
  rspec-support (~> 3.12.0)
36
- rspec-mocks (3.12.5)
35
+ rspec-mocks (3.12.6)
37
36
  diff-lcs (>= 1.2.0, < 2.0)
38
37
  rspec-support (~> 3.12.0)
39
- rspec-support (3.12.0)
40
- unf (0.1.4)
41
- unf_ext
42
- unf_ext (0.0.8.2)
38
+ rspec-support (3.12.1)
43
39
 
44
40
  PLATFORMS
45
41
  ruby
data/README.md CHANGED
@@ -21,7 +21,7 @@ To login on Keycloak's Admin API, you first need to setup a client.
21
21
 
22
22
  Go to your realm administration page and open `Clients`. Then, click on the `Create` button.
23
23
  On the first screen, enter:
24
- * `Client ID`: _e.g. my-app-admin-client_
24
+ * `Client ID`: _e.g. my-app-admin-client_
25
25
  * `Client Protocol`: select `openid-connect`
26
26
  * `Root URL`: let it blank
27
27
 
@@ -45,7 +45,7 @@ The next screen must be configured depending on how you want to authenticate:
45
45
  * In this gem's configuration (see Section `Configuration`):
46
46
  * Setup `username` and `password` according to your user's configuration
47
47
  * Setup `client_id` with your `Client ID` (_e.g. my-app-admin-client_)
48
- * If your client is `confidential`, copy its Client Secret to `client_secret`
48
+ * If your client is `confidential`, copy its Client Secret to `client_secret`
49
49
 
50
50
  ### Login with `Direct Access Grants` (Service account)
51
51
 
@@ -60,15 +60,15 @@ Using a service account to use the REST Admin API does not require to create a d
60
60
  * After saving this client
61
61
  * open the `Service Account Roles` and add relevant `realm-management.` client's roles. For instance: `view-users` if you want to search for users using this gem.
62
62
  * open the `Credentials` tab and copy the `Client Secret`
63
-
63
+
64
64
  * In this gem's configuration (see Section `Configuration`):
65
65
  * Set `use_service_account` to `true`
66
66
  * Setup `client_id` with your `Client ID` (_e.g. my-app-admin-client_)
67
- * Copy its Client Secret to `client_secret`
67
+ * Copy its Client Secret to `client_secret`
68
68
 
69
69
  ## Configuration
70
70
 
71
- To configure this gem, call `KeycloakAdmin.configure`.
71
+ To configure this gem, call `KeycloakAdmin.configure`.
72
72
  For instance, to configure this gem based on environment variables, write (and load if required) a `keycloak_admin.rb`:
73
73
  ```ruby
74
74
  KeycloakAdmin.configure do |config|
@@ -96,14 +96,14 @@ All options have a default value. However, all of them can be changed in your in
96
96
  | `client_realm_name` | `""`| String | Required | Name of the realm that contains the admin client. | `master` |
97
97
  | `client_id` | `admin-cli`| String | Required | Client that should be used to access admin capabilities. | `api-cli` |
98
98
  | `client_secret` | `nil`| String | Optional | If your client is `confidential`, this parameter must be specified. | `4e3c481c-f823-4a6a-b8a7-bf8c86e3eac3` |
99
- | `use_service_account` | `true` | Boolean | Required | `true` if the connection to the client uses a Service Account. `false` if the connection to the client uses a username/password credential. | `false` |
99
+ | `use_service_account` | `true` | Boolean | Required | `true` if the connection to the client uses a Service Account. `false` if the connection to the client uses a username/password credential. | `false` |
100
100
  | `username` | `nil`| String | Optional | Username to access the Admin REST API. Recommended if `user_service_account` is set to `false`. | `mummy` |
101
101
  | `password` | `nil`| String | Optional | Clear password to access the Admin REST API. Recommended if `user_service_account` is set to `false`. | `bobby` |
102
102
  | `logger` | `Logger.new(STDOUT)`| Logger | Optional | The logger used by `keycloak-admin` | `Rails.logger` | 
103
103
  | `rest_client_options` | `{}`| Hash | Optional | Options to pass to `RestClient` | `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }` | 
104
104
 
105
105
 
106
- ## Use Case
106
+ ## Use Cases
107
107
 
108
108
  ### Supported features
109
109
 
@@ -113,13 +113,15 @@ All options have a default value. However, all of them can be changed in your in
113
113
  * Reset credentials
114
114
  * Impersonate a user
115
115
  * Exchange a configurable token
116
- * Get list of clients
117
- * Create clients
116
+ * Get list of clients, or find a client by its id or client_id
117
+ * Create, update, and delete clients
118
118
  * Get list of groups, create/save a group
119
119
  * Get list of roles, save a role
120
120
  * Get list of realms, save/update/delete a realm
121
121
  * Get list of client role mappings for a user/group
122
122
  * Get list of members of a group
123
+ * Get list of groups that have a specific role assigned
124
+ * Get list of realm-roles assigned to a group, add a realm-role to a group
123
125
  * Save client role mappings for a user/group
124
126
  * Save realm-level role mappings for a user/group
125
127
  * Add a Group on a User
@@ -279,10 +281,26 @@ KeycloakAdmin.realm("a_realm").delete
279
281
 
280
282
  ### Get list of clients in a realm
281
283
 
282
- Returns an array of `KeycloakAdmin::ClientRepresentation`.
284
+ Returns an array of `KeycloakAdmin::ClientRepresentation` or a single `KeycloakAdmin::ClientRepresentation`
285
+
286
+ Finding a client by its `client_id` is a somewhat slow operation, as it requires fetching all clients and then filtering. Keycloak's API does not support fetching a client by its `client_id` directly.
283
287
 
284
288
  ```ruby
285
289
  KeycloakAdmin.realm("a_realm").clients.list
290
+ KeycloakAdmin.realm("a_realm").clients.get(id) # id is Keycloak's database id, not the client_id
291
+ KeycloakAdmin.realm("a_realm").clients.find_by_client_id(client_id)
292
+ ```
293
+
294
+ ### Updating a client
295
+
296
+ ```ruby
297
+ my_client = KeycloakAdmin.realm("a_realm").clients.get(id)
298
+
299
+ my_client.name = "My new client name"
300
+ my_client.description = "This is a new description"
301
+ my_client.redirect_uris << "https://www.example.com/auth/callback"
302
+
303
+ KeycloakAdmin.realm("a_realm").clients.update(client) # Returns the updated client
286
304
  ```
287
305
 
288
306
  ### Get list of groups in a realm
@@ -293,6 +311,22 @@ Returns an array of `KeycloakAdmin::GroupRepresentation`.
293
311
  KeycloakAdmin.realm("a_realm").groups.list
294
312
  ```
295
313
 
314
+ ### Search for a group
315
+
316
+ Returns an array of `KeycloakAdmin::GroupRepresentation`.
317
+
318
+ According to [the documentation](https://www.keycloak.org/docs-api/22.0.1/rest-api/index.html#_groups):
319
+ * When providing a `String` parameter, this produces an arbitrary search string
320
+ * When providing a `Hash`, you can specify other fields (_e.g_ q, max, first)
321
+
322
+ ```ruby
323
+ KeycloakAdmin.realm("a_realm").groups.search("MyGroup")
324
+ ```
325
+
326
+ ```ruby
327
+ KeycloakAdmin.realm("a_realm").groups.search({query: "MyGroup", exact: true, max: 1})
328
+ ```
329
+
296
330
  ### Save a group
297
331
 
298
332
  Returns the id of saved `group` provided, which must be of type `KeycloakAdmin::GroupRepresentation`.
@@ -335,6 +369,30 @@ You can specify paging with `first` and `max`:
335
369
  KeycloakAdmin.realm("a_realm").group("group_id").members(first:0, max:100)
336
370
  ```
337
371
 
372
+ ### Get list of groups that have a specific role assigned
373
+
374
+ Returns an array of `KeycloakAdmin::GroupRepresentation`
375
+
376
+ ```ruby
377
+ KeycloakAdmin.realm("a_realm").roles.list_groups("role_name")
378
+ ```
379
+
380
+ ### Get list of realm-roles assigned to a group
381
+
382
+ Returns an array of `KeycloakAdmin::RoleRepresentation`
383
+
384
+ ```ruby
385
+ KeycloakAdmin.realm("a_realm").groups.get_realm_level_roles("group_id")
386
+ ```
387
+
388
+ ### Add a realm-role to a group
389
+
390
+ Returns added `KeycloakAdmin::RoleRepresentation`
391
+
392
+ ```ruby
393
+ KeycloakAdmin.realm("a_realm").groups.add_realm_level_role_name!("group_id", "role_name")
394
+ ```
395
+
338
396
  ### Get list of roles in a realm
339
397
 
340
398
  Returns an array of `KeycloakAdmin::RoleRepresentation`.
@@ -415,4 +473,3 @@ From the `keycloak-admin-api` directory:
415
473
  $ docker build . -t keycloak-admin:test
416
474
  $ docker run -v `pwd`:/usr/src/app/ keycloak-admin:test rspec spec
417
475
  ```
418
-
data/bin/console ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "keycloak-admin"
6
+ require "byebug"
7
+
8
+ require "irb"
9
+ IRB.start
@@ -6,6 +6,13 @@ module KeycloakAdmin
6
6
  @realm_client = realm_client
7
7
  end
8
8
 
9
+ def get(id)
10
+ response = execute_http do
11
+ RestClient::Resource.new(clients_url(id), @configuration.rest_client_options).get(headers)
12
+ end
13
+ ClientRepresentation.from_hash(JSON.parse(response))
14
+ end
15
+
9
16
  def save(client_representation)
10
17
  execute_http do
11
18
  RestClient::Resource.new(clients_url, @configuration.rest_client_options).post(
@@ -21,6 +28,10 @@ module KeycloakAdmin
21
28
  JSON.parse(response).map { |client_as_hash| ClientRepresentation.from_hash(client_as_hash) }
22
29
  end
23
30
 
31
+ def find_by_client_id(client_id)
32
+ list.find { |client| client.client_id == client_id }
33
+ end
34
+
24
35
  def delete(id)
25
36
  execute_http do
26
37
  RestClient::Resource.new(clients_url(id), @configuration.rest_client_options).delete(headers)
@@ -28,6 +39,16 @@ module KeycloakAdmin
28
39
  true
29
40
  end
30
41
 
42
+ def update(client_representation)
43
+ execute_http do
44
+ RestClient::Resource.new(clients_url(client_representation.id), @configuration.rest_client_options).put(
45
+ create_payload(client_representation), headers
46
+ )
47
+ end
48
+
49
+ get(client_representation.id)
50
+ end
51
+
31
52
  def get_service_account_user(client_id)
32
53
  response = execute_http do
33
54
  RestClient::Resource.new(service_account_user_url(client_id), @configuration.rest_client_options).get(headers)
@@ -7,8 +7,20 @@ module KeycloakAdmin
7
7
  end
8
8
 
9
9
  def list
10
+ search(nil)
11
+ end
12
+
13
+ def search(query)
14
+ derived_headers = case query
15
+ when String
16
+ headers.merge({params: { search: query }})
17
+ when Hash
18
+ headers.merge({params: query })
19
+ else
20
+ headers
21
+ end
10
22
  response = execute_http do
11
- RestClient::Resource.new(groups_url, @configuration.rest_client_options).get(headers)
23
+ RestClient::Resource.new(groups_url, @configuration.rest_client_options).get(derived_headers)
12
24
  end
13
25
  JSON.parse(response).map { |group_as_hash| GroupRepresentation.from_hash(group_as_hash) }
14
26
  end
@@ -49,6 +61,28 @@ module KeycloakAdmin
49
61
  JSON.parse(response).map { |user_as_hash| UserRepresentation.from_hash(user_as_hash) }
50
62
  end
51
63
 
64
+ # Gets all realm-level roles for a group
65
+ def get_realm_level_roles(group_id)
66
+ url = "#{groups_url(group_id)}/role-mappings/realm"
67
+ response = execute_http do
68
+ RestClient::Resource.new(url, @configuration.rest_client_options).get(headers)
69
+ end
70
+ JSON.parse(response).map { |role_as_hash| RoleRepresentation.from_hash(role_as_hash) }
71
+ end
72
+
73
+ # Adds a realm-level role to a group via the role name
74
+ def add_realm_level_role_name!(group_id, role_name)
75
+ # creates a full role-representation object needed by the keycloak api to work
76
+ role_representation = RoleClient.new(@configuration, @realm_client).get(role_name)
77
+ url = "#{groups_url(group_id)}/role-mappings/realm"
78
+ response = execute_http do
79
+ RestClient::Resource.new(url, @configuration.rest_client_options).post(
80
+ create_payload([role_representation]), headers
81
+ )
82
+ end
83
+ role_representation
84
+ end
85
+
52
86
  def groups_url(id=nil)
53
87
  if id
54
88
  "#{@realm_client.realm_admin_url}/groups/#{id}"
@@ -13,13 +13,26 @@ module KeycloakAdmin
13
13
  JSON.parse(response).map { |role_as_hash| RoleRepresentation.from_hash(role_as_hash) }
14
14
  end
15
15
 
16
+ # Returns the role representation for the specified role name
16
17
  def get(name)
18
+ # allows special characters in the name like space
19
+ name = URI.encode_uri_component(name)
17
20
  response = execute_http do
18
21
  RestClient::Resource.new(role_name_url(name), @configuration.rest_client_options).get(headers)
19
22
  end
20
23
  RoleRepresentation.from_hash JSON.parse(response)
21
24
  end
22
25
 
26
+ # Lists all groups that have the specified role name assigned
27
+ def list_groups(name)
28
+ # allows special characters in the name like space
29
+ name = URI.encode_uri_component(name)
30
+ response = execute_http do
31
+ RestClient::Resource.new("#{role_name_url(name)}/groups", @configuration.rest_client_options).get(headers)
32
+ end
33
+ JSON.parse(response).map { |role_as_hash| GroupRepresentation.from_hash(role_as_hash) }
34
+ end
35
+
23
36
  def save(role_representation)
24
37
  execute_http do
25
38
  RestClient::Resource.new(roles_url, @configuration.rest_client_options).post(
@@ -3,7 +3,8 @@ module KeycloakAdmin
3
3
  attr_accessor :id,
4
4
  :name,
5
5
  :composite,
6
- :client_role
6
+ :client_role,
7
+ :container_id,
7
8
 
8
9
  def self.from_hash(hash)
9
10
  role = new
@@ -11,6 +12,7 @@ module KeycloakAdmin
11
12
  role.name = hash["name"]
12
13
  role.composite = hash["composite"]
13
14
  role.client_role = hash["clientRole"]
15
+ role.container_id = hash["containerId"]
14
16
  role
15
17
  end
16
18
  end
@@ -1,3 +1,3 @@
1
1
  module KeycloakAdmin
2
- VERSION = "1.0.24"
2
+ VERSION = "1.1.1"
3
3
  end
@@ -22,6 +22,49 @@ RSpec.describe KeycloakAdmin::ClientClient do
22
22
  end
23
23
  end
24
24
 
25
+ describe "#get" do
26
+ let(:realm_name) { "valid-realm" }
27
+ let(:id) { "test_client_id" }
28
+ let(:client_name) { "test_client_name" }
29
+
30
+ before(:each) do
31
+ @client_client = KeycloakAdmin.realm(realm_name).clients
32
+
33
+ stub_token_client
34
+ allow_any_instance_of(RestClient::Resource).to receive(:get).and_return '{"id":"test_client_id","name":"test_client_name"}'
35
+ end
36
+
37
+ it "finds a client" do
38
+ client = @client_client.get(id)
39
+ expect(client.name).to eq client_name
40
+ expect(client.id).to eq id
41
+ end
42
+ end
43
+
44
+ describe "#find_by_client_id" do
45
+ let(:realm_name) { "valid-realm" }
46
+ let(:client_id) { "my_client_id" }
47
+ let(:client_name) { "test_client_name" }
48
+
49
+ before(:each) do
50
+ @client_client = KeycloakAdmin.realm(realm_name).clients
51
+
52
+ stub_token_client
53
+ allow_any_instance_of(RestClient::Resource).to receive(:get).and_return '[{"id":"test_client_id","clientId": "my_client_id","name":"test_client_name"},{"id":"test_client_id_2","clientId":"client_id_2","name":"test_client_name_2"}]'
54
+ end
55
+
56
+ it "finds a client it has" do
57
+ client = @client_client.find_by_client_id(client_id)
58
+ expect(client.name).to eq client_name
59
+ expect(client.client_id).to eq client_id
60
+ end
61
+
62
+ it "returns nil if it doesn't have the client" do
63
+ client = @client_client.find_by_client_id("client_id_3")
64
+ expect(client).to be_nil
65
+ end
66
+ end
67
+
25
68
  describe "#list" do
26
69
  let(:realm_name) { "valid-realm" }
27
70
 
@@ -51,6 +94,25 @@ RSpec.describe KeycloakAdmin::ClientClient do
51
94
  end
52
95
  end
53
96
 
97
+ describe "#update" do
98
+ let(:realm_name) { "valid-realm" }
99
+ let(:client) { KeycloakAdmin::ClientRepresentation.from_hash({ "id" => "test_client_id", "clientId" => "my-client", "name" => "old_name" }) }
100
+
101
+ before(:each) do
102
+ @client_client = KeycloakAdmin.realm(realm_name).clients
103
+
104
+ stub_token_client
105
+ allow_any_instance_of(RestClient::Resource).to receive(:put).and_return ''
106
+ allow_any_instance_of(RestClient::Resource).to receive(:get).and_return '{"id":"test_client_id", "clientId": "my-client","name":"new_name"}'
107
+ end
108
+
109
+ it "updates a client" do
110
+ updated_client = @client_client.update(client)
111
+
112
+ expect(updated_client.name).to eq "new_name"
113
+ end
114
+ end
115
+
54
116
  describe "#delete" do
55
117
  let(:realm_name) { "valid-realm" }
56
118
 
@@ -12,7 +12,7 @@ RSpec.describe KeycloakAdmin::RoleRepresentation do
12
12
  end
13
13
 
14
14
  it "can convert to json" do
15
- expect(@mapper.to_json).to eq "{\"id\":\"bb79fb10-a7b4-4728-a662-82a4de7844a3\",\"name\":\"abcd\",\"composite\":true,\"clientRole\":false}"
15
+ expect(@mapper.to_json).to eq "{\"id\":\"bb79fb10-a7b4-4728-a662-82a4de7844a3\",\"name\":\"abcd\",\"composite\":true,\"clientRole\":false,\"containerId\":null}"
16
16
  end
17
17
  end
18
18
 
@@ -31,7 +31,7 @@ RSpec.describe KeycloakAdmin::RoleRepresentation do
31
31
  end
32
32
 
33
33
  it "can convert to json" do
34
- expect(@mappers.to_json).to eq "[{\"id\":\"bb79fb10-a7b4-4728-a662-82a4de7844a3\",\"name\":\"abcd\",\"composite\":true,\"clientRole\":false}]"
34
+ expect(@mappers.to_json).to eq "[{\"id\":\"bb79fb10-a7b4-4728-a662-82a4de7844a3\",\"name\":\"abcd\",\"composite\":true,\"clientRole\":false,\"containerId\":null}]"
35
35
  end
36
36
  end
37
37
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keycloak-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.24
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorent Lempereur
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-07 00:00:00.000000000 Z
11
+ date: 2024-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-cookie
@@ -87,6 +87,7 @@ files:
87
87
  - Gemfile.lock
88
88
  - MIT-LICENSE
89
89
  - README.md
90
+ - bin/console
90
91
  - keycloak-admin.gemspec
91
92
  - lib/keycloak-admin.rb
92
93
  - lib/keycloak-admin/client/attack_detection_client.rb