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.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -1
  3. data/CHANGELOG.md +70 -0
  4. data/Dockerfile +1 -1
  5. data/Gemfile.lock +7 -5
  6. data/README.md +149 -3
  7. data/lib/keycloak-admin.rb +13 -0
  8. data/lib/keycloak-admin/client/client.rb +11 -3
  9. data/lib/keycloak-admin/client/client_client.rb +24 -0
  10. data/lib/keycloak-admin/client/client_role_mappings_client.rb +32 -0
  11. data/lib/keycloak-admin/client/group_client.rb +46 -0
  12. data/lib/keycloak-admin/client/realm_client.rb +54 -0
  13. data/lib/keycloak-admin/client/role_client.rb +32 -0
  14. data/lib/keycloak-admin/client/role_mapper_client.rb +20 -0
  15. data/lib/keycloak-admin/client/user_client.rb +44 -2
  16. data/lib/keycloak-admin/representation/camel_json.rb +1 -1
  17. data/lib/keycloak-admin/representation/client_representation.rb +16 -0
  18. data/lib/keycloak-admin/representation/federated_identity_representation.rb +15 -0
  19. data/lib/keycloak-admin/representation/group_representation.rb +15 -0
  20. data/lib/keycloak-admin/representation/realm_representation.rb +14 -0
  21. data/lib/keycloak-admin/representation/representation.rb +5 -1
  22. data/lib/keycloak-admin/representation/role_representation.rb +17 -0
  23. data/lib/keycloak-admin/representation/user_representation.rb +21 -14
  24. data/lib/keycloak-admin/resource/base_role_containing_resource.rb +26 -0
  25. data/lib/keycloak-admin/resource/group_resource.rb +7 -0
  26. data/lib/keycloak-admin/resource/user_resource.rb +7 -0
  27. data/lib/keycloak-admin/version.rb +1 -1
  28. data/spec/client/client_client_spec.rb +53 -0
  29. data/spec/client/client_role_mappings_client_spec.rb +82 -0
  30. data/spec/client/group_client_spec.rb +125 -0
  31. data/spec/client/realm_client_spec.rb +108 -0
  32. data/spec/client/role_client_spec.rb +83 -0
  33. data/spec/client/role_mapper_client_spec.rb +47 -0
  34. data/spec/client/user_client_spec.rb +105 -14
  35. data/spec/representation/user_representation_spec.rb +15 -0
  36. data/spec/resource/group_resource_spec.rb +14 -0
  37. data/spec/resource/user_resource_spec.rb +14 -0
  38. data/spec/spec_helper.rb +7 -0
  39. 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.post(users_url, user_representation.to_json, headers)
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.get(users_url, headers.merge({params: { search: query }}))
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.first + camelize(lower_case_and_underscored_word)[1..-1]
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
- :created_at,
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 = new
17
- user.id = hash["id"]
18
- user.created_at = Time.at(hash["createdTimestamp"] / 1000).to_datetime
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
@@ -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,7 @@
1
+ module KeycloakAdmin
2
+ class GroupResource < BaseRoleContainingResource
3
+ def resources_name
4
+ "groups"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module KeycloakAdmin
2
+ class UserResource < BaseRoleContainingResource
3
+ def resources_name
4
+ "users"
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module KeycloakAdmin
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.5"
3
3
  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