keycloak-admin 1.1.1 → 1.1.3

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