keycloak-admin 1.1.5 → 1.1.6
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 +17 -0
- data/Gemfile.lock +1 -1
- data/README.md +12 -1
- data/lib/keycloak-admin/client/organization_client.rb +245 -0
- data/lib/keycloak-admin/client/realm_client.rb +4 -0
- data/lib/keycloak-admin/representation/identity_provider_representation.rb +4 -0
- data/lib/keycloak-admin/representation/member_representation.rb +11 -0
- data/lib/keycloak-admin/representation/organization_domain_representation.rb +18 -0
- data/lib/keycloak-admin/representation/organization_representation.rb +30 -0
- data/lib/keycloak-admin/version.rb +1 -1
- data/lib/keycloak-admin.rb +4 -0
- data/spec/client/organization_client_spec.rb +595 -0
- data/spec/representation/organization_representation_spec.rb +64 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db97e606c0a299c2daff7be37a2df4a1c2a7261d2430b62dd196b8d29dc9117d
|
|
4
|
+
data.tar.gz: a1581e41e26b30d576273036ccf87abf463db0e4a80cd1f3a0961b87f874c373
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33416a0ad66f8f054a2192f560fbd72d6d68d1a29101521d8bfa2be5bd705888f46740d325ee265ef0e983bbd611901e7902db2a14f5693bd066c250692bb417
|
|
7
|
+
data.tar.gz: 6e17f0a84457d4bf8ce7332b756b0892fbe51a606a8895e5434fca458d9b936594006057820fb3e753d1713b5fdd895fe546f82f90a60fea948f3065e4905bed
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ 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.6] - 2026-01-05
|
|
9
|
+
|
|
10
|
+
* [Feature] Support for Organizations (Multi-tenancy):
|
|
11
|
+
* **Organization Management**:
|
|
12
|
+
* Supported operations: `create!`, `update`, `get`, `delete`, `list`, and `count`.
|
|
13
|
+
* Supported searching and filtering via `exact`, `query`, and `search` parameters.
|
|
14
|
+
* **Member Management**:
|
|
15
|
+
* Added ability to list organization members with pagination and filtering (`members`).
|
|
16
|
+
* Added `members_count` to retrieve the total number of members.
|
|
17
|
+
* Added `get_member`, `add_member` (by user ID), and `delete_member`.
|
|
18
|
+
* Added helper to find all organizations associated with a specific user: `associated_with_member`.
|
|
19
|
+
* **Invitations**:
|
|
20
|
+
* Added `invite_user`: Invites a new user via email/name.
|
|
21
|
+
* Added `invite_existing_user`: Invites an existing Keycloak user to the organization by ID.
|
|
22
|
+
* **Identity Provider (IdP) Linking**:
|
|
23
|
+
* Added methods to manage IdPs linked to an organization: `identity_providers`, `get_identity_provider`, `add_identity_provider`, and `delete_identity_provider`.
|
|
24
|
+
|
|
8
25
|
## [1.1.5] - 2026-01-05
|
|
9
26
|
|
|
10
27
|
* [Feature] Added the ability to list credentials for a given user.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ 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", "1.1.
|
|
15
|
+
gem "keycloak-admin", "1.1.6"
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Login
|
|
@@ -135,6 +135,11 @@ All options have a default value. However, all of them can be changed in your in
|
|
|
135
135
|
* Execute actions emails
|
|
136
136
|
* Send forgot passsword mail
|
|
137
137
|
* Client Authorization, create, update, get, delete Resource, Scope, Policy, Permission, Policy Enforcer
|
|
138
|
+
* Get list of organizations, create/update/get/delete an organization
|
|
139
|
+
* Get list of members of an organization, add/remove members
|
|
140
|
+
* Invite new or existing users to an organization
|
|
141
|
+
* List, add, and remove Identity Providers for an organization
|
|
142
|
+
* Get list of organizations associated with a specific user
|
|
138
143
|
|
|
139
144
|
### Get an access token
|
|
140
145
|
|
|
@@ -252,6 +257,12 @@ user_id = "95985b21-d884-4bbd-b852-cb8cd365afc2"
|
|
|
252
257
|
KeycloakAdmin.realm("a_realm").users.get_redirect_impersonation(user_id)
|
|
253
258
|
```
|
|
254
259
|
|
|
260
|
+
### List all the organizations of a realm
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
KeycloakAdmin.realm("a_realm").organizations.list
|
|
264
|
+
```
|
|
265
|
+
|
|
255
266
|
### Exchange a configurable token
|
|
256
267
|
|
|
257
268
|
*Requires your Keycloak server to have deployed the Custom REST API `configurable-token`* (https://github.com/looorent/keycloak-configurable-token-api)
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
module KeycloakAdmin
|
|
2
|
+
class OrganizationClient < Client
|
|
3
|
+
def initialize(configuration, realm_client)
|
|
4
|
+
super(configuration)
|
|
5
|
+
raise ArgumentError.new("realm must be defined") unless realm_client.name_defined?
|
|
6
|
+
@realm_client = realm_client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# This endpoint does not return members
|
|
10
|
+
def list(brief_representation=true, exact=nil, first=nil, max=nil, query=nil, search=nil)
|
|
11
|
+
response = execute_http do
|
|
12
|
+
RestClient::Resource.new(organizations_url_with_parameters(brief_representation, exact, first, max, query, search), @configuration.rest_client_options).get(headers)
|
|
13
|
+
end
|
|
14
|
+
JSON.parse(response).map { |organization_as_hash| OrganizationRepresentation.from_hash(organization_as_hash) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def count(exact=nil, query=nil, search=nil)
|
|
18
|
+
response = execute_http do
|
|
19
|
+
RestClient::Resource.new(count_url(exact, query, search), @configuration.rest_client_options).get(headers)
|
|
20
|
+
end
|
|
21
|
+
response.to_i
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def delete(organization_id)
|
|
25
|
+
execute_http do
|
|
26
|
+
RestClient::Resource.new(organization_url(organization_id), @configuration.rest_client_options).delete(headers)
|
|
27
|
+
end
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update(organization_representation)
|
|
32
|
+
execute_http do
|
|
33
|
+
RestClient::Resource.new(organization_url(organization_representation.id), @configuration.rest_client_options).put(
|
|
34
|
+
create_payload(organization_representation), headers
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
get(organization_representation.id)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create!(name, alias_name, enabled, description, redirect_url=nil, domains=[], attributes={})
|
|
42
|
+
save(build(name, alias_name, enabled, description, redirect_url, domains, attributes))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# This operation does not associate members and identity providers
|
|
46
|
+
def save(organization_representation)
|
|
47
|
+
execute_http do
|
|
48
|
+
RestClient::Resource.new(organizations_url, @configuration.rest_client_options).post(
|
|
49
|
+
create_payload(organization_representation), headers
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def get(organization_id)
|
|
56
|
+
response = execute_http do
|
|
57
|
+
RestClient::Resource.new(organization_url(organization_id), @configuration.rest_client_options).get(headers)
|
|
58
|
+
end
|
|
59
|
+
OrganizationRepresentation.from_hash(JSON.parse(response))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def identity_providers(organization_id)
|
|
63
|
+
response = execute_http do
|
|
64
|
+
RestClient::Resource.new(identity_providers_url(organization_id), @configuration.rest_client_options).get(headers)
|
|
65
|
+
end
|
|
66
|
+
JSON.parse(response).map { |idp_as_hash| IdentityProviderRepresentation.from_hash(idp_as_hash) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def get_identity_provider(organization_id, identity_provider_alias)
|
|
70
|
+
raise ArgumentError.new("identity_provider_alias must be defined") if identity_provider_alias.nil?
|
|
71
|
+
response = execute_http do
|
|
72
|
+
RestClient::Resource.new("#{identity_providers_url(organization_id)}/#{identity_provider_alias}", @configuration.rest_client_options).get(headers)
|
|
73
|
+
end
|
|
74
|
+
IdentityProviderRepresentation.from_hash(JSON.parse(response))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def add_identity_provider(organization_id, identity_provider_alias)
|
|
78
|
+
raise ArgumentError.new("identity_provider_alias must be defined") if identity_provider_alias.nil?
|
|
79
|
+
execute_http do
|
|
80
|
+
RestClient::Resource.new(identity_providers_url(organization_id), @configuration.rest_client_options).post(identity_provider_alias, headers)
|
|
81
|
+
end
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def delete_identity_provider(organization_id, identity_provider_alias)
|
|
86
|
+
execute_http do
|
|
87
|
+
RestClient::Resource.new(identity_provider_url(organization_id, identity_provider_alias), @configuration.rest_client_options).delete(headers)
|
|
88
|
+
end
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def members_count(organization_id)
|
|
93
|
+
response = execute_http do
|
|
94
|
+
RestClient::Resource.new(members_count_url(organization_id), @configuration.rest_client_options).get(headers)
|
|
95
|
+
end
|
|
96
|
+
response.to_i
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def members(organization_id, exact=nil, first=nil, max=nil, membership_type=nil, search=nil)
|
|
100
|
+
response = execute_http do
|
|
101
|
+
RestClient::Resource.new(members_url_with_query_parameters(organization_id, exact, first, max, membership_type, search), @configuration.rest_client_options).get(headers)
|
|
102
|
+
end
|
|
103
|
+
JSON.parse(response).map { |member_as_hash| MemberRepresentation.from_hash(member_as_hash) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def invite_existing_user(organization_id, user_id)
|
|
107
|
+
raise ArgumentError.new("user_id must be defined") if user_id.nil?
|
|
108
|
+
execute_http do
|
|
109
|
+
RestClient::Resource.new(invite_existing_user_url(organization_id), @configuration.rest_client_options).post({id: user_id}, headers.merge(content_type: "application/x-www-form-urlencoded"))
|
|
110
|
+
end
|
|
111
|
+
true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def invite_user(organization_id, email, first_name, last_name)
|
|
115
|
+
execute_http do
|
|
116
|
+
RestClient::Resource.new(invite_user_url(organization_id), @configuration.rest_client_options).post({
|
|
117
|
+
email: email,
|
|
118
|
+
firstName: first_name,
|
|
119
|
+
lastName: last_name
|
|
120
|
+
}, headers.merge(content_type: "application/x-www-form-urlencoded"))
|
|
121
|
+
end
|
|
122
|
+
true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def add_member(organization_id, user_id)
|
|
126
|
+
raise ArgumentError.new("user_id must be defined") if user_id.nil?
|
|
127
|
+
execute_http do
|
|
128
|
+
RestClient::Resource.new(members_url(organization_id), @configuration.rest_client_options).post(user_id, headers)
|
|
129
|
+
end
|
|
130
|
+
true
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def delete_member(organization_id, member_id)
|
|
134
|
+
execute_http do
|
|
135
|
+
RestClient::Resource.new(member_url(organization_id, member_id), @configuration.rest_client_options).delete(headers)
|
|
136
|
+
end
|
|
137
|
+
true
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def get_member(organization_id, member_id)
|
|
141
|
+
response = execute_http do
|
|
142
|
+
RestClient::Resource.new(member_url(organization_id, member_id), @configuration.rest_client_options).get(headers)
|
|
143
|
+
end
|
|
144
|
+
MemberRepresentation.from_hash(JSON.parse(response))
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def associated_with_member(member_id, brief_representation=true)
|
|
148
|
+
response = execute_http do
|
|
149
|
+
RestClient::Resource.new(associated_with_member_url(member_id, brief_representation), @configuration.rest_client_options).get(headers)
|
|
150
|
+
end
|
|
151
|
+
JSON.parse(response).map { |organization_as_hash| OrganizationRepresentation.from_hash(organization_as_hash) }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def organizations_url
|
|
155
|
+
"#{@realm_client.realm_admin_url}/organizations"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def organization_url(organization_id)
|
|
159
|
+
raise ArgumentError.new("organization_id must be defined") if organization_id.nil?
|
|
160
|
+
"#{organizations_url}/#{organization_id}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def identity_providers_url(organization_id)
|
|
164
|
+
"#{organization_url(organization_id)}/identity-providers"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def identity_provider_url(organization_id, identity_provider_alias)
|
|
168
|
+
raise ArgumentError.new("identity_provider_alias must be defined") if identity_provider_alias.nil?
|
|
169
|
+
"#{identity_providers_url(organization_id)}/#{identity_provider_alias}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def count_url(exact, query, search)
|
|
173
|
+
query_parameters = {exact: exact, q: query, search: search}.compact.to_a.map { |e| "#{e[0]}=#{e[1]}" }.join("&")
|
|
174
|
+
"#{organizations_url}/count?#{query_parameters}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def organizations_url_with_parameters(brief_representation, exact, first, max, query, search)
|
|
178
|
+
query_parameters = {
|
|
179
|
+
briefRepresentation: brief_representation,
|
|
180
|
+
exact: exact,
|
|
181
|
+
first: first,
|
|
182
|
+
max: max,
|
|
183
|
+
q: query,
|
|
184
|
+
search: search
|
|
185
|
+
}.compact.to_a.map { |e| "#{e[0]}=#{e[1]}" }.join("&")
|
|
186
|
+
"#{organizations_url}?#{query_parameters}"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def associated_with_member_url(member_id, brief_representation=true)
|
|
190
|
+
"#{organizations_url}/members/#{member_id}/organizations?briefRepresentation=#{brief_representation}"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def members_count_url(organization_id)
|
|
194
|
+
"#{organization_url(organization_id)}/members/count"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def member_url(organization_id, member_id)
|
|
198
|
+
raise ArgumentError.new("member_id must be defined") if member_id.nil?
|
|
199
|
+
"#{organization_url(organization_id)}/members/#{member_id}"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def invite_existing_user_url(organization_id)
|
|
203
|
+
"#{organization_url(organization_id)}/members/invite-existing-user"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def invite_user_url(organization_id)
|
|
207
|
+
"#{organization_url(organization_id)}/members/invite-user"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def members_url(organization_id)
|
|
211
|
+
"#{organization_url(organization_id)}/members"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def members_url_with_query_parameters(organization_id, exact, first, max, membership_type, search)
|
|
215
|
+
query_parameters = {
|
|
216
|
+
exact: exact,
|
|
217
|
+
first: first,
|
|
218
|
+
max: max,
|
|
219
|
+
membershipType: membership_type,
|
|
220
|
+
search: search
|
|
221
|
+
}.compact.to_a.map { |e| "#{e[0]}=#{e[1]}" }.join("&")
|
|
222
|
+
"#{organization_url(organization_id)}/members?#{query_parameters}"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def build(name, alias_name, enabled, description, redirect_url=nil, domains=[], attributes={})
|
|
226
|
+
unless domains.is_a?(Array)
|
|
227
|
+
raise ArgumentError.new("domains must be an Array, got #{new_domains.class}")
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
unless domains.all? { |domain| domain.is_a?(KeycloakAdmin::OrganizationDomainRepresentation) }
|
|
231
|
+
raise ArgumentError.new("All items in domains must be of type OrganizationDomainRepresentation")
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
organization = OrganizationRepresentation.new
|
|
235
|
+
organization.name = name
|
|
236
|
+
organization.alias = alias_name
|
|
237
|
+
organization.enabled = enabled
|
|
238
|
+
organization.description = description
|
|
239
|
+
organization.redirect_url = redirect_url
|
|
240
|
+
organization.domains = domains
|
|
241
|
+
organization.attributes = attributes
|
|
242
|
+
organization
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
@@ -11,6 +11,7 @@ module KeycloakAdmin
|
|
|
11
11
|
:add_read_token_role_on_create,
|
|
12
12
|
:authenticate_by_default,
|
|
13
13
|
:link_only,
|
|
14
|
+
:organization_id,
|
|
14
15
|
:first_broker_login_flow_alias,
|
|
15
16
|
:config
|
|
16
17
|
|
|
@@ -30,6 +31,7 @@ module KeycloakAdmin
|
|
|
30
31
|
hash["addReadTokenRoleOnCreate"],
|
|
31
32
|
hash["authenticateByDefault"],
|
|
32
33
|
hash["linkOnly"],
|
|
34
|
+
hash["organizationId"],
|
|
33
35
|
hash["firstBrokerLoginFlowAlias"],
|
|
34
36
|
hash["config"]
|
|
35
37
|
)
|
|
@@ -47,6 +49,7 @@ module KeycloakAdmin
|
|
|
47
49
|
add_read_token_role_on_create,
|
|
48
50
|
authenticate_by_default,
|
|
49
51
|
link_only,
|
|
52
|
+
organization_id,
|
|
50
53
|
first_broker_login_flow_alias,
|
|
51
54
|
config)
|
|
52
55
|
@alias = alias_name
|
|
@@ -60,6 +63,7 @@ module KeycloakAdmin
|
|
|
60
63
|
@add_read_token_role_on_create = add_read_token_role_on_create
|
|
61
64
|
@authenticate_by_default = authenticate_by_default
|
|
62
65
|
@link_only = link_only
|
|
66
|
+
@organization_id = organization_id
|
|
63
67
|
@first_broker_login_flow_alias = first_broker_login_flow_alias
|
|
64
68
|
@config = config || {}
|
|
65
69
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module KeycloakAdmin
|
|
2
|
+
class OrganizationDomainRepresentation < Representation
|
|
3
|
+
attr_accessor :name, :verified
|
|
4
|
+
|
|
5
|
+
def initialize(name, verified)
|
|
6
|
+
@name = name
|
|
7
|
+
@verified = verified
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.from_hash(hash)
|
|
11
|
+
new(hash["name"], hash["verified"])
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module KeycloakAdmin
|
|
2
|
+
class OrganizationRepresentation < Representation
|
|
3
|
+
attr_accessor :id,
|
|
4
|
+
:name,
|
|
5
|
+
:alias,
|
|
6
|
+
:enabled,
|
|
7
|
+
:description,
|
|
8
|
+
:redirect_url,
|
|
9
|
+
:attributes,
|
|
10
|
+
:domains,
|
|
11
|
+
:members,
|
|
12
|
+
:attributes,
|
|
13
|
+
:identity_providers
|
|
14
|
+
|
|
15
|
+
def self.from_hash(hash)
|
|
16
|
+
role = new
|
|
17
|
+
role.id = hash["id"]
|
|
18
|
+
role.name = hash["name"]
|
|
19
|
+
role.alias = hash["alias"]
|
|
20
|
+
role.enabled = hash["enabled"]
|
|
21
|
+
role.description = hash["description"]
|
|
22
|
+
role.redirect_url = hash["redirectUrl"]
|
|
23
|
+
role.attributes = hash["attributes"] || {}
|
|
24
|
+
role.domains = hash["domains"].nil? ? [] : hash["domains"].map { |domain| KeycloakAdmin::OrganizationDomainRepresentation.from_hash(domain) }
|
|
25
|
+
role.members = hash["members"].nil? ? [] : hash["members"].map { |member| KeycloakAdmin::MemberRepresentation.from_hash(member) }
|
|
26
|
+
role.identity_providers = hash["identityProviders"].nil? ? [] : hash["identityProviders"].map { |provider| KeycloakAdmin::IdentityProviderRepresentation.from_hash(provider) }
|
|
27
|
+
role
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/keycloak-admin.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "keycloak-admin/client/client_role_client"
|
|
|
7
7
|
require_relative "keycloak-admin/client/client_role_mappings_client"
|
|
8
8
|
require_relative "keycloak-admin/client/group_client"
|
|
9
9
|
require_relative "keycloak-admin/client/realm_client"
|
|
10
|
+
require_relative "keycloak-admin/client/organization_client"
|
|
10
11
|
require_relative "keycloak-admin/client/role_client"
|
|
11
12
|
require_relative "keycloak-admin/client/role_mapper_client"
|
|
12
13
|
require_relative "keycloak-admin/client/token_client"
|
|
@@ -27,10 +28,13 @@ require_relative "keycloak-admin/representation/token_representation"
|
|
|
27
28
|
require_relative "keycloak-admin/representation/impersonation_redirection_representation"
|
|
28
29
|
require_relative "keycloak-admin/representation/impersonation_representation"
|
|
29
30
|
require_relative "keycloak-admin/representation/credential_representation"
|
|
31
|
+
require_relative "keycloak-admin/representation/organization_domain_representation"
|
|
32
|
+
require_relative "keycloak-admin/representation/organization_representation"
|
|
30
33
|
require_relative "keycloak-admin/representation/realm_representation"
|
|
31
34
|
require_relative "keycloak-admin/representation/role_representation"
|
|
32
35
|
require_relative "keycloak-admin/representation/federated_identity_representation"
|
|
33
36
|
require_relative "keycloak-admin/representation/user_representation"
|
|
37
|
+
require_relative "keycloak-admin/representation/member_representation"
|
|
34
38
|
require_relative "keycloak-admin/representation/identity_provider_mapper_representation"
|
|
35
39
|
require_relative "keycloak-admin/representation/identity_provider_representation"
|
|
36
40
|
require_relative "keycloak-admin/representation/attack_detection_representation"
|
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
RSpec.describe KeycloakAdmin::OrganizationClient do
|
|
2
|
+
describe "#organization_url" do
|
|
3
|
+
let(:realm_name) { "valid-realm" }
|
|
4
|
+
let(:organization_id) { nil }
|
|
5
|
+
|
|
6
|
+
before(:each) do
|
|
7
|
+
@built_url = KeycloakAdmin.realm(realm_name).organizations.organization_url(organization_id)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
context "when organization_id is defined" do
|
|
11
|
+
let(:organization_id) { "95985b21-d884-4bbd-b852-cb8cd365afc2" }
|
|
12
|
+
it "return a proper url with the organization id" do
|
|
13
|
+
expect(@built_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/organizations/95985b21-d884-4bbd-b852-cb8cd365afc2"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "#list" do
|
|
19
|
+
let(:realm_name) { "valid-realm" }
|
|
20
|
+
|
|
21
|
+
before(:each) do
|
|
22
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
23
|
+
stub_token_client
|
|
24
|
+
json_payload = <<-'payload'
|
|
25
|
+
[
|
|
26
|
+
{
|
|
27
|
+
"id": "8f6e474e-e688-4bec-99ba-5dc862594f4b",
|
|
28
|
+
"name": "My organization",
|
|
29
|
+
"alias": "myorg",
|
|
30
|
+
"enabled": true,
|
|
31
|
+
"description": "A single organization",
|
|
32
|
+
"domains": [
|
|
33
|
+
{
|
|
34
|
+
"name": "hello.com",
|
|
35
|
+
"verified": false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "gmail.com",
|
|
39
|
+
"verified": true
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
payload
|
|
45
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_payload
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "lists organizations" do
|
|
49
|
+
organizations = @organization_client.list
|
|
50
|
+
expect(organizations.length).to eq 1
|
|
51
|
+
expect(organizations[0].id).to eq "8f6e474e-e688-4bec-99ba-5dc862594f4b"
|
|
52
|
+
expect(organizations[0].name).to eq "My organization"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "passes rest client options" do
|
|
56
|
+
rest_client_options = {timeout: 10}
|
|
57
|
+
allow_any_instance_of(KeycloakAdmin::Configuration).to receive(:rest_client_options).and_return rest_client_options
|
|
58
|
+
|
|
59
|
+
expect(RestClient::Resource).to receive(:new).with(
|
|
60
|
+
"http://auth.service.io/auth/admin/realms/valid-realm/organizations?briefRepresentation=true", rest_client_options).and_call_original
|
|
61
|
+
|
|
62
|
+
organizations = @organization_client.list
|
|
63
|
+
expect(organizations.length).to eq 1
|
|
64
|
+
expect(organizations[0]).to be_a KeycloakAdmin::OrganizationRepresentation
|
|
65
|
+
expect(organizations[0].id).to eq "8f6e474e-e688-4bec-99ba-5dc862594f4b"
|
|
66
|
+
expect(organizations[0].name).to eq "My organization"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "#count" do
|
|
71
|
+
let(:realm_name) { "valid-realm" }
|
|
72
|
+
|
|
73
|
+
before(:each) do
|
|
74
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
75
|
+
stub_token_client
|
|
76
|
+
json_payload = "2"
|
|
77
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_payload
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "count organizations" do
|
|
81
|
+
count = @organization_client.count(false, nil, "test")
|
|
82
|
+
expect(count).to eq 2
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context "when building the count url" do
|
|
86
|
+
let(:exact) { nil }
|
|
87
|
+
let(:query) { nil }
|
|
88
|
+
let(:search) { nil }
|
|
89
|
+
|
|
90
|
+
before(:each) do
|
|
91
|
+
@count_url = @organization_client.count_url(exact, query, search)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context "with everything null" do
|
|
95
|
+
it "return a proper url" do
|
|
96
|
+
expect(@count_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/organizations/count?"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context "with exact=false" do
|
|
101
|
+
let(:exact) { false }
|
|
102
|
+
it "return a proper url" do
|
|
103
|
+
expect(@count_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/organizations/count?exact=false"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context "with exact=true" do
|
|
108
|
+
let(:exact) { true }
|
|
109
|
+
it "return a proper url" do
|
|
110
|
+
expect(@count_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/organizations/count?exact=true"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
context "with query=test" do
|
|
115
|
+
let(:query) { "test" }
|
|
116
|
+
it "return a proper url" do
|
|
117
|
+
expect(@count_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/organizations/count?q=test"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
context "with search=nameoforg" do
|
|
122
|
+
let(:search) { "nameoforg" }
|
|
123
|
+
it "return a proper url" do
|
|
124
|
+
expect(@count_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/organizations/count?search=nameoforg"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
context "with every argument setup" do
|
|
129
|
+
let(:exact) { true }
|
|
130
|
+
let(:query) { "a query" }
|
|
131
|
+
let(:search) { "a name" }
|
|
132
|
+
it "return a proper url" do
|
|
133
|
+
expect(@count_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/organizations/count?exact=true&q=a query&search=a name"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe "#associated_with_member" do
|
|
140
|
+
let(:realm_name) { "valid-realm" }
|
|
141
|
+
|
|
142
|
+
before(:each) do
|
|
143
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
144
|
+
stub_token_client
|
|
145
|
+
json_payload = <<-'payload'
|
|
146
|
+
[
|
|
147
|
+
{
|
|
148
|
+
"id": "8f6e474e-e688-4bec-99ba-5dc862594f4b",
|
|
149
|
+
"name": "My organization",
|
|
150
|
+
"alias": "myorg",
|
|
151
|
+
"enabled": true,
|
|
152
|
+
"description": "A single organization",
|
|
153
|
+
"domains": [
|
|
154
|
+
{
|
|
155
|
+
"name": "hello.com",
|
|
156
|
+
"verified": false
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"name": "gmail.com",
|
|
160
|
+
"verified": true
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
payload
|
|
166
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_payload
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it "list organizations of members organizations" do
|
|
170
|
+
organizations = @organization_client.associated_with_member("648ebe7f-e4ba-4d82-a87d-c585c866d0e7")
|
|
171
|
+
expect(organizations.size).to eq 1
|
|
172
|
+
expect(organizations[0]).to be_a KeycloakAdmin::OrganizationRepresentation
|
|
173
|
+
expect(organizations[0].id).to eq "8f6e474e-e688-4bec-99ba-5dc862594f4b"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe "#delete" do
|
|
178
|
+
let(:realm_name) { "valid-realm" }
|
|
179
|
+
|
|
180
|
+
before(:each) do
|
|
181
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
182
|
+
stub_token_client
|
|
183
|
+
allow_any_instance_of(RestClient::Resource).to receive(:delete).and_return ""
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "deletes an organization" do
|
|
187
|
+
result = @organization_client.delete("2904e1a1-e5f4-4143-8725-003e54cc8b58")
|
|
188
|
+
expect(result).to be(true)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it "raises a delete error" do
|
|
192
|
+
rest_client_options = {timeout: 10}
|
|
193
|
+
allow_any_instance_of(KeycloakAdmin::Configuration).to receive(:rest_client_options).and_return rest_client_options
|
|
194
|
+
|
|
195
|
+
expect(RestClient::Resource).to receive(:new).with(
|
|
196
|
+
"http://auth.service.io/auth/admin/realms/valid-realm/organizations/2904e1a1-e5f4-4143-8725-003e54cc8b58", rest_client_options).and_raise("error")
|
|
197
|
+
|
|
198
|
+
expect { @organization_client.delete("2904e1a1-e5f4-4143-8725-003e54cc8b58") }.to raise_error("error")
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
describe "#identity_providers" do
|
|
203
|
+
let(:realm_name) { "valid-realm" }
|
|
204
|
+
|
|
205
|
+
before(:each) do
|
|
206
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
207
|
+
stub_token_client
|
|
208
|
+
json_payload = <<-'payload'
|
|
209
|
+
[
|
|
210
|
+
{
|
|
211
|
+
"alias": "google",
|
|
212
|
+
"displayName": "",
|
|
213
|
+
"internalId": "59b28b03-07db-4281-b637-4040368df082",
|
|
214
|
+
"providerId": "google",
|
|
215
|
+
"enabled": true,
|
|
216
|
+
"updateProfileFirstLoginMode": "on",
|
|
217
|
+
"trustEmail": false,
|
|
218
|
+
"storeToken": false,
|
|
219
|
+
"addReadTokenRoleOnCreate": false,
|
|
220
|
+
"authenticateByDefault": false,
|
|
221
|
+
"linkOnly": false,
|
|
222
|
+
"hideOnLogin": true,
|
|
223
|
+
"organizationId": "8f6e474e-e688-4bec-99ba-5dc862594f4b",
|
|
224
|
+
"config": {
|
|
225
|
+
"syncMode": "LEGACY",
|
|
226
|
+
"clientSecret": "**********",
|
|
227
|
+
"clientId": "test",
|
|
228
|
+
"kc.org.broker.redirect.mode.email-matches": "false"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
payload
|
|
233
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_payload
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it "get identity providers" do
|
|
237
|
+
identity_providers = @organization_client.identity_providers("8f6e474e-e688-4bec-99ba-5dc862594f4b")
|
|
238
|
+
expect(identity_providers.size).to eq 1
|
|
239
|
+
expect(identity_providers[0]).to be_a KeycloakAdmin::IdentityProviderRepresentation
|
|
240
|
+
expect(identity_providers[0].alias).to eq "google"
|
|
241
|
+
expect(identity_providers[0].organization_id).to eq "8f6e474e-e688-4bec-99ba-5dc862594f4b"
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
describe "#add_identity_provider" do
|
|
246
|
+
let(:realm_name) { "valid-realm" }
|
|
247
|
+
|
|
248
|
+
before(:each) do
|
|
249
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
250
|
+
stub_token_client
|
|
251
|
+
allow_any_instance_of(RestClient::Resource).to receive(:post).and_return ""
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it "adds one identity provider" do
|
|
255
|
+
@organization_client.add_identity_provider("8f6e474e-e688-4bec-99ba-5dc862594f4b", "google")
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
describe "#get_identity_provider" do
|
|
260
|
+
let(:realm_name) { "valid-realm" }
|
|
261
|
+
|
|
262
|
+
before(:each) do
|
|
263
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
264
|
+
json_payload = <<-'payload'
|
|
265
|
+
{
|
|
266
|
+
"alias": "google",
|
|
267
|
+
"displayName": "",
|
|
268
|
+
"internalId": "59b28b03-07db-4281-b637-4040368df082",
|
|
269
|
+
"providerId": "google",
|
|
270
|
+
"enabled": true,
|
|
271
|
+
"updateProfileFirstLoginMode": "on",
|
|
272
|
+
"trustEmail": false,
|
|
273
|
+
"storeToken": false,
|
|
274
|
+
"addReadTokenRoleOnCreate": false,
|
|
275
|
+
"authenticateByDefault": false,
|
|
276
|
+
"linkOnly": false,
|
|
277
|
+
"hideOnLogin": true,
|
|
278
|
+
"organizationId": "8f6e474e-e688-4bec-99ba-5dc862594f4b",
|
|
279
|
+
"config": {
|
|
280
|
+
"syncMode": "LEGACY",
|
|
281
|
+
"clientSecret": "**********",
|
|
282
|
+
"clientId": "test",
|
|
283
|
+
"kc.org.broker.redirect.mode.email-matches": "false"
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
payload
|
|
287
|
+
stub_token_client
|
|
288
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_payload
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
it "get identity provider" do
|
|
292
|
+
idp = @organization_client.get_identity_provider("8f6e474e-e688-4bec-99ba-5dc862594f4b", "google")
|
|
293
|
+
expect(idp).to be_a KeycloakAdmin::IdentityProviderRepresentation
|
|
294
|
+
expect(idp.alias).to eq "google"
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
describe "#get" do
|
|
299
|
+
let(:realm_name) { "valid-realm" }
|
|
300
|
+
|
|
301
|
+
before(:each) do
|
|
302
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
303
|
+
stub_token_client
|
|
304
|
+
json_payload = <<-'payload'
|
|
305
|
+
{
|
|
306
|
+
"id": "8f6e474e-e688-4bec-99ba-5dc862594f4b",
|
|
307
|
+
"name": "My organization",
|
|
308
|
+
"alias": "myorg",
|
|
309
|
+
"enabled": true,
|
|
310
|
+
"description": "A single organization",
|
|
311
|
+
"domains": [
|
|
312
|
+
{
|
|
313
|
+
"name": "hello.com",
|
|
314
|
+
"verified": false
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
"name": "gmail.com",
|
|
318
|
+
"verified": true
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
}
|
|
322
|
+
payload
|
|
323
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_payload
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
it "get organization" do
|
|
327
|
+
organization = @organization_client.get("8f6e474e-e688-4bec-99ba-5dc862594f4b")
|
|
328
|
+
expect(organization).to be
|
|
329
|
+
expect(organization).to be_a KeycloakAdmin::OrganizationRepresentation
|
|
330
|
+
expect(organization.id).to eq "8f6e474e-e688-4bec-99ba-5dc862594f4b"
|
|
331
|
+
expect(organization.name).to eq "My organization"
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
it "passes rest client options" do
|
|
335
|
+
rest_client_options = {timeout: 10}
|
|
336
|
+
allow_any_instance_of(KeycloakAdmin::Configuration).to receive(:rest_client_options).and_return rest_client_options
|
|
337
|
+
|
|
338
|
+
expect(RestClient::Resource).to receive(:new).with(
|
|
339
|
+
"http://auth.service.io/auth/admin/realms/valid-realm/organizations/8f6e474e-e688-4bec-99ba-5dc862594f4b", rest_client_options).and_call_original
|
|
340
|
+
|
|
341
|
+
organization = @organization_client.get("8f6e474e-e688-4bec-99ba-5dc862594f4b")
|
|
342
|
+
expect(organization).to be
|
|
343
|
+
expect(organization).to be_a KeycloakAdmin::OrganizationRepresentation
|
|
344
|
+
expect(organization.id).to eq "8f6e474e-e688-4bec-99ba-5dc862594f4b"
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
describe "#members_count" do
|
|
349
|
+
let(:realm_name) { "valid-realm" }
|
|
350
|
+
|
|
351
|
+
before(:each) do
|
|
352
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
353
|
+
stub_token_client
|
|
354
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return "2"
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it "get count of members" do
|
|
358
|
+
count = @organization_client.members_count("8f6e474e-e688-4bec-99ba-5dc862594f4b")
|
|
359
|
+
expect(count).to eq 2
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
describe "#members" do
|
|
364
|
+
let(:realm_name) { "valid-realm" }
|
|
365
|
+
|
|
366
|
+
before(:each) do
|
|
367
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
368
|
+
json_payload = <<-'payload'
|
|
369
|
+
[
|
|
370
|
+
{
|
|
371
|
+
"id": "648ebe7f-e4ba-4d82-a87d-c585c866d0e7",
|
|
372
|
+
"username": "admin",
|
|
373
|
+
"emailVerified": false,
|
|
374
|
+
"attributes": {
|
|
375
|
+
"is_temporary_admin": [
|
|
376
|
+
"true"
|
|
377
|
+
]
|
|
378
|
+
},
|
|
379
|
+
"enabled": true,
|
|
380
|
+
"createdTimestamp": 1767600090489,
|
|
381
|
+
"totp": false,
|
|
382
|
+
"disableableCredentialTypes": [],
|
|
383
|
+
"requiredActions": [],
|
|
384
|
+
"notBefore": 0,
|
|
385
|
+
"membershipType": "UNMANAGED"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"id": "2167481a-6a08-44f3-aa9a-42e33afa6834",
|
|
389
|
+
"username": "client",
|
|
390
|
+
"emailVerified": true,
|
|
391
|
+
"enabled": true,
|
|
392
|
+
"createdTimestamp": 1767601626861,
|
|
393
|
+
"totp": false,
|
|
394
|
+
"disableableCredentialTypes": [],
|
|
395
|
+
"requiredActions": [],
|
|
396
|
+
"notBefore": 0,
|
|
397
|
+
"membershipType": "MANAGED"
|
|
398
|
+
}
|
|
399
|
+
]
|
|
400
|
+
payload
|
|
401
|
+
stub_token_client
|
|
402
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_payload
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
it "get members" do
|
|
406
|
+
members = @organization_client.members("8f6e474e-e688-4bec-99ba-5dc862594f4b")
|
|
407
|
+
expect(members.size).to eq 2
|
|
408
|
+
expect(members[0]).to be_a KeycloakAdmin::MemberRepresentation
|
|
409
|
+
expect(members[0].id).to eq "648ebe7f-e4ba-4d82-a87d-c585c866d0e7"
|
|
410
|
+
expect(members[0].membership_type).to eq "UNMANAGED"
|
|
411
|
+
expect(members[1]).to be_a KeycloakAdmin::MemberRepresentation
|
|
412
|
+
expect(members[1].id).to eq "2167481a-6a08-44f3-aa9a-42e33afa6834"
|
|
413
|
+
expect(members[1].membership_type).to eq "MANAGED"
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
describe "#invite_existing_user" do
|
|
418
|
+
let(:realm_name) { "valid-realm" }
|
|
419
|
+
|
|
420
|
+
before(:each) do
|
|
421
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
422
|
+
stub_token_client
|
|
423
|
+
allow_any_instance_of(RestClient::Resource).to receive(:post).and_return ""
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
it "invites an existing user" do
|
|
427
|
+
@organization_client.invite_existing_user("8f6e474e-e688-4bec-99ba-5dc862594f4b", "9a2ff47d-759c-4126-a281-8e4a7c6465e4")
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
describe "#invite_user" do
|
|
432
|
+
let(:realm_name) { "valid-realm" }
|
|
433
|
+
|
|
434
|
+
before(:each) do
|
|
435
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
436
|
+
stub_token_client
|
|
437
|
+
allow_any_instance_of(RestClient::Resource).to receive(:post).and_return ""
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
it "invites an existing user" do
|
|
441
|
+
@organization_client.invite_user("8f6e474e-e688-4bec-99ba-5dc862594f4b", "hello@acme.com", "John", "Doe")
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
describe "#delete_member" do
|
|
446
|
+
let(:realm_name) { "valid-realm" }
|
|
447
|
+
|
|
448
|
+
before(:each) do
|
|
449
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
450
|
+
stub_token_client
|
|
451
|
+
allow_any_instance_of(RestClient::Resource).to receive(:delete).and_return ""
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
it "deletes a member" do
|
|
455
|
+
result = @organization_client.delete_member("8f6e474e-e688-4bec-99ba-5dc862594f4b", "e226d9d3-868e-453b-9bfc-d9d9cc534526")
|
|
456
|
+
expect(result).to be(true)
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
describe "#get_member" do
|
|
461
|
+
let(:realm_name) { "valid-realm" }
|
|
462
|
+
|
|
463
|
+
before(:each) do
|
|
464
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
465
|
+
json_payload = <<-'payload'
|
|
466
|
+
{
|
|
467
|
+
"id": "9a2ff47d-759c-4126-a281-8e4a7c6465e4",
|
|
468
|
+
"username": "hello",
|
|
469
|
+
"email": "hello@gmail.com",
|
|
470
|
+
"emailVerified": true,
|
|
471
|
+
"enabled": true,
|
|
472
|
+
"createdTimestamp": 1767608088024,
|
|
473
|
+
"totp": false,
|
|
474
|
+
"disableableCredentialTypes": [],
|
|
475
|
+
"requiredActions": [],
|
|
476
|
+
"notBefore": 0,
|
|
477
|
+
"membershipType": "UNMANAGED"
|
|
478
|
+
}
|
|
479
|
+
payload
|
|
480
|
+
stub_token_client
|
|
481
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_payload
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
it "gets a member" do
|
|
485
|
+
member = @organization_client.get_member("8f6e474e-e688-4bec-99ba-5dc862594f4b", "9a2ff47d-759c-4126-a281-8e4a7c6465e4")
|
|
486
|
+
expect(member).to be
|
|
487
|
+
expect(member).to be_a KeycloakAdmin::MemberRepresentation
|
|
488
|
+
expect(member.id).to eq "9a2ff47d-759c-4126-a281-8e4a7c6465e4"
|
|
489
|
+
expect(member.membership_type).to eq "UNMANAGED"
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
describe "#add_member" do
|
|
494
|
+
let(:realm_name) { "valid-realm" }
|
|
495
|
+
|
|
496
|
+
before(:each) do
|
|
497
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
498
|
+
stub_token_client
|
|
499
|
+
allow_any_instance_of(RestClient::Resource).to receive(:post).and_return ""
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
it "creates a member from an existing user" do
|
|
503
|
+
result = @organization_client.add_member("8f6e474e-e688-4bec-99ba-5dc862594f4b", "9a2ff47d-759c-4126-a281-8e4a7c6465e4")
|
|
504
|
+
expect(result).to be true
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
describe "#add_member" do
|
|
509
|
+
let(:realm_name) { "valid-realm" }
|
|
510
|
+
|
|
511
|
+
before(:each) do
|
|
512
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
513
|
+
stub_token_client
|
|
514
|
+
allow_any_instance_of(RestClient::Resource).to receive(:post).and_return ""
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
it "creates a member from an existing user" do
|
|
518
|
+
result = @organization_client.add_member("8f6e474e-e688-4bec-99ba-5dc862594f4b", "9a2ff47d-759c-4126-a281-8e4a7c6465e4")
|
|
519
|
+
expect(result).to be true
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
describe "#update" do
|
|
524
|
+
let(:realm_name) { "valid-realm" }
|
|
525
|
+
let(:json_payload) do
|
|
526
|
+
<<-'payload'
|
|
527
|
+
{
|
|
528
|
+
"id": "8f6e474e-e688-4bec-99ba-5dc862594f4b",
|
|
529
|
+
"name": "Company",
|
|
530
|
+
"alias": "company",
|
|
531
|
+
"enabled": true,
|
|
532
|
+
"description": "A test organization",
|
|
533
|
+
"redirectUrl": "https://myapp.acme.com/redirect",
|
|
534
|
+
"attributes": {
|
|
535
|
+
"hello": [
|
|
536
|
+
"yes"
|
|
537
|
+
]
|
|
538
|
+
},
|
|
539
|
+
"domains": [
|
|
540
|
+
{
|
|
541
|
+
"name": "hello.com",
|
|
542
|
+
"verified": false
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
"name": "help.com",
|
|
546
|
+
"verified": false
|
|
547
|
+
}
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
payload
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
before(:each) do
|
|
554
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
555
|
+
stub_token_client
|
|
556
|
+
allow_any_instance_of(RestClient::Resource).to receive(:put).and_return ""
|
|
557
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_payload
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
it "updates an organization" do
|
|
561
|
+
organization = KeycloakAdmin::OrganizationRepresentation.from_json(json_payload)
|
|
562
|
+
updated_organization = @organization_client.update(organization)
|
|
563
|
+
expect(updated_organization).to be
|
|
564
|
+
expect(updated_organization).to be_a KeycloakAdmin::OrganizationRepresentation
|
|
565
|
+
expect(updated_organization.id).to eq "8f6e474e-e688-4bec-99ba-5dc862594f4b"
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
describe "#create!" do
|
|
570
|
+
let(:realm_name) { "valid-realm" }
|
|
571
|
+
before(:each) do
|
|
572
|
+
@organization_client = KeycloakAdmin.realm(realm_name).organizations
|
|
573
|
+
stub_token_client
|
|
574
|
+
allow_any_instance_of(RestClient::Resource).to receive(:post).and_return ""
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
it "creates an organization" do
|
|
578
|
+
@organization_client.create!(
|
|
579
|
+
"new name",
|
|
580
|
+
"alias_name",
|
|
581
|
+
"enabled",
|
|
582
|
+
"description",
|
|
583
|
+
"http://redirect_url",
|
|
584
|
+
[
|
|
585
|
+
KeycloakAdmin::OrganizationDomainRepresentation.new("acme.com", true),
|
|
586
|
+
KeycloakAdmin::OrganizationDomainRepresentation.new("doe.com", false)
|
|
587
|
+
],
|
|
588
|
+
{
|
|
589
|
+
"advanced": ["yes", "no"],
|
|
590
|
+
"hello": ["maybe"]
|
|
591
|
+
}
|
|
592
|
+
)
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
RSpec.describe KeycloakAdmin::OrganizationRepresentation do
|
|
3
|
+
describe ".from_json" do
|
|
4
|
+
it "parse a single organization" do
|
|
5
|
+
json_payload = <<-'payload'
|
|
6
|
+
{
|
|
7
|
+
"id": "8f6e474e-e688-4bec-99ba-5dc862594f4b",
|
|
8
|
+
"name": "My organization",
|
|
9
|
+
"alias": "myorg",
|
|
10
|
+
"enabled": true,
|
|
11
|
+
"description": "A single organization",
|
|
12
|
+
"redirectUrl": "https://myapp.acme.com",
|
|
13
|
+
"attributes": {
|
|
14
|
+
"advanced": [
|
|
15
|
+
"yes"
|
|
16
|
+
],
|
|
17
|
+
"days": [
|
|
18
|
+
"monday",
|
|
19
|
+
"friday"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"domains": [
|
|
23
|
+
{
|
|
24
|
+
"name": "hello.com",
|
|
25
|
+
"verified": false
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "gmail.com",
|
|
29
|
+
"verified": true
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
payload
|
|
34
|
+
|
|
35
|
+
organization = described_class.from_json(json_payload)
|
|
36
|
+
expect(organization).to be
|
|
37
|
+
expect(organization).to be_a described_class
|
|
38
|
+
expect(organization.id).to eq "8f6e474e-e688-4bec-99ba-5dc862594f4b"
|
|
39
|
+
expect(organization.name).to eq "My organization"
|
|
40
|
+
expect(organization.alias).to eq "myorg"
|
|
41
|
+
expect(organization.description).to eq "A single organization"
|
|
42
|
+
expect(organization.redirect_url).to eq "https://myapp.acme.com"
|
|
43
|
+
expect(organization.enabled).to be true
|
|
44
|
+
|
|
45
|
+
expect(organization.domains.size).to eq 2
|
|
46
|
+
expect(organization.domains[0]).to be_a KeycloakAdmin::OrganizationDomainRepresentation
|
|
47
|
+
expect(organization.domains[0].name).to eq "hello.com"
|
|
48
|
+
expect(organization.domains[0].verified).to be false
|
|
49
|
+
expect(organization.domains[1]).to be_a KeycloakAdmin::OrganizationDomainRepresentation
|
|
50
|
+
expect(organization.domains[1].name).to eq "gmail.com"
|
|
51
|
+
expect(organization.domains[1].verified).to be true
|
|
52
|
+
|
|
53
|
+
expect(organization.attributes.size).to eq 2
|
|
54
|
+
expect(organization.attributes["advanced"].size).to eq 1
|
|
55
|
+
expect(organization.attributes["advanced"][0]).to eq "yes"
|
|
56
|
+
expect(organization.attributes["days"].size).to eq 2
|
|
57
|
+
expect(organization.attributes["days"][0]).to eq "monday"
|
|
58
|
+
expect(organization.attributes["days"][1]).to eq "friday"
|
|
59
|
+
|
|
60
|
+
expect(organization.members.size).to eq 0
|
|
61
|
+
expect(organization.identity_providers.size).to eq 0
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
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: 1.1.
|
|
4
|
+
version: 1.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lorent Lempereur
|
|
@@ -104,6 +104,7 @@ files:
|
|
|
104
104
|
- lib/keycloak-admin/client/configurable_token_client.rb
|
|
105
105
|
- lib/keycloak-admin/client/group_client.rb
|
|
106
106
|
- lib/keycloak-admin/client/identity_provider_client.rb
|
|
107
|
+
- lib/keycloak-admin/client/organization_client.rb
|
|
107
108
|
- lib/keycloak-admin/client/realm_client.rb
|
|
108
109
|
- lib/keycloak-admin/client/role_client.rb
|
|
109
110
|
- lib/keycloak-admin/client/role_mapper_client.rb
|
|
@@ -125,6 +126,9 @@ files:
|
|
|
125
126
|
- lib/keycloak-admin/representation/identity_provider_representation.rb
|
|
126
127
|
- lib/keycloak-admin/representation/impersonation_redirection_representation.rb
|
|
127
128
|
- lib/keycloak-admin/representation/impersonation_representation.rb
|
|
129
|
+
- lib/keycloak-admin/representation/member_representation.rb
|
|
130
|
+
- lib/keycloak-admin/representation/organization_domain_representation.rb
|
|
131
|
+
- lib/keycloak-admin/representation/organization_representation.rb
|
|
128
132
|
- lib/keycloak-admin/representation/protocol_mapper_representation.rb
|
|
129
133
|
- lib/keycloak-admin/representation/realm_representation.rb
|
|
130
134
|
- lib/keycloak-admin/representation/representation.rb
|
|
@@ -147,6 +151,7 @@ files:
|
|
|
147
151
|
- spec/client/configurable_token_client_spec.rb
|
|
148
152
|
- spec/client/group_client_spec.rb
|
|
149
153
|
- spec/client/identity_provider_client_spec.rb
|
|
154
|
+
- spec/client/organization_client_spec.rb
|
|
150
155
|
- spec/client/realm_client_spec.rb
|
|
151
156
|
- spec/client/role_client_spec.rb
|
|
152
157
|
- spec/client/role_mapper_client_spec.rb
|
|
@@ -165,6 +170,7 @@ files:
|
|
|
165
170
|
- spec/representation/identity_provider_mapper_representation_spec.rb
|
|
166
171
|
- spec/representation/identity_provider_representation_spec.rb
|
|
167
172
|
- spec/representation/impersonation_representation_spec.rb
|
|
173
|
+
- spec/representation/organization_representation_spec.rb
|
|
168
174
|
- spec/representation/protocol_mapper_representation_spec.rb
|
|
169
175
|
- spec/representation/role_representation_spec.rb
|
|
170
176
|
- spec/representation/session_representation_spec.rb
|