prx_auth-rails 0.3.0 → 1.4.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/Guardfile +8 -0
  4. data/README.md +44 -2
  5. data/Rakefile +17 -0
  6. data/app/controllers/prx_auth/rails/sessions_controller.rb +107 -0
  7. data/app/views/prx_auth/rails/sessions/auth_error.html.erb +15 -0
  8. data/app/views/prx_auth/rails/sessions/show.html.erb +38 -0
  9. data/config/routes.rb +7 -0
  10. data/lib/prx_auth/rails.rb +10 -2
  11. data/lib/prx_auth/rails/configuration.rb +28 -0
  12. data/lib/prx_auth/rails/engine.rb +9 -0
  13. data/lib/prx_auth/rails/ext/controller.rb +79 -1
  14. data/lib/prx_auth/rails/railtie.rb +1 -1
  15. data/lib/prx_auth/rails/token.rb +35 -0
  16. data/lib/prx_auth/rails/version.rb +1 -1
  17. data/prx_auth-rails.gemspec +13 -3
  18. data/test/dummy/Rakefile +6 -0
  19. data/test/dummy/app/assets/config/manifest.js +2 -0
  20. data/test/dummy/app/assets/images/.keep +0 -0
  21. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  22. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  23. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  24. data/test/dummy/app/controllers/application_controller.rb +8 -0
  25. data/test/dummy/app/controllers/concerns/.keep +0 -0
  26. data/test/dummy/app/helpers/application_helper.rb +2 -0
  27. data/test/dummy/app/javascript/packs/application.js +15 -0
  28. data/test/dummy/app/jobs/application_job.rb +7 -0
  29. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  30. data/test/dummy/app/models/application_record.rb +3 -0
  31. data/test/dummy/app/models/concerns/.keep +0 -0
  32. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  33. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  34. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  35. data/test/dummy/bin/rails +5 -0
  36. data/test/dummy/bin/rake +5 -0
  37. data/test/dummy/bin/setup +33 -0
  38. data/test/dummy/bin/spring +10 -0
  39. data/test/dummy/config.ru +6 -0
  40. data/test/dummy/config/application.rb +22 -0
  41. data/test/dummy/config/boot.rb +5 -0
  42. data/test/dummy/config/cable.yml +10 -0
  43. data/test/dummy/config/database.yml +25 -0
  44. data/test/dummy/config/environment.rb +5 -0
  45. data/test/dummy/config/environments/development.rb +76 -0
  46. data/test/dummy/config/environments/production.rb +120 -0
  47. data/test/dummy/config/environments/test.rb +60 -0
  48. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  49. data/test/dummy/config/initializers/assets.rb +12 -0
  50. data/test/dummy/config/initializers/backtrace_silencers.rb +8 -0
  51. data/test/dummy/config/initializers/content_security_policy.rb +28 -0
  52. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  53. data/test/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  54. data/test/dummy/config/initializers/inflections.rb +16 -0
  55. data/test/dummy/config/initializers/mime_types.rb +4 -0
  56. data/test/dummy/config/initializers/permissions_policy.rb +11 -0
  57. data/test/dummy/config/initializers/prx_auth.rb +8 -0
  58. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  59. data/test/dummy/config/locales/en.yml +33 -0
  60. data/test/dummy/config/puma.rb +43 -0
  61. data/test/dummy/config/routes.rb +3 -0
  62. data/test/dummy/config/spring.rb +6 -0
  63. data/test/dummy/config/storage.yml +34 -0
  64. data/test/dummy/lib/assets/.keep +0 -0
  65. data/test/dummy/log/.keep +0 -0
  66. data/test/dummy/public/404.html +67 -0
  67. data/test/dummy/public/422.html +67 -0
  68. data/test/dummy/public/500.html +66 -0
  69. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  70. data/test/dummy/public/apple-touch-icon.png +0 -0
  71. data/test/dummy/public/favicon.ico +0 -0
  72. data/test/dummy/storage/.keep +0 -0
  73. data/test/log/development.log +0 -0
  74. data/test/prx_auth/rails/configuration_test.rb +36 -0
  75. data/test/prx_auth/rails/sessions_controller_test.rb +98 -0
  76. data/test/prx_auth/rails/token_test.rb +45 -0
  77. data/test/test_helper.rb +35 -0
  78. metadata +234 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aca25d4b6fa954267347e83a2f3565f88d6590130894f87813153cb89b7540af
4
- data.tar.gz: 3375fab69a09cce240744a2b6acb8a25ea08eca18640db3e47f2479f6c1038c9
3
+ metadata.gz: 6af8c934d225009086d72c5503a3ad3db62b95c073063f019a31277c9eacc5bf
4
+ data.tar.gz: ab82780e90f78e9a3a31515078c9b7530354ad83bf3fd402a05c78514a0c762d
5
5
  SHA512:
6
- metadata.gz: 249ed7915081dd4a1efe69b69bc631a3353c0536697306d8e4794512f2a79d55b788a0a8fc665f1b47048044122a591c961c898b7853d279c85a8722f015448b
7
- data.tar.gz: e00d920cbef8460ea423784aba18e002b2c150d78b87922960fae49ce0f5492c7ffb45e19f2cd4b1a4118f95029e63bfa22b036c81accb9c94102267d47cf1e9
6
+ metadata.gz: 7af00c69f65cabbb5da5a1ea9fe0a79d85a3b004d9c437fa5c1a5539be22fc21ac346e2956d3f6cebbf07765e6b988d9d291c1b384bf8a56b78a8782d2f8f6af
7
+ data.tar.gz: c9a17a75d94bf7158b96147c877020f2093c07eb6e5ba345fb4ba37d6fd51df72fb2d5db4fc22ca04832c6954b5f7b9154252fb40a5de4e5878d006b096f02b6
data/.gitignore CHANGED
@@ -14,4 +14,10 @@ rdoc
14
14
  spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
+ test/dummy/db/
18
+ test/dummy/log/development.log
19
+ test/dummy/log/test.log
20
+ test/log/test.log
17
21
  tmp
22
+ .ruby-version
23
+ .DS_Store
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ guard :minitest, all_after_pass: true do
2
+ watch(%r{^test/(.*)\/?test_(.*)\.rb})
3
+ watch(%r{^lib/(.*/)?([^/]+)\.rb}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
4
+ watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
5
+ watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
6
+ watch(%r{^test/.+_test\.rb})
7
+ watch(%r{^test/test_helper\.rb}) { 'test' }
8
+ end
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # PrxAuth::Rails
2
2
 
3
- Rails integration for next generation PRX Authorization system.
3
+ Rails integration for next generation PRX Authorization system. This
4
+ provides common OpenId authorization patterns used in PRX apps.
4
5
 
5
6
  ## Installation
6
7
 
@@ -14,7 +15,48 @@ And then execute:
14
15
 
15
16
  ## Usage
16
17
 
17
- That should be it, I think.
18
+ Installing the gem in a Rails project will automatically add the
19
+ appropriate Rack middleware to your Rails application and add two
20
+ methods to your controllers. These methods are:
21
+
22
+ * `prx_auth_token`: returns a token (similar to PrxAuth::Token) which
23
+ automatically namespaces queries. The main methods you will be
24
+ interested in are `authorized?`, `globally_authorized?` and `resources`.
25
+ More information can be found in PrxAuth.
26
+
27
+ * `prx_authenticated?`: returns whether or not this request includes a
28
+ valid PrxAuth token.
29
+
30
+ This will let set up the Rails app to be ready for HTTP requests
31
+ associated with an OpenId access token.
32
+
33
+ ### Configuration
34
+
35
+ Generally, configuration is not required and the gem aims for great
36
+ defaults, but you can override some settings if you need to change the
37
+ default behavior.
38
+
39
+ If you're using the Rails server-side session flow, you must supply the
40
+ client_id via configuration.
41
+
42
+ In your rails app, add a file to config/initializers called
43
+ `prx_auth.rb`:
44
+
45
+ ```ruby
46
+ PrxAuth::Rails.configure do |config|
47
+
48
+ # enables automatic installation of token parser middleware
49
+ config.install_middleware = false # default: true
50
+
51
+ # automatically adds namespace to all scoped queries, e.g. .authorized?(:foo) will be treated
52
+ # as .authorized?(:my_great_ns, :foo). Has no impact on unscoped queries.
53
+ config.namespace = :my_great_ns # default: derived from Rails::Application name.
54
+ # e.g. class Feeder < Rails::Application => :feeder
55
+
56
+ # Set up the PRX OpenID client_id if using the backend rails sessions flow.
57
+ config.client_id = '<some client id>'
58
+ end
59
+ ```
18
60
 
19
61
  ## Contributing
20
62
 
data/Rakefile CHANGED
@@ -1 +1,18 @@
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
+
1
8
  require "bundler/gem_tasks"
9
+ require 'rake'
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,107 @@
1
+ module PrxAuth::Rails
2
+ class SessionsController < ApplicationController
3
+ include PrxAuth::Rails::Engine.routes.url_helpers
4
+
5
+ skip_before_action :authenticate!
6
+
7
+ before_action :set_nonce!, only: :show
8
+
9
+ ID_NONCE_SESSION_KEY = 'id_prx_openid_nonce'.freeze
10
+
11
+ def new
12
+ set_nonce! unless fetch_nonce.present?
13
+
14
+ config = PrxAuth::Rails.configuration
15
+
16
+ id_auth_params = {
17
+ client_id: config.prx_client_id,
18
+ nonce: fetch_nonce,
19
+ response_type: 'id_token token',
20
+ scope: 'openid apps',
21
+ prompt: 'necessary'
22
+ }
23
+
24
+ redirect_to '//' + config.id_host + '/authorize?' + id_auth_params.to_query
25
+ end
26
+
27
+ def show
28
+ end
29
+
30
+ def auth_error
31
+ @auth_error_message = params.require(:error)
32
+ end
33
+
34
+ def create
35
+ jwt_id_claims = id_claims
36
+ jwt_access_claims = access_claims
37
+
38
+ jwt_access_claims['id_token'] = jwt_id_claims.as_json
39
+
40
+ result_path = if valid_nonce?(jwt_id_claims['nonce']) &&
41
+ users_match?(jwt_id_claims, jwt_access_claims)
42
+ sign_in_user(jwt_access_claims)
43
+ lookup_and_register_accounts_names
44
+ after_sign_in_path_for(current_user)
45
+ else
46
+ auth_error_sessions_path(error: 'verification_failed')
47
+ end
48
+ reset_nonce!
49
+
50
+ redirect_to result_path
51
+ end
52
+
53
+ private
54
+
55
+ def after_sign_in_path_for(_)
56
+ return super if defined?(super)
57
+
58
+ "/"
59
+ end
60
+
61
+ def id_claims
62
+ id_token = params.require('id_token')
63
+ validate_token(id_token)
64
+ end
65
+
66
+ def access_claims
67
+ access_token = params.require('access_token')
68
+ validate_token(access_token)
69
+ end
70
+
71
+ def reset_nonce!
72
+ session[ID_NONCE_SESSION_KEY] = nil
73
+ end
74
+
75
+ def set_nonce!
76
+ n = session[ID_NONCE_SESSION_KEY]
77
+ return n if n.present?
78
+
79
+ session[ID_NONCE_SESSION_KEY] = SecureRandom.hex
80
+ end
81
+
82
+ def fetch_nonce
83
+ session[ID_NONCE_SESSION_KEY]
84
+ end
85
+
86
+ def valid_nonce?(nonce)
87
+ return false if fetch_nonce.nil?
88
+
89
+ fetch_nonce == nonce
90
+ end
91
+
92
+ def users_match?(claims1, claims2)
93
+ return false if claims1['sub'].nil? || claims2['sub'].nil?
94
+
95
+ claims1['sub'] == claims2['sub']
96
+ end
97
+
98
+ def validate_token(token)
99
+ id_host = PrxAuth::Rails.configuration.id_host
100
+ prx_auth_cert = Rack::PrxAuth::Certificate.new("https://#{id_host}/api/v1/certs")
101
+ auth_validator = Rack::PrxAuth::AuthValidator.new(token, prx_auth_cert, id_host)
102
+ auth_validator.
103
+ claims.
104
+ with_indifferent_access
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,15 @@
1
+ <div class='main'>
2
+ <section>
3
+ <h3>Not authorized for this application.</h3>
4
+
5
+ <p>Message was: <pre><%= @auth_error_message %></pre>
6
+ <% if @auth_error_message == 'invalid_scope' %>
7
+ Did you add a row in the account_applications table on id.prx?
8
+ <% end %>
9
+ </p>
10
+
11
+ <p>
12
+ <a href="<%= new_sessions_path %>">Try logging in again</a>
13
+ </p>
14
+ </section>
15
+ </div>
@@ -0,0 +1,38 @@
1
+ <div style="display:none;">
2
+ <%= form_for(:sessions, :url => PrxAuth::Rails::Engine.routes.url_helpers.sessions_path) do |f| %>
3
+ <%= hidden_field_tag :access_token, '', id: 'access-token-field' %>
4
+ <%= hidden_field_tag :id_token, '', id: 'id-token-field' %>
5
+ <%= f.submit id: 'sessions-form-submit' %>
6
+ <% end %>
7
+ </div>
8
+
9
+ <script type='application/javascript'>
10
+
11
+ function parseURLFragment() {
12
+ let hashParams = {};
13
+ let e,
14
+ a = /\+/g, // Regex for replacing addition symbol with a space
15
+ r = /([^&;=]+)=?([^&;]*)/g,
16
+ d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
17
+ q = window.location.hash.substring(1);
18
+
19
+ while (e = r.exec(q))
20
+ hashParams[d(e[1])] = d(e[2]);
21
+
22
+ return hashParams;
23
+ }
24
+
25
+ window.addEventListener("load", () => {
26
+ var idToken = document.querySelector("#id-token-field");
27
+ var accessToken = document.querySelector("#access-token-field");
28
+ var submit = document.querySelector("input#sessions-form-submit[type=submit]");
29
+
30
+ var hashParams = parseURLFragment();
31
+
32
+ accessToken.value = hashParams['access_token'];
33
+ idToken.value = hashParams['id_token'];
34
+
35
+ submit.click();
36
+ });
37
+
38
+ </script>
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ PrxAuth::Rails::Engine.routes.draw do
2
+ scope module: 'prx_auth/rails' do
3
+ resource 'sessions', except: :index, :defaults => { :format => 'html' } do
4
+ get 'auth_error', to: 'sessions#auth_error'
5
+ end
6
+ end
7
+ end
@@ -1,10 +1,18 @@
1
1
  require "prx_auth/rails/version"
2
+ require "prx_auth/rails/configuration"
2
3
  require "prx_auth/rails/railtie" if defined?(Rails)
4
+ require "prx_auth/rails/engine" if defined?(Rails)
5
+
3
6
  module PrxAuth
4
7
  module Rails
5
8
  class << self
6
- attr_accessor :middleware
9
+ attr_accessor :configuration
10
+
11
+ def configure
12
+ yield configuration
13
+ end
7
14
  end
8
- self.middleware = true
15
+
16
+ self.configuration = Configuration.new
9
17
  end
10
18
  end
@@ -0,0 +1,28 @@
1
+ class PrxAuth::Rails::Configuration
2
+ attr_accessor :install_middleware,
3
+ :namespace,
4
+ :prx_client_id,
5
+ :id_host
6
+
7
+
8
+ def initialize
9
+ @install_middleware = true
10
+ if defined?(::Rails)
11
+ klass = ::Rails.application.class
12
+ parent_name = if ::Rails::VERSION::MAJOR >= 6
13
+ klass.module_parent_name
14
+ else
15
+ klass.parent_name
16
+ end
17
+ klass_name = if parent_name.present?
18
+ parent_name
19
+ else
20
+ klass.name
21
+ end
22
+
23
+ @namespace = klass_name.underscore.intern
24
+ @prx_client_id = nil
25
+ @id_host = nil
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ module PrxAuth
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ config.to_prepare do
5
+ ::ApplicationController.helper_method [:current_user, :account_name_for]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,13 +1,91 @@
1
+ require 'prx_auth/rails/token'
2
+ require 'open-uri'
3
+
1
4
  module PrxAuth
2
5
  module Rails
3
6
  module Controller
7
+
8
+ PRX_ACCOUNT_NAME_MAPPING_KEY = 'prx.account.name.mapping'.freeze
9
+
4
10
  def prx_auth_token
5
- request.env['prx.auth']
11
+ rack_auth_token = env_prx_auth_token
12
+ return rack_auth_token if rack_auth_token.present?
13
+
14
+ session['prx.auth'] && Rack::PrxAuth::TokenData.new(session['prx.auth'])
6
15
  end
7
16
 
8
17
  def prx_authenticated?
9
18
  !!prx_auth_token
10
19
  end
20
+
21
+ def authenticate!
22
+ return true if current_user.present?
23
+
24
+ redirect_to PrxAuth::Rails::Engine.routes.url_helpers.new_sessions_path
25
+ end
26
+
27
+ def current_user
28
+ return if prx_auth_token.nil?
29
+
30
+ PrxAuth::Rails::Token.new(prx_auth_token)
31
+ end
32
+
33
+ def lookup_and_register_accounts_names
34
+ session[PRX_ACCOUNT_NAME_MAPPING_KEY] =
35
+ lookup_account_names_mapping
36
+ end
37
+
38
+ def account_name_for(id)
39
+ id = id.to_i
40
+
41
+ session[PRX_ACCOUNT_NAME_MAPPING_KEY] ||= {}
42
+
43
+ name =
44
+ if session[PRX_ACCOUNT_NAME_MAPPING_KEY].has_key?(id)
45
+ session[PRX_ACCOUNT_NAME_MAPPING_KEY][id]
46
+ else
47
+ session[PRX_ACCOUNT_NAME_MAPPING_KEY][id] = lookup_account_name_for(id)
48
+ end
49
+
50
+ name = "[#{id}] Unknown Account Name" unless name.present?
51
+
52
+ name
53
+ end
54
+
55
+ def sign_in_user(token)
56
+ session['prx.auth'] = token
57
+ end
58
+
59
+ private
60
+
61
+ def lookup_account_name_for(id)
62
+ id = id.to_i
63
+
64
+ res = lookup_account_names_mapping([id])
65
+ res[id]
66
+ end
67
+
68
+ def lookup_account_names_mapping(ids=current_user.resources)
69
+ id_host = PrxAuth::Rails.configuration.id_host
70
+ ids_param = ids.map(&:to_s).join(',')
71
+
72
+ options = {}
73
+ options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if ::Rails.env.development?
74
+
75
+ accounts = URI.open("https://#{id_host}/api/v1/accounts?account_ids=#{ids_param}", options).read
76
+
77
+ mapping = JSON.parse(accounts)['accounts'].map { |acct| [acct['id'], acct['display_name']] }.to_h
78
+
79
+ mapping
80
+ end
81
+
82
+ def env_prx_auth_token
83
+ if !defined? @_prx_auth_token
84
+ @_prx_auth_token = request.env['prx.auth'] && PrxAuth::Rails::Token.new(request.env['prx.auth'])
85
+ else
86
+ @_prx_auth_token
87
+ end
88
+ end
11
89
  end
12
90
  end
13
91
  end
@@ -9,7 +9,7 @@ module PrxAuth::Rails
9
9
  end
10
10
 
11
11
  initializer 'prx_auth.insert_middleware' do |app|
12
- if PrxAuth::Rails.middleware
12
+ if PrxAuth::Rails.configuration.install_middleware
13
13
  app.config.middleware.insert_after Rack::Head, Rack::PrxAuth
14
14
  end
15
15
  end