ruby-keycloak-admin 26.0.8

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3e0ce0221f1b819d51a1b15b34e9c143f5200df7dbfb470f078e75d971c53792
4
+ data.tar.gz: ab7bfe86c2b7967661c9f0df6f7ca3deb7d894ff73bfdee7df74dd6c1a89583f
5
+ SHA512:
6
+ metadata.gz: 292f27418bf59e5db36b15b476a4a666c4cc4c68ff0685e2b22555fb3a3931ec25d194993169c98e4cfe17c0a78196e6584befa9c2bf89bd88d8b53be28ae76f
7
+ data.tar.gz: 815897d5d7ce17ecd3d84ca7602fc135f905e610f44d6347fca0802d11e96532245066884697586472145163ca63205b0ea01b04fc1c4f515aee0de2b8b5cb42
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require 'time'
5
+
6
+ require 'keycloak/admin/error'
7
+
8
+ module Keycloak
9
+ module Admin
10
+ class Agent # :nodoc: all
11
+ include HTTParty
12
+ headers 'Accept' => 'application/json', 'Content-Type' => 'application/json'
13
+ debug_output $stdout if ENV['KEYCLOAK_ADMIN_DEBUG']
14
+
15
+ attr_accessor :base_url, :username, :password, :realm, :grant_type, :always_terminate
16
+
17
+ def initialize
18
+ @base_url = 'http://localhost:8080'
19
+ @realm = 'master'
20
+ @username = 'nil'
21
+ @password = 'nil'
22
+ @grant_type = 'password'
23
+ @always_terminate = false
24
+ end
25
+
26
+ def logout
27
+ terminate_session
28
+ end
29
+
30
+ %i[get post put delete head].each do |method|
31
+ define_method method do |path, params: {}|
32
+ request(method, path, params:)
33
+ end
34
+ end
35
+
36
+ def request(method, path, params: {})
37
+ refresh_session || create_session
38
+
39
+ params[:headers] ||= {}
40
+ params[:headers][:Authorization] = "Bearer #{@session['access_token']}"
41
+
42
+ response = self.class.send(
43
+ method,
44
+ "#{@base_url}/#{path}",
45
+ body: params[:body],
46
+ headers: params[:headers]
47
+ )
48
+
49
+ terminate_session if @always_terminate
50
+
51
+ Keycloak::Admin.raise_error(response) if !response.code.between?(200, 299)
52
+
53
+ response.parsed_response
54
+ end
55
+
56
+ private
57
+
58
+ def create_session
59
+ return false if !session_expired?
60
+
61
+ response = self.class.post(
62
+ "#{@base_url}/realms/master/protocol/openid-connect/token",
63
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
64
+ body: {
65
+ grant_type: 'password',
66
+ client_id: 'admin-cli',
67
+ username: @username,
68
+ password: @password
69
+ }
70
+ )
71
+
72
+ Keycloak::Admin.raise_error(response) if !response.code.between?(200, 299)
73
+
74
+ store_session(response)
75
+
76
+ true
77
+ end
78
+
79
+ def refresh_session
80
+ return false if !session_running?
81
+ return false if refresh_expired?
82
+
83
+ response = self.class.post(
84
+ "#{@base_url}/realms/master/protocol/openid-connect/token",
85
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
86
+ body: {
87
+ grant_type: 'refresh_token',
88
+ client_id: 'admin-cli',
89
+ refresh_token: @session['refresh_token']
90
+ }
91
+ )
92
+
93
+ return false if response.code.eql?(400)
94
+ return Keycloak::Admin.raise_error(response) if !response.code.between?(200, 299)
95
+
96
+ store_session(response)
97
+
98
+ true
99
+ end
100
+
101
+ def terminate_session
102
+ return false if !session_running?
103
+ return false if refresh_expired?
104
+
105
+ response = self.class.post(
106
+ "#{@base_url}/realms/master/protocol/openid-connect/logout",
107
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
108
+ body: {
109
+ client_id: 'admin-cli',
110
+ refresh_token: @session['refresh_token']
111
+ }
112
+ )
113
+
114
+ @session = nil
115
+
116
+ Keycloak::Admin.raise_error(response) if !response.code.between?(200, 299)
117
+
118
+ true
119
+ end
120
+
121
+ def store_session(response)
122
+ @session = response.parsed_response
123
+ @session['expires_at'] = Time.now + @session['expires_in']
124
+ @session['refresh_expires_at'] = Time.now + @session['refresh_expires_in']
125
+ end
126
+
127
+ def session_running?
128
+ !@session.nil?
129
+ end
130
+
131
+ def session_expired?
132
+ return true if @session.nil?
133
+
134
+ Time.now > @session['expires_at']
135
+ end
136
+
137
+ def refresh_expired?
138
+ return true if @session.nil?
139
+
140
+ Time.now > @session['refresh_expires_at']
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'keycloak/admin/resource'
4
+
5
+ module Keycloak
6
+ module Admin
7
+ ##
8
+ # Clients resource.
9
+ #
10
+ # The class is based on Keycloak::Admin::Resource and
11
+ # Keycloak::Admin::Resource::Pagination.
12
+ module Clients
13
+ class << self
14
+ include Keycloak::Admin::Resource
15
+
16
+ private
17
+
18
+ def resource
19
+ "admin/realms/#{@agent.realm}/clients"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keycloak
4
+ module Admin
5
+ class ConfigurationError < StandardError; end # :nodoc:
6
+ class ConnectionFailedError < StandardError; end # :nodoc:
7
+ class NotFoundError < StandardError; end # :nodoc:
8
+ class UnauthorizedError < StandardError; end # :nodoc:
9
+ class ForbiddenError < StandardError; end # :nodoc:
10
+ class BadRequestError < StandardError; end # :nodoc:
11
+ class ConflictError < StandardError; end # :nodoc:
12
+ class InternalServerError < StandardError; end # :nodoc:
13
+ class ServiceUnavailableError < StandardError; end # :nodoc:
14
+ class MethodNotAllowedError < StandardError; end # :nodoc:
15
+ class BadGatewayError < StandardError; end # :nodoc:
16
+
17
+ class << self
18
+ def raise_error(response)
19
+ error_classes = {
20
+ 400 => BadRequestError,
21
+ 401 => UnauthorizedError,
22
+ 403 => ForbiddenError,
23
+ 404 => NotFoundError,
24
+ 405 => MethodNotAllowedError,
25
+ 409 => ConflictError,
26
+ 500 => InternalServerError,
27
+ 502 => BadGatewayError,
28
+ 503 => ServiceUnavailableError
29
+ }
30
+ error_class = error_classes[response.code] || StandardError
31
+
32
+ raise error_class, error_message(response)
33
+ end
34
+
35
+ private
36
+
37
+ def error_message(response)
38
+ parsed_response = response.parsed_response
39
+
40
+ return parsed_response if response.parsed_response.is_a?(String)
41
+
42
+ if response.parsed_response.is_a?(Hash)
43
+ return parsed_response['errorMessage'] ||
44
+ parsed_response['error'] ||
45
+ 'Unknown error'
46
+ end
47
+
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'keycloak/admin/resource'
4
+ require 'keycloak/admin/resource/pagination'
5
+
6
+ module Keycloak
7
+ module Admin
8
+ ##
9
+ # Groups resource.
10
+ #
11
+ # The class is based on Keycloak::Admin::Resource and
12
+ # Keycloak::Admin::Resource::Pagination. It provides following
13
+ # additional methods:
14
+ #
15
+ # * ::add
16
+ # * ::remove
17
+ # * ::members
18
+ module Groups
19
+ class << self
20
+ include Keycloak::Admin::Resource
21
+ include Keycloak::Admin::Resource::Pagination
22
+
23
+ ##
24
+ # Add user to group.
25
+ def add(id, user_id)
26
+ @agent.put("/admin/realms/#{@agent.realm}/users/#{user_id}/groups/#{id}")
27
+
28
+ true
29
+ end
30
+
31
+ ##
32
+ # Remove user from group.
33
+ def remove(id, user_id)
34
+ @agent.delete("/admin/realms/#{@agent.realm}/users/#{user_id}/groups/#{id}")
35
+
36
+ true
37
+ end
38
+
39
+ ##
40
+ # Get group members.
41
+ def members(id)
42
+ objects = []
43
+ (1..pages).each do |page|
44
+ first = (page * MAX_ENTRIES) - MAX_ENTRIES
45
+ objects << @agent.get("#{resource}/#{id}/members?first=#{first}&max=#{MAX_ENTRIES}")
46
+ end
47
+
48
+ objects.flatten.map { |object| mash(object) }
49
+ end
50
+
51
+ ##
52
+ # Create subgroup.
53
+ def subgroup(id, attributes)
54
+ @agent.post("#{resource}/#{id}/children", params: { body: attributes.to_json })
55
+
56
+ true
57
+ end
58
+
59
+ private
60
+
61
+ def resource
62
+ "admin/realms/#{@agent.realm}/groups"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'keycloak/admin/resource'
4
+
5
+ module Keycloak
6
+ module Admin
7
+ ##
8
+ # Realms resource.
9
+ #
10
+ # The class is based on Keycloak::Admin::Resource and
11
+ # Keycloak::Admin::Resource::Pagination. The method #find_by_id is renamed
12
+ # to +find_by_name+.
13
+ module Realms
14
+ class << self
15
+ include Keycloak::Admin::Resource
16
+ if method_defined? :find_by_id
17
+ alias find_by_name find_by_id
18
+ undef find_by_id
19
+ end
20
+
21
+ private
22
+
23
+ def resource
24
+ '/admin/realms'
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keycloak
4
+ module Admin
5
+ module Resource
6
+ ##
7
+ # Abstract class extension for Keycloak::Admin resources with methods
8
+ # providing pagination.
9
+ #
10
+ # * #all
11
+ module Pagination
12
+ MAX_ENTRIES = 100
13
+
14
+ ##
15
+ # List all resources.
16
+ def all
17
+ objects = []
18
+ (1..pages).each do |page|
19
+ first = (page * MAX_ENTRIES) - MAX_ENTRIES
20
+ objects << @agent.get("#{resource}?first=#{first}&max=#{MAX_ENTRIES}")
21
+ end
22
+
23
+ objects.flatten.map { |object| mash(object) }
24
+ end
25
+
26
+ private
27
+
28
+ def count
29
+ @agent.get("#{resource}/count")
30
+ end
31
+
32
+ def pages
33
+ count = count()
34
+ count = count['count'] if count.is_a?(Hash)
35
+
36
+ return 0 if count.zero?
37
+ return 1 if count <= MAX_ENTRIES
38
+
39
+ pages = count / MAX_ENTRIES
40
+ pages += 1 if (count % MAX_ENTRIES).positive?
41
+
42
+ pages
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hashie'
4
+
5
+ module Keycloak
6
+ module Admin
7
+ ##
8
+ # Abstract class for Keycloak::Admin resources with common methods.
9
+ #
10
+ # * #all
11
+ # * #create
12
+ # * #count
13
+ # * #delete
14
+ # * #find_by, alias #lookup
15
+ # * #find_by_id, alias #get
16
+ # * #update, alias #edit
17
+ module Resource
18
+ attr_writer :agent
19
+
20
+ ##
21
+ # List all resource objects.
22
+ def all
23
+ objects = @agent.get(resource)
24
+
25
+ objects.map { |object| mash(object) }
26
+ end
27
+
28
+ ##
29
+ # Create a new resource object.
30
+ def create(attributes)
31
+ @agent.post(resource, params: { body: attributes.to_json })
32
+
33
+ true
34
+ end
35
+
36
+ ##
37
+ # Delete a resource object by id.
38
+ def delete(id)
39
+ @agent.delete("#{resource}/#{id}")
40
+
41
+ true
42
+ end
43
+
44
+ ##
45
+ # Find resource objects by attributes, name and value.
46
+ def find_by(lookup)
47
+ objects = all
48
+
49
+ lookup.each do |key, value|
50
+ objects = objects.select do |object|
51
+ match_value?(object, key, value)
52
+ end
53
+ break if !objects
54
+ end
55
+
56
+ objects || []
57
+ end
58
+ alias lookup find_by
59
+
60
+ ##
61
+ # Find a resource object by id.
62
+ def find_by_id(id)
63
+ object = @agent.get("#{resource}/#{id}")
64
+
65
+ mash(object)
66
+ end
67
+ alias get find_by_id
68
+
69
+ ##
70
+ # Update a resource object by id.
71
+ def update(id, attributes)
72
+ @agent.put("#{resource}/#{id}", params: { body: attributes.to_json })
73
+
74
+ true
75
+ end
76
+ alias edit update
77
+
78
+ private
79
+
80
+ def resource
81
+ raise NotImplementedError
82
+ end
83
+
84
+ def match_value?(object, key, value)
85
+ return false if !object.key?(key)
86
+
87
+ if value.is_a?(String)
88
+ object[key].match?(value)
89
+ else
90
+ object[key] == value
91
+ end
92
+ end
93
+
94
+ def mash(object)
95
+ if object.is_a?(Array)
96
+ object.map { |item| Hashie::Mash.new(item) }
97
+ else
98
+ Hashie::Mash.new(object)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'keycloak/admin/resource'
4
+ require 'keycloak/admin/resource/pagination'
5
+
6
+ module Keycloak
7
+ module Admin
8
+ ##
9
+ # Users resource.
10
+ #
11
+ # The class is based on Keycloak::Admin::Resource and
12
+ # Keycloak::Admin::Resource::Pagination. It provides following
13
+ # additional methods:
14
+ #
15
+ # * ::join
16
+ # * ::leave
17
+ # * ::logout
18
+ # * ::password
19
+ # * ::sessions
20
+ module Users
21
+ class << self
22
+ include Keycloak::Admin::Resource
23
+ include Keycloak::Admin::Resource::Pagination
24
+
25
+ ##
26
+ # Add user to group.
27
+ def join(id, group_id)
28
+ @agent.put("#{resource}/#{id}/groups/#{group_id}")
29
+
30
+ true
31
+ end
32
+
33
+ ##
34
+ # Remove user from group.
35
+ def leave(id, group_id)
36
+ @agent.delete("#{resource}/#{id}/groups/#{group_id}")
37
+
38
+ true
39
+ end
40
+
41
+ ##
42
+ # User membership.
43
+ def membership(id)
44
+ objects = []
45
+ (1..pages).each do |page|
46
+ first = (page * MAX_ENTRIES) - MAX_ENTRIES
47
+ objects << @agent.get("#{resource}/#{id}/groups?first=#{first}&max=#{MAX_ENTRIES}")
48
+ end
49
+
50
+ objects.flatten.map { |object| mash(object) }
51
+ end
52
+
53
+ ##
54
+ # Logout user from all sessions.
55
+ def logout(id)
56
+ @agent.post("#{resource}/#{id}/logout")
57
+
58
+ true
59
+ end
60
+
61
+ ##
62
+ # Set new password for user.
63
+ def password(id, attributes)
64
+ # { temporary: false, value: 'password' }
65
+ attributes[:type] = 'password'
66
+ @agent.put("#{resource}/#{id}/reset-password", params: { body: attributes.to_json })
67
+
68
+ true
69
+ end
70
+
71
+ ##
72
+ # Get user sessions.
73
+ def sessions(id)
74
+ objects = @agent.get("#{resource}/#{id}/sessions")
75
+
76
+ objects.map { |object| mash(object) }
77
+ end
78
+
79
+ private
80
+
81
+ def resource
82
+ "admin/realms/#{@agent.realm}/users"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keycloak
4
+ module Admin
5
+ VERSION = '26.0.8'
6
+ end
7
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'keycloak/admin/version'
4
+ require 'keycloak/admin/agent'
5
+ require 'keycloak/admin/error'
6
+
7
+ require 'keycloak/admin/realms'
8
+ require 'keycloak/admin/clients'
9
+ require 'keycloak/admin/users'
10
+ require 'keycloak/admin/groups'
11
+
12
+ module Keycloak
13
+ ##
14
+ # Keycloak::Admin is a Ruby client for the Keycloak administration API.
15
+ #
16
+ # This is the main module. It provides methods to configure the connection to
17
+ # any Keycloak service, as well as methods to manage and control the several
18
+ # Keycloak resources.
19
+ module Admin
20
+ class << self
21
+ ##
22
+ # Configure Keycloak::Admin, the handler for all administration tasks.
23
+ #
24
+ # Provide Keycloak server base *url*, master realm admin user
25
+ # *credentials* and id of *realm* to be managed.
26
+ #
27
+ # Keycloak::Admin.configure do |config|
28
+ # config.username = 'admin',
29
+ # config.password = 'admin',
30
+ # config.realm = 'zone', # default: master
31
+ # config.base_url = 'https://keycloak.example.com' # default: http://localhost:8080
32
+ # end
33
+ def configure
34
+ @agent ||= Keycloak::Admin::Agent.new
35
+ yield @agent
36
+ @configured = true
37
+
38
+ true
39
+ end
40
+
41
+ ##
42
+ # Verify if Keycloak::Admin is configured.
43
+ def configured?
44
+ @configured.nil? ? false : @configured
45
+ end
46
+
47
+ ##
48
+ # Raise error if Keycloak::Admin is not configured.
49
+ def configured!
50
+ raise Keycloak::Admin::ConfigurationError, 'Keycloak::Admin is not configured' if !configured?
51
+
52
+ true
53
+ end
54
+
55
+ ##
56
+ # Reset Keycloak::Admin configuration.
57
+ def reset!
58
+ @agent&.logout
59
+ @agent = nil
60
+ @configured = nil
61
+ end
62
+
63
+ ##
64
+ # Manage server realms. See Keycloak::Admin::Realms for usage.
65
+ def realms
66
+ configured!
67
+ Keycloak::Admin::Realms.agent = @agent
68
+
69
+ Keycloak::Admin::Realms
70
+ end
71
+
72
+ ##
73
+ # Manage realm clients. See Keycloak::Admin::Clients for usage.
74
+ def clients
75
+ configured!
76
+ Keycloak::Admin::Clients.agent = @agent
77
+
78
+ Keycloak::Admin::Clients
79
+ end
80
+
81
+ ##
82
+ # Manage realm users. See Keycloak::Admin::Users for usage.
83
+ def users
84
+ configured!
85
+ Keycloak::Admin::Users.agent = @agent
86
+
87
+ Keycloak::Admin::Users
88
+ end
89
+
90
+ ##
91
+ # Manage realm groups. See Keycloak::Admin::Groups for usage.
92
+ def groups
93
+ configured!
94
+ Keycloak::Admin::Groups.agent = @agent
95
+
96
+ Keycloak::Admin::Groups
97
+ end
98
+
99
+ ##
100
+ # Verify if the Keycloak server is ready.
101
+ #
102
+ # Beware, method returns +nil+ if the server does not respond (404 not
103
+ # found) to health checks.
104
+ def ready?
105
+ configured!
106
+
107
+ begin
108
+ @agent.head('health/ready')
109
+ true
110
+ rescue Keycloak::Admin::NotFoundError
111
+ nil
112
+ rescue Keycloak::Admin::ServiceUnavailableError
113
+ false
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-keycloak-admin
3
+ version: !ruby/object:Gem::Version
4
+ version: 26.0.8
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Schäfer
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-01-15 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: hashie
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 5.0.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 5.0.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: httparty
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.21.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.21.0
40
+ description: |+
41
+ Ruby Keycloack admin API wrapper.
42
+
43
+ This gem provides a simple wrapper for the Keycloak admin API.
44
+
45
+ * https://www.keycloak.org
46
+ * https://www.keycloak.org/documentation
47
+ * https://www.keycloak.org/docs-api/26.0.8/rest-api/index.html
48
+
49
+ email:
50
+ - github@blackox.org
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - lib/keycloak/admin.rb
56
+ - lib/keycloak/admin/agent.rb
57
+ - lib/keycloak/admin/clients.rb
58
+ - lib/keycloak/admin/error.rb
59
+ - lib/keycloak/admin/groups.rb
60
+ - lib/keycloak/admin/realms.rb
61
+ - lib/keycloak/admin/resource.rb
62
+ - lib/keycloak/admin/resource/pagination.rb
63
+ - lib/keycloak/admin/users.rb
64
+ - lib/keycloak/admin/version.rb
65
+ homepage: https://github.com/tschaefer/keycloak-admin
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ rubygems_mfa_required: 'true'
70
+ source_code_uri: https://github.com/tschaefer/keycloak-admin
71
+ bug_tracker_uri: https://github.com/tschaefer/keycloak-admin/issues
72
+ post_install_message: All your Keycloak are belong to us!
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '3.1'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.6.2
88
+ specification_version: 4
89
+ summary: Ruby Keycloack admin API wrapper.
90
+ test_files: []
91
+ ...