keycloak-admin 0.7.0 → 0.7.5
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 +5 -5
- data/.gitignore +2 -1
- data/CHANGELOG.md +70 -0
- data/Dockerfile +1 -1
- data/Gemfile.lock +7 -5
- data/README.md +149 -3
- data/lib/keycloak-admin.rb +13 -0
- data/lib/keycloak-admin/client/client.rb +11 -3
- data/lib/keycloak-admin/client/client_client.rb +24 -0
- data/lib/keycloak-admin/client/client_role_mappings_client.rb +32 -0
- data/lib/keycloak-admin/client/group_client.rb +46 -0
- data/lib/keycloak-admin/client/realm_client.rb +54 -0
- data/lib/keycloak-admin/client/role_client.rb +32 -0
- data/lib/keycloak-admin/client/role_mapper_client.rb +20 -0
- data/lib/keycloak-admin/client/user_client.rb +44 -2
- data/lib/keycloak-admin/representation/camel_json.rb +1 -1
- data/lib/keycloak-admin/representation/client_representation.rb +16 -0
- data/lib/keycloak-admin/representation/federated_identity_representation.rb +15 -0
- data/lib/keycloak-admin/representation/group_representation.rb +15 -0
- data/lib/keycloak-admin/representation/realm_representation.rb +14 -0
- data/lib/keycloak-admin/representation/representation.rb +5 -1
- data/lib/keycloak-admin/representation/role_representation.rb +17 -0
- data/lib/keycloak-admin/representation/user_representation.rb +21 -14
- data/lib/keycloak-admin/resource/base_role_containing_resource.rb +26 -0
- data/lib/keycloak-admin/resource/group_resource.rb +7 -0
- data/lib/keycloak-admin/resource/user_resource.rb +7 -0
- data/lib/keycloak-admin/version.rb +1 -1
- data/spec/client/client_client_spec.rb +53 -0
- data/spec/client/client_role_mappings_client_spec.rb +82 -0
- data/spec/client/group_client_spec.rb +125 -0
- data/spec/client/realm_client_spec.rb +108 -0
- data/spec/client/role_client_spec.rb +83 -0
- data/spec/client/role_mapper_client_spec.rb +47 -0
- data/spec/client/user_client_spec.rb +105 -14
- data/spec/representation/user_representation_spec.rb +15 -0
- data/spec/resource/group_resource_spec.rb +14 -0
- data/spec/resource/user_resource_spec.rb +14 -0
- data/spec/spec_helper.rb +7 -0
- metadata +25 -4
@@ -5,6 +5,36 @@ module KeycloakAdmin
|
|
5
5
|
@realm_name = realm_name
|
6
6
|
end
|
7
7
|
|
8
|
+
def list
|
9
|
+
response = execute_http do
|
10
|
+
RestClient::Resource.new(realm_list_url, @configuration.rest_client_options).get(headers)
|
11
|
+
end
|
12
|
+
JSON.parse(response).map { |realm_as_hash| RealmRepresentation.from_hash(realm_as_hash) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def delete
|
16
|
+
execute_http do
|
17
|
+
RestClient::Resource.new(realm_admin_url, @configuration.rest_client_options).delete(headers)
|
18
|
+
end
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def save(realm_representation)
|
23
|
+
execute_http do
|
24
|
+
RestClient::Resource.new(realm_list_url, @configuration.rest_client_options).post(
|
25
|
+
realm_representation.to_json, headers
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def update(realm_representation_body)
|
31
|
+
execute_http do
|
32
|
+
RestClient::Resource.new(realm_admin_url, @configuration.rest_client_options).put(
|
33
|
+
realm_representation_body.to_json, headers
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
8
38
|
def realm_url
|
9
39
|
if @realm_name
|
10
40
|
"#{server_url}/realms/#{@realm_name}"
|
@@ -21,6 +51,10 @@ module KeycloakAdmin
|
|
21
51
|
end
|
22
52
|
end
|
23
53
|
|
54
|
+
def realm_list_url
|
55
|
+
"#{server_url}/admin/realms"
|
56
|
+
end
|
57
|
+
|
24
58
|
def token
|
25
59
|
TokenClient.new(@configuration, self)
|
26
60
|
end
|
@@ -29,10 +63,30 @@ module KeycloakAdmin
|
|
29
63
|
ConfigurableTokenClient.new(@configuration, self)
|
30
64
|
end
|
31
65
|
|
66
|
+
def clients
|
67
|
+
ClientClient.new(@configuration, self)
|
68
|
+
end
|
69
|
+
|
70
|
+
def groups
|
71
|
+
GroupClient.new(@configuration, self)
|
72
|
+
end
|
73
|
+
|
74
|
+
def group(group_id)
|
75
|
+
GroupResource.new(@configuration, self, group_id)
|
76
|
+
end
|
77
|
+
|
78
|
+
def roles
|
79
|
+
RoleClient.new(@configuration, self)
|
80
|
+
end
|
81
|
+
|
32
82
|
def users
|
33
83
|
UserClient.new(@configuration, self)
|
34
84
|
end
|
35
85
|
|
86
|
+
def user(user_id)
|
87
|
+
UserResource.new(@configuration, self, user_id)
|
88
|
+
end
|
89
|
+
|
36
90
|
def name_defined?
|
37
91
|
!@realm_name.nil?
|
38
92
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module KeycloakAdmin
|
2
|
+
class RoleClient < 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
|
+
def list
|
10
|
+
response = execute_http do
|
11
|
+
RestClient::Resource.new(roles_url, @configuration.rest_client_options).get(headers)
|
12
|
+
end
|
13
|
+
JSON.parse(response).map { |role_as_hash| RoleRepresentation.from_hash(role_as_hash) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def save(role_representation)
|
17
|
+
execute_http do
|
18
|
+
RestClient::Resource.new(roles_url, @configuration.rest_client_options).post(
|
19
|
+
role_representation.to_json, headers
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def roles_url(id=nil)
|
25
|
+
if id
|
26
|
+
"#{@realm_client.realm_admin_url}/roles/#{id}"
|
27
|
+
else
|
28
|
+
"#{@realm_client.realm_admin_url}/roles"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module KeycloakAdmin
|
2
|
+
class RoleMapperClient < Client
|
3
|
+
def initialize(configuration, user_resource)
|
4
|
+
super(configuration)
|
5
|
+
@user_resource = user_resource
|
6
|
+
end
|
7
|
+
|
8
|
+
def save_realm_level(role_representation_list)
|
9
|
+
execute_http do
|
10
|
+
RestClient::Resource.new(realm_level_url, @configuration.rest_client_options).post(
|
11
|
+
role_representation_list.to_json, headers
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def realm_level_url
|
17
|
+
"#{@user_resource.resource_url}/role-mappings/realm"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -13,7 +13,9 @@ module KeycloakAdmin
|
|
13
13
|
|
14
14
|
def save(user_representation)
|
15
15
|
execute_http do
|
16
|
-
RestClient.
|
16
|
+
RestClient::Resource.new(users_url, @configuration.rest_client_options).post(
|
17
|
+
user_representation.to_json, headers
|
18
|
+
)
|
17
19
|
end
|
18
20
|
user_representation
|
19
21
|
end
|
@@ -30,12 +32,17 @@ module KeycloakAdmin
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def search(query)
|
35
|
+
derived_headers = query ? headers.merge({params: { search: query }}) : headers
|
33
36
|
response = execute_http do
|
34
|
-
RestClient.
|
37
|
+
RestClient::Resource.new(users_url, @configuration.rest_client_options).get(derived_headers)
|
35
38
|
end
|
36
39
|
JSON.parse(response).map { |user_as_hash| UserRepresentation.from_hash(user_as_hash) }
|
37
40
|
end
|
38
41
|
|
42
|
+
def list
|
43
|
+
search(nil)
|
44
|
+
end
|
45
|
+
|
39
46
|
def delete(user_id)
|
40
47
|
execute_http do
|
41
48
|
RestClient::Resource.new(users_url(user_id), @configuration.rest_client_options).delete(headers)
|
@@ -43,6 +50,13 @@ module KeycloakAdmin
|
|
43
50
|
true
|
44
51
|
end
|
45
52
|
|
53
|
+
def groups(user_id)
|
54
|
+
response = execute_http do
|
55
|
+
RestClient::Resource.new(groups_url(user_id), @configuration.rest_client_options).get(headers)
|
56
|
+
end
|
57
|
+
JSON.parse(response).map { |group_as_hash| GroupRepresentation.from_hash(group_as_hash) }
|
58
|
+
end
|
59
|
+
|
46
60
|
def update_password(user_id, new_password)
|
47
61
|
execute_http do
|
48
62
|
RestClient.put(reset_password_url(user_id), {
|
@@ -66,6 +80,23 @@ module KeycloakAdmin
|
|
66
80
|
ImpersonationRedirectionRepresentation.from_url(impersonation_url(user_id), headers)
|
67
81
|
end
|
68
82
|
|
83
|
+
def link_idp(user_id, idp_id, idp_user_id, idp_username)
|
84
|
+
fed_id_rep = FederatedIdentityRepresentation.new
|
85
|
+
fed_id_rep.user_id = idp_user_id
|
86
|
+
fed_id_rep.user_name = idp_username
|
87
|
+
fed_id_rep.identity_provider = idp_id
|
88
|
+
|
89
|
+
execute_http do
|
90
|
+
RestClient.post(federated_identity_url(user_id, idp_id), fed_id_rep.to_json, headers)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def unlink_idp(user_id, idp_id)
|
95
|
+
execute_http do
|
96
|
+
RestClient::Resource.new(federated_identity_url(user_id, idp_id), @configuration.rest_client_options).delete(headers)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
69
100
|
def users_url(id=nil)
|
70
101
|
if id
|
71
102
|
"#{@realm_client.realm_admin_url}/users/#{id}"
|
@@ -79,11 +110,22 @@ module KeycloakAdmin
|
|
79
110
|
"#{users_url(user_id)}/reset-password"
|
80
111
|
end
|
81
112
|
|
113
|
+
def groups_url(user_id)
|
114
|
+
raise ArgumentError.new("user_id must be defined") if user_id.nil?
|
115
|
+
"#{users_url(user_id)}/groups"
|
116
|
+
end
|
117
|
+
|
82
118
|
def impersonation_url(user_id)
|
83
119
|
raise ArgumentError.new("user_id must be defined") if user_id.nil?
|
84
120
|
"#{users_url(user_id)}/impersonation"
|
85
121
|
end
|
86
122
|
|
123
|
+
def federated_identity_url(user_id, identity_provider)
|
124
|
+
raise ArgumentError.new("user_id must be defined") if user_id.nil?
|
125
|
+
raise ArgumentError.new("identity_provider must be defined") if identity_provider.nil?
|
126
|
+
"#{users_url(user_id)}/federated-identity/#{identity_provider}"
|
127
|
+
end
|
128
|
+
|
87
129
|
private
|
88
130
|
|
89
131
|
def build(username, email, password, email_verified, locale)
|
@@ -5,7 +5,7 @@ module KeycloakAdmin
|
|
5
5
|
if first_letter_in_uppercase
|
6
6
|
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
7
7
|
else
|
8
|
-
lower_case_and_underscored_word
|
8
|
+
lower_case_and_underscored_word[0] + camelize(lower_case_and_underscored_word)[1..-1]
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module KeycloakAdmin
|
2
|
+
class ClientRepresentation < Representation
|
3
|
+
attr_accessor :id,
|
4
|
+
:name,
|
5
|
+
:client_id
|
6
|
+
# TODO: Add more attributes
|
7
|
+
|
8
|
+
def self.from_hash(hash)
|
9
|
+
client = new
|
10
|
+
client.id = hash["id"]
|
11
|
+
client.name = hash["name"]
|
12
|
+
client.client_id = hash["clientId"]
|
13
|
+
client
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -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
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module KeycloakAdmin
|
2
|
+
class GroupRepresentation < Representation
|
3
|
+
attr_accessor :id,
|
4
|
+
:name,
|
5
|
+
:path
|
6
|
+
|
7
|
+
def self.from_hash(hash)
|
8
|
+
group = new
|
9
|
+
group.id = hash["id"]
|
10
|
+
group.name = hash["name"]
|
11
|
+
group.path = hash["path"]
|
12
|
+
group
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module KeycloakAdmin
|
2
|
+
class RealmRepresentation < Representation
|
3
|
+
attr_accessor :id,
|
4
|
+
:realm
|
5
|
+
# TODO: Add more attributes
|
6
|
+
|
7
|
+
def self.from_hash(hash)
|
8
|
+
realm = new
|
9
|
+
realm.id = hash["id"]
|
10
|
+
realm.realm = hash["realm"]
|
11
|
+
realm
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -4,8 +4,12 @@ require_relative "camel_json"
|
|
4
4
|
class Representation
|
5
5
|
include ::KeycloakAdmin::CamelJson
|
6
6
|
|
7
|
+
def as_json(options=nil)
|
8
|
+
Hash[instance_variables.map { |ivar| [ivar.to_s[1..-1], instance_variable_get(ivar)] }]
|
9
|
+
end
|
10
|
+
|
7
11
|
def to_json(options=nil)
|
8
|
-
snaked_hash = as_json
|
12
|
+
snaked_hash = as_json(options)
|
9
13
|
snaked_hash.keys.reduce({}) do |camelized_hash, key|
|
10
14
|
camelized_hash[camelize(key, false)] = snaked_hash[key]
|
11
15
|
camelized_hash
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module KeycloakAdmin
|
2
|
+
class RoleRepresentation < Representation
|
3
|
+
attr_accessor :id,
|
4
|
+
:name,
|
5
|
+
:composite,
|
6
|
+
:client_role
|
7
|
+
|
8
|
+
def self.from_hash(hash)
|
9
|
+
role = new
|
10
|
+
role.id = hash["id"]
|
11
|
+
role.name = hash["name"]
|
12
|
+
role.composite = hash["composite"]
|
13
|
+
role.client_role = hash["clientRole"]
|
14
|
+
role
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module KeycloakAdmin
|
2
2
|
class UserRepresentation < Representation
|
3
3
|
attr_accessor :id,
|
4
|
-
:
|
4
|
+
:created_timestamp,
|
5
5
|
:attributes,
|
6
6
|
:origin,
|
7
7
|
:username,
|
@@ -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
|
17
|
-
user.id
|
18
|
-
user.
|
19
|
-
user.origin
|
20
|
-
user.username
|
21
|
-
user.email
|
22
|
-
user.enabled
|
23
|
-
user.email_verified
|
24
|
-
user.first_name
|
25
|
-
user.last_name
|
26
|
-
user.attributes
|
27
|
-
user.credentials
|
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
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module KeycloakAdmin
|
2
|
+
class BaseRoleContainingResource
|
3
|
+
def initialize(configuration, realm_client, resource_id)
|
4
|
+
@configuration = configuration
|
5
|
+
raise ArgumentError.new("realm must be defined") unless realm_client.name_defined?
|
6
|
+
@realm_client = realm_client
|
7
|
+
@resource_id = resource_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def resources_name
|
11
|
+
raise NotImplementedError.new('must override in subclass')
|
12
|
+
end
|
13
|
+
|
14
|
+
def resource_url
|
15
|
+
"#{@realm_client.realm_admin_url}/#{resources_name}/#{@resource_id}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def client_role_mappings(client_id)
|
19
|
+
ClientRoleMappingsClient.new(@configuration, self, client_id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def role_mapper
|
23
|
+
RoleMapperClient.new(@configuration, self)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
RSpec.describe KeycloakAdmin::ClientClient do
|
2
|
+
describe "#clients_url" do
|
3
|
+
let(:realm_name) { "valid-realm" }
|
4
|
+
let(:client_id) { nil }
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@built_url = KeycloakAdmin.realm(realm_name).clients.clients_url(client_id)
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when client_id is not defined" do
|
11
|
+
let(:client_id) { nil }
|
12
|
+
it "return a proper url without client id" do
|
13
|
+
expect(@built_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/clients"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when client_id is defined" do
|
18
|
+
let(:client_id) { "95985b21-d884-4bbd-b852-cb8cd365afc2" }
|
19
|
+
it "return a proper url with the client id" do
|
20
|
+
expect(@built_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/clients/95985b21-d884-4bbd-b852-cb8cd365afc2"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#list" do
|
26
|
+
let(:realm_name) { "valid-realm" }
|
27
|
+
|
28
|
+
before(:each) do
|
29
|
+
@client_client = KeycloakAdmin.realm(realm_name).clients
|
30
|
+
|
31
|
+
stub_token_client
|
32
|
+
allow_any_instance_of(RestClient::Resource).to receive(:get).and_return '[{"id":"test_client_id","name":"test_client_name"}]'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "lists clients" do
|
36
|
+
clients = @client_client.list
|
37
|
+
expect(clients.length).to eq 1
|
38
|
+
expect(clients[0].name).to eq "test_client_name"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "passes rest client options" do
|
42
|
+
rest_client_options = {verify_ssl: OpenSSL::SSL::VERIFY_NONE}
|
43
|
+
allow_any_instance_of(KeycloakAdmin::Configuration).to receive(:rest_client_options).and_return rest_client_options
|
44
|
+
|
45
|
+
expect(RestClient::Resource).to receive(:new).with(
|
46
|
+
"http://auth.service.io/auth/admin/realms/valid-realm/clients", rest_client_options).and_call_original
|
47
|
+
|
48
|
+
clients = @client_client.list
|
49
|
+
expect(clients.length).to eq 1
|
50
|
+
expect(clients[0].name).to eq "test_client_name"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|