keycloak-admin 0.7.6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4496dbc0b6fbd52610640546a149a2bc42cc7fb114acc210876c88c8b3626216
4
- data.tar.gz: f9426c7701e10cac266df4a75a39fc20a717d3d31e92de88ca7bd3a6e2716a92
3
+ metadata.gz: 4e6887f2f57beb2339c74a7600a787b3f19cc5eaa171cbf43e94c1c899b75152
4
+ data.tar.gz: 10639852645add2d77c73719f5ab315ccb0bf9f5e34a4e716b72a919c087d5a5
5
5
  SHA512:
6
- metadata.gz: 17ce324b2a3e555c756a095443902fbb616f88a91168b2f07245ca055c3714bd0aff0435afd1346b81fa456f60eaf5a07b3b1d4a01d66f0af3b3e39490d916f2
7
- data.tar.gz: c7e81693e55556d5bb8f6fa4ead25a1172c9e5cb3f5e77a34ed420c92baa1b574a4b0746e5dfc314097a3a3f28c9d97e4f651444fee76a60dda89bda65e6a8ff
6
+ metadata.gz: ea1fded8289d7d3cd89d816cf0949eed2aa07f2cab0bf86afb2a56e26d60dc30d218ce40ef51a4091e3d9c27369c7e4c8316997aa2a5fbed129c457728056de4
7
+ data.tar.gz: 65301ff7dd19a5240bdd389f61e23615b8aeee911e08d833fac0c9102a27d5ed279c5bf7ba2adc7726fa12583c4b18ab2d297b38e17555beab1b47caa7b5fcdd
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ 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.0.0] - 2021-08-03
9
+
10
+ * Add `totp` on Users
11
+ * Add `required_actions` on Users
12
+
13
+ ## [0.7.9] - 2020-10-22
14
+
15
+ * Extend `search` function to use complex queries (thanks to @hobbypunk90)
16
+
17
+ ## [0.7.8] - 2020-10-15
18
+
19
+ * Bug: `rest_client_options` default value does not match the documentation (was `nil` by default, should be `{}`)
20
+ * Update documentation about client setup (based on Keycloak 11)
21
+
22
+ ## [0.7.7] - 2020-07-10
23
+
24
+ * Fix: `Replace request method shorthand with .execute for proper RestClient option support` (thanks to @RomanHargrave)
25
+ * When sending action emails, add lifespan as an optional parameter (thanks to @hobbypunk90)
26
+
8
27
  ## [0.7.6] - 2020-06-22
9
28
 
10
29
  Thanks to @hobbypunk90
@@ -21,7 +40,7 @@ Thanks to @RomanHargrave
21
40
 
22
41
  ## [0.7.3] - 2019-07-11
23
42
 
24
- Thanks to @cederigo=
43
+ Thanks to @cederigo:
25
44
  * For a given user, get her list of groups
26
45
 
27
46
  ## [0.7.2] - 2019-06-17
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- keycloak-admin (0.7.6)
4
+ keycloak-admin (1.0.0)
5
5
  http-cookie (~> 1.0, >= 1.0.3)
6
6
  rest-client (~> 2.0)
7
7
 
@@ -13,11 +13,11 @@ GEM
13
13
  domain_name (0.5.20190701)
14
14
  unf (>= 0.0.5, < 1.0.0)
15
15
  http-accept (1.7.0)
16
- http-cookie (1.0.3)
16
+ http-cookie (1.0.4)
17
17
  domain_name (~> 0.5)
18
18
  mime-types (3.3.1)
19
19
  mime-types-data (~> 3.2015)
20
- mime-types-data (3.2020.0512)
20
+ mime-types-data (3.2021.0704)
21
21
  netrc (0.11.0)
22
22
  rest-client (2.1.0)
23
23
  http-accept (>= 1.7.0, < 2.0)
data/README.md CHANGED
@@ -12,37 +12,59 @@ This gem *does not* require Rails.
12
12
  For example, using `bundle`, add this line to your Gemfile.
13
13
 
14
14
  ```ruby
15
- gem "keycloak-admin", "0.7.6"
15
+ gem "keycloak-admin", "1.0.0"
16
16
  ```
17
17
 
18
18
  ## Login
19
19
 
20
- You can choose your login process between two different login methods: `username/password` and `Account Service`.
21
-
22
- ### Login with username/password
23
-
24
- Using this login method requires to create a user (and her credentials).
25
- * In Keycloak
26
- * Make your client `confidential` or `public`
27
- * Do not check `Service Accounts Enabled`
28
- * In this gem's configuration
29
- * Set `use_service_account` to `false`
30
- * Setup `username` and `password`
31
- * Setup `client_secret` if your client is `confidential`
32
-
33
- ### Login with an Account Service
34
-
35
- Using a service account to use the REST Admin API does not require to create a dedicated user (http://www.keycloak.org/docs/2.5/server_admin/topics/clients/oidc/service-accounts.html).
36
-
37
- * In Keycloak
38
- * Make your client `confidential`
39
- * Check its toggle `Service Accounts Enabled`
40
- * Disable both `Standard Flow Enabled` and `Implicit Flow Enabled `
41
- * Enable `Direct Access Grants Enabled`
42
- * After saving this client, 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.
43
- * In this gem's configuration
20
+ To login on Keycloak's Admin API, you first need to setup a client.
21
+
22
+ Go to your realm administration page and open `Clients`. Then, click on the `Create` button.
23
+ On the first screen, enter:
24
+ * `Client ID`: _e.g. my-app-admin-client_
25
+ * `Client Protocol`: select `openid-connect`
26
+ * `Root URL`: let it blank
27
+
28
+ The next screen must be configured depending on how you want to authenticate:
29
+ * `username/password` with a user of the realm
30
+ * `Direct Access Grants` with a service account
31
+
32
+ ### Login with username/password (realm user)
33
+
34
+ * In Keycloak, during the client setup:
35
+ * `Access Type`: `public` or `confidential`
36
+ * `Service Accounts Enabled` (when `confidential`): `false`
37
+ * After saving your client, if you have chosen a `confidential` client, go to `Credentials` tab and copy the `Client Secret`
38
+
39
+ * In Keycloak, create a dedicated user (and her credentials):
40
+ * Go to `Users`
41
+ * Click on the `Add user` button
42
+ * Setup her mandatory information, depending on your realm's configuration
43
+ * On the `Credentials` tab, create her a password (toggle off `Temporary`)
44
+
45
+ * In this gem's configuration (see Section `Configuration`):
46
+ * Setup `username` and `password` according to your user's configuration
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`
49
+
50
+ ### Login with `Direct Access Grants` (Service account)
51
+
52
+ Using a service account to use the REST Admin API does not require to create a dedicated user (https://www.keycloak.org/docs/latest/server_admin/#_service_accounts).
53
+
54
+ * In Keycloak, during the client setup:
55
+ * `Access Type`: `confidential`
56
+ * `Service Accounts Enabled` (when `confidential`): `true`
57
+ * `Standard Flow Enabled`: `false`
58
+ * `Implicit Flow Enabled`: `false`
59
+ * `Direct Access Grants Enabled`: `true`
60
+ * After saving this client
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
+ * open the `Credentials` tab and copy the `Client Secret`
63
+
64
+ * In this gem's configuration (see Section `Configuration`):
44
65
  * Set `use_service_account` to `true`
45
- * Setup `client_secret`
66
+ * Setup `client_id` with your `Client ID` (_e.g. my-app-admin-client_)
67
+ * Copy its Client Secret to `client_secret`
46
68
 
47
69
  ## Configuration
48
70
 
@@ -69,7 +91,8 @@ All options have a default value. However, all of them can be changed in your in
69
91
 
70
92
  | Option | Default Value | Type | Required? | Description | Example |
71
93
  | ---- | ----- | ------ | ----- | ------ | ----- |
72
- | `server_url` | `nil`| String | Required | The base url where your Keycloak server is located. This value can be retrieved in your Keycloak client configuration. | `server_domain` | `nil`| String | Required | Public domain that identify your authentication cookies. | `auth.service.io` |
94
+ | `server_url` | `nil` | String | Required | The base url where your Keycloak server is located (a URL that starts with `http` and that ends with `/auth`). This value can be retrieved in your Keycloak client configuration. | `http://auth:8080/auth`
95
+ | `server_domain` | `nil`| String | Required | Public domain that identify your authentication cookies. | `auth.service.io` |
73
96
  | `client_realm_name` | `""`| String | Required | Name of the realm that contains the admin client. | `master` |
74
97
  | `client_id` | `admin-cli`| String | Required | Client that should be used to access admin capabilities. | `api-cli` |
75
98
  | `client_secret` | `nil`| String | Optional | If your client is `confidential`, this parameter must be specified. | `4e3c481c-f823-4a6a-b8a7-bf8c86e3eac3` |
@@ -122,10 +145,18 @@ KeycloakAdmin.realm("a_realm").users.get(user_id)
122
145
 
123
146
  Returns an array of `KeycloakAdmin::UserRepresentation`.
124
147
 
148
+ According to [the documentation](https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_users_resource):
149
+ * When providing a `String` parameter, this produces an arbitrary search string
150
+ * When providing a `Hash`, you can search for specific field (_e.g_ an email)
151
+
125
152
  ```ruby
126
153
  KeycloakAdmin.realm("a_realm").users.search("a_username_or_an_email")
127
154
  ```
128
155
 
156
+ ```ruby
157
+ KeycloakAdmin.realm("a_realm").users.search({ email: "john@doe.com" })
158
+ ```
159
+
129
160
  ### List all users in a realm
130
161
 
131
162
  Returns an array of `KeycloakAdmin::UserRepresentation`.
@@ -343,8 +374,4 @@ From the `keycloak-admin-api` directory:
343
374
  ```
344
375
  $ docker build . -t keycloak-admin:test
345
376
  $ docker run -v `pwd`:/usr/src/app/ keycloak-admin:test bundle exec rspec spec
346
- ```
347
-
348
- ## Future work
349
-
350
- * Allow authentication using JWT assertions
377
+ ```
@@ -55,7 +55,7 @@ module KeycloakAdmin
55
55
  config.use_service_account = true
56
56
  config.username = nil
57
57
  config.password = nil
58
- config.rest_client_options = nil
58
+ config.rest_client_options = {}
59
59
  end
60
60
  end
61
61
 
@@ -16,13 +16,18 @@ module KeycloakAdmin
16
16
 
17
17
  def exchange_with(user_access_token, token_lifespan_in_seconds)
18
18
  response = execute_http do
19
- RestClient.post(token_url, {
20
- tokenLifespanInSeconds: token_lifespan_in_seconds
21
- }.to_json, {
22
- Authorization: "Bearer #{user_access_token}",
23
- content_type: :json,
24
- accept: :json
25
- })
19
+ RestClient::Request.execute(
20
+ @configuration.rest_client_options.merge(
21
+ method: :post,
22
+ url: token_url,
23
+ payload: { tokenLifespanInSeconds: token_lifespan_in_seconds }.to_json,
24
+ headers: {
25
+ Authorization: "Bearer #{user_access_token}",
26
+ content_type: :json,
27
+ accept: :json
28
+ }
29
+ )
30
+ )
26
31
  end
27
32
  TokenRepresentation.from_json(response.body)
28
33
  end
@@ -21,7 +21,14 @@ module KeycloakAdmin
21
21
  end
22
22
 
23
23
  def update(user_id, user_representation_body)
24
- RestClient.put(users_url(user_id), user_representation_body.to_json, headers)
24
+ RestClient::Request.execute(
25
+ @configuration.rest_client_options.merge(
26
+ method: :put,
27
+ url: users_url(user_id),
28
+ payload: user_representation_body.to_json,
29
+ headers: headers
30
+ )
31
+ )
25
32
  end
26
33
 
27
34
  def get(user_id)
@@ -31,8 +38,22 @@ module KeycloakAdmin
31
38
  UserRepresentation.from_hash(JSON.parse(response))
32
39
  end
33
40
 
41
+ ##
42
+ # Query can be a string or a hash.
43
+ # * String: It's used as search query
44
+ # * Hash: Used for complex search queries.
45
+ # For its documentation see: https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_users_resource
46
+ ##
34
47
  def search(query)
35
- derived_headers = query ? headers.merge({params: { search: query }}) : headers
48
+ derived_headers = case query
49
+ when String
50
+ headers.merge({params: { search: query }})
51
+ when Hash
52
+ headers.merge({params: query })
53
+ else
54
+ headers
55
+ end
56
+
36
57
  response = execute_http do
37
58
  RestClient::Resource.new(users_url, @configuration.rest_client_options).get(derived_headers)
38
59
  end
@@ -59,22 +80,26 @@ module KeycloakAdmin
59
80
 
60
81
  def update_password(user_id, new_password)
61
82
  execute_http do
62
- RestClient.put(reset_password_url(user_id), {
63
- type: "password",
64
- value: new_password,
65
- temporary: false
66
- }.to_json, headers)
83
+ RestClient::Request.execute(
84
+ @configuration.rest_client_options.merge(
85
+ method: :put,
86
+ url: reset_password_url(user_id),
87
+ payload: { type: 'password', value: new_password, temporary: false }.to_json,
88
+ headers: headers
89
+ )
90
+ )
67
91
  end
68
92
  user_id
69
93
  end
70
94
 
71
- def forgot_password(user_id)
72
- execute_actions_email(user_id, ["UPDATE_PASSWORD"])
95
+ def forgot_password(user_id, lifespan=nil)
96
+ execute_actions_email(user_id, ["UPDATE_PASSWORD"], lifespan)
73
97
  end
74
98
 
75
- def execute_actions_email(user_id, actions=[])
99
+ def execute_actions_email(user_id, actions=[], lifespan=nil)
76
100
  execute_http do
77
- RestClient.put(execute_actions_email_url(user_id), actions.to_json, headers)
101
+ lifespan_param = lifespan.nil? ? "" : "lifespan=#{lifespan.seconds}"
102
+ RestClient.put("#{execute_actions_email_url(user_id)}?#{lifespan_param}", actions.to_json, headers)
78
103
  end
79
104
  user_id
80
105
  end
@@ -82,7 +107,14 @@ module KeycloakAdmin
82
107
  def impersonate(user_id)
83
108
  impersonation = get_redirect_impersonation(user_id)
84
109
  response = execute_http do
85
- RestClient.post(impersonation.impersonation_url, impersonation.body.to_json, impersonation.headers)
110
+ RestClient::Request.execute(
111
+ @configuration.rest_client_options.merge(
112
+ method: :post,
113
+ url: impersonation.impersonation_url,
114
+ payload: impersonation.body.to_json,
115
+ headers: impersonation.headers
116
+ )
117
+ )
86
118
  end
87
119
  ImpersonationRepresentation.from_response(response, @configuration.server_domain)
88
120
  end
@@ -98,7 +130,14 @@ module KeycloakAdmin
98
130
  fed_id_rep.identity_provider = idp_id
99
131
 
100
132
  execute_http do
101
- RestClient.post(federated_identity_url(user_id, idp_id), fed_id_rep.to_json, headers)
133
+ RestClient::Request.execute(
134
+ @configuration.rest_client_options.merge(
135
+ method: :post,
136
+ url: federated_identity_url(user_id, idp_id),
137
+ payload: fed_id_rep.to_json,
138
+ headers: headers
139
+ )
140
+ )
102
141
  end
103
142
  end
104
143
 
@@ -10,8 +10,10 @@ module KeycloakAdmin
10
10
  :email_verified,
11
11
  :first_name,
12
12
  :last_name,
13
+ :totp,
13
14
  :credentials,
14
- :federated_identities
15
+ :federated_identities,
16
+ :required_actions
15
17
 
16
18
  def self.from_hash(hash)
17
19
  user = new
@@ -25,6 +27,8 @@ module KeycloakAdmin
25
27
  user.first_name = hash["firstName"]
26
28
  user.last_name = hash["lastName"]
27
29
  user.attributes = hash["attributes"]
30
+ user.required_actions = hash["requiredActions"] || []
31
+ user.totp = hash["totp"] || false
28
32
  user.credentials = hash["credentials"]&.map{ |hash| CredentialRepresentation.from_hash(hash) } || []
29
33
  user.federated_identities = hash["federatedIdentities"]&.map { |hash| FederatedIdentityRepresentation.from_hash(hash) } || []
30
34
  user
@@ -1,3 +1,3 @@
1
1
  module KeycloakAdmin
2
- VERSION = "0.7.6"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -158,7 +158,7 @@ RSpec.describe KeycloakAdmin::TokenClient do
158
158
  @user_client = KeycloakAdmin.realm(realm_name).users
159
159
 
160
160
  stub_token_client
161
- allow_any_instance_of(RestClient::Resource).to receive(:get).and_return '{"username":"test_username","createdTimestamp":1559347200}'
161
+ allow_any_instance_of(RestClient::Resource).to receive(:get).and_return '{"username":"test_username","createdTimestamp":1559347200, "requiredActions":["CONFIGURE_TOTP"], "totp": true}'
162
162
  end
163
163
 
164
164
  it "parses the response" do
@@ -175,6 +175,8 @@ RSpec.describe KeycloakAdmin::TokenClient do
175
175
 
176
176
  user = @user_client.get('test_user_id')
177
177
  expect(user.username).to eq 'test_username'
178
+ expect(user.totp).to be true
179
+ expect(user.required_actions).to eq ["CONFIGURE_TOTP"]
178
180
  end
179
181
  end
180
182
 
@@ -192,12 +194,24 @@ RSpec.describe KeycloakAdmin::TokenClient do
192
194
  allow_any_instance_of(RestClient::Resource).to receive(:get).and_return '[{"username":"test_username","createdTimestamp":1559347200}]'
193
195
  end
194
196
 
195
- it "finds a user" do
197
+ it "finds a user using a string" do
196
198
  users = @user_client.search("test_username")
197
199
  expect(users.length).to eq 1
198
200
  expect(users[0].username).to eq "test_username"
199
201
  end
200
202
 
203
+ it "finds a user using nil does not fail" do
204
+ users = @user_client.search(nil)
205
+ expect(users.length).to eq 1
206
+ expect(users[0].username).to eq "test_username"
207
+ end
208
+
209
+ it "finds a user using a hash" do
210
+ users = @user_client.search({ search: "test_username"})
211
+ expect(users.length).to eq 1
212
+ expect(users[0].username).to eq "test_username"
213
+ end
214
+
201
215
  it "passes rest client options" do
202
216
  rest_client_options = {verify_ssl: OpenSSL::SSL::VERIFY_NONE}
203
217
  allow_any_instance_of(KeycloakAdmin::Configuration).to receive(:rest_client_options).and_return rest_client_options
@@ -9,7 +9,7 @@ RSpec.describe KeycloakAdmin::UserRepresentation do
9
9
  end
10
10
 
11
11
  it "can convert to json" do
12
- expect(@user.to_json).to eq '{"id":null,"createdTimestamp":1559836000,"origin":null,"username":"test_username","email":null,"enabled":true,"emailVerified":null,"firstName":null,"lastName":null,"attributes":null,"credentials":[],"federatedIdentities":[]}'
12
+ expect(@user.to_json).to eq '{"id":null,"createdTimestamp":1559836000,"origin":null,"username":"test_username","email":null,"enabled":true,"emailVerified":null,"firstName":null,"lastName":null,"attributes":null,"requiredActions":[],"totp":false,"credentials":[],"federatedIdentities":[]}'
13
13
  end
14
14
  end
15
15
  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: 0.7.6
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorent Lempereur
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-22 00:00:00.000000000 Z
11
+ date: 2021-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-cookie