clonk 1.0.0alpha3 → 1.0.0alpha4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0c722bf99f97acd973f4cf92eeaa6823b92abc6cdd99cf77f7e32cac22915e7
4
- data.tar.gz: d0af13e57c50e1baca63d908dfee9d3c1a5dbec49b890899fdb7451262b3af36
3
+ metadata.gz: 6f7aa0b12c57241a41d20426c06220f03c74247ae3d685c25a1062052ed2297a
4
+ data.tar.gz: 16fe259d548ca99777ca99442894377c2ef99bce191ce069af0c507afd85df78
5
5
  SHA512:
6
- metadata.gz: 392a00ab4d0805bc945b4f0cc45901bc98f51dbf79e2a068812e711e106943496dddba917b7cc13b5f1f4ce211926c36969515c47e51deb2107a02ac28e48adb
7
- data.tar.gz: edea7bd7824a35e0e7161a543cff8fb773288c7513c6152b2379c3ee2b17e1bdef181149d1007073b48ae9f131473e37488f7ac1ead6a9d8f8a75c7e1caab63b
6
+ metadata.gz: 794fd86cc27312327b5b4f42739d151d29dc3c489d28b215c28cca805502e35f0a87ed64447bf358e1791a5327e45fa4e8de814574206bc869d0cc1b542b67e6
7
+ data.tar.gz: 7a0643e05af7c708f404194d310ab6301848fa1054f8cee66b6344bbd5613ca6015dfc9145cc9dc13bd2bbbabc0bc07c83920c77444adc80b1a1bf0639d1fc52
@@ -0,0 +1,8 @@
1
+ lib/clonk.rb 20e9972dadb66fb1771722d9b06e3ccdad9e1022
2
+ lib/clonk/role.rb 56ea266896bcb2b45cc48ee633c83639d2135296
3
+ lib/clonk/user.rb a7da95bf320c5aaa7cc7708d9fe2ffd10f4eda23
4
+ lib/clonk/group.rb 29edd3c17b7babce87bb544333d42455ec1a5c2e
5
+ lib/clonk/realm.rb 0f933b0fd76545c049a5cb228706f13117fbcbb3
6
+ lib/clonk/client.rb 41af98a3b30b4adff27b5d62c6b26559335b369f
7
+ lib/clonk/policy.rb 8b8840f6763a1a320759a3c9d052758f25b17463
8
+ lib/clonk/permission.rb 4466f7c1d3bef61521df03ee0b92c85f7be6cb66
File without changes
Binary file
Binary file
Binary file
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ clonk (1.0.0alpha3)
5
+ faraday
6
+ faraday_middleware
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ faraday (0.15.3)
12
+ multipart-post (>= 1.2, < 3)
13
+ faraday_middleware (0.12.2)
14
+ faraday (>= 0.7.4, < 1.0)
15
+ multipart-post (2.0.0)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ clonk!
22
+
23
+ BUNDLED WITH
24
+ 1.16.2
@@ -0,0 +1,59 @@
1
+ # Clonk [![Gem Version](https://badge.fury.io/rb/clonk.svg)](https://badge.fury.io/rb/clonk)
2
+
3
+ This is a gem that'll help you seed an instance of Red Hat SSO. Right now, it's still a work-in-progress, but it shouldn't be long before it's got everything I think it needs.
4
+
5
+ It makes some assumptions about what's in your environment at the moment, using `dotenv`. While developing, I'm keeping the following variables in there.
6
+
7
+ ```
8
+ SSO_REALM="master"
9
+ SSO_BASE_URL="http://localhost:8080/auth"
10
+ SSO_USERNAME="user"
11
+ SSO_PASSWORD="password"
12
+ ```
13
+
14
+ It's recommended to spin up an SSO instance alongside this to see what effects you're having on it. Here, it's important that the `preview` profile is used, so that we can use the `realm-management` client to take care of permissions in the realm.
15
+
16
+ ```
17
+ docker run --rm -p 8080:8080 -e JAVA_OPTS_APPEND="-Dkeycloak.profile=preview" -e SSO_ADMIN_USERNAME=user -e SSO_ADMIN_PASSWORD=password registry.access.redhat.com/redhat-sso-7/sso72-openshift
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ Clonk exposes SSO objects as ActiveRecord-esque models. As a short demonstration:
23
+
24
+ ```
25
+ 2.5.1 :003 > Clonk::Group.all
26
+ => [#<Clonk::Group:0x00007fe7139ecda0 @name="another-test", @id="42b28060-7f4f-4b6d-82fd-6af031881a9e", @realm="master">, #<Clonk::Group:0x00007fe713808228 @name="chaos-chaos", @id="05af3f68-44d6-4973-8834-e957822e43ef", @realm="master">]
27
+ 2.5.1 :004 > Clonk::Group.all.first.config
28
+ => {"id"=>"42b28060-7f4f-4b6d-82fd-6af031881a9e", "name"=>"another-test", "path"=>"/another-test", "attributes"=>{}, "realmRoles"=>[], "clientRoles"=>{}, "subGroups"=>[], "access"=>{"view"=>true, "manage"=>true, "manageMembership"=>true}}
29
+ ```
30
+
31
+ `Group.all` casts all groups in the realm to `Clonk::Group` objects...
32
+
33
+ ...but you can access their plain JSON config with `Group#config`.
34
+
35
+ Right now, you can check the `lib` folder for details of which methods are exposed, but I'm looking to add documentation in the very near future. Think of this as a soft launch.
36
+
37
+ ## Why?
38
+
39
+ When developing against Red Hat SSO and Keycloak, I've personally struggled to deal with the documentation. There are a lot of assumptions that are made about what you know and what you don't. So I've made this gem...
40
+
41
+ ### ...to help devs use SSO platforms with their Rails apps
42
+
43
+ Sometimes, Devise just doesn't cut it, especially if you want to allow users to sign into multiple apps with the same credentials. Running a SSO instance that all your apps can call off to can make things more flexible!
44
+
45
+ ### ...to document API endpoints that are documented either confusingly or not at all
46
+
47
+ SSO is a huge project, so it's sort of understandable that perhaps its documentation isn't too easy to understand at first glance...especially if you've never used an SSO backend before. Some API endpoints that'll be used in this gem aren't even documented.
48
+
49
+ ### ...to better integrate Ruby/Rails with SSO
50
+
51
+ I guess this comes back to the first one. SSO integration with Rails apps could be really powerful. Especially if it's wrapped in that familiar ActiveRecord style.
52
+
53
+ ### ...to make seeding SSO a lot more readable in future
54
+
55
+ I seeded SSO with more `curl` requests than you could shake a stick at. Suffice to say, it didn't look like the prettiest piece of code...
56
+
57
+ ### ...to transfer what I've learned
58
+
59
+ This gem goes hand-in-hand with a blog post I'm writing, but I'm hoping it'll go further than just that tutorial. Fingers crossed this gem will have its tendrils deep in a lot of SSO instances out there...which sounds a little ominous, but...yeah, you get the gist!
@@ -0,0 +1,15 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = %q{clonk}
6
+ s.version = "1.0.0alpha4"
7
+ s.authors = ["Simon Fish"]
8
+ s.date = %q{2018-11-19}
9
+ s.summary = %q{Keycloak/RHSSO admin API client}
10
+ s.files = `git ls-files`.split("\n")
11
+ s.require_paths = Dir.glob("lib/**/*")
12
+ s.add_runtime_dependency 'faraday'
13
+ s.add_runtime_dependency 'faraday_middleware'
14
+ s.metadata["yard.run"] = "yardoc"
15
+ end
@@ -0,0 +1,162 @@
1
+ module Clonk
2
+
3
+ ##
4
+ # This class represents a client within SSO. A client allows a user to authenticate against SSO with their credentials.
5
+
6
+ class Client
7
+ attr_accessor :id
8
+ attr_reader :name
9
+
10
+ def initialize(clients_response, realm)
11
+ @id = clients_response['id']
12
+ @name = clients_response['clientId']
13
+ @realm = realm
14
+ end
15
+
16
+ ##
17
+ # Returns the defaults for some fields. The rest should be defined on a relevant call.
18
+
19
+ def self.defaults
20
+ {
21
+ fullScopeAllowed: false
22
+ }
23
+ end
24
+
25
+ ##
26
+ # Creates a client within SSO and returns the created client as a Client.
27
+ # Note: 'dag' stands for Direct Access Grants
28
+
29
+ def self.create(realm: REALM, name: nil, public_client: true, dag_enabled: true)
30
+ # TODO: Client with a secret
31
+ response = Clonk.response(
32
+ protocol: :post,
33
+ path: "#{Clonk.realm_admin_root(realm)}/clients",
34
+ data: defaults.merge(
35
+ clientId: name,
36
+ publicClient: public_client,
37
+ directAccessGrantsEnabled: dag_enabled
38
+ )
39
+ )
40
+ new_client_id = response.headers[:location].split('/')[-1]
41
+ new_from_id(new_client_id, realm)
42
+ end
43
+
44
+ ##
45
+ # Gets the config inside SSO for a particular client.
46
+ # This allows for access to many attributes that Clonk might not directly
47
+ # handle yet.
48
+ # You may be able to extend Clonk's functionality by using Clonk.response
49
+ # or Clonk.parsed_response with routes in the SSO API alongside this data.
50
+
51
+ def self.get_config(id, realm = REALM)
52
+ Clonk.parsed_response(
53
+ path: "#{Clonk.realm_admin_root(realm)}/clients/#{id}"
54
+ )
55
+ end
56
+
57
+ ##
58
+ # Gets the config inside SSO for this client.
59
+
60
+ def config
61
+ self.class.get_config(@id, @realm)
62
+ end
63
+
64
+ ##
65
+ # Creates a new Client instance from a client that exists in SSO
66
+
67
+ def self.new_from_id(id, realm = REALM)
68
+ new(get_config(id, realm), realm)
69
+ end
70
+
71
+ ##
72
+ # Returns an array of the clients that exist in the given realm.
73
+
74
+ def self.all(realm: REALM)
75
+ Clonk.parsed_response(
76
+ path: "#{Clonk.realm_admin_root(realm)}/clients"
77
+ ).map { |client| new_from_id(client['id'], realm) }
78
+ end
79
+
80
+ ##
81
+ # Searches for clients with the given name in the given realm.
82
+
83
+ def self.where(name: nil, realm: REALM)
84
+ all(realm: realm).select { |client| client.name == name }
85
+ end
86
+
87
+ ##
88
+ # Searches for exactly one client with the given name in the given realm.
89
+
90
+ def self.find_by(name: nil, realm: REALM)
91
+ where(name: name, realm: realm)&.first
92
+ end
93
+
94
+ ##
95
+ # Returns the URL that can be used to fetch this client's data from the API.
96
+
97
+ def url
98
+ "#{Clonk.realm_admin_root(@realm)}/clients/#{@id}"
99
+ end
100
+
101
+ ##
102
+ # Maps the given role into the scope of the client. If a user has that role,
103
+ # it will be visible in tokens given by this client during authentication.
104
+
105
+ def map_scope(client: nil, role: nil, realm: REALM)
106
+ Clonk.parsed_response(
107
+ protocol: :post,
108
+ data: [role.config],
109
+ path: "#{url}/scope-mappings/clients/#{client.id}"
110
+ )
111
+ end
112
+
113
+ ##
114
+ # Creates a role within this client.
115
+ # it will be visible in tokens given by this client during authentication,
116
+ # as it is already in scope.
117
+
118
+ def create_role(realm: REALM, name: nil, description: nil, scope_param_required: false)
119
+ # TODO: Create realm roles
120
+ Clonk.parsed_response(protocol: :post,
121
+ path: "#{url}/roles",
122
+ data: {
123
+ name: name,
124
+ description: description,
125
+ scopeParamRequired: scope_param_required
126
+ })
127
+ end
128
+
129
+ ##
130
+ # Lists the client's permission IDs, if permissions are enabled.
131
+ # These will be returned as either a boolean (false) if disabled,
132
+ # or a hash of permission types and IDs.
133
+
134
+ def permissions
135
+ Clonk.parsed_response(
136
+ path: "#{url}/management/permissions"
137
+ )['scopePermissions'] || false
138
+ end
139
+
140
+ ##
141
+ # Enables or disables permissions for a client
142
+
143
+ def set_permissions(enabled: true)
144
+ Clonk.parsed_response(
145
+ protocol: :put,
146
+ path: "#{url}/management/permissions",
147
+ data: {
148
+ enabled: enabled
149
+ }
150
+ )
151
+ end
152
+
153
+ ##
154
+ # Returns the client's secret
155
+
156
+ def secret
157
+ Clonk.parsed_response(
158
+ path: "#{url}/client-secret"
159
+ )['value']
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clonk
4
+ class Group
5
+
6
+ attr_accessor :id
7
+ attr_reader :name
8
+
9
+ def initialize(group_response, realm = REALM)
10
+ @name = group_response['name']
11
+ @id = group_response['id']
12
+ @realm = realm
13
+ end
14
+
15
+ # Gets config inside SSO for group with ID in realm
16
+ def self.get_config(id, realm = REALM)
17
+ Clonk.parsed_response(
18
+ path: "#{Clonk.realm_admin_root(realm)}/groups/#{id}"
19
+ )
20
+ end
21
+
22
+ def config
23
+ self.class.get_config(@id, @realm)
24
+ end
25
+
26
+ # Creates a new Group instance from a group that exists in SSO
27
+ def self.new_from_id(id, realm = REALM)
28
+ new(get_config(id, realm), realm)
29
+ end
30
+
31
+ # Creates a new group in SSO and casts it to an instance
32
+ def self.create(name: nil, realm: REALM)
33
+ new_group = new({ 'name' => name }, realm)
34
+ response = new_group.save(realm)
35
+ new_group.id = response.headers[:location].split('/')[-1]
36
+ new_group
37
+ end
38
+
39
+ def save(realm = REALM)
40
+ if @id
41
+ Clonk.parsed_response(
42
+ protocol: :put,
43
+ path: "#{Clonk.realm_admin_root(@realm)}/groups/#{@id}",
44
+ data: config.merge('name' => @name)
45
+ )
46
+ else
47
+ Clonk.response(
48
+ protocol: :post,
49
+ path: "#{Clonk.realm_admin_root(realm)}/groups",
50
+ data: { name: @name }
51
+ )
52
+ end
53
+ end
54
+
55
+ def create_subgroup(name: nil)
56
+ response = Clonk.parsed_response(
57
+ protocol: :post,
58
+ path: "#{url}/children",
59
+ data: { name: name }
60
+ )
61
+ self.class.new_from_id(response['id'], @realm)
62
+ end
63
+
64
+ def subgroups
65
+ config["subGroups"].map { |group| self.class.new_from_id(group['id'], @realm) }
66
+ end
67
+
68
+ def self.all(realm: REALM, flattened: false)
69
+ response = Clonk.parsed_response(
70
+ protocol: :get,
71
+ path: "#{Clonk.realm_admin_root(realm)}/groups",
72
+ )
73
+ response += response.map { |group| group['subGroups'] } if flattened
74
+ response.flatten
75
+ .map { |group| new_from_id(group['id'], realm) }
76
+ end
77
+
78
+ def self.where(name: nil, realm: REALM)
79
+ all(flattened: true, realm: realm)
80
+ .select { |group| group['name'] == name }
81
+ .map { |group| new_from_id(group['id'], realm) }
82
+ end
83
+
84
+ def self.find_by(name: nil, realm: REALM)
85
+ where(name: name, realm: realm)&.first
86
+ end
87
+
88
+ def self.find(id: nil, realm: REALM)
89
+ if all.find { |group| group['id'] == id }
90
+ new_from_id(id, realm)
91
+ end
92
+ end
93
+
94
+ def url
95
+ "#{Clonk.realm_admin_root(@realm)}/groups/#{@id}"
96
+ end
97
+
98
+ def map_role(role: nil)
99
+ client_path = role.container_id == @realm ? 'realm' : "clients/#{role.container_id}"
100
+ response = Clonk.parsed_response(
101
+ protocol: :post,
102
+ data: [role.config],
103
+ path: "#{url}/role-mappings/#{client_path}"
104
+ )
105
+ end
106
+
107
+ def add_user(user: nil, realm: REALM)
108
+ Clonk.parsed_response(
109
+ protocol: :put,
110
+ path: "#{user.url}/groups/#{@id}",
111
+ data: {
112
+ groupId: @id,
113
+ userId: user.id,
114
+ realm: @realm
115
+ }
116
+ )
117
+ end
118
+
119
+ def set_permissions(enabled: true)
120
+ Clonk.parsed_response(
121
+ protocol: :put,
122
+ path: "#{url}/management/permissions",
123
+ data: {
124
+ enabled: enabled
125
+ }
126
+ )
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,82 @@
1
+ module Clonk
2
+ class Permission
3
+ def initialize(permission_response, realm)
4
+ @id = permission_response['id']
5
+ @realm = realm
6
+ end
7
+
8
+ ##
9
+ # Returns the API URL for this permission.
10
+ # Argument is necessary as permissions are sometimes treated as policies
11
+ # within SSO for some reason, especially when fetching scopes, resources and
12
+ # policies.
13
+
14
+ def url(prefix: 'permission/scope')
15
+ client_url = Clonk::Client.find_by(realm: @realm, name: 'realm-management').url
16
+ "#{client_url}/authz/resource-server/#{prefix}/#{@id}"
17
+ end
18
+
19
+ ##
20
+ # Creates a new Permission instance from a given permission ID and realm.
21
+
22
+ def self.new_from_id(id: nil, realm: REALM)
23
+ client_url = Clonk::Client.find_by(realm: realm, name: 'realm-management').url
24
+ response = Clonk.parsed_response(
25
+ path: "#{client_url}/authz/resource-server/permission/scope/#{id}"
26
+ )
27
+ new(response, realm)
28
+ end
29
+
30
+ ##
31
+ # Returns the config within SSO for this permission.
32
+
33
+ def config
34
+ Clonk.parsed_response(
35
+ path: "#{url}"
36
+ )
37
+ end
38
+
39
+ ##
40
+ # Returns the policy IDs associated with this permission.
41
+
42
+ def policies
43
+ Clonk.parsed_response(
44
+ path: "#{url(prefix: 'policy')}/associatedPolicies"
45
+ ).map { |policy| policy['id'] }
46
+ end
47
+
48
+ ##
49
+ # Returns the resource IDs associated with this permission.
50
+
51
+ def resources
52
+ Clonk.parsed_response(
53
+ path: "#{url(prefix: 'policy')}/resources"
54
+ ).map { |resource| resource['_id'] }
55
+ end
56
+
57
+ ##
58
+ # Returns the scope IDs associated with this permission.
59
+
60
+ def scopes
61
+ Clonk.parsed_response(
62
+ path: "#{url(prefix: 'policy')}/scopes"
63
+ ).map { |scope| scope['id'] }
64
+ end
65
+
66
+ ##
67
+ # Adds the given policy/resource/scope IDs to this permission in SSO.
68
+
69
+ def update(policies: [], resources: [], scopes: [])
70
+ data = config.merge(
71
+ policies: self.policies + policies,
72
+ resources: self.resources + resources,
73
+ scopes: self.scopes + scopes
74
+ )
75
+ Clonk.parsed_response(
76
+ path: url,
77
+ data: data,
78
+ protocol: :put
79
+ )
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,81 @@
1
+ module Clonk
2
+ class Policy
3
+
4
+ attr_accessor :id
5
+ attr_reader :name
6
+
7
+ def initialize(policy_response, realm)
8
+ @id = policy_response['id']
9
+ @name = policy_response['name']
10
+ @realm = realm
11
+ end
12
+
13
+ ##
14
+ # Gets config inside SSO for policy with ID in realm.
15
+
16
+ def self.get_config(id, realm = REALM)
17
+ Clonk.parsed_response(
18
+ path: "#{Clonk.realm_admin_root(realm)}/clients/#{Clonk::Client.find_by(name: 'realm-management').id}/authz/resource-server/policy/role/#{id}"
19
+ )
20
+ end
21
+
22
+ ##
23
+ # Gets config inside SSO for policy.
24
+
25
+ def config
26
+ self.class.get_config(@id, @realm)
27
+ end
28
+
29
+ ##
30
+ # Creates a new Policy instance from a policy that exists in SSO
31
+
32
+ def self.new_from_id(id, realm = REALM)
33
+ new(get_config(id, realm), realm)
34
+ end
35
+
36
+ ##
37
+ # Returns defaults for a policy.
38
+ # I've found no reason to override these, but then again, I'm not 100% sure
39
+ # how they work. Overrides will be added to necessary methods if requested.
40
+
41
+ def self.defaults
42
+ {
43
+ logic: 'POSITIVE',
44
+ decisionStrategy: 'UNANIMOUS'
45
+ }
46
+ end
47
+
48
+ ##
49
+ # Returns a policy definition that can then be used to create a policy in SSO.
50
+ # Only defines role, group and client policies
51
+ #--
52
+ # TODO: Expand to allow for other policy types
53
+ # TODO: Don't assume role as default type
54
+ #++
55
+
56
+ def self.define(type: :role, name: nil, objects: [], description: nil, groups_claim: nil)
57
+ defaults.merge(
58
+ type: type,
59
+ name: name,
60
+ roles: (objects.map { |role| { id: role.id, required: true } } if type == :role),
61
+ groups: (objects.map { |group| { id: group.id, extendChildren: false } } if type == :group),
62
+ groupsClaim: (groups_claim if type == :group),
63
+ clients: (objects.map { |client| client.id } if type == :client),
64
+ description: description
65
+ ).delete_if { |k,v| v.nil?}
66
+ end
67
+
68
+ ##
69
+ # Defines and creates a policy in SSO.
70
+
71
+ def self.create(type: :role, name: nil, objects: [], description: nil, groups_claim: nil, realm: REALM)
72
+ data = self.define(type: type, name: name, objects: objects, description: description, groups_claim: groups_claim)
73
+ realm_management_url = Clonk::Client.find_by(name: 'realm-management', realm: realm).url
74
+ Clonk.parsed_response(
75
+ protocol: :post,
76
+ path: "#{realm_management_url}/authz/resource-server/policy/#{type}",
77
+ data: data
78
+ )
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,52 @@
1
+ module Clonk
2
+ class Realm
3
+ def initialize(realm_response)
4
+ @name = realm_response['id']
5
+ end
6
+
7
+ ##
8
+ # Creates a realm with the given name, returning it as a Realm object
9
+ def self.create(name: nil)
10
+ Clonk.parsed_response(
11
+ protocol: :post,
12
+ path: '/auth/admin/realms',
13
+ data: { id: name, realm: name, enabled: true }
14
+ )
15
+ new_from_id(id: name)
16
+ end
17
+
18
+ ##
19
+ # Returns all realms in this instance of SSO.
20
+
21
+ def self.all
22
+ Clonk.parsed_response(
23
+ path: '/auth/admin/realms'
24
+ ).map { |realm| new_from_id(id: realm['id'])}
25
+ end
26
+
27
+ ##
28
+ # Returns the realm with the given name.
29
+
30
+ def self.find_by(name: nil)
31
+ Clonk.parsed_response(
32
+ path: "/auth/admin/realms/#{name}"
33
+ )
34
+ end
35
+
36
+ ##
37
+ # Gets the config for this realm in SSO.
38
+
39
+ def config
40
+ Clonk.parsed_response(
41
+ path: "/auth/admin/realms/#{@name}"
42
+ )
43
+ end
44
+
45
+ ##
46
+ # Creates a new Realm object from a given realm ID.
47
+
48
+ def self.new_from_id(id: nil)
49
+ new(find_by(name: id))
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,74 @@
1
+ module Clonk
2
+ class Role
3
+ attr_accessor :id
4
+ attr_accessor :container_id
5
+ attr_reader :name
6
+
7
+ def initialize(role_response, realm)
8
+ @id = role_response['id']
9
+ @realm = realm
10
+ @container_id = role_response['containerId']
11
+ @name = role_response['name']
12
+ end
13
+
14
+ ##
15
+ # Returns all roles for the given client. Can also return all roles
16
+ # applicable to a specific target object.
17
+ #--
18
+ # FIXME: target_type should really be inherited from classname of incoming
19
+ # object
20
+ #++
21
+
22
+ def self.all(client: nil, target: nil, target_type: nil, realm: REALM)
23
+ # need this to work with realms too
24
+ case target
25
+ when nil
26
+ path = "#{Clonk.realm_admin_root(realm)}/clients/#{client.id}/roles"
27
+ else
28
+ path = "#{Clonk.realm_admin_root(realm)}/#{target_type}s/#{target.id}/role-mappings/clients/#{client.id}/available"
29
+ end
30
+ Clonk.parsed_response(
31
+ path: path
32
+ ).map { |role| new_from_id(role['id'], realm) }
33
+ end
34
+
35
+ ##
36
+ # Returns all roles with the given name.
37
+
38
+ def self.where(client: nil, target: nil, target_type: nil, realm: REALM, name: nil)
39
+ all(client: client, target: target, target_type: target_type, realm: realm)
40
+ .select { |role| role.name == name }
41
+ end
42
+
43
+
44
+ ##
45
+ # Returns the first role with the given name.
46
+
47
+ def self.find_by(client: nil, target: nil, target_type: nil, realm: REALM, name: nil)
48
+ where(client: client, target: target, target_type: target_type, realm: realm, name: name)&.first
49
+ end
50
+
51
+ ##
52
+ # Gets config inside SSO for role with ID in realm
53
+
54
+ def self.get_config(id, realm = REALM)
55
+ Clonk.parsed_response(
56
+ path: "#{Clonk.realm_admin_root(realm)}/roles-by-id/#{id}"
57
+ )
58
+ end
59
+
60
+ ##
61
+ # Gets config inside SSO for this role
62
+
63
+ def config
64
+ self.class.get_config(@id, @realm)
65
+ end
66
+
67
+ ##
68
+ # Creates a new Role instance from a role that exists in SSO
69
+
70
+ def self.new_from_id(id, realm = REALM)
71
+ new(get_config(id, realm), realm)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,107 @@
1
+ module Clonk
2
+ class User
3
+ attr_accessor :id
4
+ attr_reader :username
5
+
6
+ def initialize(user_response, realm)
7
+ @username = user_response['username']
8
+ @id = user_response['id']
9
+ @realm = realm
10
+ end
11
+
12
+ ##
13
+ # Gets config inside SSO for user with ID in realm
14
+
15
+ def self.get_config(id, realm = REALM)
16
+ Clonk.parsed_response(
17
+ path: "#{Clonk.realm_admin_root(realm)}/users/#{id}"
18
+ )
19
+ end
20
+
21
+ ##
22
+ # Gets config inside SSO for this user
23
+
24
+ def config
25
+ self.class.get_config(@id, @realm)
26
+ end
27
+
28
+ ##
29
+ # Creates a new User instance from a user that exists in SSO
30
+
31
+ def self.new_from_id(id, realm = REALM)
32
+ new(get_config(id, realm), realm)
33
+ end
34
+
35
+ ##
36
+ # Creates a user in SSO, returning a User instance with their ID and
37
+ # username
38
+
39
+ def self.create(realm: REALM, username: nil, enabled: true)
40
+ response = Clonk.response(protocol: :post,
41
+ path: "#{Clonk.realm_admin_root(realm)}/users",
42
+ data: { username: username, enabled: enabled }
43
+ )
44
+ self.new_from_id(response.headers[:location].split('/')[-1], realm)
45
+ end
46
+
47
+ ##
48
+ # Returns all users in the given realm
49
+
50
+ def self.all(realm: REALM)
51
+ Clonk.parsed_response(
52
+ path: "#{Clonk.realm_admin_root(realm)}/users"
53
+ ).map { |user| new_from_id(user['id'], realm) }
54
+ end
55
+
56
+ ##
57
+ # Returns all users in the given realm with the given username
58
+
59
+ def self.where(username: nil, realm: REALM)
60
+ all(realm: realm).select { |user| user.username == username }
61
+ end
62
+
63
+ ##
64
+ # returns a user in the given realm with the given username
65
+
66
+ def self.find_by(username: nil, realm: REALM)
67
+ where(username: username, realm: realm)&.first
68
+ end
69
+
70
+ ##
71
+ # Returns the API URL from which the user is accessible.
72
+
73
+ def url
74
+ "#{Clonk.realm_admin_root(@realm)}/users/#{@id}"
75
+ end
76
+
77
+ ##
78
+ # Maps a role to a user.
79
+
80
+ def map_role(role: nil)
81
+ client_path = role.container_id == @realm ? 'realm' : "clients/#{role.container_id}"
82
+ response = Clonk.parsed_response(
83
+ protocol: :post,
84
+ data: [role.config],
85
+ path: "#{url}/role-mappings/#{client_path}"
86
+ )
87
+ end
88
+
89
+ ##
90
+ # Sets the user's password.
91
+ #--
92
+ # FIXME: Currently always a permanent password Make that temporary flag do things.
93
+ #++
94
+
95
+ def set_password(password: nil, temporary: false)
96
+ Clonk.parsed_response(
97
+ protocol: :put,
98
+ data: {
99
+ type: 'password',
100
+ value: password,
101
+ temporary: false
102
+ },
103
+ path: "#{url}/reset-password"
104
+ )
105
+ end
106
+ end
107
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clonk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0alpha3
4
+ version: 1.0.0alpha4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Fish
@@ -44,8 +44,23 @@ executables: []
44
44
  extensions: []
45
45
  extra_rdoc_files: []
46
46
  files:
47
+ - ".yardoc/checksums"
48
+ - ".yardoc/complete"
49
+ - ".yardoc/object_types"
50
+ - ".yardoc/objects/root.dat"
51
+ - ".yardoc/proxy_types"
47
52
  - Gemfile
53
+ - Gemfile.lock
54
+ - README.md
55
+ - clonk.gemspec
48
56
  - lib/clonk.rb
57
+ - lib/clonk/client.rb
58
+ - lib/clonk/group.rb
59
+ - lib/clonk/permission.rb
60
+ - lib/clonk/policy.rb
61
+ - lib/clonk/realm.rb
62
+ - lib/clonk/role.rb
63
+ - lib/clonk/user.rb
49
64
  homepage:
50
65
  licenses: []
51
66
  metadata:
@@ -53,7 +68,15 @@ metadata:
53
68
  post_install_message:
54
69
  rdoc_options: []
55
70
  require_paths:
56
- - lib
71
+ - lib/clonk.rb
72
+ - lib/clonk
73
+ - lib/clonk/realm.rb
74
+ - lib/clonk/group.rb
75
+ - lib/clonk/policy.rb
76
+ - lib/clonk/role.rb
77
+ - lib/clonk/client.rb
78
+ - lib/clonk/permission.rb
79
+ - lib/clonk/user.rb
57
80
  required_ruby_version: !ruby/object:Gem::Requirement
58
81
  requirements:
59
82
  - - ">="