keycloak-admin 0.7.0 → 0.7.5

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