keycloak-admin 1.1.1 → 1.1.3

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/Dockerfile +24 -0
  3. data/.github/workflows/ci.yml +83 -0
  4. data/CHANGELOG.md +12 -2
  5. data/Gemfile.lock +8 -8
  6. data/README.md +277 -4
  7. data/lib/keycloak-admin/client/client_authz_permission_client.rb +81 -0
  8. data/lib/keycloak-admin/client/client_authz_policy_client.rb +76 -0
  9. data/lib/keycloak-admin/client/client_authz_resource_client.rb +93 -0
  10. data/lib/keycloak-admin/client/client_authz_scope_client.rb +71 -0
  11. data/lib/keycloak-admin/client/group_client.rb +41 -13
  12. data/lib/keycloak-admin/client/realm_client.rb +16 -0
  13. data/lib/keycloak-admin/client/role_client.rb +12 -10
  14. data/lib/keycloak-admin/client/user_client.rb +1 -0
  15. data/lib/keycloak-admin/representation/client_authz_permission_representation.rb +34 -0
  16. data/lib/keycloak-admin/representation/client_authz_policy_config_representation.rb +15 -0
  17. data/lib/keycloak-admin/representation/client_authz_policy_representation.rb +27 -0
  18. data/lib/keycloak-admin/representation/client_authz_resource_representation.rb +26 -0
  19. data/lib/keycloak-admin/representation/client_authz_scope_representation.rb +17 -0
  20. data/lib/keycloak-admin/representation/group_representation.rb +9 -5
  21. data/lib/keycloak-admin/version.rb +1 -1
  22. data/lib/keycloak-admin.rb +9 -0
  23. data/spec/client/client_authz_permission_client_spec.rb +170 -0
  24. data/spec/client/client_authz_policy_client_spec.rb +170 -0
  25. data/spec/client/client_authz_resource_client_spec.rb +150 -0
  26. data/spec/client/client_authz_scope_client_spec.rb +134 -0
  27. data/spec/client/client_client_spec.rb +2 -2
  28. data/spec/client/client_role_mappings_client_spec.rb +2 -2
  29. data/spec/client/group_client_spec.rb +137 -15
  30. data/spec/client/identity_provider_client_spec.rb +1 -1
  31. data/spec/client/realm_client_spec.rb +4 -4
  32. data/spec/client/role_client_spec.rb +12 -16
  33. data/spec/client/role_mapper_client_spec.rb +1 -1
  34. data/spec/client/token_client_spec.rb +1 -1
  35. data/spec/client/user_client_spec.rb +5 -5
  36. data/spec/configuration_spec.rb +1 -1
  37. data/spec/integration/client_authorization_spec.rb +95 -0
  38. data/spec/representation/client_authz_permission_representation_spec.rb +52 -0
  39. data/spec/representation/client_authz_policy_representation_spec.rb +47 -0
  40. data/spec/representation/client_authz_resource_representation_spec.rb +33 -0
  41. data/spec/representation/client_authz_scope_representation_spec.rb +19 -0
  42. data/spec/representation/group_representation_spec.rb +7 -0
  43. metadata +23 -3
@@ -0,0 +1,93 @@
1
+ module KeycloakAdmin
2
+ class ClientAuthzResourceClient < Client
3
+ def initialize(configuration, realm_client, client_id)
4
+ super(configuration)
5
+ raise ArgumentError.new("realm must be defined") unless realm_client.name_defined?
6
+ @realm_client = realm_client
7
+ @client_id = client_id
8
+ end
9
+
10
+ def list
11
+ response = execute_http do
12
+ RestClient::Resource.new(authz_resources_url(@client_id), @configuration.rest_client_options).get(headers)
13
+ end
14
+ JSON.parse(response).map { |role_as_hash| ClientAuthzResourceRepresentation.from_hash(role_as_hash) }
15
+ end
16
+
17
+ def get(resource_id)
18
+ response = execute_http do
19
+ RestClient::Resource.new(authz_resources_url(@client_id, resource_id), @configuration.rest_client_options).get(headers)
20
+ end
21
+ ClientAuthzResourceRepresentation.from_hash(JSON.parse(response))
22
+ end
23
+
24
+ def update(resource_id, client_authz_resource_representation)
25
+ raise "scope[:name] is mandatory and the only necessary attribute to add scope to resource" if client_authz_resource_representation[:scopes] && client_authz_resource_representation[:scopes].any?{|a| !a[:name]}
26
+
27
+ existing_resource = get(resource_id)
28
+ new_resource = build(
29
+ client_authz_resource_representation[:name] || existing_resource.name,
30
+ client_authz_resource_representation[:type] || existing_resource.type,
31
+ (client_authz_resource_representation[:uris] || [] ) + existing_resource.uris,
32
+ client_authz_resource_representation[:owner_managed_access] || existing_resource.owner_managed_access,
33
+ client_authz_resource_representation[:display_name] || existing_resource.display_name,
34
+ (client_authz_resource_representation[:scopes] || []) + existing_resource.scopes.map{|s| {name: s.name}},
35
+ client_authz_resource_representation[:attributes] || existing_resource.attributes
36
+ )
37
+
38
+ execute_http do
39
+ RestClient::Resource.new(authz_resources_url(@client_id, resource_id), @configuration.rest_client_options).put(new_resource.to_json, headers)
40
+ end
41
+ get(resource_id)
42
+ end
43
+
44
+ def create!(name, type, uris, owner_managed_access, display_name, scopes, attributes = {})
45
+ save(build(name, type, uris, owner_managed_access, display_name, scopes, attributes))
46
+ end
47
+
48
+ def find_by(name, type, uris, owner, scope)
49
+ response = execute_http do
50
+ url = "#{authz_resources_url(@client_id)}?name=#{name}&type=#{type}&uris=#{uris}&owner=#{owner}&scope=#{scope}&deep=true&first=0&max=100"
51
+ RestClient::Resource.new(url, @configuration.rest_client_options).get(headers)
52
+ end
53
+ JSON.parse(response).map { |role_as_hash| ClientAuthzResourceRepresentation.from_hash(role_as_hash) }
54
+ end
55
+
56
+ def save(client_authz_resource_representation)
57
+ response = execute_http do
58
+ RestClient::Resource.new(authz_resources_url(@client_id), @configuration.rest_client_options).post(client_authz_resource_representation.to_json, headers)
59
+ end
60
+ ClientAuthzResourceRepresentation.from_hash(JSON.parse(response))
61
+ end
62
+
63
+ def delete(resource_id)
64
+ execute_http do
65
+ RestClient::Resource.new(authz_resources_url(@client_id, resource_id), @configuration.rest_client_options).delete(headers)
66
+ end
67
+ true
68
+ end
69
+
70
+ def authz_resources_url(client_id, id = nil)
71
+ if id
72
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/resource/#{id}"
73
+ else
74
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/resource"
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def build(name, type, uris, owner_managed_access, display_name, scopes, attributes={})
81
+ resource = ClientAuthzResourceRepresentation.new
82
+ resource.name = name
83
+ resource.type = type
84
+ resource.uris = uris
85
+ resource.owner_managed_access = owner_managed_access
86
+ resource.display_name = display_name
87
+ resource.scopes = scopes
88
+ resource.attributes = attributes || {}
89
+ resource
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,71 @@
1
+ module KeycloakAdmin
2
+ class ClientAuthzScopeClient < Client
3
+ def initialize(configuration, realm_client, client_id, resource_id = nil)
4
+ super(configuration)
5
+ raise ArgumentError.new("realm must be defined") unless realm_client.name_defined?
6
+ @realm_client = realm_client
7
+ @client_id = client_id
8
+ @resource_id = resource_id
9
+ end
10
+
11
+ def create!(name, display_name, icon_uri)
12
+ response = save(build(name, display_name, icon_uri))
13
+ ClientAuthzScopeRepresentation.from_hash(JSON.parse(response))
14
+ end
15
+
16
+ def list
17
+ response = execute_http do
18
+ RestClient::Resource.new(authz_scopes_url(@client_id, @resource_id), @configuration.rest_client_options).get(headers)
19
+ end
20
+ JSON.parse(response).map { |role_as_hash| ClientAuthzScopeRepresentation.from_hash(role_as_hash) }
21
+ end
22
+
23
+ def delete(scope_id)
24
+ execute_http do
25
+ RestClient::Resource.new(authz_scopes_url(@client_id, nil, scope_id), @configuration.rest_client_options).delete(headers)
26
+ end
27
+ true
28
+ end
29
+
30
+ def get(scope_id)
31
+ response = execute_http do
32
+ RestClient::Resource.new(authz_scopes_url(@client_id, nil, scope_id), @configuration.rest_client_options).get(headers)
33
+ end
34
+ ClientAuthzScopeRepresentation.from_hash(JSON.parse(response))
35
+ end
36
+
37
+ def search(name)
38
+ url = "#{authz_scopes_url(@client_id)}?first=0&max=11&deep=false&name=#{name}"
39
+ response = execute_http do
40
+ RestClient::Resource.new(url, @configuration.rest_client_options).get(headers)
41
+ end
42
+ JSON.parse(response).map { |role_as_hash| ClientAuthzScopeRepresentation.from_hash(role_as_hash) }
43
+ end
44
+
45
+ def authz_scopes_url(client_id, resource_id = nil, id = nil)
46
+ if resource_id
47
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/resource/#{resource_id}/scopes"
48
+ elsif id
49
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/scope/#{id}"
50
+ else
51
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/scope"
52
+ end
53
+ end
54
+
55
+ def save(scope_representation)
56
+ execute_http do
57
+ RestClient::Resource.new(authz_scopes_url(@client_id), @configuration.rest_client_options).post(
58
+ create_payload(scope_representation), headers
59
+ )
60
+ end
61
+ end
62
+
63
+ def build(name, display_name, icon_uri)
64
+ scope = ClientAuthzScopeRepresentation.new
65
+ scope.name = name
66
+ scope.icon_uri = icon_uri
67
+ scope.display_name = display_name
68
+ scope
69
+ end
70
+ end
71
+ end
@@ -6,6 +6,21 @@ module KeycloakAdmin
6
6
  @realm_client = realm_client
7
7
  end
8
8
 
9
+ def get(group_id)
10
+ response = execute_http do
11
+ RestClient::Resource.new(groups_url(group_id), @configuration.rest_client_options).get(headers)
12
+ end
13
+ GroupRepresentation.from_hash(JSON.parse(response))
14
+ end
15
+
16
+ def children(parent_id)
17
+ response = execute_http do
18
+ url = "#{groups_url(parent_id)}/children"
19
+ RestClient::Resource.new(url, @configuration.rest_client_options).get(headers)
20
+ end
21
+ JSON.parse(response).map { |group_as_hash| GroupRepresentation.from_hash(group_as_hash) }
22
+ end
23
+
9
24
  def list
10
25
  search(nil)
11
26
  end
@@ -25,29 +40,39 @@ module KeycloakAdmin
25
40
  JSON.parse(response).map { |group_as_hash| GroupRepresentation.from_hash(group_as_hash) }
26
41
  end
27
42
 
28
- def create!(name, path = nil)
29
- response = save(build(name, path))
43
+ def create!(name, path = nil, attributes = {})
44
+ response = save(build(name, path, attributes))
30
45
  created_id(response)
31
46
  end
32
47
 
33
48
  def save(group_representation)
34
49
  execute_http do
35
- RestClient::Resource.new(groups_url, @configuration.rest_client_options).post(
36
- create_payload(group_representation), headers
37
- )
50
+ payload = create_payload(group_representation)
51
+ if group_representation.id
52
+ RestClient::Resource.new(groups_url(group_representation.id), @configuration.rest_client_options).put(payload, headers)
53
+ else
54
+ RestClient::Resource.new(groups_url, @configuration.rest_client_options).post(payload, headers)
55
+ end
38
56
  end
39
57
  end
40
58
 
41
- def create_subgroup!(parent_id, name)
59
+ def create_subgroup!(parent_id, name, attributes = {})
42
60
  url = "#{groups_url(parent_id)}/children"
43
61
  response = execute_http do
44
62
  RestClient::Resource.new(url, @configuration.rest_client_options).post(
45
- create_payload(build(name, nil)), headers
63
+ create_payload(build(name, nil, attributes)), headers
46
64
  )
47
65
  end
48
66
  created_id(response)
49
67
  end
50
-
68
+
69
+ def delete(group_id)
70
+ execute_http do
71
+ RestClient::Resource.new(groups_url(group_id), @configuration.rest_client_options).delete(headers)
72
+ end
73
+ true
74
+ end
75
+
51
76
  def members(group_id, first=0, max=100)
52
77
  url = "#{groups_url(group_id)}/members"
53
78
  query = {first: first.try(:to_i), max: max.try(:to_i)}.compact
@@ -93,11 +118,14 @@ module KeycloakAdmin
93
118
 
94
119
  private
95
120
 
96
- def build(name, path)
97
- group = GroupRepresentation.new
98
- group.name = name
99
- group.path = path
100
- group
121
+ def build(name, path, attributes)
122
+ GroupRepresentation.from_hash(
123
+ {
124
+ "name" => name,
125
+ "path" => path,
126
+ "attributes" => attributes
127
+ }
128
+ )
101
129
  end
102
130
  end
103
131
  end
@@ -99,6 +99,22 @@ module KeycloakAdmin
99
99
  UserResource.new(@configuration, self, user_id)
100
100
  end
101
101
 
102
+ def authz_scopes(client_id, resource_id = nil)
103
+ ClientAuthzScopeClient.new(@configuration, self, client_id, resource_id)
104
+ end
105
+
106
+ def authz_resources(client_id)
107
+ ClientAuthzResourceClient.new(@configuration, self, client_id)
108
+ end
109
+
110
+ def authz_permissions(client_id, type, resource_id = nil)
111
+ ClientAuthzPermissionClient.new(@configuration, self, client_id, type, resource_id)
112
+ end
113
+
114
+ def authz_policies(client_id, type)
115
+ ClientAuthzPolicyClient.new(@configuration, self, client_id, type)
116
+ end
117
+
102
118
  def name_defined?
103
119
  !@realm_name.nil?
104
120
  end
@@ -35,23 +35,25 @@ module KeycloakAdmin
35
35
 
36
36
  def save(role_representation)
37
37
  execute_http do
38
- RestClient::Resource.new(roles_url, @configuration.rest_client_options).post(
39
- create_payload(role_representation), headers
40
- )
38
+ payload = create_payload(role_representation)
39
+ if role_representation.id
40
+ RestClient::Resource.new(role_id_url(role_representation.id), @configuration.rest_client_options).put(payload, headers)
41
+ else
42
+ RestClient::Resource.new(roles_url, @configuration.rest_client_options).post(payload, headers)
43
+ end
41
44
  end
42
45
  end
43
46
 
44
- def roles_url(id=nil)
45
- if id
46
- "#{@realm_client.realm_admin_url}/roles/#{id}"
47
- else
48
- "#{@realm_client.realm_admin_url}/roles"
49
- end
47
+ def roles_url
48
+ "#{@realm_client.realm_admin_url}/roles"
49
+ end
50
+
51
+ def role_id_url(id)
52
+ "#{@realm_client.realm_admin_url}/roles-by-id/#{id}"
50
53
  end
51
54
 
52
55
  def role_name_url(name)
53
56
  "#{@realm_client.realm_admin_url}/roles/#{name}"
54
57
  end
55
-
56
58
  end
57
59
  end
@@ -20,6 +20,7 @@ module KeycloakAdmin
20
20
  user_representation
21
21
  end
22
22
 
23
+ # pay attention that, since Keycloak 24.0.4, partial updates of attributes are not authorized anymore
23
24
  def update(user_id, user_representation_body)
24
25
  raise ArgumentError.new("user_id must be defined") if user_id.nil?
25
26
  RestClient::Request.execute(
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ ## ### {"resources":["4f55e984-d1ec-405c-a25c-1387f88acd5c"],"policies":["e9e3bc49-fe11-4287-b6fc-fa8be4930ffa"],"name":"delme policy","description":"delme polidy ","decisionStrategy":"UNANIMOUS","resourceType":""}
5
+ #
6
+ module KeycloakAdmin
7
+ class ClientAuthzPermissionRepresentation < Representation
8
+ attr_accessor :id,
9
+ :name,
10
+ :description,
11
+ :decision_strategy,
12
+ :resource_type,
13
+ :resources,
14
+ :policies,
15
+ :scopes,
16
+ :logic,
17
+ :type
18
+
19
+ def self.from_hash(hash)
20
+ resource = new
21
+ resource.id = hash["id"]
22
+ resource.name = hash["name"]
23
+ resource.description = hash["description"]
24
+ resource.decision_strategy = hash["decisionStrategy"]
25
+ resource.resource_type = hash["resourceType"]
26
+ resource.resources = hash["resources"]
27
+ resource.policies = hash["policies"]
28
+ resource.scopes = hash["scopes"]
29
+ resource.logic = hash["logic"]
30
+ resource.type = hash["type"]
31
+ resource
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ module KeycloakAdmin
2
+ class ClientAuthzPolicyConfigRepresentation < Representation
3
+ attr_accessor :roles,
4
+ :code
5
+
6
+ def self.from_hash(hash)
7
+ resource = new
8
+ resource.code = hash["code"]
9
+ resource.roles = JSON.parse(hash["roles"] || '[]').map do |str|
10
+ RoleRepresentation.from_hash(str)
11
+ end
12
+ resource
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ module KeycloakAdmin
2
+ class ClientAuthzPolicyRepresentation < Representation
3
+ attr_accessor :id,
4
+ :name,
5
+ :description,
6
+ :type,
7
+ :logic,
8
+ :decision_strategy,
9
+ :config,
10
+ :fetch_roles,
11
+ :roles
12
+
13
+ def self.from_hash(hash)
14
+ resource = new
15
+ resource.id = hash["id"]
16
+ resource.name = hash["name"]
17
+ resource.description = hash["description"]
18
+ resource.type = hash["type"]
19
+ resource.logic = hash["logic"]
20
+ resource.decision_strategy = hash["decisionStrategy"]
21
+ resource.roles = hash["roles"]
22
+ resource.fetch_roles = hash["fetchRoles"]
23
+ resource.config = ClientAuthzPolicyConfigRepresentation.from_hash((hash["config"] || {}))
24
+ resource
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ module KeycloakAdmin
2
+
3
+ class ClientAuthzResourceRepresentation < Representation
4
+ attr_accessor :id,
5
+ :name,
6
+ :type,
7
+ :uris,
8
+ :owner_managed_access,
9
+ :display_name,
10
+ :attributes,
11
+ :scopes
12
+
13
+ def self.from_hash(hash)
14
+ resource = new
15
+ resource.id = hash["_id"]
16
+ resource.type = hash["type"]
17
+ resource.name = hash["name"]
18
+ resource.owner_managed_access = hash["ownerManagedAccess"]
19
+ resource.uris = hash["uris"]
20
+ resource.display_name = hash["displayName"]
21
+ resource.attributes = hash.fetch("attributes", {}).map { |k, v| [k.to_sym, Array(v)] }.to_h
22
+ resource.scopes = (hash["scopes"] || []).map { |scope_hash| ClientAuthzScopeRepresentation.from_hash(scope_hash) }
23
+ resource
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module KeycloakAdmin
2
+ class ClientAuthzScopeRepresentation < Representation
3
+ attr_accessor :id,
4
+ :name,
5
+ :icon_uri,
6
+ :display_name
7
+
8
+ def self.from_hash(hash)
9
+ scope = new
10
+ scope.id = hash["id"]
11
+ scope.name = hash["name"]
12
+ scope.icon_uri = hash["iconUri"]
13
+ scope.display_name = hash["displayName"]
14
+ scope
15
+ end
16
+ end
17
+ end
@@ -3,14 +3,18 @@ module KeycloakAdmin
3
3
  attr_accessor :id,
4
4
  :name,
5
5
  :path,
6
+ :attributes,
7
+ :sub_group_count,
6
8
  :sub_groups
7
9
 
8
10
  def self.from_hash(hash)
9
- group = new
10
- group.id = hash["id"]
11
- group.name = hash["name"]
12
- group.path = hash["path"]
13
- group.sub_groups = hash.fetch("subGroups", []).map { |sub_group_hash| self.from_hash(sub_group_hash) }
11
+ group = new
12
+ group.id = hash["id"]
13
+ group.name = hash["name"]
14
+ group.path = hash["path"]
15
+ group.attributes = hash.fetch("attributes", {}).map { |k, v| [k.to_sym, Array(v)] }.to_h
16
+ group.sub_group_count = hash["subGroupCount"]
17
+ group.sub_groups = hash.fetch("subGroups", []).map { |sub_group_hash| self.from_hash(sub_group_hash) }
14
18
  group
15
19
  end
16
20
  end
@@ -1,3 +1,3 @@
1
1
  module KeycloakAdmin
2
- VERSION = "1.1.1"
2
+ VERSION = "1.1.3"
3
3
  end
@@ -14,6 +14,10 @@ require_relative "keycloak-admin/client/user_client"
14
14
  require_relative "keycloak-admin/client/identity_provider_client"
15
15
  require_relative "keycloak-admin/client/configurable_token_client"
16
16
  require_relative "keycloak-admin/client/attack_detection_client"
17
+ require_relative "keycloak-admin/client/client_authz_scope_client"
18
+ require_relative "keycloak-admin/client/client_authz_resource_client"
19
+ require_relative "keycloak-admin/client/client_authz_policy_client"
20
+ require_relative "keycloak-admin/client/client_authz_permission_client"
17
21
  require_relative "keycloak-admin/representation/camel_json"
18
22
  require_relative "keycloak-admin/representation/representation"
19
23
  require_relative "keycloak-admin/representation/protocol_mapper_representation"
@@ -31,6 +35,11 @@ require_relative "keycloak-admin/representation/identity_provider_mapper_represe
31
35
  require_relative "keycloak-admin/representation/identity_provider_representation"
32
36
  require_relative "keycloak-admin/representation/attack_detection_representation"
33
37
  require_relative "keycloak-admin/representation/session_representation"
38
+ require_relative "keycloak-admin/representation/client_authz_scope_representation"
39
+ require_relative "keycloak-admin/representation/client_authz_resource_representation"
40
+ require_relative "keycloak-admin/representation/client_authz_policy_representation"
41
+ require_relative "keycloak-admin/representation/client_authz_policy_config_representation"
42
+ require_relative "keycloak-admin/representation/client_authz_permission_representation"
34
43
  require_relative "keycloak-admin/resource/base_role_containing_resource"
35
44
  require_relative "keycloak-admin/resource/group_resource"
36
45
  require_relative "keycloak-admin/resource/user_resource"