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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +139 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/rails_keycloak_authorization_manifest.js +1 -0
  6. data/app/assets/stylesheets/rails_keycloak_authorization/application.css +15 -0
  7. data/app/controllers/concerns/rails_keycloak_authorization/with_htmx_layout.rb +15 -0
  8. data/app/controllers/concerns/rails_keycloak_authorization/with_keycloak_admin.rb +35 -0
  9. data/app/controllers/concerns/rails_keycloak_authorization/with_routes_reader.rb +26 -0
  10. data/app/controllers/rails_keycloak_authorization/application_controller.rb +4 -0
  11. data/app/controllers/rails_keycloak_authorization/management_controller.rb +9 -0
  12. data/app/controllers/rails_keycloak_authorization/permissions_controller.rb +39 -0
  13. data/app/controllers/rails_keycloak_authorization/policies_controller.rb +19 -0
  14. data/app/controllers/rails_keycloak_authorization/resources_controller.rb +31 -0
  15. data/app/controllers/rails_keycloak_authorization/routes_controller.rb +23 -0
  16. data/app/controllers/rails_keycloak_authorization/scopes_controller.rb +48 -0
  17. data/app/helpers/rails_keycloak_authorization/application_helper.rb +4 -0
  18. data/app/helpers/rails_keycloak_authorization/resources_helper.rb +5 -0
  19. data/app/jobs/rails_keycloak_authorization/application_job.rb +4 -0
  20. data/app/mailers/rails_keycloak_authorization/application_mailer.rb +6 -0
  21. data/app/models/rails_keycloak_authorization/application_record.rb +5 -0
  22. data/app/services/rails_keycloak_authorization/keycloak_admin_ruby_agent.rb +145 -0
  23. data/app/views/layouts/rails_keycloak_authorization/application.html.erb +14 -0
  24. data/app/views/layouts/rails_keycloak_authorization/htmx.html.erb +14 -0
  25. data/app/views/rails_keycloak_authorization/management/index.html.erb +79 -0
  26. data/app/views/rails_keycloak_authorization/permissions/index.html.erb +41 -0
  27. data/app/views/rails_keycloak_authorization/permissions/resource_scopes_select.html.erb +7 -0
  28. data/app/views/rails_keycloak_authorization/policies/index.html.erb +23 -0
  29. data/app/views/rails_keycloak_authorization/resources/index.html.erb +5 -0
  30. data/app/views/rails_keycloak_authorization/resources/new.html.erb +4 -0
  31. data/app/views/rails_keycloak_authorization/resources/show.html.erb +28 -0
  32. data/app/views/rails_keycloak_authorization/routes/index.html.erb +58 -0
  33. data/app/views/rails_keycloak_authorization/routes/show.html.erb +6 -0
  34. data/app/views/rails_keycloak_authorization/scopes/index.html.erb +9 -0
  35. data/app/views/rails_keycloak_authorization/scopes/show.html.erb +33 -0
  36. data/config/routes.rb +13 -0
  37. data/lib/rails_keycloak_authorization/engine.rb +8 -0
  38. data/lib/rails_keycloak_authorization/version.rb +3 -0
  39. data/lib/rails_keycloak_authorization.rb +77 -0
  40. data/lib/tasks/rails_keycloak_authorization_tasks.rake +170 -0
  41. 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,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -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,4 @@
1
+ module RailsKeycloakAuthorization
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module RailsKeycloakAuthorization
2
+ class ManagementController < ApplicationController
3
+ layout false
4
+
5
+ def index
6
+ render layout: "rails_keycloak_authorization/htmx"
7
+ end
8
+ end
9
+ end
@@ -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,4 @@
1
+ module RailsKeycloakAuthorization
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module RailsKeycloakAuthorization
2
+ module ResourcesHelper
3
+
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module RailsKeycloakAuthorization
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module RailsKeycloakAuthorization
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module RailsKeycloakAuthorization
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ 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>