prx_auth-rails 0.3.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
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