keycloak_rack 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.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +68 -0
  3. data/.gitignore +8 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +220 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +7 -0
  8. data/Appraisals +16 -0
  9. data/CHANGELOG.md +10 -0
  10. data/CODE_OF_CONDUCT.md +132 -0
  11. data/Gemfile +5 -0
  12. data/LICENSE +19 -0
  13. data/README.md +288 -0
  14. data/Rakefile +10 -0
  15. data/bin/appraisal +29 -0
  16. data/bin/console +6 -0
  17. data/bin/fix-appraisals +14 -0
  18. data/bin/rake +29 -0
  19. data/bin/rspec +29 -0
  20. data/bin/rubocop +29 -0
  21. data/bin/yard +29 -0
  22. data/bin/yardoc +29 -0
  23. data/bin/yri +29 -0
  24. data/gemfiles/rack_only.gemfile +5 -0
  25. data/gemfiles/rack_only.gemfile.lock +204 -0
  26. data/gemfiles/rails_6_0.gemfile +9 -0
  27. data/gemfiles/rails_6_0.gemfile.lock +323 -0
  28. data/gemfiles/rails_6_1.gemfile +9 -0
  29. data/gemfiles/rails_6_1.gemfile.lock +326 -0
  30. data/keycloak_rack.gemspec +56 -0
  31. data/lib/keycloak_rack.rb +59 -0
  32. data/lib/keycloak_rack/authenticate.rb +115 -0
  33. data/lib/keycloak_rack/authorize_realm.rb +53 -0
  34. data/lib/keycloak_rack/authorize_resource.rb +54 -0
  35. data/lib/keycloak_rack/config.rb +84 -0
  36. data/lib/keycloak_rack/container.rb +53 -0
  37. data/lib/keycloak_rack/decoded_token.rb +191 -0
  38. data/lib/keycloak_rack/flexible_struct.rb +20 -0
  39. data/lib/keycloak_rack/http_client.rb +86 -0
  40. data/lib/keycloak_rack/import.rb +9 -0
  41. data/lib/keycloak_rack/key_fetcher.rb +20 -0
  42. data/lib/keycloak_rack/key_resolver.rb +64 -0
  43. data/lib/keycloak_rack/middleware.rb +132 -0
  44. data/lib/keycloak_rack/railtie.rb +14 -0
  45. data/lib/keycloak_rack/read_token.rb +40 -0
  46. data/lib/keycloak_rack/resource_role_map.rb +8 -0
  47. data/lib/keycloak_rack/role_map.rb +15 -0
  48. data/lib/keycloak_rack/session.rb +44 -0
  49. data/lib/keycloak_rack/skip_authentication.rb +44 -0
  50. data/lib/keycloak_rack/types.rb +42 -0
  51. data/lib/keycloak_rack/version.rb +6 -0
  52. data/lib/keycloak_rack/with_config.rb +15 -0
  53. data/spec/dummy/.ruby-version +1 -0
  54. data/spec/dummy/README.md +24 -0
  55. data/spec/dummy/Rakefile +8 -0
  56. data/spec/dummy/app/controllers/application_controller.rb +22 -0
  57. data/spec/dummy/app/controllers/test_controller.rb +9 -0
  58. data/spec/dummy/config.ru +8 -0
  59. data/spec/dummy/config/application.rb +52 -0
  60. data/spec/dummy/config/boot.rb +3 -0
  61. data/spec/dummy/config/environment.rb +7 -0
  62. data/spec/dummy/config/environments/development.rb +51 -0
  63. data/spec/dummy/config/environments/test.rb +51 -0
  64. data/spec/dummy/config/initializers/application_controller_renderer.rb +9 -0
  65. data/spec/dummy/config/initializers/backtrace_silencers.rb +10 -0
  66. data/spec/dummy/config/initializers/cors.rb +17 -0
  67. data/spec/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  68. data/spec/dummy/config/initializers/inflections.rb +17 -0
  69. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  70. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  71. data/spec/dummy/config/keycloak.yml +12 -0
  72. data/spec/dummy/config/locales/en.yml +33 -0
  73. data/spec/dummy/config/routes.rb +5 -0
  74. data/spec/dummy/public/robots.txt +1 -0
  75. data/spec/dummy/tmp/development_secret.txt +1 -0
  76. data/spec/factories/decoded_token.rb +18 -0
  77. data/spec/factories/session.rb +21 -0
  78. data/spec/factories/token_payload.rb +40 -0
  79. data/spec/keycloak_rack/authorize_realm_spec.rb +15 -0
  80. data/spec/keycloak_rack/authorize_resource_spec.rb +19 -0
  81. data/spec/keycloak_rack/decoded_token_spec.rb +31 -0
  82. data/spec/keycloak_rack/key_resolver_spec.rb +95 -0
  83. data/spec/keycloak_rack/middleware_spec.rb +172 -0
  84. data/spec/keycloak_rack/rails_integration_spec.rb +43 -0
  85. data/spec/keycloak_rack/session_spec.rb +37 -0
  86. data/spec/keycloak_rack/skip_authentication_spec.rb +55 -0
  87. data/spec/spec_helper.rb +101 -0
  88. data/spec/support/contexts/mocked_keycloak.rb +63 -0
  89. data/spec/support/contexts/mocked_rack_application.rb +41 -0
  90. data/spec/support/test_key.pem +27 -0
  91. data/spec/support/token_helper.rb +76 -0
  92. metadata +616 -0
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeycloakRack
4
+ # Check if the request should be skipped based on the request method and path.
5
+ #
6
+ # @api private
7
+ # @!visibility private
8
+ class SkipAuthentication
9
+ include Dry::Monads[:result]
10
+
11
+ include Import[config: "keycloak-rack.config"]
12
+
13
+ delegate :skip_paths, to: :config
14
+
15
+ # @return [Dry::Monads::Success(Boolean)]
16
+ def call(env)
17
+ method = env["REQUEST_METHOD"].to_s.downcase
18
+ path = env["PATH_INFO"]
19
+
20
+ return Success(true) if preflight?(method, env)
21
+ return Success(true) if should_skip?(method, path)
22
+
23
+ Success(false)
24
+ end
25
+
26
+ private
27
+
28
+ def should_skip?(method, path)
29
+ method_paths = skip_paths.fetch(method, [])
30
+
31
+ method_paths.any? do |path_pattern|
32
+ if path_pattern.kind_of?(Regexp)
33
+ path_pattern.match? path
34
+ else
35
+ path_pattern == path
36
+ end
37
+ end
38
+ end
39
+
40
+ def preflight?(method, headers)
41
+ method == "options" && headers["HTTP_ACCESS_CONTROL_REQUEST_METHOD"].present?
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeycloakRack
4
+ # dry-rb types for this gem.
5
+ #
6
+ # @see https://dry-rb.org/gems/dry-types
7
+ # @api private
8
+ # @!visibility private
9
+ module Types
10
+ include Dry.Types
11
+
12
+ # A type to make indifferent hashes
13
+ #
14
+ # @api private
15
+ IndifferentHash = Types.Constructor(::ActiveSupport::HashWithIndifferentAccess) do |value|
16
+ Types::Coercible::Hash[value].with_indifferent_access
17
+ end
18
+
19
+ # A type to validate skip paths
20
+ #
21
+ # @api private
22
+ SkipPaths = Types::Hash.map(
23
+ Types::Coercible::String,
24
+ Types::Array.of(Types::String | Types.Instance(Regexp))
25
+ )
26
+
27
+ # A type to make arrays of strings
28
+ StringList = Types::Array.of(Types::String).default { [] }
29
+
30
+ # A type to parse timestamps
31
+ # @api private
32
+ Timestamp = Types.Constructor(::Time) do |value|
33
+ # :nocov:
34
+ case value
35
+ when Integer then ::Time.at(value)
36
+ when ::Time then value
37
+ when Types.Interface(:to_time) then value.to_time
38
+ end
39
+ # :nocov:
40
+ end.optional
41
+ end
42
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeycloakRack
4
+ # Gem version
5
+ VERSION = "1.0.0"
6
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeycloakRack
4
+ # Adds `config` as a property without using {KeycloakRack::Import},
5
+ # for instances where dependency injection doesn't make sense.
6
+ #
7
+ # @!visibility private
8
+ module WithConfig
9
+ # @!attribute [r] config
10
+ # @return [KeycloakRack::Config]
11
+ def config
12
+ KeycloakRack::Container["keycloak-rack.config"]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1 @@
1
+ 2.7.2
@@ -0,0 +1,24 @@
1
+ # README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
4
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5
+
6
+ require_relative "config/application"
7
+
8
+ Rails.application.load_tasks
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationController < ActionController::API
4
+ before_action :authenticate_user!
5
+
6
+ # @return [void]
7
+ def authenticate_user!
8
+ request.env["keycloak:session"].authenticate! do |m|
9
+ m.success(:authenticated) do |_, token|
10
+ @current_user = { keycloak_id: token.keycloak_id }
11
+ end
12
+
13
+ m.success do
14
+ @current_user = { anonymous: true }
15
+ end
16
+
17
+ m.failure do |code, reason|
18
+ render json: { errors: [{ message: "Auth Failure" }] }, status: :forbidden
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TestController < ApplicationController
4
+ def root
5
+ render json: @current_user
6
+ rescue StandardError => e
7
+ render json: { error: e, backtrace: e.backtrace }, status: :internal_server_error
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is used by Rack-based servers to start the application.
4
+
5
+ require_relative "config/environment"
6
+
7
+ run Rails.application
8
+ Rails.application.load_server
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "boot"
4
+
5
+ require "rails"
6
+ # Pick the frameworks you want:
7
+ require "active_model/railtie"
8
+ require "active_job/railtie"
9
+ # require "active_record/railtie"
10
+ # require "active_storage/engine"
11
+ require "action_controller/railtie"
12
+ # require "action_mailer/railtie"
13
+ # require "action_mailbox/engine"
14
+ # require "action_text/engine"
15
+ require "action_view/railtie"
16
+ # require "action_cable/engine"
17
+ # require "sprockets/railtie"
18
+ # require "rails/test_unit/railtie"
19
+
20
+ require "keycloak_rack"
21
+
22
+ # Require the gems listed in Gemfile, including any gems
23
+ # you've limited to :test, :development, or :production.
24
+ Bundler.require(*Rails.groups)
25
+
26
+ module Dummy
27
+ class Application < Rails::Application
28
+ # Initialize configuration defaults for originally generated Rails version.
29
+
30
+ case ENV["BUNDLE_GEMFILE"]
31
+ when /rails_6_0/
32
+ config.load_defaults 6.0
33
+ config.hosts << "www.example.com"
34
+ when /rails_6_1/
35
+ config.load_defaults 6.1
36
+ config.hosts << "www.example.com"
37
+ end
38
+
39
+ # Configuration for the application, engines, and railties goes here.
40
+ #
41
+ # These settings can be overridden in specific environments using the files
42
+ # in config/environments, which are processed later.
43
+ #
44
+ # config.time_zone = "Central Time (US & Canada)"
45
+ # config.eager_load_paths << Rails.root.join("extras")
46
+
47
+ # Only loads a smaller set of middleware suitable for API only apps.
48
+ # Middleware like session, flash, cookies can be added back manually.
49
+ # Skip views, helpers and assets when generating a new resource.
50
+ config.api_only = true
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load the Rails application.
4
+ require_relative "application"
5
+
6
+ # Initialize the Rails application.
7
+ Rails.application.initialize!
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/integer/time"
4
+
5
+ # The test environment is used exclusively to run your application's
6
+ # test suite. You never need to work with it otherwise. Remember that
7
+ # your test database is "scratch space" for the test suite and is wiped
8
+ # and recreated between test runs. Don't rely on the data there!
9
+
10
+ Rails.application.configure do
11
+ # Settings specified here will take precedence over those in config/application.rb.
12
+
13
+ config.cache_classes = true
14
+
15
+ # Do not eager load code on boot. This avoids loading your whole application
16
+ # just for the purpose of running a single test. If you are using a tool that
17
+ # preloads Rails for running tests, you may have to set it to true.
18
+ config.eager_load = false
19
+
20
+ # Configure public file server for tests with Cache-Control for performance.
21
+ config.public_file_server.enabled = true
22
+ config.public_file_server.headers = {
23
+ 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
24
+ }
25
+
26
+ # Show full error reports and disable caching.
27
+ config.consider_all_requests_local = true
28
+ config.action_controller.perform_caching = false
29
+ config.cache_store = :null_store
30
+
31
+ # Raise exceptions instead of rendering exception templates.
32
+ config.action_dispatch.show_exceptions = false
33
+
34
+ # Disable request forgery protection in test environment.
35
+ config.action_controller.allow_forgery_protection = false
36
+
37
+ # Print deprecation notices to the stderr.
38
+ config.active_support.deprecation = :stderr
39
+
40
+ # Raise exceptions for disallowed deprecations.
41
+ config.active_support.disallowed_deprecation = :raise
42
+
43
+ # Tell Active Support which deprecation messages to disallow.
44
+ config.active_support.disallowed_deprecation_warnings = []
45
+
46
+ # Raises error for missing translations.
47
+ # config.i18n.raise_on_missing_translations = true
48
+
49
+ # Annotate rendered view with file names.
50
+ # config.action_view.annotate_rendered_view_with_filenames = true
51
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/integer/time"
4
+
5
+ # The test environment is used exclusively to run your application's
6
+ # test suite. You never need to work with it otherwise. Remember that
7
+ # your test database is "scratch space" for the test suite and is wiped
8
+ # and recreated between test runs. Don't rely on the data there!
9
+
10
+ Rails.application.configure do
11
+ # Settings specified here will take precedence over those in config/application.rb.
12
+
13
+ config.cache_classes = true
14
+
15
+ # Do not eager load code on boot. This avoids loading your whole application
16
+ # just for the purpose of running a single test. If you are using a tool that
17
+ # preloads Rails for running tests, you may have to set it to true.
18
+ config.eager_load = false
19
+
20
+ # Configure public file server for tests with Cache-Control for performance.
21
+ config.public_file_server.enabled = true
22
+ config.public_file_server.headers = {
23
+ 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
24
+ }
25
+
26
+ # Show full error reports and disable caching.
27
+ config.consider_all_requests_local = true
28
+ config.action_controller.perform_caching = false
29
+ config.cache_store = :null_store
30
+
31
+ # Raise exceptions instead of rendering exception templates.
32
+ config.action_dispatch.show_exceptions = false
33
+
34
+ # Disable request forgery protection in test environment.
35
+ config.action_controller.allow_forgery_protection = false
36
+
37
+ # Print deprecation notices to the stderr.
38
+ config.active_support.deprecation = :stderr
39
+
40
+ # Raise exceptions for disallowed deprecations.
41
+ config.active_support.disallowed_deprecation = :raise
42
+
43
+ # Tell Active Support which deprecation messages to disallow.
44
+ config.active_support.disallowed_deprecation_warnings = []
45
+
46
+ # Raises error for missing translations.
47
+ # config.i18n.raise_on_missing_translations = true
48
+
49
+ # Annotate rendered view with file names.
50
+ # config.action_view.annotate_rendered_view_with_filenames = true
51
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ # Be sure to restart your server when you modify this file.
3
+
4
+ # ActiveSupport::Reloader.to_prepare do
5
+ # ApplicationController.renderer.defaults.merge!(
6
+ # http_host: 'example.org',
7
+ # https: false
8
+ # )
9
+ # end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Be sure to restart your server when you modify this file.
4
+
5
+ # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
6
+ # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }
7
+
8
+ # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code
9
+ # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'".
10
+ Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"]
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ # Be sure to restart your server when you modify this file.
3
+
4
+ # Avoid CORS issues when API is called from the frontend app.
5
+ # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
6
+
7
+ # Read more: https://github.com/cyu/rack-cors
8
+
9
+ # Rails.application.config.middleware.insert_before 0, Rack::Cors do
10
+ # allow do
11
+ # origins 'example.com'
12
+ #
13
+ # resource '*',
14
+ # headers: :any,
15
+ # methods: [:get, :post, :put, :patch, :delete, :options, :head]
16
+ # end
17
+ # end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Be sure to restart your server when you modify this file.
4
+
5
+ # Configure sensitive parameters which will be filtered from the log file.
6
+ Rails.application.config.filter_parameters += [
7
+ :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
8
+ ]
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ # Be sure to restart your server when you modify this file.
3
+
4
+ # Add new inflection rules using the following format. Inflections
5
+ # are locale specific, and you may define rules for as many different
6
+ # locales as you wish. All of these examples are active by default:
7
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
8
+ # inflect.plural /^(ox)$/i, '\1en'
9
+ # inflect.singular /^(ox)en/i, '\1'
10
+ # inflect.irregular 'person', 'people'
11
+ # inflect.uncountable %w( fish sheep )
12
+ # end
13
+
14
+ # These inflection rules are supported but not enabled by default:
15
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
16
+ # inflect.acronym 'RESTful'
17
+ # end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # Be sure to restart your server when you modify this file.
3
+
4
+ # Add new mime types for use in respond_to blocks:
5
+ # Mime::Type.register "text/richtext", :rtf