keycloak-admin 0.7.3 → 0.7.8

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
- SHA1:
3
- metadata.gz: 8dc193e7b260cc860b03a8eaaa53180fc6944e71
4
- data.tar.gz: 229097dbd02866fa93fa51b834b39dcfba7a97a9
2
+ SHA256:
3
+ metadata.gz: b7d17b8a5f55e87b615da11c9554bfeec127629207e81bd4e4950fb85971010c
4
+ data.tar.gz: '097affd8e18b7e778557fe4ecc787e93127bcef133a4dd9e6db590fc7ad65982'
5
5
  SHA512:
6
- metadata.gz: 084c52d8b2cdd9a38d68f01c1bba6db6ab6d0735efdef1a6d61af31fb248a1ce6d72d2b50e5bf8487edb60d6846d3b15fbdcb3e850202b5413632a26b83d1c50
7
- data.tar.gz: 588b84ec13efd6538998bba3d0d7422bf3abdd03525f40e0860e84bc675afd7acd0856f0876f44d11cdb0c5bd2e1528df3c76e6b861ffbfa8c01e7f973d0571d
6
+ metadata.gz: ffc55b0f6c7719ce1fa38b93838fe2d137fe3cd6395fe1717ac4ef352484bdfedab0363811f88bf9e82815d1ae6f94ec008ab1fb4db5038f638cc816d66362fd
7
+ data.tar.gz: b8053bb57699928d793523b9d0274a284ebafbedd8e05d8294fbcdebcca967d69531dddf2048fd890b8e51dc3bb9bea2c83a9aee19e34bac13fccbed95a547b7
@@ -5,9 +5,33 @@ 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
+ ## [0.7.8] - 2020-10-15
9
+
10
+ * Bug: `rest_client_options` default value does not match the documentation (was `nil` by default, should be `{}`)
11
+ * Update documentation about client setup (based on Keycloak 11)
12
+
13
+ ## [0.7.7] - 2020-07-10
14
+
15
+ * Fix: `Replace request method shorthand with .execute for proper RestClient option support` (thanks to @RomanHargrave)
16
+ * When sending action emails, add lifespan as an optional parameter (thanks to @hobbypunk90)
17
+
18
+ ## [0.7.6] - 2020-06-22
19
+
20
+ Thanks to @hobbypunk90
21
+ * Support for action emails and send forgot passsword mail
22
+
23
+ ## [0.7.5] - 2020-03-28
24
+
25
+ Thanks to @RomanHargrave
26
+ * Support for working with federated identity provider (broker) links
27
+
28
+ ## [0.7.4] - 2019-10-17
29
+
30
+ * Support for Rails 6
31
+
8
32
  ## [0.7.3] - 2019-07-11
9
33
 
10
- Thanks to @cederigo=
34
+ Thanks to @cederigo:
11
35
  * For a given user, get her list of groups
12
36
 
13
37
  ## [0.7.2] - 2019-06-17
data/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ruby:2.3
1
+ FROM ruby:2.6.5
2
2
  RUN mkdir -p /usr/src/app/lib/keycloak-admin
3
3
  WORKDIR /usr/src/app
4
4
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- keycloak-admin (0.7.3)
4
+ keycloak-admin (0.7.8)
5
5
  http-cookie (~> 1.0, >= 1.0.3)
6
6
  rest-client (~> 2.0)
7
7
 
@@ -12,13 +12,15 @@ GEM
12
12
  diff-lcs (1.3)
13
13
  domain_name (0.5.20190701)
14
14
  unf (>= 0.0.5, < 1.0.0)
15
+ http-accept (1.7.0)
15
16
  http-cookie (1.0.3)
16
17
  domain_name (~> 0.5)
17
- mime-types (3.2.2)
18
+ mime-types (3.3.1)
18
19
  mime-types-data (~> 3.2015)
19
- mime-types-data (3.2019.0331)
20
+ mime-types-data (3.2020.0512)
20
21
  netrc (0.11.0)
21
- rest-client (2.0.2)
22
+ rest-client (2.1.0)
23
+ http-accept (>= 1.7.0, < 2.0)
22
24
  http-cookie (>= 1.0.2, < 2.0)
23
25
  mime-types (>= 1.16, < 4.0)
24
26
  netrc (~> 0.8)
@@ -37,7 +39,7 @@ GEM
37
39
  rspec-support (3.7.0)
38
40
  unf (0.1.4)
39
41
  unf_ext
40
- unf_ext (0.0.7.6)
42
+ unf_ext (0.0.7.7)
41
43
 
42
44
  PLATFORMS
43
45
  ruby
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.3"
15
+ gem "keycloak-admin", "0.7.8"
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
 
@@ -97,6 +119,9 @@ All options have a default value. However, all of them can be changed in your in
97
119
  * Get list of client role mappings for a user/group
98
120
  * Save client role mappings for a user/group
99
121
  * Save realm-level role mappings for a user/group
122
+ * Link/Unlink users to federated identity provider brokers
123
+ * Execute actions emails
124
+ * Send forgot passsword mail
100
125
 
101
126
  ### Get an access token
102
127
 
@@ -340,8 +365,4 @@ From the `keycloak-admin-api` directory:
340
365
  ```
341
366
  $ docker build . -t keycloak-admin:test
342
367
  $ docker run -v `pwd`:/usr/src/app/ keycloak-admin:test bundle exec rspec spec
343
- ```
344
-
345
- ## Future work
346
-
347
- * Allow authentication using JWT assertions
368
+ ```
@@ -21,6 +21,7 @@ require_relative "keycloak-admin/representation/impersonation_representation"
21
21
  require_relative "keycloak-admin/representation/credential_representation"
22
22
  require_relative "keycloak-admin/representation/realm_representation"
23
23
  require_relative "keycloak-admin/representation/role_representation"
24
+ require_relative "keycloak-admin/representation/federated_identity_representation"
24
25
  require_relative "keycloak-admin/representation/user_representation"
25
26
  require_relative "keycloak-admin/resource/base_role_containing_resource"
26
27
  require_relative "keycloak-admin/resource/group_resource"
@@ -54,7 +55,7 @@ module KeycloakAdmin
54
55
  config.use_service_account = true
55
56
  config.username = nil
56
57
  config.password = nil
57
- config.rest_client_options = nil
58
+ config.rest_client_options = {}
58
59
  end
59
60
  end
60
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)
@@ -59,11 +66,26 @@ module KeycloakAdmin
59
66
 
60
67
  def update_password(user_id, new_password)
61
68
  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)
69
+ RestClient::Request.execute(
70
+ @configuration.rest_client_options.merge(
71
+ method: :put,
72
+ url: reset_password_url(user_id),
73
+ payload: { type: 'password', value: new_password, temporary: false }.to_json,
74
+ headers: headers
75
+ )
76
+ )
77
+ end
78
+ user_id
79
+ end
80
+
81
+ def forgot_password(user_id, lifespan=nil)
82
+ execute_actions_email(user_id, ["UPDATE_PASSWORD"], lifespan)
83
+ end
84
+
85
+ def execute_actions_email(user_id, actions=[], lifespan=nil)
86
+ execute_http do
87
+ lifespan_param = lifespan.nil? ? "" : "lifespan=#{lifespan.seconds}"
88
+ RestClient.put("#{execute_actions_email_url(user_id)}?#{lifespan_param}", actions.to_json, headers)
67
89
  end
68
90
  user_id
69
91
  end
@@ -71,7 +93,14 @@ module KeycloakAdmin
71
93
  def impersonate(user_id)
72
94
  impersonation = get_redirect_impersonation(user_id)
73
95
  response = execute_http do
74
- RestClient.post(impersonation.impersonation_url, impersonation.body.to_json, impersonation.headers)
96
+ RestClient::Request.execute(
97
+ @configuration.rest_client_options.merge(
98
+ method: :post,
99
+ url: impersonation.impersonation_url,
100
+ payload: impersonation.body.to_json,
101
+ headers: impersonation.headers
102
+ )
103
+ )
75
104
  end
76
105
  ImpersonationRepresentation.from_response(response, @configuration.server_domain)
77
106
  end
@@ -80,6 +109,30 @@ module KeycloakAdmin
80
109
  ImpersonationRedirectionRepresentation.from_url(impersonation_url(user_id), headers)
81
110
  end
82
111
 
112
+ def link_idp(user_id, idp_id, idp_user_id, idp_username)
113
+ fed_id_rep = FederatedIdentityRepresentation.new
114
+ fed_id_rep.user_id = idp_user_id
115
+ fed_id_rep.user_name = idp_username
116
+ fed_id_rep.identity_provider = idp_id
117
+
118
+ execute_http do
119
+ RestClient::Request.execute(
120
+ @configuration.rest_client_options.merge(
121
+ method: :post,
122
+ url: federated_identity_url(user_id, idp_id),
123
+ payload: fed_id_rep.to_json,
124
+ headers: headers
125
+ )
126
+ )
127
+ end
128
+ end
129
+
130
+ def unlink_idp(user_id, idp_id)
131
+ execute_http do
132
+ RestClient::Resource.new(federated_identity_url(user_id, idp_id), @configuration.rest_client_options).delete(headers)
133
+ end
134
+ end
135
+
83
136
  def users_url(id=nil)
84
137
  if id
85
138
  "#{@realm_client.realm_admin_url}/users/#{id}"
@@ -93,6 +146,11 @@ module KeycloakAdmin
93
146
  "#{users_url(user_id)}/reset-password"
94
147
  end
95
148
 
149
+ def execute_actions_email_url(user_id)
150
+ raise ArgumentError.new("user_id must be defined") if user_id.nil?
151
+ "#{users_url(user_id)}/execute-actions-email"
152
+ end
153
+
96
154
  def groups_url(user_id)
97
155
  raise ArgumentError.new("user_id must be defined") if user_id.nil?
98
156
  "#{users_url(user_id)}/groups"
@@ -103,6 +161,12 @@ module KeycloakAdmin
103
161
  "#{users_url(user_id)}/impersonation"
104
162
  end
105
163
 
164
+ def federated_identity_url(user_id, identity_provider)
165
+ raise ArgumentError.new("user_id must be defined") if user_id.nil?
166
+ raise ArgumentError.new("identity_provider must be defined") if identity_provider.nil?
167
+ "#{users_url(user_id)}/federated-identity/#{identity_provider}"
168
+ end
169
+
106
170
  private
107
171
 
108
172
  def build(username, email, password, email_verified, locale)
@@ -0,0 +1,15 @@
1
+ module KeycloakAdmin
2
+ class FederatedIdentityRepresentation < Representation
3
+ attr_accessor :identity_provider,
4
+ :user_id,
5
+ :user_name
6
+
7
+ def self.from_hash(hash)
8
+ rep = new
9
+ rep.identity_provider = hash['identityProvider']
10
+ rep.user_id = hash['userId']
11
+ rep.user_name = hash['userName']
12
+ rep
13
+ end
14
+ end
15
+ end
@@ -4,12 +4,12 @@ require_relative "camel_json"
4
4
  class Representation
5
5
  include ::KeycloakAdmin::CamelJson
6
6
 
7
- def as_json
7
+ def as_json(options=nil)
8
8
  Hash[instance_variables.map { |ivar| [ivar.to_s[1..-1], instance_variable_get(ivar)] }]
9
9
  end
10
10
 
11
11
  def to_json(options=nil)
12
- snaked_hash = as_json
12
+ snaked_hash = as_json(options)
13
13
  snaked_hash.keys.reduce({}) do |camelized_hash, key|
14
14
  camelized_hash[camelize(key, false)] = snaked_hash[key]
15
15
  camelized_hash
@@ -10,21 +10,23 @@ module KeycloakAdmin
10
10
  :email_verified,
11
11
  :first_name,
12
12
  :last_name,
13
- :credentials
13
+ :credentials,
14
+ :federated_identities
14
15
 
15
16
  def self.from_hash(hash)
16
- user = new
17
- user.id = hash["id"]
18
- user.created_timestamp = hash["createdTimestamp"]
19
- user.origin = hash["origin"]
20
- user.username = hash["username"]
21
- user.email = hash["email"]
22
- user.enabled = hash["enabled"]
23
- user.email_verified = hash["emailVerified"]
24
- user.first_name = hash["firstName"]
25
- user.last_name = hash["lastName"]
26
- user.attributes = hash["attributes"]
27
- user.credentials = hash["credentials"]&.map{ |hash| CredentialRepresentation.from_hash(hash) } || []
17
+ user = new
18
+ user.id = hash["id"]
19
+ user.created_timestamp = hash["createdTimestamp"]
20
+ user.origin = hash["origin"]
21
+ user.username = hash["username"]
22
+ user.email = hash["email"]
23
+ user.enabled = hash["enabled"]
24
+ user.email_verified = hash["emailVerified"]
25
+ user.first_name = hash["firstName"]
26
+ user.last_name = hash["lastName"]
27
+ user.attributes = hash["attributes"]
28
+ user.credentials = hash["credentials"]&.map{ |hash| CredentialRepresentation.from_hash(hash) } || []
29
+ user.federated_identities = hash["federatedIdentities"]&.map { |hash| FederatedIdentityRepresentation.from_hash(hash) } || []
28
30
  user
29
31
  end
30
32
 
@@ -32,5 +34,10 @@ module KeycloakAdmin
32
34
  @credentials ||= []
33
35
  @credentials.push(credential_representation)
34
36
  end
37
+
38
+ def add_federated_identity(federated_identity_representation)
39
+ @federated_identities ||= []
40
+ @federated_identities.push(federated_identity_representation)
41
+ end
35
42
  end
36
43
  end
@@ -1,3 +1,3 @@
1
1
  module KeycloakAdmin
2
- VERSION = "0.7.3"
2
+ VERSION = "0.7.8"
3
3
  end
@@ -72,6 +72,31 @@ RSpec.describe KeycloakAdmin::TokenClient do
72
72
  end
73
73
  end
74
74
 
75
+ describe "#execute_actions_email_url" do
76
+ let(:realm_name) { "valid-realm" }
77
+ let(:user_id) { nil }
78
+
79
+ before(:each) do
80
+ @client = KeycloakAdmin.realm(realm_name).users
81
+ end
82
+
83
+ context "when user_id is not defined" do
84
+ let(:user_id) { nil }
85
+ it "raises an error" do
86
+ expect {
87
+ @client.execute_actions_email_url(user_id)
88
+ }.to raise_error(ArgumentError)
89
+ end
90
+ end
91
+
92
+ context "when user_id is defined" do
93
+ let(:user_id) { 42 }
94
+ it "return a proper url" do
95
+ expect(@client.execute_actions_email_url(user_id)).to eq "http://auth.service.io/auth/admin/realms/valid-realm/users/42/execute-actions-email"
96
+ end
97
+ end
98
+ end
99
+
75
100
  describe "#impersonation_url" do
76
101
  let(:realm_name) { "valid-realm" }
77
102
  let(:user_id) { nil }
@@ -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":[]}'
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":[]}'
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.3
4
+ version: 0.7.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorent Lempereur
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-11 00:00:00.000000000 Z
11
+ date: 2020-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-cookie
@@ -103,6 +103,7 @@ files:
103
103
  - lib/keycloak-admin/representation/camel_json.rb
104
104
  - lib/keycloak-admin/representation/client_representation.rb
105
105
  - lib/keycloak-admin/representation/credential_representation.rb
106
+ - lib/keycloak-admin/representation/federated_identity_representation.rb
106
107
  - lib/keycloak-admin/representation/group_representation.rb
107
108
  - lib/keycloak-admin/representation/impersonation_redirection_representation.rb
108
109
  - lib/keycloak-admin/representation/impersonation_representation.rb
@@ -150,8 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
151
  - !ruby/object:Gem::Version
151
152
  version: '0'
152
153
  requirements: []
153
- rubyforge_project:
154
- rubygems_version: 2.6.4
154
+ rubygems_version: 3.0.3
155
155
  signing_key:
156
156
  specification_version: 4
157
157
  summary: Keycloak Admin REST API client written in Ruby