keycloak-admin 0.7.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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