prx_auth-rails 1.0.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/README.md +25 -6
  4. data/Rakefile +12 -4
  5. data/app/controllers/prx_auth/rails/sessions_controller.rb +121 -0
  6. data/app/views/prx_auth/rails/sessions/auth_error.html.erb +15 -0
  7. data/app/views/prx_auth/rails/sessions/show.html.erb +38 -0
  8. data/config/routes.rb +7 -0
  9. data/lib/prx_auth/rails.rb +1 -0
  10. data/lib/prx_auth/rails/configuration.rb +15 -4
  11. data/lib/prx_auth/rails/engine.rb +9 -0
  12. data/lib/prx_auth/rails/ext/controller.rb +81 -4
  13. data/lib/prx_auth/rails/token.rb +5 -1
  14. data/lib/prx_auth/rails/version.rb +1 -1
  15. data/prx_auth-rails.gemspec +4 -2
  16. data/test/dummy/Rakefile +6 -0
  17. data/test/dummy/app/assets/config/manifest.js +2 -0
  18. data/test/dummy/app/assets/images/.keep +0 -0
  19. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  20. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  21. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  22. data/test/dummy/app/controllers/application_controller.rb +8 -0
  23. data/test/dummy/app/controllers/concerns/.keep +0 -0
  24. data/test/dummy/app/helpers/application_helper.rb +2 -0
  25. data/test/dummy/app/javascript/packs/application.js +15 -0
  26. data/test/dummy/app/jobs/application_job.rb +7 -0
  27. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  28. data/test/dummy/app/models/application_record.rb +3 -0
  29. data/test/dummy/app/models/concerns/.keep +0 -0
  30. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  31. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  32. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  33. data/test/dummy/bin/rails +5 -0
  34. data/test/dummy/bin/rake +5 -0
  35. data/test/dummy/bin/setup +33 -0
  36. data/test/dummy/bin/spring +10 -0
  37. data/test/dummy/config.ru +6 -0
  38. data/test/dummy/config/application.rb +22 -0
  39. data/test/dummy/config/boot.rb +5 -0
  40. data/test/dummy/config/cable.yml +10 -0
  41. data/test/dummy/config/database.yml +25 -0
  42. data/test/dummy/config/environment.rb +5 -0
  43. data/test/dummy/config/environments/development.rb +76 -0
  44. data/test/dummy/config/environments/production.rb +120 -0
  45. data/test/dummy/config/environments/test.rb +60 -0
  46. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  47. data/test/dummy/config/initializers/assets.rb +12 -0
  48. data/test/dummy/config/initializers/backtrace_silencers.rb +8 -0
  49. data/test/dummy/config/initializers/content_security_policy.rb +28 -0
  50. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  51. data/test/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  52. data/test/dummy/config/initializers/inflections.rb +16 -0
  53. data/test/dummy/config/initializers/mime_types.rb +4 -0
  54. data/test/dummy/config/initializers/permissions_policy.rb +11 -0
  55. data/test/dummy/config/initializers/prx_auth.rb +8 -0
  56. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  57. data/test/dummy/config/locales/en.yml +33 -0
  58. data/test/dummy/config/puma.rb +43 -0
  59. data/test/dummy/config/routes.rb +3 -0
  60. data/test/dummy/config/spring.rb +6 -0
  61. data/test/dummy/config/storage.yml +34 -0
  62. data/test/dummy/lib/assets/.keep +0 -0
  63. data/test/dummy/log/.keep +0 -0
  64. data/test/dummy/public/404.html +67 -0
  65. data/test/dummy/public/422.html +67 -0
  66. data/test/dummy/public/500.html +66 -0
  67. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  68. data/test/dummy/public/apple-touch-icon.png +0 -0
  69. data/test/dummy/public/favicon.ico +0 -0
  70. data/test/dummy/storage/.keep +0 -0
  71. data/test/prx_auth/rails/configuration_test.rb +18 -12
  72. data/test/prx_auth/rails/sessions_controller_test.rb +104 -0
  73. data/test/prx_auth/rails/token_test.rb +1 -1
  74. data/test/test_helper.rb +20 -9
  75. metadata +156 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb5677550d3e64273eddb57f84d6dc658d3f9ed82a2cb01e6966d9267ad76b53
4
- data.tar.gz: d6f0ec6305622e5dbdaaf747e78678361d70d92410f84f77396db5d1254de4ec
3
+ metadata.gz: '081a5943f3b2b9a79035ea23b3c9d1273ba09938ea3e7351025cb4c3a836b108'
4
+ data.tar.gz: da3cc2f617261d7e22ad031a43fa903114aa537edb89e90b6de3ab8e132ee7b6
5
5
  SHA512:
6
- metadata.gz: 720cfa888bbc17b9a677bc8c1944f8735db028a8196f1f170dd18b301c547687b52f466f1c1759f09820064a8317dc4d1499855a60c80a81ba4d2dd464df84aa
7
- data.tar.gz: 151ffa59471a1c5c7543148c93f29e02ed67cbcc2d06551f9451d1599375962685bcf460179570a5ea363a237c1958dcadba8a9b234eaf73261f4fc124a879aa
6
+ metadata.gz: 801452a31c08d21d7c78ff49048f8bf4247d41a0ed7d491bb78165c558464149c9415017939b14351ade8848d81c143b9a1a1ea23cac6605521e654a651720b7
7
+ data.tar.gz: d4e3bd24d1c11838c1275db062c9bb9c4494a12cd13f39f3ef36cba20d5e539480920c06fd638ced3c9a372b4413797ab1e625e2ecb6e2d8f01bfccbfcfb0d8d
data/.gitignore CHANGED
@@ -14,6 +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
18
22
  .ruby-version
19
23
  .DS_Store
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,17 +15,32 @@ And then execute:
14
15
 
15
16
  ## Usage
16
17
 
17
- Installing the gem in a Rails project will automatically add the appropriate Rack middleware to your Rails application and add two methods to your controllers. These methods are:
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:
18
21
 
19
- * `prx_auth_token`: returns a token (similar to PrxAuth::Token) which automatically namespaces queries. The main methods you will be interested in are `authorized?`, `globally_authorized?` and `resources`. More information can be found in PrxAuth.
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.
20
26
 
21
- * `prx_authenticated?`: returns whether or not this request includes a valid PrxAuth token.
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.
22
32
 
23
33
  ### Configuration
24
34
 
25
- Generally, configuration is not required and the gem aims for great defaults, but you can override some settings if you need to change the default behavior.
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.
26
41
 
27
- In your rails app, add a file to config/initializers called `prx_auth.rb`:
42
+ In your rails app, add a file to config/initializers called
43
+ `prx_auth.rb`:
28
44
 
29
45
  ```ruby
30
46
  PrxAuth::Rails.configure do |config|
@@ -36,6 +52,9 @@ PrxAuth::Rails.configure do |config|
36
52
  # as .authorized?(:my_great_ns, :foo). Has no impact on unscoped queries.
37
53
  config.namespace = :my_great_ns # default: derived from Rails::Application name.
38
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>'
39
58
  end
40
59
  ```
41
60
 
data/Rakefile CHANGED
@@ -1,10 +1,18 @@
1
- require 'bundler/gem_tasks'
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"
2
9
  require 'rake'
3
- require 'rake/testtask'
10
+ require "rake/testtask"
4
11
 
5
- Rake::TestTask.new do |t|
12
+ Rake::TestTask.new(:test) do |t|
6
13
  t.libs << 'test'
7
- t.pattern = 'test/**/*test.rb'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
8
16
  end
9
17
 
10
18
  task default: :test
@@ -0,0 +1,121 @@
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 destroy
31
+ sign_out_user
32
+ redirect_to after_sign_out_path
33
+ end
34
+
35
+ def auth_error
36
+ @auth_error_message = params.require(:error)
37
+ end
38
+
39
+ def create
40
+ jwt_id_claims = id_claims
41
+ jwt_access_claims = access_claims
42
+
43
+ jwt_access_claims['id_token'] = jwt_id_claims.as_json
44
+
45
+ result_path = if valid_nonce?(jwt_id_claims['nonce']) &&
46
+ users_match?(jwt_id_claims, jwt_access_claims)
47
+ sign_in_user(jwt_access_claims)
48
+ lookup_and_register_accounts_names
49
+ after_sign_in_path_for(current_user)
50
+ else
51
+ auth_error_sessions_path(error: 'verification_failed')
52
+ end
53
+ reset_nonce!
54
+
55
+ redirect_to result_path
56
+ end
57
+
58
+ private
59
+
60
+ def after_sign_in_path_for(_)
61
+ return super if defined?(super)
62
+
63
+ "/"
64
+ end
65
+
66
+ def after_sign_out_path
67
+ return super if defined?(super)
68
+
69
+ "https://#{id_host}/session/sign_out"
70
+ end
71
+
72
+ def id_claims
73
+ id_token = params.require('id_token')
74
+ validate_token(id_token)
75
+ end
76
+
77
+ def access_claims
78
+ access_token = params.require('access_token')
79
+ validate_token(access_token)
80
+ end
81
+
82
+ def reset_nonce!
83
+ session[ID_NONCE_SESSION_KEY] = nil
84
+ end
85
+
86
+ def set_nonce!
87
+ n = session[ID_NONCE_SESSION_KEY]
88
+ return n if n.present?
89
+
90
+ session[ID_NONCE_SESSION_KEY] = SecureRandom.hex
91
+ end
92
+
93
+ def fetch_nonce
94
+ session[ID_NONCE_SESSION_KEY]
95
+ end
96
+
97
+ def valid_nonce?(nonce)
98
+ return false if fetch_nonce.nil?
99
+
100
+ fetch_nonce == nonce
101
+ end
102
+
103
+ def users_match?(claims1, claims2)
104
+ return false if claims1['sub'].nil? || claims2['sub'].nil?
105
+
106
+ claims1['sub'] == claims2['sub']
107
+ end
108
+
109
+ def validate_token(token)
110
+ prx_auth_cert = Rack::PrxAuth::Certificate.new("https://#{id_host}/api/v1/certs")
111
+ auth_validator = Rack::PrxAuth::AuthValidator.new(token, prx_auth_cert, id_host)
112
+ auth_validator.
113
+ claims.
114
+ with_indifferent_access
115
+ end
116
+
117
+ def id_host
118
+ PrxAuth::Rails.configuration.id_host
119
+ end
120
+ end
121
+ 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,6 +1,7 @@
1
1
  require "prx_auth/rails/version"
2
2
  require "prx_auth/rails/configuration"
3
3
  require "prx_auth/rails/railtie" if defined?(Rails)
4
+ require "prx_auth/rails/engine" if defined?(Rails)
4
5
 
5
6
  module PrxAuth
6
7
  module Rails
@@ -1,17 +1,28 @@
1
1
  class PrxAuth::Rails::Configuration
2
- attr_accessor :install_middleware, :namespace
2
+ attr_accessor :install_middleware,
3
+ :namespace,
4
+ :prx_client_id,
5
+ :id_host
6
+
3
7
 
4
8
  def initialize
5
9
  @install_middleware = true
6
10
  if defined?(::Rails)
7
11
  klass = ::Rails.application.class
8
- klass_name = if klass.parent_name.present?
9
- klass.parent_name
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
10
19
  else
11
20
  klass.name
12
21
  end
13
22
 
14
23
  @namespace = klass_name.underscore.intern
24
+ @prx_client_id = nil
25
+ @id_host = nil
15
26
  end
16
27
  end
17
- 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,19 +1,96 @@
1
1
  require 'prx_auth/rails/token'
2
+ require 'open-uri'
2
3
 
3
4
  module PrxAuth
4
5
  module Rails
5
6
  module Controller
7
+
8
+ PRX_ACCOUNT_NAME_MAPPING_KEY = 'prx.account.name.mapping'.freeze
9
+ PRX_TOKEN_SESSION_KEY = 'prx.auth'.freeze
10
+
6
11
  def prx_auth_token
12
+ rack_auth_token = env_prx_auth_token
13
+ return rack_auth_token if rack_auth_token.present?
14
+
15
+ session[PRX_TOKEN_SESSION_KEY] && Rack::PrxAuth::TokenData.new(session[PRX_TOKEN_SESSION_KEY])
16
+ end
17
+
18
+ def prx_authenticated?
19
+ !!prx_auth_token
20
+ end
21
+
22
+ def authenticate!
23
+ return true if current_user.present?
24
+
25
+ redirect_to PrxAuth::Rails::Engine.routes.url_helpers.new_sessions_path
26
+ end
27
+
28
+ def current_user
29
+ return if prx_auth_token.nil?
30
+
31
+ PrxAuth::Rails::Token.new(prx_auth_token)
32
+ end
33
+
34
+ def lookup_and_register_accounts_names
35
+ session[PRX_ACCOUNT_NAME_MAPPING_KEY] =
36
+ lookup_account_names_mapping
37
+ end
38
+
39
+ def account_name_for(id)
40
+ id = id.to_i
41
+
42
+ session[PRX_ACCOUNT_NAME_MAPPING_KEY] ||= {}
43
+
44
+ name =
45
+ if session[PRX_ACCOUNT_NAME_MAPPING_KEY].has_key?(id)
46
+ session[PRX_ACCOUNT_NAME_MAPPING_KEY][id]
47
+ else
48
+ session[PRX_ACCOUNT_NAME_MAPPING_KEY][id] = lookup_account_name_for(id)
49
+ end
50
+
51
+ name = "[#{id}] Unknown Account Name" unless name.present?
52
+
53
+ name
54
+ end
55
+
56
+ def sign_in_user(token)
57
+ session[PRX_TOKEN_SESSION_KEY] = token
58
+ end
59
+
60
+ def sign_out_user
61
+ session.delete(PRX_TOKEN_SESSION_KEY)
62
+ end
63
+
64
+ private
65
+
66
+ def lookup_account_name_for(id)
67
+ id = id.to_i
68
+
69
+ res = lookup_account_names_mapping([id])
70
+ res[id]
71
+ end
72
+
73
+ def lookup_account_names_mapping(ids=current_user.resources)
74
+ id_host = PrxAuth::Rails.configuration.id_host
75
+ ids_param = ids.map(&:to_s).join(',')
76
+
77
+ options = {}
78
+ options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if ::Rails.env.development?
79
+
80
+ accounts = URI.open("https://#{id_host}/api/v1/accounts?account_ids=#{ids_param}", options).read
81
+
82
+ mapping = JSON.parse(accounts)['accounts'].map { |acct| [acct['id'], acct['display_name']] }.to_h
83
+
84
+ mapping
85
+ end
86
+
87
+ def env_prx_auth_token
7
88
  if !defined? @_prx_auth_token
8
89
  @_prx_auth_token = request.env['prx.auth'] && PrxAuth::Rails::Token.new(request.env['prx.auth'])
9
90
  else
10
91
  @_prx_auth_token
11
92
  end
12
93
  end
13
-
14
- def prx_authenticated?
15
- !!prx_auth_token
16
- end
17
94
  end
18
95
  end
19
96
  end