prx_auth-rails 0.1.0 → 1.3.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 (78) hide show
  1. checksums.yaml +5 -5
  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 +108 -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 +5 -0
  13. data/lib/prx_auth/rails/ext/controller.rb +32 -1
  14. data/lib/prx_auth/rails/railtie.rb +2 -2
  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 +94 -0
  76. data/test/prx_auth/rails/token_test.rb +45 -0
  77. data/test/test_helper.rb +35 -0
  78. metadata +243 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ac6efcb3433ddd7f49eaa06285689d79320b8318
4
- data.tar.gz: 44c91e2e17b65173218c068abeb642e972123cbd
2
+ SHA256:
3
+ metadata.gz: 3405e4e72d2c39585111bef4fca339a73ce11ec86a2c288f5c82d8934c0620fe
4
+ data.tar.gz: 3450063f4c4fdae2464f755222fafee08c627eda3aa8fee1d267db1474db58de
5
5
  SHA512:
6
- metadata.gz: 542bb85398b96f70a29e1db48f1a7a14b9b1a5a8136dc2284ffcccff2bf10c55fbddef9c8141b4b89ea00a2877b9c30a96e210ba4ac044677fe228ed6bde3873
7
- data.tar.gz: f611e92844200d7b7a23f483054722c474f15e4a8081854678617b1939187f1fcabce14b4c5b4442649141a64b8a3a8537d6bc802f623d6380013ec806415fba
6
+ metadata.gz: edf33f5cf4bc105818bb069554506f0b2d9ef5b8bb18cd1602b2ceb6384991804b5b3268c6bd5c519c08e8503df51c132a9bec045f13995c1d55088194fff489
7
+ data.tar.gz: 4aa62706ee892c2335756e2c3ae61171b77acc69bfe0ec06433449726f8e884cee4af2031e55e418c6780e9aedc8bfb1c78359c072c089389422a2d4aa15c95b
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
@@ -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,108 @@
1
+ require 'open-uri'
2
+
3
+ module PrxAuth::Rails
4
+ class SessionsController < ApplicationController
5
+ include PrxAuth::Rails::Engine.routes.url_helpers
6
+
7
+ skip_before_action :authenticate!
8
+
9
+ before_action :set_nonce!, only: :show
10
+
11
+ ID_NONCE_SESSION_KEY = 'id_prx_openid_nonce'.freeze
12
+
13
+ def new
14
+ set_nonce! unless fetch_nonce.present?
15
+
16
+ config = PrxAuth::Rails.configuration
17
+
18
+ id_auth_params = {
19
+ client_id: config.prx_client_id,
20
+ nonce: fetch_nonce,
21
+ response_type: 'id_token token',
22
+ scope: 'openid apps',
23
+ prompt: 'necessary'
24
+ }
25
+
26
+ redirect_to '//' + config.id_host + '/authorize?' + id_auth_params.to_query
27
+ end
28
+
29
+ def show
30
+ end
31
+
32
+ def auth_error
33
+ @auth_error_message = params.require(:error)
34
+ end
35
+
36
+ def create
37
+ jwt_id_claims = id_claims
38
+ jwt_access_claims = access_claims
39
+
40
+ jwt_access_claims['id_token'] = jwt_id_claims.as_json
41
+
42
+ result_path = if valid_nonce?(jwt_id_claims['nonce']) &&
43
+ users_match?(jwt_id_claims, jwt_access_claims)
44
+ sign_in_user(jwt_access_claims)
45
+ after_sign_in_path_for(current_user)
46
+ else
47
+ auth_error_sessions_path(error: 'verification_failed')
48
+ end
49
+ reset_nonce!
50
+
51
+ redirect_to result_path
52
+ end
53
+
54
+ private
55
+
56
+ def after_sign_in_path_for(_)
57
+ return super if defined?(super)
58
+
59
+ "/"
60
+ end
61
+
62
+ def id_claims
63
+ id_token = params.require('id_token')
64
+ validate_token(id_token)
65
+ end
66
+
67
+ def access_claims
68
+ access_token = params.require('access_token')
69
+ validate_token(access_token)
70
+ end
71
+
72
+ def reset_nonce!
73
+ session[ID_NONCE_SESSION_KEY] = nil
74
+ end
75
+
76
+ def set_nonce!
77
+ n = session[ID_NONCE_SESSION_KEY]
78
+ return n if n.present?
79
+
80
+ session[ID_NONCE_SESSION_KEY] = SecureRandom.hex
81
+ end
82
+
83
+ def fetch_nonce
84
+ session[ID_NONCE_SESSION_KEY]
85
+ end
86
+
87
+ def valid_nonce?(nonce)
88
+ return false if fetch_nonce.nil?
89
+
90
+ fetch_nonce == nonce
91
+ end
92
+
93
+ def users_match?(claims1, claims2)
94
+ return false if claims1['sub'].nil? || claims2['sub'].nil?
95
+
96
+ claims1['sub'] == claims2['sub']
97
+ end
98
+
99
+ def validate_token(token)
100
+ id_host = PrxAuth::Rails.configuration.id_host
101
+ prx_auth_cert = Rack::PrxAuth::Certificate.new("https://#{id_host}/api/v1/certs")
102
+ auth_validator = Rack::PrxAuth::AuthValidator.new(token, prx_auth_cert, id_host)
103
+ auth_validator.
104
+ claims.
105
+ with_indifferent_access
106
+ end
107
+ end
108
+ 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>
@@ -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,5 @@
1
+ module PrxAuth
2
+ module Rails
3
+ class Engine < ::Rails::Engine; end
4
+ end
5
+ end
@@ -1,13 +1,44 @@
1
+ require 'prx_auth/rails/token'
2
+
1
3
  module PrxAuth
2
4
  module Rails
3
5
  module Controller
4
6
  def prx_auth_token
5
- request.env['prx.auth']
7
+ rack_auth_token = env_prx_auth_token
8
+ return rack_auth_token if rack_auth_token.present?
9
+
10
+ session['prx.auth'] && Rack::PrxAuth::TokenData.new(session['prx.auth'])
6
11
  end
7
12
 
8
13
  def prx_authenticated?
9
14
  !!prx_auth_token
10
15
  end
16
+
17
+ def authenticate!
18
+ return true if current_user.present?
19
+
20
+ redirect_to PrxAuth::Rails::Engine.routes.url_helpers.new_sessions_path
21
+ end
22
+
23
+ def current_user
24
+ return if prx_auth_token.nil?
25
+
26
+ PrxAuth::Rails::Token.new(prx_auth_token)
27
+ end
28
+
29
+ def sign_in_user(token)
30
+ session['prx.auth'] = token
31
+ end
32
+
33
+ private
34
+
35
+ def env_prx_auth_token
36
+ if !defined? @_prx_auth_token
37
+ @_prx_auth_token = request.env['prx.auth'] && PrxAuth::Rails::Token.new(request.env['prx.auth'])
38
+ else
39
+ @_prx_auth_token
40
+ end
41
+ end
11
42
  end
12
43
  end
13
44
  end
@@ -9,8 +9,8 @@ module PrxAuth::Rails
9
9
  end
10
10
 
11
11
  initializer 'prx_auth.insert_middleware' do |app|
12
- if PrxAuth::Rails.middleware
13
- app.config.middleware.insert ActionDispatch::ParamsParser, Rack::PrxAuth
12
+ if PrxAuth::Rails.configuration.install_middleware
13
+ app.config.middleware.insert_after Rack::Head, Rack::PrxAuth
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,35 @@
1
+ require 'rack/prx_auth'
2
+
3
+ class PrxAuth::Rails::Token
4
+ def initialize(token_data)
5
+ @token_data = token_data
6
+ @namespace = PrxAuth::Rails.configuration.namespace
7
+ end
8
+
9
+ def authorized?(resource, namespace=nil, scope=nil)
10
+ namespace, scope = @namespace, namespace if scope.nil? && !namespace.nil?
11
+ @token_data.authorized?(resource, namespace, scope)
12
+ end
13
+
14
+ def globally_authorized?(namespace, scope=nil)
15
+ namespace, scope = @namespace, namespace if scope.nil?
16
+ @token_data.globally_authorized?(namespace, scope)
17
+ end
18
+
19
+ def resources(namespace=nil, scope=nil)
20
+ namespace, scope = @namespace, namespace if scope.nil? && !namespace.nil?
21
+ @token_data.resources(namespace, scope)
22
+ end
23
+
24
+ def scopes
25
+ @token_data.scopes
26
+ end
27
+
28
+ def user_id
29
+ @token_data.user_id
30
+ end
31
+
32
+ def authorized_account_ids(scope)
33
+ @token_data.authorized_account_ids(scope)
34
+ end
35
+ end
@@ -1,5 +1,5 @@
1
1
  module PrxAuth
2
2
  module Rails
3
- VERSION = "0.1.0"
3
+ VERSION = "1.3.0"
4
4
  end
5
5
  end