keycloak_3scale_users 1.0.0

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: 7c2647c7ccacc50153428151e92ba38308d311dea2fdf2df956a81e7a9e552b7
4
+ data.tar.gz: 62ce9a03215a875db9f572ae020ab6f59e4643aa5e4a0f95a509ca8683af9df7
5
+ SHA512:
6
+ metadata.gz: 51666a60549f903da5bfc41382ac07e43f7478050685edbd8bed3a9e800c5ef976fb859847d8003d08ca4da641b2773739becbd913f61161f4c7b443291413ec
7
+ data.tar.gz: ef5a8741e99d8fd08fe8f44bbe7fed3affb38795fc18377f6bc296c73f608b7b0691133154ce84dff9ee68159c931ac5de3e3eea35d54b56ac8763904a5fce70
@@ -0,0 +1,22 @@
1
+ name: CI (build & test)
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v3
14
+ - name: Set up Ruby
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: '3.2.0'
18
+ - name: Install dependencies
19
+ run: bundle install
20
+ - name: run tests suite
21
+ run: rspec
22
+
data/.gitignore ADDED
@@ -0,0 +1,56 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ # vendor/Pods/
33
+
34
+ ## Documentation cache and generated files:
35
+ /.yardoc/
36
+ /_yardoc/
37
+ /doc/
38
+ /rdoc/
39
+
40
+ ## Environment normalization:
41
+ /.bundle/
42
+ /vendor/bundle
43
+ /lib/bundler/man/
44
+
45
+ # for a library or gem, you might want to ignore these files since the code is
46
+ # intended to run in multiple environments; otherwise, check them in:
47
+ # Gemfile.lock
48
+ # .ruby-version
49
+ # .ruby-gemset
50
+
51
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
52
+ .rvmrc
53
+
54
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
55
+ # .rubocop-https?--*
56
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ 3scale-migration
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.3
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in keycloak_3scale_users.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
8
+ gem 'nokogiri'
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ keycloak_3scale_users (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.5.0)
10
+ nokogiri (1.14.3)
11
+ racc (~> 1.4)
12
+ racc (1.6.2)
13
+ rake (12.3.3)
14
+ rspec (3.12.0)
15
+ rspec-core (~> 3.12.0)
16
+ rspec-expectations (~> 3.12.0)
17
+ rspec-mocks (~> 3.12.0)
18
+ rspec-core (3.12.1)
19
+ rspec-support (~> 3.12.0)
20
+ rspec-expectations (3.12.2)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.12.0)
23
+ rspec-mocks (3.12.5)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.12.0)
26
+ rspec-support (3.12.0)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ keycloak_3scale_users!
33
+ nokogiri
34
+ rake (~> 12.0)
35
+ rspec (~> 3.0)
36
+
37
+ BUNDLED WITH
38
+ 2.4.12
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Abdullah Barrak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # Keycloak 3scale Users Migration
2
+
3
+ [![CI (build & test)](https://github.com/abarrak/3scale-keycloak-users-migration/actions/workflows/ci.yml/badge.svg)](https://github.com/abarrak/3scale-keycloak-users-migration/actions/workflows/ci.yml)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'keycloak_3scale_users'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install keycloak_3scale_users
20
+
21
+ ## Usage
22
+
23
+ After installation, use the binary to apply the migrate operation.
24
+
25
+ Pass the required arguments as follows:
26
+
27
+ ```bash
28
+ keycloak_3scale_users <3scale-api-base-url> <3scale-token> <keycloak-url> <keycloak-realm-name> <keyclock_client_id> <keyclock-admin-user> <keycloak-admin-password> <main-redirect-url>
29
+ ```
30
+
31
+ (Poor's man positional only).
32
+
33
+ ## Development
34
+
35
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
36
+
37
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
38
+
39
+ ## Contributing
40
+
41
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/keycloak_3scale_users.
42
+
43
+
44
+ ## License
45
+
46
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "keycloak_3scale_users"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "keycloak_3scale_users"
5
+
6
+ Keycloak3scaleUsers::Core.run if __FILE__ == $0
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,25 @@
1
+ require_relative 'lib/keycloak_3scale_users/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "keycloak_3scale_users"
5
+ spec.version = Keycloak3scaleUsers::VERSION
6
+ spec.authors = ["Abdullah Barrak"]
7
+ spec.email = ["abdullah@abarrak.com"]
8
+
9
+ spec.summary = '3scale to Keycloak users migration.'
10
+ spec.description = 'A CLI gem for migrating the end users accounts of 3scale to keycloak realm.'
11
+ spec.homepage = "https://github.com/abarrak/3scale-keycloak-users-migration"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/abarrak/3scale-keycloak-users-migration"
17
+ spec.metadata["changelog_uri"] = "https://github.com/abarrak/3scale-keycloak-users-migration/CHANGELOG"
18
+
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "bin"
23
+ spec.executables << 'keycloak_3scale_users'
24
+ spec.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+ require 'net/http'
3
+ require 'openssl'
4
+ require 'uri'
5
+ require 'logger'
6
+ require 'rubygems'
7
+ require 'nokogiri'
8
+ require 'csv'
9
+
10
+ ##
11
+ # Parametes
12
+ # -----
13
+ # * <3scale-api-base-url>
14
+ # * <3scale-token>
15
+ # * <keycloak-url>
16
+ # * <keycloak-realm-name>
17
+ # * <keyclock_client_id>
18
+ # * <keyclock-admin-user>
19
+ # * <keycloak-admin-password>
20
+ # * <main-redirect-url>
21
+ #
22
+ #
23
+ module Keycloak3scaleUsers
24
+ module Core
25
+ User = Struct.new(:email, :username, :first_name, :last_name)
26
+
27
+ def self.run
28
+ print_welcome
29
+ set_parameters
30
+ import_current_users
31
+ migrate_to_sso
32
+ collect_keyclock_user_ids
33
+ send_email_notifications
34
+ rescue
35
+ p $!
36
+ raise
37
+ exit 1
38
+ end
39
+
40
+ class << self
41
+ protected
42
+
43
+ def print_welcome
44
+ puts ">> Starting Migration Script <<\n\n"
45
+ end
46
+
47
+ def set_parameters
48
+ param_keys = %w(
49
+ threescale_url threescale_token keyclock_url keyclock_realm keyclock_client_id
50
+ keyclock_admin_user keycloak_admin_password rabet_url
51
+ )
52
+ arguments = ARGV.collect { |arg| arg.chomp }
53
+
54
+ param_keys.zip(arguments).each do |key, arg|
55
+ instance_variable_set "@#{key}", arg
56
+ puts "> parameter #{key} is set to #{arg}"
57
+ end
58
+
59
+ param_keys.each do |key|
60
+ param = instance_variable_get "@#{key}"
61
+ if param.nil? || param.empty?
62
+ puts "\n> Error: input parameter #{key} is empty"
63
+ exit 1
64
+ end
65
+ end
66
+ puts "> All parameters are set!\n\n"
67
+ end
68
+
69
+ def import_current_users
70
+ puts ">> 1. Starting the import process .."
71
+ count = 0
72
+
73
+ base_url = instance_variable_get :@threescale_url
74
+ token = instance_variable_get :@threescale_token
75
+ pages = 500
76
+ endpoint = "/admin/api/accounts.xml?access_token=#{token}&page=1&per_page=#{pages}"
77
+
78
+ http = set_http_client URI.parse(base_url)
79
+ headers = authenticate
80
+ response = http.request Net::HTTP::Get.new(endpoint, headers)
81
+
82
+ unless Net::HTTPSuccess === response
83
+ puts "endpoint responded with non-success #{response.code} code.\nResponse: #{response.body}"
84
+ exit 1
85
+ end
86
+ @users = pares_users_from_xml response.body
87
+
88
+ puts ">> Imported #{@users.count} users successfully from 3scale system.\n>> Done.\n\n"
89
+ end
90
+
91
+ def migrate_to_sso
92
+ puts ">> 2. Starting the migration process .."
93
+
94
+ @users.each do |user|
95
+ import_user_to_sso user
96
+ end
97
+
98
+ puts "> Done.\n\n"
99
+ end
100
+
101
+ def send_email_notifications
102
+ puts ">> 4. Sending email notifications .."
103
+
104
+ @user_ids.each do |id|
105
+ send_email_to_user id
106
+ end
107
+
108
+ puts "> Done.\n\n"
109
+ end
110
+
111
+ def set_http_client uri
112
+ http = Net::HTTP.new(uri.host, uri.port)
113
+ http.use_ssl = uri.scheme == 'https' ? true : false
114
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
115
+ http
116
+ end
117
+
118
+ def authenticate bearer_token = ''
119
+ headers = {}
120
+ headers['Authorization'] = "Bearer #{bearer_token}" unless bearer_token.empty?
121
+ headers['User-Agent'] = "SSO Migration Script"
122
+ headers
123
+ end
124
+
125
+ def pares_users_from_xml response_body
126
+ doc = Nokogiri::XML response_body
127
+ xpath_node = '//accounts//account//users//user'
128
+ users = doc.xpath
129
+ emails, usernames, first_names, last_names = ['//email', '//username', '//first_name', '//last_name'].collect { |path| doc.xpath path }
130
+ user_count = emails.count
131
+
132
+ users = []
133
+ 1.upto(user_count).each do |i|
134
+ e = emails[i]&.text
135
+ u = usernames[i]&.text
136
+ f = first_names[i]&.text
137
+ l = last_names[i]&.text
138
+ users << User.new(e, u, f, l) unless e.nil?
139
+ end
140
+ puts "> All users dump:\n"
141
+ puts users, "\n"
142
+ users
143
+ end
144
+
145
+ def parse_as_json response
146
+ body = response.body
147
+ body = body.nil? || body.empty? ? body : JSON.parse(body)
148
+
149
+ rescue JSON::ParserError => error
150
+ puts "Parsing response body as JSON failed! Returning raw body. \nDetails: \n#{error.message}"
151
+ exit 1
152
+ end
153
+
154
+ def import_user_to_sso user
155
+ base_url = instance_variable_get :@keyclock_url
156
+ realm = instance_variable_get :@keyclock_realm
157
+ endpoint = "/auth/admin/realms/#{realm}/users"
158
+
159
+ http = set_http_client URI.parse(base_url)
160
+ headers = authenticate(generate_keycloak_bearer_token).merge({ "Content-Type" => "application/json" })
161
+ request = Net::HTTP::Post.new(endpoint, headers)
162
+ request.body = JSON.dump({
163
+ "createdTimestamp": 1588880747548,
164
+ "username": user.email,
165
+ "enabled": true,
166
+ "emailVerified": true,
167
+ "firstName": user.first_name,
168
+ "lastName": user.last_name,
169
+ "email": user.email,
170
+ "disableableCredentialTypes": [],
171
+ "requiredActions": [],
172
+ "notBefore": 0,
173
+ "access": {
174
+ "manageGroupMembership": false,
175
+ "view": true,
176
+ "mapRoles": false,
177
+ "impersonate": false,
178
+ "manage": false
179
+ }
180
+ })
181
+ response = http.request request
182
+
183
+ unless Net::HTTPSuccess === response
184
+ puts "endpoint responded with non-success #{response.code} code.\nResponse: #{response.body}"
185
+ puts "Error: failed to import user #{user.email} .."
186
+ else
187
+ puts "> importing user #{user.email} to keyclock successfully.\n\n"
188
+ end
189
+ end
190
+
191
+ def generate_keycloak_bearer_token
192
+ return @token if @token_time && (Time.now - @token_time < 40.to_f)
193
+
194
+ base_url = instance_variable_get :@keyclock_url
195
+ realm = instance_variable_get :@keyclock_realm
196
+ admin_user = instance_variable_get :@keyclock_admin_user
197
+ admin_pass = instance_variable_get :@keycloak_admin_password
198
+
199
+ endpoint = "/auth/realms/master/protocol/openid-connect/token"
200
+ @token_time = Time.now
201
+
202
+ http = set_http_client URI.parse(base_url)
203
+ headers = authenticate.merge({ "Content-Type" => "application/x-www-form-urlencoded" })
204
+ request = Net::HTTP::Post.new endpoint, headers
205
+ response = http.request request
206
+ request.body = "client_id=admin-cli&username=#{admin_user}&password=#{admin_pass}&grant_type=password"
207
+
208
+ response = http.request request
209
+ @token = parse_as_json(response)['access_token']
210
+ puts "> [[ Keyclock token is regenerated ]], #{@token.slice(30, 40)}..."
211
+ @token
212
+ end
213
+
214
+ def collect_keyclock_user_ids
215
+ puts ">> 3. Getting ids for all imported users to keyclock .."
216
+ base_url = instance_variable_get :@keyclock_url
217
+ realm = instance_variable_get :@keyclock_realm
218
+ users_count = 1000
219
+ endpoint = "/auth/admin/realms/#{realm}/users?count=#{users_count}"
220
+
221
+ http = set_http_client URI.parse(base_url)
222
+ headers = authenticate(generate_keycloak_bearer_token).merge({ "Content-Type" => "application/json" })
223
+ request = Net::HTTP::Get.new(endpoint, headers)
224
+ response = http.request request
225
+
226
+ @user_ids = parse_as_json(response).collect { |user| user['id'] }
227
+ puts "> All users dump:"
228
+ puts @user_ids, "\n"
229
+
230
+ unless Net::HTTPSuccess === response
231
+ puts "endpoint responded with non-success #{response.code} code.\nResponse: #{response.body}"
232
+ puts "Error: failed to get users list from Keyclock .."
233
+ exit 1
234
+ else
235
+ puts "> Reterived all users (total: #{@user_ids.count}) from keyclok successfully.\n\n"
236
+ end
237
+ @user_ids
238
+ end
239
+
240
+ def send_email_to_user user_id
241
+ base_url = instance_variable_get :@keyclock_url
242
+ realm = instance_variable_get :@keyclock_realm
243
+ client_id = instance_variable_get :@keyclock_client_id
244
+ rabet_url = instance_variable_get :@rabet_url
245
+ endpoint = "/auth/admin/realms/#{realm}/users/#{user_id}/execute-actions-email"
246
+ endpoint = endpoint.dup + "?redirect_uri=#{rabet_url}&client_id=#{client_id}"
247
+
248
+
249
+ http = set_http_client URI.parse(base_url)
250
+ headers = authenticate(generate_keycloak_bearer_token).merge({ "Content-Type" => "application/json" })
251
+ request = Net::HTTP::Put.new(endpoint, headers)
252
+ request.body = JSON.dump [ "UPDATE_PASSWORD" ]
253
+ response = http.request request
254
+
255
+ unless Net::HTTPSuccess === response
256
+ puts "endpoint responded with non-success #{response.code} code.\nResponse: #{response.body}"
257
+ puts "Error: failed to send set actions email to user id #{user_id} .."
258
+ else
259
+ puts "> Restet password email to user id #{user_id} is sent successfully."
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,3 @@
1
+ module Keycloak3scaleUsers
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'keycloak_3scale_users/core'
2
+ require_relative 'keycloak_3scale_users/version'
3
+
4
+ module Keycloak3scaleUsers
5
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: keycloak_3scale_users
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Abdullah Barrak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-04-18 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A CLI gem for migrating the end users accounts of 3scale to keycloak
14
+ realm.
15
+ email:
16
+ - abdullah@abarrak.com
17
+ executables:
18
+ - keycloak_3scale_users
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".github/workflows/ci.yml"
23
+ - ".gitignore"
24
+ - ".rspec"
25
+ - ".ruby-gemset"
26
+ - ".ruby-version"
27
+ - Gemfile
28
+ - Gemfile.lock
29
+ - LICENSE.txt
30
+ - README.md
31
+ - Rakefile
32
+ - bin/console
33
+ - bin/keycloak_3scale_users
34
+ - bin/setup
35
+ - keycloak_3scale_users.gemspec
36
+ - lib/keycloak_3scale_users.rb
37
+ - lib/keycloak_3scale_users/core.rb
38
+ - lib/keycloak_3scale_users/version.rb
39
+ homepage: https://github.com/abarrak/3scale-keycloak-users-migration
40
+ licenses:
41
+ - MIT
42
+ metadata:
43
+ homepage_uri: https://github.com/abarrak/3scale-keycloak-users-migration
44
+ source_code_uri: https://github.com/abarrak/3scale-keycloak-users-migration
45
+ changelog_uri: https://github.com/abarrak/3scale-keycloak-users-migration/CHANGELOG
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.7.0
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.1.6
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: 3scale to Keycloak users migration.
65
+ test_files: []