keycloak_rails 1.0.0.pre.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +184 -0
- data/Rakefile +3 -0
- data/lib/app/models/keycloak_rails/concerns/sso_recipient.rb +13 -0
- data/lib/app/models/keycloak_rails/sso.rb +5 -0
- data/lib/generators/keycloak_rails/config/config_generator.rb +12 -0
- data/lib/generators/keycloak_rails/install/install_generator.rb +17 -0
- data/lib/generators/keycloak_rails/keycloak_rails.rb +31 -0
- data/lib/keycloak_rails/client.rb +122 -0
- data/lib/keycloak_rails/controller/helpers.rb +82 -0
- data/lib/keycloak_rails/controller/magic_links.rb +47 -0
- data/lib/keycloak_rails/controller/omniauth.rb +15 -0
- data/lib/keycloak_rails/controller/passwords.rb +19 -0
- data/lib/keycloak_rails/controller/registrations.rb +47 -0
- data/lib/keycloak_rails/controller/sessions.rb +33 -0
- data/lib/keycloak_rails/controller/unlocks.rb +15 -0
- data/lib/keycloak_rails/curl.rb +58 -0
- data/lib/keycloak_rails/engine.rb +22 -0
- data/lib/keycloak_rails/user.rb +90 -0
- data/lib/keycloak_rails/version.rb +3 -0
- data/lib/keycloak_rails.rb +70 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f02d892062dce2d81c45f4de13d30c78201eb376675a47bb91b1292ac8963487
|
4
|
+
data.tar.gz: a8272329fa865c85099399b1f819c525aa65483c0cd13cf6e711a18525d9f866
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: df5bf2f2e77dacbf7c6211ccdf70adb1046398a46d94f31777af3c2987085b1db30fc95daead3dcde8b838105cd69f1ee42d1f407ea3c7f35eb869af6fd58e42
|
7
|
+
data.tar.gz: 4d43eaffc5aafd0822c399a6ee45e31e135e01abd72b4fdd79c1beef773c14672d814eda9edf9336b61c97a05ec1fba1cb13431d91c2966d1a9e2cceff55dd8b
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2022 Nucleus Healthcare LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
# KeycloakRails
|
2
|
+
Keycloak_rails is an api wrapper for open source project [Keycloak](https://www.keycloak.org/)
|
3
|
+
|
4
|
+
* the gem assumes that you have a configured and ready to use keycloak server
|
5
|
+
* the gem is still in beta and the docs does not reflect the latest updates, multiple bugs might occur
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem "keycloak_rails"
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install keycloak_rails
|
22
|
+
```
|
23
|
+
|
24
|
+
## Getting started
|
25
|
+
to generate keycloack_rails initializer execute:
|
26
|
+
```bash
|
27
|
+
$ bundle exec rails g keycloak_rails:config
|
28
|
+
```
|
29
|
+
|
30
|
+
go to `config/initializers/keycloack_rails.rb`
|
31
|
+
|
32
|
+
where you will find
|
33
|
+
```ruby
|
34
|
+
# frozen_string_literal: true
|
35
|
+
|
36
|
+
# Keycloak Rails initializer
|
37
|
+
|
38
|
+
KeycloakRails.configure do |config|
|
39
|
+
####################################################
|
40
|
+
# Rails app controllers to manage auth
|
41
|
+
# config.sessions_controller = 'sessions'
|
42
|
+
# config.registrations_controller = 'registrations'
|
43
|
+
# config.unlocks_controller = 'unlocks'
|
44
|
+
# config.passwords_controller = 'passwords'
|
45
|
+
# config.omniauth_controller = 'omniauth'
|
46
|
+
####################################################
|
47
|
+
# keyclaok rails need your user model name
|
48
|
+
# config.user_model = 'user'
|
49
|
+
####################################################
|
50
|
+
# Auth server info
|
51
|
+
# config.auth_server_url = ''
|
52
|
+
# config.realm = 'realm'
|
53
|
+
# config.public_key = "public_key"
|
54
|
+
# config.secret = ''
|
55
|
+
# config.client_id = 'client_id'
|
56
|
+
####################################################
|
57
|
+
end
|
58
|
+
```
|
59
|
+
uncomment config options and enter your apps info
|
60
|
+
|
61
|
+
**Note** do not uncomment controller config if you just want to use keycloak_rails user/client helpers
|
62
|
+
|
63
|
+
## use
|
64
|
+
### with controller helpers
|
65
|
+
if you decided to use all of keycloak rails functionallity (pass controller options) keycloack rails will automatically hook up to named controllers and extend the base classes with our controller concerns which will provide the following methods
|
66
|
+
#### KeycloakRails::Controller::Helpers
|
67
|
+
***
|
68
|
+
This concern will be inherited by all controllers as it extends application controller
|
69
|
+
|
70
|
+
the following helpers will be added to your app
|
71
|
+
```ruby
|
72
|
+
ensure_active_session # redirects to root if user not logged in
|
73
|
+
ensure_no_active_session # redirects to root if user is logged in
|
74
|
+
current_user # returns current user by session cookie
|
75
|
+
user_has_active_sso_session? # returns true if current user has an active session in auth server
|
76
|
+
```
|
77
|
+
|
78
|
+
#### KeycloakRails::Controller::Sessions
|
79
|
+
***
|
80
|
+
extends the controller passed to `KeycloakRails.config.sessions_controller`
|
81
|
+
|
82
|
+
In your app
|
83
|
+
|
84
|
+
|
85
|
+
`keycloak_rails.rb`
|
86
|
+
```ruby
|
87
|
+
KeycloakRails.configure do |config|
|
88
|
+
config.sessions_controller = 'sessions'
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
`app/controllers/sessions_controller.rb`
|
93
|
+
```ruby
|
94
|
+
class SessionsController < ApplicationController
|
95
|
+
skip_before_action :ensure_active_session, only: %i[new log_in]
|
96
|
+
before_action :ensure_no_active_session, only: %i[new log_in]
|
97
|
+
|
98
|
+
def new; end
|
99
|
+
|
100
|
+
def log_in
|
101
|
+
start_sso_session(params[:email], params[:password])
|
102
|
+
# keycloak_rails will take care of setting the session cookie & current_user for you
|
103
|
+
end
|
104
|
+
|
105
|
+
def log_out
|
106
|
+
end_sso_session
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
|
112
|
+
#### KeycloakRails::Controller::Registrations
|
113
|
+
***
|
114
|
+
The main idea behind keycloack_rails is to make adding sso easy to an existing rails app thats already in prod, and the registrations module is the backbone to achive that.
|
115
|
+
|
116
|
+
In your app
|
117
|
+
|
118
|
+
|
119
|
+
`keycloak_rails.rb`
|
120
|
+
```ruby
|
121
|
+
KeycloakRails.configure do |config|
|
122
|
+
config.registrations_controller = 'registrations'
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
`app/controllers/registrations_controller.rb`
|
127
|
+
```ruby
|
128
|
+
class RegistrationsController < ApplicationController
|
129
|
+
skip_before_action :ensure_active_session, only: %i[new create_user]
|
130
|
+
before_action :ensure_no_active_session, only: %i[new create_user]
|
131
|
+
|
132
|
+
def new; end
|
133
|
+
|
134
|
+
def sign_up
|
135
|
+
sso_user = create_sso_user(email: params[:email], password: params[:password],
|
136
|
+
first_name: params[:first_name], last_name: params[:last_name])
|
137
|
+
user = User.create!(sso_user)
|
138
|
+
# sso_user = { sso_sub: user_keycloak_sub,
|
139
|
+
# email: params[:email],
|
140
|
+
# first_name: params[:first_name],
|
141
|
+
# last_name: params[:last_name] }
|
142
|
+
# as shown above the sso_sub returned from will need to be added to the DB user record
|
143
|
+
# the sso sub is a uniqe identifier generated by keycloak auth server
|
144
|
+
# it can be used to link multiple apps together
|
145
|
+
if user
|
146
|
+
render json: user
|
147
|
+
else
|
148
|
+
render json: user.errors
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
#### KeycloakRails::Controller::Passwords
|
157
|
+
***
|
158
|
+
#### KeycloakRails::Controller::Unlocks
|
159
|
+
***
|
160
|
+
#### KeycloakRails::Controller::Omniauth
|
161
|
+
***
|
162
|
+
|
163
|
+
### without controller helpers
|
164
|
+
#### KeycloakRails::User
|
165
|
+
|
166
|
+
#### KeycloakRails::Client
|
167
|
+
|
168
|
+
|
169
|
+
## Architecte plan
|
170
|
+
|
171
|
+
### Engine Strecture
|
172
|
+
<img width="573" alt="Screen Shot 2022-11-20 at 1 11 50 AM" src="https://user-images.githubusercontent.com/84993125/202890379-b7f8abe9-105c-4d7d-bdf8-c5768f4111af.png">
|
173
|
+
|
174
|
+
### Some use cases
|
175
|
+
|auth request|redirect|protected route request
|
176
|
+
|:-:|:-:|:-:|
|
177
|
+
|![auth request](https://user-images.githubusercontent.com/84993125/202890457-7d58c789-368a-4423-9064-4c50a8ffa296.png)|![redirect](https://user-images.githubusercontent.com/84993125/202890476-2420025e-0f23-4102-8a63-e961411eff16.png)|![protected route request](https://user-images.githubusercontent.com/84993125/202890490-854bda28-2dd3-41b8-8f06-ce80de4825e9.png)
|
178
|
+
|
179
|
+
## Contributing
|
180
|
+
Contribution directions go here.
|
181
|
+
|
182
|
+
## License
|
183
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
184
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
module Generators
|
5
|
+
class ConfigGenerator < Rails::Generators::Base
|
6
|
+
source_root(File.expand_path(File.dirname(__FILE__)))
|
7
|
+
def copy_initializer
|
8
|
+
copy_file '../keycloak_rails.rb', 'config/initializers/keycloak_rails.rb'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root(File.expand_path(File.dirname(__FILE__)))
|
7
|
+
|
8
|
+
TABLE_NAME = 'keycloak_rails_sso'.freeze
|
9
|
+
|
10
|
+
desc "Generates a name space SSO model to store user subs."
|
11
|
+
|
12
|
+
def generate_keycloak_rails_model
|
13
|
+
generate :migration, "create_#{TABLE_NAME}", "recipient:references{polymorphic}", "sub:string:index"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Keycloak Rails initializer
|
4
|
+
|
5
|
+
KeycloakRails.configure do |config|
|
6
|
+
####################################################
|
7
|
+
# config options
|
8
|
+
# decode token strategy can be :local or :cloud
|
9
|
+
# config.decode_token_strategy = :local
|
10
|
+
# config.signature_algo = 'RS256'
|
11
|
+
# config.allow_magic_links = true
|
12
|
+
####################################################
|
13
|
+
# Rails app controllers to manage auth
|
14
|
+
# config.sessions_controller = 'sessions'
|
15
|
+
# config.registrations_controller = 'registrations'
|
16
|
+
# config.unlocks_controller = 'unlocks'
|
17
|
+
# config.passwords_controller = 'passwords'
|
18
|
+
# config.omniauth_controller = 'omniauth'
|
19
|
+
####################################################
|
20
|
+
# Rails app models
|
21
|
+
# config.user_model = 'user'
|
22
|
+
####################################################
|
23
|
+
# Auth server info
|
24
|
+
# config.auth_server_url = ''
|
25
|
+
# config.realm = 'realm'
|
26
|
+
# only needed if decode_token_strategy = :local
|
27
|
+
# config.public_key = "public_key"
|
28
|
+
# config.secret = ''
|
29
|
+
# config.client_id = 'client_id'
|
30
|
+
####################################################
|
31
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# client lvl access to sso server established with client id and secret
|
5
|
+
# can use basic auth or bearer client_token
|
6
|
+
# perms for this lvl of access are controllered by sso server client roles
|
7
|
+
class Client
|
8
|
+
def initialize
|
9
|
+
@curl = KeycloakRails::Curl.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_user(email:, password:, first_name:, last_name:)
|
13
|
+
request = @curl.post(path: "admin/realms/#{KeycloakRails.realm}/users",
|
14
|
+
headers: { 'Authorization': client_token, 'Content-Type': 'application/json' },
|
15
|
+
body: { username: email, email: email, firstName: first_name, lastName: last_name,
|
16
|
+
attributes: {}, groups: [], enabled: true }.to_json)
|
17
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
18
|
+
|
19
|
+
set_perm_password(email, password)
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_user_has_active_session?
|
23
|
+
KeycloakRails.currnet_session_cookie && current_cookie_active?
|
24
|
+
end
|
25
|
+
|
26
|
+
def current_cookie_active?
|
27
|
+
token_introspection['active'] ? true : KeycloakRails.destroy_auth_cookies
|
28
|
+
end
|
29
|
+
|
30
|
+
# private
|
31
|
+
|
32
|
+
def token_introspection
|
33
|
+
request = @curl.post(path: KeycloakRails.openid_config['introspection_endpoint'],
|
34
|
+
headers: { 'Authorization': basic_auth_token,
|
35
|
+
'Content-Type': 'application/x-www-form-urlencoded' },
|
36
|
+
body: { "token": KeycloakRails.currnet_session_cookie })
|
37
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
38
|
+
|
39
|
+
request[:response]
|
40
|
+
end
|
41
|
+
|
42
|
+
def verify_email(user_id)
|
43
|
+
request = @curl.put(path: "/admin/realms/#{KeycloakRails.realm}/users/#{user_id}",
|
44
|
+
headers: { 'Authorization': client_token, 'Content-Type': 'application/json' },
|
45
|
+
body: { "emailVerified": true }.to_json)
|
46
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
47
|
+
|
48
|
+
request[:response]
|
49
|
+
end
|
50
|
+
|
51
|
+
def update_user_attributes(user_id, attributes)
|
52
|
+
request = @curl.put(path: "/admin/realms/#{KeycloakRails.realm}/users/#{user_id}",
|
53
|
+
headers: { 'Authorization': client_token, 'Content-Type': 'application/json' },
|
54
|
+
body: attributes.to_json)
|
55
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
56
|
+
|
57
|
+
request[:response]
|
58
|
+
end
|
59
|
+
|
60
|
+
def require_set_otp(user_email)
|
61
|
+
user = user_by_username(user_email)
|
62
|
+
required_actions = user['requiredActions'].push("CONFIGURE_TOTP")
|
63
|
+
request = @curl.put(path: "/admin/realms/#{KeycloakRails.realm}/users/#{user['id']}",
|
64
|
+
headers: { 'Authorization': client_token, 'Content-Type': 'application/json' },
|
65
|
+
body: { "requiredActions": required_actions }.to_json)
|
66
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
67
|
+
|
68
|
+
request[:response]
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_perm_password(email, password)
|
72
|
+
user = user_by_username(email)
|
73
|
+
request = @curl.put(path: "/admin/realms/#{KeycloakRails.realm}/users/#{user['id']}/reset-password",
|
74
|
+
headers: { 'Authorization': client_token, 'Content-Type': 'application/json' },
|
75
|
+
body: { 'type': 'password', 'temporary': false, 'value': password }.to_json)
|
76
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
77
|
+
|
78
|
+
request[:response]
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_magic_link(email:, redirect_uri:, expiration_seconds: 3600, force_create: false, send_email: false, client_id: KeycloakRails.client_id)
|
82
|
+
request = @curl.post(path: "/auth/realms/#{KeycloakRails.realm}/magic-link",
|
83
|
+
headers: { 'Authorization': client_token, 'Content-Type': 'application/json' },
|
84
|
+
body: { "email": email, "client_id": client_id,
|
85
|
+
"redirect_uri": redirect_uri, "expiration_seconds": expiration_seconds,
|
86
|
+
"force_create": force_create, "update_profile": force_create,
|
87
|
+
"send_email": send_email }.to_json)
|
88
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
89
|
+
|
90
|
+
request[:response]
|
91
|
+
end
|
92
|
+
|
93
|
+
def user_by_username(email)
|
94
|
+
request = @curl.get(path: "admin/realms/#{KeycloakRails.realm}/users?username=#{email}&exact=true",
|
95
|
+
headers: { 'Authorization': client_token, 'Content-Type': 'application/json' },
|
96
|
+
body: { username: email, exact: true }.to_json)
|
97
|
+
|
98
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
99
|
+
|
100
|
+
request[:response]&.first
|
101
|
+
end
|
102
|
+
|
103
|
+
def basic_auth_token
|
104
|
+
"Basic #{Base64.strict_encode64("#{KeycloakRails.client_id}:#{KeycloakRails.secret}")}"
|
105
|
+
end
|
106
|
+
|
107
|
+
# <---- USE WISELY!!!! ----->
|
108
|
+
def client_token
|
109
|
+
"bearer #{fetch_client_token['access_token']}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def fetch_client_token
|
113
|
+
request = @curl.post(path: "realms/#{KeycloakRails.realm}/protocol/openid-connect/token",
|
114
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
115
|
+
body: { 'grant_type': 'client_credentials', 'client_id': KeycloakRails.client_id,
|
116
|
+
'client_secret': KeycloakRails.secret })
|
117
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
118
|
+
|
119
|
+
request[:response]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# name space
|
5
|
+
module Controller
|
6
|
+
# controller helpers added to ActionController::Base class
|
7
|
+
module Helpers
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
helper_method :generate_magic_link, :current_user
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
kick_start_auth_server_connection
|
15
|
+
set_auth_cookies_procs
|
16
|
+
set_destroy_auth_cookies
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def ensure_active_session(accept_magic_link_handshake: false)
|
21
|
+
redirect_to root_path unless user_has_active_sso_session?(accept_magic_link_handshake: accept_magic_link_handshake)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ensure_no_active_session
|
25
|
+
redirect_to root_path if user_has_active_sso_session?
|
26
|
+
end
|
27
|
+
|
28
|
+
def user_has_active_sso_session?(accept_magic_link_handshake: false)
|
29
|
+
(keycloak_client.current_user_has_active_session? && current_user) ||
|
30
|
+
(accept_magic_link_handshake && ensure_magic_link_code)
|
31
|
+
end
|
32
|
+
|
33
|
+
def keycloak_client
|
34
|
+
@keycloak_client ||= KeycloakRails::Client.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def keycloak_user
|
38
|
+
@keycloak_user ||= KeycloakRails::User.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# need to think of away to capture the user model if we wanna set current_user
|
42
|
+
# initial thought: use Dry::Configurable object defined on the Keycloak module to capture the model name and meta program my way from there
|
43
|
+
def current_user
|
44
|
+
@current_user ||= KeycloakRails::Sso.includes(:recipient).find_by(sub: keycloak_user.active_user_sub)&.recipient
|
45
|
+
# have a join table keycloak_rails_subs sso_sub:string #{KeycloakRails.user_model}_id:refrence
|
46
|
+
# to KeycloakRails.user_model.rb and add has_one :keycloak_rails_sub
|
47
|
+
# delegate sso_sub to {KeycloakRails.user_model}
|
48
|
+
#
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy_current_user
|
52
|
+
@current_user = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def destroy_session_cookie
|
56
|
+
cookies.permanent[:keycloak_session_token] = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def kick_start_auth_server_connection
|
60
|
+
keycloak_client
|
61
|
+
keycloak_user
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_auth_cookies_procs
|
65
|
+
KeycloakRails.session_cookie_proc = -> { cookies.encrypted.permanent[:keycloak_session_token] }
|
66
|
+
KeycloakRails.refresh_cookie_proc = -> { cookies.encrypted.permanent[:keycloak_refresh_token] }
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_auth_cookies(tokens)
|
70
|
+
cookies.encrypted.permanent[:keycloak_session_token] = tokens['access_token']
|
71
|
+
cookies.encrypted.permanent[:keycloak_refresh_token] = tokens['refresh_token']
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_destroy_auth_cookies
|
76
|
+
KeycloakRails.destroy_session_cookie_proc = -> { cookies.permanent[:keycloak_session_token] = nil }
|
77
|
+
KeycloakRails.destroy_refresh_cookie_proc = -> { cookies.permanent[:keycloak_refresh_token] = nil }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# name space
|
5
|
+
module Controller
|
6
|
+
# controller helpers added to ActionController::Base class
|
7
|
+
# only loaded if KeycloakRails.allow_magic_link = true
|
8
|
+
module MagicLinks
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
|
13
|
+
def generate_magic_link(url:, email: , expiration_seconds: 3600, force_create: false, send_email: false, client_id: KeycloakRails.client_id)
|
14
|
+
magic_link_obj = keycloak_client.get_magic_link(email: email,
|
15
|
+
redirect_uri: url,
|
16
|
+
expiration_seconds: expiration_seconds,
|
17
|
+
force_create: force_create,
|
18
|
+
send_email: send_email,
|
19
|
+
client_id: client_id)
|
20
|
+
magic_link_obj.except(force_create ? nil : 'user_id')
|
21
|
+
.except(send_email ? nil : 'sent')
|
22
|
+
end
|
23
|
+
|
24
|
+
def ensure_magic_link_code
|
25
|
+
return false unless magic_link_params[:code] && magic_link_params[:session_state]
|
26
|
+
return false unless login_by_handshake
|
27
|
+
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def login_by_handshake(persist_session: true)
|
32
|
+
redirect_uri = url_for(only_path: false, overwrite_params: nil)
|
33
|
+
tokens = keycloak_user.fetch_tokens_by_handshake(code: magic_link_params[:code], redirect_uri: redirect_uri)
|
34
|
+
persist_session ? set_auth_cookies(tokens) : tokens
|
35
|
+
end
|
36
|
+
|
37
|
+
def client_user?
|
38
|
+
!!current_user
|
39
|
+
end
|
40
|
+
|
41
|
+
def magic_link_params
|
42
|
+
params.permit(:session_state, :code)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# name space
|
5
|
+
module Controller
|
6
|
+
# controller helpers added to KeycloakRails.omniauth controller
|
7
|
+
module Omniauth
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# name space
|
5
|
+
module Controller
|
6
|
+
# controller helpers added to KeycloakRails passwords controller
|
7
|
+
module Passwords
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
def reset_password(new_password:, new_password_confirmation:, email: current_user.email)
|
12
|
+
raise StandardError, 'Passwords must match' unless new_password == new_password_confirmation
|
13
|
+
|
14
|
+
keycloak_client.set_perm_password(email, new_password)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# name space
|
5
|
+
module Controller
|
6
|
+
# controller helpers added to KeycloakRails.registrations controller
|
7
|
+
module Registrations
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
def create_or_find_sso_user(email:, password:, first_name:, last_name:, password_confirmation: nil, set_session: true)
|
12
|
+
user = keycloak_client.user_by_username(email)
|
13
|
+
if user
|
14
|
+
{ sso_sub: user['id'], email: email,
|
15
|
+
first_name: first_name, last_name: last_name }
|
16
|
+
else
|
17
|
+
create_sso_user(email: email, password: password, first_name: first_name, last_name: last_name, password_confirmation: password_confirmation, set_session: set_session)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_sso_user(email:, password:, first_name:, last_name:, password_confirmation: nil, set_session: true)
|
22
|
+
raise StandardError, 'Passwords must match' if password_confirmation && password != password_confirmation
|
23
|
+
|
24
|
+
keycloak_client.create_user(email: email,
|
25
|
+
password: password,
|
26
|
+
first_name: first_name,
|
27
|
+
last_name: last_name)
|
28
|
+
if set_session
|
29
|
+
tokens = keycloak_user.fetch_tokens(email: email, password: password)
|
30
|
+
set_auth_cookies(tokens)
|
31
|
+
end
|
32
|
+
{ sso_sub: keycloak_user.active_user_sub, email: email,
|
33
|
+
first_name: first_name, last_name: last_name }
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_sso_record_attributes(user_sub, attributes)
|
37
|
+
attributes = attributes.transform_keys { |key| key.to_s.camelize(:lower) }
|
38
|
+
keycloak_client.update_user_attributes(user_sub, attributes)
|
39
|
+
end
|
40
|
+
|
41
|
+
def mark_email_verified(user_sub)
|
42
|
+
keycloak_client.verify_email(user_sub)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# name space
|
5
|
+
module Controller
|
6
|
+
# controller helpers added to KeycloakRails.sessions controller
|
7
|
+
module Sessions
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
def start_sso_session(email, password)
|
12
|
+
tokens = keycloak_user.fetch_tokens(email: email, password: password)
|
13
|
+
set_auth_cookies(tokens)
|
14
|
+
current_user
|
15
|
+
redirect_to_app_root
|
16
|
+
end
|
17
|
+
|
18
|
+
def end_sso_session
|
19
|
+
redirect_uri = url_for(only_path: false, overwrite_params: nil)
|
20
|
+
keycloak_user.end_session(redirect_uri)
|
21
|
+
destroy_current_user
|
22
|
+
KeycloakRails.destroy_auth_cookies
|
23
|
+
redirect_to_app_root
|
24
|
+
end
|
25
|
+
|
26
|
+
def redirect_to_app_root
|
27
|
+
# redirect_back(fallback_location: root_path)
|
28
|
+
redirect_to root_path
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# name space
|
5
|
+
module Controller
|
6
|
+
# controller helpers added to KeycloakRails.unlocks controller
|
7
|
+
module Unlocks
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# https client with farady v > 2.0.0
|
5
|
+
class Curl
|
6
|
+
def initialize
|
7
|
+
@faraday = Faraday.new(url: KeycloakRails.auth_server_url)
|
8
|
+
end
|
9
|
+
|
10
|
+
def post(path: '', headers: { 'Content-Type': 'application/json' }, body: {})
|
11
|
+
request = @faraday.post(path) do |req|
|
12
|
+
req.headers = headers
|
13
|
+
req.body = body
|
14
|
+
end
|
15
|
+
extract_response(request)
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(path: '', headers: { 'Content-Type': 'application/json' }, body: {})
|
19
|
+
request = @faraday.get(path) do |req|
|
20
|
+
req.headers = headers
|
21
|
+
req.body = body
|
22
|
+
end
|
23
|
+
extract_response(request)
|
24
|
+
end
|
25
|
+
|
26
|
+
def patch(path: '', headers: { 'Content-Type': 'application/json' }, body: {})
|
27
|
+
request = @faraday.patch(path) do |req|
|
28
|
+
req.headers = headers
|
29
|
+
req.body = body
|
30
|
+
end
|
31
|
+
extract_response(request)
|
32
|
+
end
|
33
|
+
|
34
|
+
def put(path: '', headers: { 'Content-Type': 'application/json' }, body: {})
|
35
|
+
request = @faraday.put(path) do |req|
|
36
|
+
req.headers = headers
|
37
|
+
req.body = body
|
38
|
+
end
|
39
|
+
extract_response(request)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def extract_response(request)
|
45
|
+
case request.status
|
46
|
+
when 200..299 then response_to request, message: 'succeeded', status: :ok
|
47
|
+
when 300..399 then response_to request, message: 'succeeded', status: :ok
|
48
|
+
when 400..499 then response_to request, message: 'something went wrong', status: :unprocessed
|
49
|
+
when 500..599 then response_to request, message: 'something went wrong', status: :unprocessed
|
50
|
+
else response_to request, message: 'something went wrong', status: :unprocessed
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def response_to(request, message: "", status: :ok)
|
55
|
+
{ response: request.body && request.body != '' ? JSON.parse(request.body) : {}, message: message, status: status }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
isolate_namespace(KeycloakRails)
|
6
|
+
|
7
|
+
initializer('keycloack_rails', after: :load_config_initializers) do
|
8
|
+
ActionController::Base.include KeycloakRails::Controller::Helpers
|
9
|
+
ActionController::Base.include KeycloakRails::Controller::Sessions
|
10
|
+
ActionController::Base.include KeycloakRails::Controller::Registrations
|
11
|
+
ActionController::Base.include KeycloakRails::Controller::Passwords
|
12
|
+
ActionController::Base.include KeycloakRails::Controller::MagicLinks if KeycloakRails.allow_magic_links
|
13
|
+
end
|
14
|
+
|
15
|
+
config.after_initialize do
|
16
|
+
if KeycloakRails.user_model
|
17
|
+
user_klass = KeycloakRails.user_model
|
18
|
+
user_klass.singularize.classify.constantize.include KeycloakRails::SsoRecipient
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeycloakRails
|
4
|
+
# User lvl access to sso server established session_token after auth with username and password
|
5
|
+
# min perms
|
6
|
+
class User
|
7
|
+
def initialize
|
8
|
+
@curl = KeycloakRails::Curl.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch_tokens(email:, password:, otp_password: nil)
|
12
|
+
request = @curl.post(path: KeycloakRails.openid_config['token_endpoint'],
|
13
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
14
|
+
body: { 'grant_type': 'password', 'client_id': KeycloakRails.client_id,
|
15
|
+
'client_secret': KeycloakRails.secret, 'username': email,
|
16
|
+
'password': password }.merge((otp_password ? { 'totp': otp_password } : {})))
|
17
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
18
|
+
|
19
|
+
request[:response]
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch_tokens_by_handshake(code:, redirect_uri:)
|
23
|
+
request = @curl.post(path: KeycloakRails.openid_config['token_endpoint'],
|
24
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
25
|
+
body: { 'grant_type': 'authorization_code', 'client_id': KeycloakRails.client_id,
|
26
|
+
'client_secret': KeycloakRails.secret, code: code,
|
27
|
+
"redirect_uri": redirect_uri })
|
28
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
29
|
+
|
30
|
+
request[:response]
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch_current_user_info
|
34
|
+
request = @curl.post(path: KeycloakRails.openid_config['userinfo_endpoint'],
|
35
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
36
|
+
body: { access_token: access_token })
|
37
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
38
|
+
|
39
|
+
request[:response]
|
40
|
+
end
|
41
|
+
|
42
|
+
def end_session(redirect_uri)
|
43
|
+
request = @curl.post(path: KeycloakRails.openid_config['end_session_endpoint'],
|
44
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
45
|
+
body: { 'client_id': KeycloakRails.client_id,
|
46
|
+
'client_secret': KeycloakRails.secret,
|
47
|
+
'refresh_token': KeycloakRails.current_refresh_cookie,
|
48
|
+
'post_logout_redirect_uri': redirect_uri })
|
49
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
50
|
+
|
51
|
+
request[:response]
|
52
|
+
end
|
53
|
+
|
54
|
+
# gets user sub by making an api call to auth server
|
55
|
+
def fetch_active_user_sub
|
56
|
+
fetch_current_user_info['sub']
|
57
|
+
end
|
58
|
+
|
59
|
+
# gets user sub by decoding the session_cookie
|
60
|
+
def decode_active_user_sub
|
61
|
+
decoded_access_token.first['sub']
|
62
|
+
end
|
63
|
+
|
64
|
+
def active_user_sub
|
65
|
+
return unless access_token
|
66
|
+
case KeycloakRails.decode_token_strategy
|
67
|
+
when :local then decode_active_user_sub
|
68
|
+
when :cloud then fetch_active_user_sub
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# private
|
73
|
+
|
74
|
+
def access_token
|
75
|
+
KeycloakRails.currnet_session_cookie
|
76
|
+
end
|
77
|
+
|
78
|
+
def decoded_access_token
|
79
|
+
decode(access_token)
|
80
|
+
end
|
81
|
+
|
82
|
+
def decoded_refresh_token
|
83
|
+
decode(refresh_token)
|
84
|
+
end
|
85
|
+
|
86
|
+
def decode(token)
|
87
|
+
JWT.decode token, KeycloakRails.public_key, false, { algorithm: KeycloakRails.signature_algo }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# requires all dependencies
|
4
|
+
require 'faraday'
|
5
|
+
require 'jwt'
|
6
|
+
require 'dry/configurable'
|
7
|
+
|
8
|
+
# requires all modules and classes
|
9
|
+
require 'keycloak_rails/version'
|
10
|
+
require 'keycloak_rails/engine'
|
11
|
+
require 'keycloak_rails/client'
|
12
|
+
require 'keycloak_rails/user'
|
13
|
+
require 'keycloak_rails/curl'
|
14
|
+
require 'keycloak_rails/controller/helpers'
|
15
|
+
require 'keycloak_rails/controller/magic_links'
|
16
|
+
require 'keycloak_rails/controller/omniauth'
|
17
|
+
require 'keycloak_rails/controller/passwords'
|
18
|
+
require 'keycloak_rails/controller/registrations'
|
19
|
+
require 'keycloak_rails/controller/sessions'
|
20
|
+
require 'keycloak_rails/controller/unlocks'
|
21
|
+
require 'app/models/keycloak_rails/sso'
|
22
|
+
require 'app/models/keycloak_rails/concerns/sso_recipient'
|
23
|
+
|
24
|
+
module KeycloakRails
|
25
|
+
extend Dry::Configurable
|
26
|
+
|
27
|
+
setting :sessions_controller, reader: true
|
28
|
+
setting :registrations_controller, reader: true
|
29
|
+
setting :unlocks_controller, reader: true
|
30
|
+
setting :passwords_controller, reader: true
|
31
|
+
setting :omniauth_controller, reader: true
|
32
|
+
setting :user_model, reader: true
|
33
|
+
setting :realm, reader: true
|
34
|
+
setting :public_key, reader: true
|
35
|
+
setting :auth_server_url, reader: true
|
36
|
+
setting :secret, reader: true
|
37
|
+
setting :client_id, reader: true
|
38
|
+
setting :decode_token_strategy, reader: true, default: :local
|
39
|
+
setting :signature_algo, reader: true
|
40
|
+
setting :allow_magic_links, reader: true, default: false
|
41
|
+
|
42
|
+
class << self
|
43
|
+
attr_accessor :session_cookie_proc, :destroy_session_cookie_proc, :refresh_cookie_proc, :destroy_refresh_cookie_proc
|
44
|
+
|
45
|
+
def currnet_session_cookie
|
46
|
+
session_cookie_proc.call
|
47
|
+
end
|
48
|
+
|
49
|
+
def current_refresh_cookie
|
50
|
+
refresh_cookie_proc.call
|
51
|
+
end
|
52
|
+
|
53
|
+
def destroy_auth_cookies
|
54
|
+
destroy_session_cookie_proc.call
|
55
|
+
destroy_refresh_cookie_proc.call
|
56
|
+
end
|
57
|
+
|
58
|
+
def openid_config
|
59
|
+
@openid_config ||= fetch_openid_configuration
|
60
|
+
end
|
61
|
+
|
62
|
+
def fetch_openid_configuration
|
63
|
+
request = Curl.new.get(path: "realms/#{realm}/.well-known/openid-configuration",
|
64
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' })
|
65
|
+
raise StandardError, request[:response] unless request[:status] == :ok
|
66
|
+
|
67
|
+
request[:response]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: keycloak_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.pre.beta
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Omar Luqman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-configurable
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.16'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: jwt
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 6.0.3
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 6.0.3
|
69
|
+
description: A rails wrapper for open source SSO project Keycloak.
|
70
|
+
email:
|
71
|
+
- oluqman@nucleushealthcare.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- MIT-LICENSE
|
77
|
+
- README.md
|
78
|
+
- Rakefile
|
79
|
+
- lib/app/models/keycloak_rails/concerns/sso_recipient.rb
|
80
|
+
- lib/app/models/keycloak_rails/sso.rb
|
81
|
+
- lib/generators/keycloak_rails/config/config_generator.rb
|
82
|
+
- lib/generators/keycloak_rails/install/install_generator.rb
|
83
|
+
- lib/generators/keycloak_rails/keycloak_rails.rb
|
84
|
+
- lib/keycloak_rails.rb
|
85
|
+
- lib/keycloak_rails/client.rb
|
86
|
+
- lib/keycloak_rails/controller/helpers.rb
|
87
|
+
- lib/keycloak_rails/controller/magic_links.rb
|
88
|
+
- lib/keycloak_rails/controller/omniauth.rb
|
89
|
+
- lib/keycloak_rails/controller/passwords.rb
|
90
|
+
- lib/keycloak_rails/controller/registrations.rb
|
91
|
+
- lib/keycloak_rails/controller/sessions.rb
|
92
|
+
- lib/keycloak_rails/controller/unlocks.rb
|
93
|
+
- lib/keycloak_rails/curl.rb
|
94
|
+
- lib/keycloak_rails/engine.rb
|
95
|
+
- lib/keycloak_rails/user.rb
|
96
|
+
- lib/keycloak_rails/version.rb
|
97
|
+
homepage: https://github.com/Laborocity/keycloak_rails
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
metadata:
|
101
|
+
homepage_uri: https://github.com/Laborocity/keycloak_rails
|
102
|
+
source_code_uri: https://github.com/Laborocity/keycloak_rails
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 1.3.1
|
117
|
+
requirements: []
|
118
|
+
rubygems_version: 3.3.7
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: "%q{API wrapper for Key Cloak SSO server.}"
|
122
|
+
test_files: []
|