rails_keycloak_authorization 0.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +139 -0
- data/Rakefile +8 -0
- data/app/assets/config/rails_keycloak_authorization_manifest.js +1 -0
- data/app/assets/stylesheets/rails_keycloak_authorization/application.css +15 -0
- data/app/controllers/concerns/rails_keycloak_authorization/with_htmx_layout.rb +15 -0
- data/app/controllers/concerns/rails_keycloak_authorization/with_keycloak_admin.rb +35 -0
- data/app/controllers/concerns/rails_keycloak_authorization/with_routes_reader.rb +26 -0
- data/app/controllers/rails_keycloak_authorization/application_controller.rb +4 -0
- data/app/controllers/rails_keycloak_authorization/management_controller.rb +9 -0
- data/app/controllers/rails_keycloak_authorization/permissions_controller.rb +39 -0
- data/app/controllers/rails_keycloak_authorization/policies_controller.rb +19 -0
- data/app/controllers/rails_keycloak_authorization/resources_controller.rb +31 -0
- data/app/controllers/rails_keycloak_authorization/routes_controller.rb +23 -0
- data/app/controllers/rails_keycloak_authorization/scopes_controller.rb +48 -0
- data/app/helpers/rails_keycloak_authorization/application_helper.rb +4 -0
- data/app/helpers/rails_keycloak_authorization/resources_helper.rb +5 -0
- data/app/jobs/rails_keycloak_authorization/application_job.rb +4 -0
- data/app/mailers/rails_keycloak_authorization/application_mailer.rb +6 -0
- data/app/models/rails_keycloak_authorization/application_record.rb +5 -0
- data/app/services/rails_keycloak_authorization/keycloak_admin_ruby_agent.rb +145 -0
- data/app/views/layouts/rails_keycloak_authorization/application.html.erb +14 -0
- data/app/views/layouts/rails_keycloak_authorization/htmx.html.erb +14 -0
- data/app/views/rails_keycloak_authorization/management/index.html.erb +79 -0
- data/app/views/rails_keycloak_authorization/permissions/index.html.erb +41 -0
- data/app/views/rails_keycloak_authorization/permissions/resource_scopes_select.html.erb +7 -0
- data/app/views/rails_keycloak_authorization/policies/index.html.erb +23 -0
- data/app/views/rails_keycloak_authorization/resources/index.html.erb +5 -0
- data/app/views/rails_keycloak_authorization/resources/new.html.erb +4 -0
- data/app/views/rails_keycloak_authorization/resources/show.html.erb +28 -0
- data/app/views/rails_keycloak_authorization/routes/index.html.erb +58 -0
- data/app/views/rails_keycloak_authorization/routes/show.html.erb +6 -0
- data/app/views/rails_keycloak_authorization/scopes/index.html.erb +9 -0
- data/app/views/rails_keycloak_authorization/scopes/show.html.erb +33 -0
- data/config/routes.rb +13 -0
- data/lib/rails_keycloak_authorization/engine.rb +8 -0
- data/lib/rails_keycloak_authorization/version.rb +3 -0
- data/lib/rails_keycloak_authorization.rb +77 -0
- data/lib/tasks/rails_keycloak_authorization_tasks.rake +170 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 473a44e0abf7c0db82ad24417b0e20a546219e28967dc41cdee8753a954b0c3a
|
4
|
+
data.tar.gz: d2d21f2d7c7c291fcfc267ed9e7b83c66684714d952341b875d46ac2db2bdad8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dccfd5e98b565f2c1da1911ab0d588b08a96a15790b0c6147e868c57b7f4f4e7fd444a9a1fa2280ccb4ca1d303d6a4601db8475931e511e2d6bcd8a0170417a7
|
7
|
+
data.tar.gz: f8175f8a52eb1e2bac762fe7e3ba4e2cac8ffdc322291e667901f958781c551d06920e3f80bcdb4c25f2430069e6e50f1a13f3153b5c9681117aef07d9e1a817
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Mohammed O. Tillawy
|
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,139 @@
|
|
1
|
+
# Rails Keycloak Authorization
|
2
|
+
|
3
|
+
Rails middleware to authorize requests using [Keycloak](https://www.keycloak.org) and gem [keycloak-admin-ruby](https://github.com/looorent/keycloak-admin-ruby).
|
4
|
+
|
5
|
+
This gem uses JWT token to authorize requests.
|
6
|
+
To read more how this gem works:
|
7
|
+
|
8
|
+
* [Keycloak authorization services](https://www.keycloak.org/docs/latest/authorization_services/index.html#_service_overview)
|
9
|
+
* [Policy Enforcement Point (PEP)](https://www.keycloak.org/docs/latest/authorization_services/index.html#_enforcer_overview)
|
10
|
+
|
11
|
+
|
12
|
+
For the moment it only support permission_resource_format=uri. it does not support permission_resource_format=resource.
|
13
|
+
|
14
|
+
It does **not** support rails cookie-based-sessions, so it is only suitable for APIs.
|
15
|
+
|
16
|
+
This gem uses regular-expression for URLs matching, so it is very powerful and flexible.
|
17
|
+
|
18
|
+
## How it works
|
19
|
+
|
20
|
+
This gem is a middleware that checks if the request is authorized by Keycloak.
|
21
|
+
It will check if the request's token is valid and if the user has the required roles to access the requested resource.
|
22
|
+
|
23
|
+
Keycloak setup for authorization has many options, the following conventions were followed building this gem:
|
24
|
+
|
25
|
+
| Rails component | Keycloak component |
|
26
|
+
|-------------------|---------------------|
|
27
|
+
| Controller | Authz Resource |
|
28
|
+
| Controller Action | Authz Scope |
|
29
|
+
| Route | permission subject |
|
30
|
+
|
31
|
+
|
32
|
+
## Flow
|
33
|
+
|
34
|
+
```mermaid
|
35
|
+
sequenceDiagram
|
36
|
+
actor User
|
37
|
+
User->>Application: Request ${URL} with ${JWT_TOKEN}
|
38
|
+
create participant Keycloak
|
39
|
+
Application-->>Keycloak: is ${JWT_TOKEN} authorized for ${URL}?
|
40
|
+
note right of Keycloak: Keycloak will validate the token <br/> and check if the user has the required roles
|
41
|
+
destroy Keycloak
|
42
|
+
Keycloak-->>Application: ${JWT_TOKEN} is authorized for ${URL}
|
43
|
+
destroy Application
|
44
|
+
Application-->>User: Response ${URL} with ${DATA}
|
45
|
+
```
|
46
|
+
|
47
|
+
|
48
|
+
## Configuration
|
49
|
+
|
50
|
+
In order to use this gem, you need to configure it in an initializer file. You can create a new file in `config/initializers/rails_keycloak_authorization.rb` with the following content:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
# The Keycloak realm
|
54
|
+
RailsKeycloakAuthorization.keycloak_realm = ENV.fetch("KEYCLOAK_AUTH_CLIENT_REALM_NAME", "dummy")
|
55
|
+
# The client id in the realm
|
56
|
+
RailsKeycloakAuthorization.client_id = ENV.fetch("KEYCLOAK_AUTH_CLIENT_ID", "dummy-client")
|
57
|
+
# Keycloak server url
|
58
|
+
RailsKeycloakAuthorization.keycloak_server_url = ENV.fetch("KEYCLOAK_SERVER_URL", "http://localhost:8080")
|
59
|
+
# Patterns that are protected by the middleware, Array of regular-expressions.
|
60
|
+
RailsKeycloakAuthorization.match_patterns = [
|
61
|
+
/^\/organizations(\.json)?/,
|
62
|
+
/^\/api/,
|
63
|
+
/internal/
|
64
|
+
]
|
65
|
+
```
|
66
|
+
|
67
|
+
## How to easily test it?
|
68
|
+
|
69
|
+
Create development environment with Keycloak and Tofu:
|
70
|
+
* checkout the source-code of this project
|
71
|
+
* `git checkout https://github.com/tillawy/rails_keycloak_authorization.git`
|
72
|
+
* `cd rails_keycloak_authorization`
|
73
|
+
* Run keycloak in a [Docker](https://docs.docker.com/get-docker/) container
|
74
|
+
* `cd docker`
|
75
|
+
* `docker-compose up`
|
76
|
+
* verify keycloak is running at `http://localhost:8080`, username: `admin`, password: `admin`
|
77
|
+
* Run tofu to setup keycloak realm & client
|
78
|
+
* `brew install opentofu`
|
79
|
+
* `cd ../tofu`
|
80
|
+
* `tofu -chdir=tofu init`
|
81
|
+
* `tofu -chdir=tofu apply -auto-approve`
|
82
|
+
|
83
|
+
Running the previous steps should:
|
84
|
+
* Start Keycloak server
|
85
|
+
* Create realm called: `Dummy`
|
86
|
+
* Create openid-client called: `dummy-client` in realm `dummy` with:
|
87
|
+
* client secret `dummy-client-super-secret-xxx`
|
88
|
+
* valid_redirect_uri `http://localhost:3000/*`
|
89
|
+
* Create user `test@test.com` with password `test`
|
90
|
+
* Create openid-client called: `keycloak-admin` in realm `master` with:
|
91
|
+
* client secret `keycloak-admin-client-secret-xxx`
|
92
|
+
* role to manager users in realm `dummy`
|
93
|
+
|
94
|
+
Run the server:
|
95
|
+
|
96
|
+
`bundle exec rails s`
|
97
|
+
|
98
|
+
make the first request (should fail) `Authorization Failed`:
|
99
|
+
|
100
|
+
```shell
|
101
|
+
bash test/curl/test.curl.bash
|
102
|
+
```
|
103
|
+
|
104
|
+
How let us setup Authorization:
|
105
|
+
|
106
|
+
* Open rka http://localhost:3000/rka/management/
|
107
|
+
* On the first tab `Rails Routes`
|
108
|
+
* The first route `/organizations(.:format)`, click inspect
|
109
|
+
* Click on `Create Resource?` to create Authz Resource for controller
|
110
|
+
* Click on `Create Scope?` to create resource for controller action
|
111
|
+
* Click on `Attach scope index to resource` to attach the scope (action: index) to the resource (controller: organizations_controller)
|
112
|
+
* Select the second tab `Keycloak Policies`
|
113
|
+
* From the Role dropdown list select `default-roles-dummy`
|
114
|
+
* Click `Create`
|
115
|
+
* Select the third tab `Keycloak Permissions`
|
116
|
+
* From the Policy dropdown list select `RKA-Policy`
|
117
|
+
* From the Resource dropdown list select `organization_controllers`
|
118
|
+
* Another dropdown will appear Select Scope, select `index`
|
119
|
+
* Click `Create`
|
120
|
+
|
121
|
+
Now let us run the test `bash test/curl/test.curl.bash` again, it should pass.
|
122
|
+
|
123
|
+
## Installation
|
124
|
+
Add this line to your application's Gemfile:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
gem "rails_keycloak_authorization"
|
128
|
+
```
|
129
|
+
|
130
|
+
And then execute:
|
131
|
+
```bash
|
132
|
+
$ bundle
|
133
|
+
```
|
134
|
+
|
135
|
+
## Contributing
|
136
|
+
Contribution directions go here.
|
137
|
+
|
138
|
+
## License
|
139
|
+
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 @@
|
|
1
|
+
//= link_directory ../stylesheets/rails_keycloak_authorization .css
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RailsKeycloakAuthorization
|
2
|
+
# This concern is to explicitly force htmx layout with routes, layout=htmx
|
3
|
+
module WithHtmxLayout
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
included do
|
6
|
+
before_action do
|
7
|
+
if request.params[:layout] == "htmx"
|
8
|
+
self.class.layout "rails_keycloak_authorization/htmx"
|
9
|
+
else
|
10
|
+
self.class.layout false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RailsKeycloakAuthorization
|
2
|
+
module WithKeycloakAdmin
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
included do
|
5
|
+
before_action :keycloak_admin_configure
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
def realm_name
|
15
|
+
ENV.fetch('KEYCLOAK_AUTH_CLIENT_REALM_NAME')
|
16
|
+
end
|
17
|
+
|
18
|
+
def openid_client
|
19
|
+
KeycloakAdmin.realm(realm_name).clients.find_by_client_id(ENV["KEYCLOAK_AUTH_CLIENT_ID"])
|
20
|
+
end
|
21
|
+
|
22
|
+
def keycloak_admin_configure
|
23
|
+
KeycloakAdmin.configure do |config|
|
24
|
+
config.use_service_account = true
|
25
|
+
config.server_url = ENV["KEYCLOAK_SERVER_URL"]
|
26
|
+
config.server_domain = ENV["KEYCLOAK_SERVER_DOMAIN"]
|
27
|
+
config.client_id = ENV["KEYCLOAK_ADMIN_CLIENT_ID"]
|
28
|
+
config.client_realm_name = ENV["KEYCLOAK_ADMIN_REALM_NAME"]
|
29
|
+
config.client_secret = ENV["KEYCLOAK_ADMIN_CLIENT_SECRET"]
|
30
|
+
config.logger = Rails.logger
|
31
|
+
config.rest_client_options = { timeout: 3, verify_ssl: Rails.env.production? }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsKeycloakAuthorization
|
4
|
+
module WithRoutesReader
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def available_routes
|
9
|
+
available_route_names.map do |name|
|
10
|
+
Rails.application.routes.named_routes.get(name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def route(route_name)
|
15
|
+
Rails.application.routes.named_routes[route_name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def available_route_names
|
19
|
+
names = Rails.application.routes.named_routes.names.filter do |route_name|
|
20
|
+
route_name.present? && !%w[rails action_mailbox active_storage auth root].any? { |exclude| route_name.to_s.include?(exclude) }
|
21
|
+
end
|
22
|
+
names.uniq
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RailsKeycloakAuthorization
|
2
|
+
class PermissionsController < ApplicationController
|
3
|
+
include WithKeycloakAdmin
|
4
|
+
include WithHtmxLayout
|
5
|
+
include ResourcesHelper
|
6
|
+
|
7
|
+
def index
|
8
|
+
@permissions = KeycloakAdminRubyAgent.list_keycloak_permissions
|
9
|
+
@resources = KeycloakAdminRubyAgent.list_keycloak_resources_for_controllers
|
10
|
+
@policies = KeycloakAdminRubyAgent.list_keycloak_policies
|
11
|
+
end
|
12
|
+
|
13
|
+
def resource_scopes_select
|
14
|
+
@resource_scopes = KeycloakAdmin
|
15
|
+
.realm(realm_name)
|
16
|
+
.authz_scopes(openid_client.id, params[:keycloak_resource_id])
|
17
|
+
.list
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
resource = KeycloakAdmin.realm(realm_name).authz_resources(openid_client.id).get(params[:keycloak_resource_id] )
|
22
|
+
scope = KeycloakAdmin.realm(realm_name).authz_scopes(openid_client.id, resource.id).get(params[:keycloak_scope_id])
|
23
|
+
KeycloakAdmin
|
24
|
+
.realm(realm_name)
|
25
|
+
.authz_permissions(openid_client.id, :scope)
|
26
|
+
.create!(
|
27
|
+
"RKA #{resource.name} #{scope.name} ",
|
28
|
+
"RailsKeycloakRails permission #{resource.name}",
|
29
|
+
"UNANIMOUS",
|
30
|
+
"POSITIVE",
|
31
|
+
[ params[:keycloak_resource_id] ],
|
32
|
+
[ params[:keycloak_policy_id] ],
|
33
|
+
[ params[:keycloak_scope_id] ],
|
34
|
+
"scope"
|
35
|
+
)
|
36
|
+
redirect_to permissions_path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RailsKeycloakAuthorization
|
2
|
+
class PoliciesController < ApplicationController
|
3
|
+
include WithKeycloakAdmin
|
4
|
+
include WithHtmxLayout
|
5
|
+
include ResourcesHelper
|
6
|
+
|
7
|
+
|
8
|
+
def index
|
9
|
+
@default_policy_name = KeycloakAdminRubyAgent.policy_name
|
10
|
+
@policies = KeycloakAdminRubyAgent.list_keycloak_policies
|
11
|
+
@realm_roles = KeycloakAdminRubyAgent.list_roles
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
KeycloakAdminRubyAgent.create_keycloak_policy(params[:keycloak_realm_role_id])
|
16
|
+
redirect_to policies_path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module RailsKeycloakAuthorization
|
2
|
+
class ResourcesController < ApplicationController
|
3
|
+
include WithKeycloakAdmin
|
4
|
+
include WithHtmxLayout
|
5
|
+
include ResourcesHelper
|
6
|
+
include WithRoutesReader
|
7
|
+
|
8
|
+
def create
|
9
|
+
KeycloakAdminRubyAgent.create_keycloak_resource(params[:route_id])
|
10
|
+
redirect_to resource_path(params[:route_id])
|
11
|
+
end
|
12
|
+
|
13
|
+
def index
|
14
|
+
@route_names = Rails.application.routes.named_routes.names
|
15
|
+
@controller_names = @route_names.map { |route_name|
|
16
|
+
Rails.application.routes.named_routes[route_name].defaults[:controller]
|
17
|
+
}.filter{|route_name|
|
18
|
+
!route_name.nil? && !route_name.include?("rails") && !route_name.include?("action_mailbox") && !route_name.include?("active_storage") && !route_name.include?("oauth") }.uniq
|
19
|
+
@resources = KeycloakAdmin.realm(realm_name).authz_resources(openid_client.id).list
|
20
|
+
end
|
21
|
+
|
22
|
+
def new
|
23
|
+
@route = route(params[:route_id])
|
24
|
+
end
|
25
|
+
|
26
|
+
def show
|
27
|
+
@route = route(params[:id])
|
28
|
+
@keycloak_resource = KeycloakAdminRubyAgent.keycloak_resource(@route.defaults[:controller])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RailsKeycloakAuthorization
|
2
|
+
class RoutesController < ApplicationController
|
3
|
+
include WithKeycloakAdmin
|
4
|
+
include WithHtmxLayout
|
5
|
+
include WithRoutesReader
|
6
|
+
|
7
|
+
before_action :set_route, only: [:show]
|
8
|
+
|
9
|
+
def index
|
10
|
+
@routes = available_routes
|
11
|
+
end
|
12
|
+
|
13
|
+
def show
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
|
19
|
+
def set_route
|
20
|
+
@route = Rails.application.routes.named_routes[params[:id]]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RailsKeycloakAuthorization
|
2
|
+
class ScopesController < ApplicationController
|
3
|
+
include WithKeycloakAdmin
|
4
|
+
include WithHtmxLayout
|
5
|
+
|
6
|
+
def index
|
7
|
+
@scopes = KeycloakAdmin
|
8
|
+
.realm(realm_name)
|
9
|
+
.authz_scopes(openid_client.id, params[:keycloak_resource_id])
|
10
|
+
.list
|
11
|
+
end
|
12
|
+
|
13
|
+
def show
|
14
|
+
@keycloak_scope_name = params[:keycloak_scope_name]
|
15
|
+
@keycloak_resource_id = params[:keycloak_resource_id]
|
16
|
+
|
17
|
+
@available_scope = KeycloakAdmin
|
18
|
+
.realm(realm_name)
|
19
|
+
.authz_scopes(openid_client.id)
|
20
|
+
.search(params[:keycloak_scope_name])
|
21
|
+
.first
|
22
|
+
|
23
|
+
@resource_scope = KeycloakAdmin
|
24
|
+
.realm(realm_name)
|
25
|
+
.authz_scopes(openid_client.id, params[:keycloak_resource_id])
|
26
|
+
.list
|
27
|
+
.detect{|s| s.name == params[:keycloak_scope_name]}
|
28
|
+
end
|
29
|
+
|
30
|
+
def new
|
31
|
+
@keycloak_scope_name = params[:keycloak_scope_name]
|
32
|
+
@keycloak_resource_id = params[:keycloak_resource_id]
|
33
|
+
end
|
34
|
+
|
35
|
+
def attach
|
36
|
+
keycloak_scope_name = params[:keycloak_scope_name]
|
37
|
+
keycloak_resource_id = params[:keycloak_resource_id]
|
38
|
+
|
39
|
+
KeycloakAdminRubyAgent.attach_scope_to_resource(keycloak_scope_name, keycloak_resource_id)
|
40
|
+
redirect_to scope_path("scope", keycloak_resource_id: keycloak_resource_id, keycloak_scope_name: keycloak_scope_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create
|
44
|
+
scope = KeycloakAdminRubyAgent.create_keycloak_scope(params[:keycloak_scope_name])
|
45
|
+
redirect_to scope_path(scope.id, keycloak_resource_id: params[:keycloak_resource_id], keycloak_scope_name: params[:keycloak_scope_name])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module RailsKeycloakAuthorization
|
2
|
+
class KeycloakAdminRubyAgent
|
3
|
+
class << self
|
4
|
+
|
5
|
+
POLICY_NAME = "RKA-Policy"
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
keycloak_admin_configure
|
10
|
+
end
|
11
|
+
|
12
|
+
def policy_name
|
13
|
+
POLICY_NAME
|
14
|
+
end
|
15
|
+
|
16
|
+
def list_keycloak_resources_for_controllers
|
17
|
+
KeycloakAdmin.realm(realm_name)
|
18
|
+
.authz_resources(openid_client.id)
|
19
|
+
.find_by("",
|
20
|
+
resource_type_for_controller,
|
21
|
+
"",
|
22
|
+
"",
|
23
|
+
"")
|
24
|
+
end
|
25
|
+
|
26
|
+
def list_keycloak_permissions
|
27
|
+
KeycloakAdmin.realm(realm_name)
|
28
|
+
.authz_permissions(openid_client.id, "scope")
|
29
|
+
.find_by(nil, nil)
|
30
|
+
end
|
31
|
+
|
32
|
+
def list_keycloak_policies
|
33
|
+
KeycloakAdmin.realm(realm_name)
|
34
|
+
.authz_policies(openid_client.id, 'role')
|
35
|
+
.find_by(POLICY_NAME, "role")
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_keycloak_policy(keycloak_realm_role_id)
|
39
|
+
KeycloakAdmin
|
40
|
+
.realm(realm_name)
|
41
|
+
.authz_policies(openid_client.id, 'role')
|
42
|
+
.create!("#{POLICY_NAME}",
|
43
|
+
"#{POLICY_NAME} default policy",
|
44
|
+
"role",
|
45
|
+
"POSITIVE",
|
46
|
+
"UNANIMOUS",
|
47
|
+
true,
|
48
|
+
[{id: keycloak_realm_role_id, required: true}]
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def list_policies
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def list_roles
|
57
|
+
KeycloakAdmin.realm(realm_name).roles.list
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_keycloak_scope(keycloak_scope_name)
|
61
|
+
KeycloakAdmin
|
62
|
+
.realm(realm_name)
|
63
|
+
.authz_scopes(openid_client.id)
|
64
|
+
.create!(
|
65
|
+
keycloak_scope_name,
|
66
|
+
"RKA #{keycloak_scope_name}",
|
67
|
+
""
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_keycloak_resource(route_id)
|
72
|
+
route = WithRoutesReader.route(route_id)
|
73
|
+
resource_name = resource_name_for(route.defaults[:controller])
|
74
|
+
|
75
|
+
KeycloakAdmin
|
76
|
+
.realm(realm_name)
|
77
|
+
.authz_resources(openid_client.id)
|
78
|
+
.create!(
|
79
|
+
resource_name,
|
80
|
+
resource_type_for_controller,
|
81
|
+
[],
|
82
|
+
true,
|
83
|
+
"RKA #{resource_name}",
|
84
|
+
[])
|
85
|
+
end
|
86
|
+
|
87
|
+
def keycloak_resource(controller_name)
|
88
|
+
resource_name = resource_name_for(controller_name)
|
89
|
+
KeycloakAdmin
|
90
|
+
.realm(realm_name)
|
91
|
+
.authz_resources(openid_client.id)
|
92
|
+
.find_by(resource_name,
|
93
|
+
resource_type_for_controller,
|
94
|
+
"",
|
95
|
+
"",
|
96
|
+
"")
|
97
|
+
.first
|
98
|
+
rescue
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def realm_name
|
103
|
+
ENV.fetch("KEYCLOAK_AUTH_CLIENT_REALM_NAME")
|
104
|
+
end
|
105
|
+
|
106
|
+
def openid_client
|
107
|
+
KeycloakAdmin
|
108
|
+
.realm(realm_name)
|
109
|
+
.clients
|
110
|
+
.find_by_client_id(ENV.fetch("KEYCLOAK_AUTH_CLIENT_ID"))
|
111
|
+
end
|
112
|
+
|
113
|
+
def resource_type_for_controller
|
114
|
+
type_for(openid_client.client_id)
|
115
|
+
end
|
116
|
+
|
117
|
+
def type_for(openid_client_id)
|
118
|
+
"urn:#{openid_client_id}:rka:resources:controllers"
|
119
|
+
end
|
120
|
+
def resource_name_for(controller_name)
|
121
|
+
"#{controller_name}_controller"
|
122
|
+
end
|
123
|
+
|
124
|
+
def keycloak_admin_configure
|
125
|
+
KeycloakAdmin.configure do |config|
|
126
|
+
config.use_service_account = true
|
127
|
+
config.server_url = ENV.fetch("KEYCLOAK_SERVER_URL")
|
128
|
+
config.server_domain = ENV.fetch("KEYCLOAK_SERVER_DOMAIN")
|
129
|
+
config.client_id = ENV.fetch("KEYCLOAK_ADMIN_CLIENT_ID")
|
130
|
+
config.client_realm_name = ENV.fetch("KEYCLOAK_ADMIN_REALM_NAME")
|
131
|
+
config.client_secret = ENV.fetch("KEYCLOAK_ADMIN_CLIENT_SECRET")
|
132
|
+
config.logger = Rails.logger
|
133
|
+
config.rest_client_options = { timeout: 5, verify_ssl: Rails.env.production? }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.attach_scope_to_resource(keycloak_scope_name, keycloak_resource_id)
|
139
|
+
KeycloakAdmin.realm(realm_name)
|
140
|
+
.authz_resources(openid_client.id)
|
141
|
+
.update(keycloak_resource_id, scopes: [{name: keycloak_scope_name}])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Rails Keycloak Authorization</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
<%= stylesheet_link_tag "rails_keycloak_authorization/application", media: "all" %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Rails Keycloak Authorization</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
<script src="https://unpkg.com/htmx.org@2.0.0/dist/htmx.js" integrity="sha384-Xh+GLLi0SMFPwtHQjT72aPG19QvKB8grnyRbYBNIdHWc2NkCrz65jlU7YrzO6qRp" crossorigin="anonymous"></script>
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
9
|
+
<%= stylesheet_link_tag "rails_keycloak_authorization/application", media: "all" %>
|
10
|
+
</head>
|
11
|
+
<body hx-headers='{"X-CSRF-Token": "<%= form_authenticity_token %>"}'>
|
12
|
+
<%= yield %>
|
13
|
+
</body>
|
14
|
+
</html>
|