prx_auth-rails 1.7.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a692b8cc459acfdf888599245ff2db7d7ed92a3c967fbccacdd871aa4e78cd7
4
- data.tar.gz: 17439da5ec7e7cc7f85d00083a47aaebb6d6bd495db25fa75581bf0758726d6c
3
+ metadata.gz: fedf5cbb7d3255c080ab645aea53038bd7cf2e9bae4c4852841df32278a44e3b
4
+ data.tar.gz: a33847b1bbf64bd0ed4d4839003edc2cf8f0cbc72731ffdf98828ae49e0c966e
5
5
  SHA512:
6
- metadata.gz: 2a185d858e8a50a33e1f18c85e7dabf5f01a46fbc068dd74d222bb48ab03732ced79c60affd45f7e70efd628cfaaf259e9d4781c390470de86a06dd46158bf0c
7
- data.tar.gz: d8f2b9c1c1367c2954575950f1ddc731f329f59d111efcb11282d78651a06cf22c74557f834d51ff6fa3ca7360d1cb06ad4736cd2a41d0ff28c5554913fcc193
6
+ metadata.gz: 243d784cb14623fe63af154eb6c7146c767cb96f0edcb19b4977ae66d70dc3db5c6137448b1c82a892316a0a3754d9886429d0861a20593a43fa18749b7b5ef9
7
+ data.tar.gz: 2844850399e1525cae1257cdd3e9f46f2ed0336ef4bf9e30b4737e0e535e0198f7f284855821b2bf092d1daf3afda6f6ea12a2bcb2517f37bd2b8a3c9b8a7661
@@ -1,16 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PrxAuth::Rails
2
4
  class SessionsController < ApplicationController
3
5
  include PrxAuth::Rails::Engine.routes.url_helpers
4
6
 
5
7
  skip_before_action :authenticate!
6
8
 
7
- before_action :set_nonce!, only: :show
9
+ before_action :set_nonce!, only: [:new, :show]
10
+ before_action :set_after_sign_in_path
8
11
 
9
- ID_NONCE_SESSION_KEY = 'id_prx_openid_nonce'.freeze
12
+ ID_NONCE_SESSION_KEY = 'id_prx_openid_nonce'
10
13
 
11
14
  def new
12
- set_nonce! unless fetch_nonce.present?
13
-
14
15
  config = PrxAuth::Rails.configuration
15
16
 
16
17
  id_auth_params = {
@@ -37,30 +38,27 @@ module PrxAuth::Rails
37
38
  end
38
39
 
39
40
  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
41
+ if valid_nonce? && users_match?
42
+ clear_nonce!
43
+ sign_in_user(access_token)
44
+ redirect_to after_sign_in_path_for(current_user)
45
+ else
46
+ clear_nonce!
47
+ redirect_to auth_error_sessions_path(error: 'verification_failed')
48
+ end
56
49
  end
57
50
 
58
51
  private
59
52
 
60
53
  def after_sign_in_path_for(_)
61
- return super if defined?(super)
62
-
63
- "/"
54
+ back_path = after_sign_in_user_redirect
55
+ if back_path.present?
56
+ back_path
57
+ elsif defined?(super)
58
+ super
59
+ else
60
+ '/'
61
+ end
64
62
  end
65
63
 
66
64
  def after_sign_out_path
@@ -69,49 +67,46 @@ module PrxAuth::Rails
69
67
  "https://#{id_host}/session/sign_out"
70
68
  end
71
69
 
70
+ def id_token
71
+ params.require('id_token')
72
+ end
73
+
74
+ def access_token
75
+ params.require('access_token')
76
+ end
77
+
72
78
  def id_claims
73
- id_token = params.require('id_token')
74
- validate_token(id_token)
79
+ @id_claims ||= validate_token(id_token)
75
80
  end
76
81
 
77
82
  def access_claims
78
- access_token = params.require('access_token')
79
- validate_token(access_token)
83
+ @access_claims ||= validate_token(access_token)
80
84
  end
81
85
 
82
- def reset_nonce!
83
- session[ID_NONCE_SESSION_KEY] = nil
86
+ def clear_nonce!
87
+ session.delete(ID_NONCE_SESSION_KEY)
84
88
  end
85
89
 
86
90
  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
+ session[ID_NONCE_SESSION_KEY] ||= SecureRandom.hex
91
92
  end
92
93
 
93
94
  def fetch_nonce
94
95
  session[ID_NONCE_SESSION_KEY]
95
96
  end
96
97
 
97
- def valid_nonce?(nonce)
98
- return false if fetch_nonce.nil?
99
-
100
- fetch_nonce == nonce
98
+ def valid_nonce?
99
+ id_claims['nonce'].present? && id_claims['nonce'] == fetch_nonce
101
100
  end
102
101
 
103
- def users_match?(claims1, claims2)
104
- return false if claims1['sub'].nil? || claims2['sub'].nil?
105
-
106
- claims1['sub'] == claims2['sub']
102
+ def users_match?
103
+ id_claims['sub'].present? && id_claims['sub'] == access_claims['sub']
107
104
  end
108
105
 
109
106
  def validate_token(token)
110
107
  prx_auth_cert = Rack::PrxAuth::Certificate.new("https://#{id_host}/api/v1/certs")
111
108
  auth_validator = Rack::PrxAuth::AuthValidator.new(token, prx_auth_cert, id_host)
112
- auth_validator.
113
- claims.
114
- with_indifferent_access
109
+ auth_validator.claims.with_indifferent_access
115
110
  end
116
111
 
117
112
  def id_host
@@ -2,7 +2,11 @@ module PrxAuth
2
2
  module Rails
3
3
  class Engine < ::Rails::Engine
4
4
  config.to_prepare do
5
- ::ApplicationController.helper_method [:current_user, :account_name_for]
5
+ ::ApplicationController.helper_method [
6
+ :current_user, :prx_jwt,
7
+ :current_user_info, :current_user_name, :current_user_apps,
8
+ :account_name_for, :account_for, :accounts_for,
9
+ ]
6
10
  end
7
11
  end
8
12
  end
@@ -4,15 +4,33 @@ require 'open-uri'
4
4
  module PrxAuth
5
5
  module Rails
6
6
  module Controller
7
+ class SessionTokenExpiredError < RuntimeError; end
7
8
 
8
- PRX_ACCOUNT_NAME_MAPPING_KEY = 'prx.account.name.mapping'.freeze
9
- PRX_TOKEN_SESSION_KEY = 'prx.auth'.freeze
9
+ PRX_AUTH_ENV_KEY = 'prx.auth'.freeze
10
+ PRX_JWT_SESSION_KEY = 'prx.auth.jwt'.freeze
11
+ # subtracted from the JWT ttl
12
+ PRX_JWT_REFRESH_TTL = 300.freeze
13
+ PRX_ACCOUNT_MAPPING_SESSION_KEY = 'prx.auth.account.mapping'.freeze
14
+ PRX_USER_INFO_SESSION_KEY = 'prx.auth.info'.freeze
15
+ PRX_REFRESH_BACK_KEY = 'prx.auth.back'.freeze
10
16
 
11
17
  def prx_auth_token
12
- rack_auth_token = env_prx_auth_token
13
- return rack_auth_token if rack_auth_token.present?
18
+ env_token || session_token
19
+ rescue SessionTokenExpiredError
20
+ session.delete(PRX_JWT_SESSION_KEY)
21
+ session.delete(PRX_ACCOUNT_MAPPING_SESSION_KEY)
22
+ session.delete(PRX_USER_INFO_SESSION_KEY)
23
+ nil
24
+ end
25
+
26
+ def set_after_sign_in_path
27
+ return if self.class == PrxAuth::Rails::SessionsController
28
+
29
+ session[PRX_REFRESH_BACK_KEY] = request.fullpath
30
+ end
14
31
 
15
- session[PRX_TOKEN_SESSION_KEY] && Rack::PrxAuth::TokenData.new(session[PRX_TOKEN_SESSION_KEY])
32
+ def prx_jwt
33
+ session[PRX_JWT_SESSION_KEY]
16
34
  end
17
35
 
18
36
  def prx_authenticated?
@@ -25,70 +43,116 @@ module PrxAuth
25
43
  redirect_to PrxAuth::Rails::Engine.routes.url_helpers.new_sessions_path
26
44
  end
27
45
 
46
+ def prx_auth_needs_refresh?(jwt_ttl)
47
+ request.get? && jwt_ttl < PRX_JWT_REFRESH_TTL
48
+ end
49
+
28
50
  def current_user
29
- return if prx_auth_token.nil?
51
+ prx_auth_token
52
+ end
30
53
 
31
- PrxAuth::Rails::Token.new(prx_auth_token)
54
+ def current_user_info
55
+ session[PRX_USER_INFO_SESSION_KEY] ||= fetch_userinfo
32
56
  end
33
57
 
34
- def lookup_and_register_accounts_names
35
- session[PRX_ACCOUNT_NAME_MAPPING_KEY] =
36
- lookup_account_names_mapping
58
+ def current_user_name
59
+ current_user_info['name'] || current_user_info['preferred_username'] || current_user_info['email']
37
60
  end
38
61
 
39
- def account_name_for(id)
40
- id = id.to_i
62
+ def current_user_apps
63
+ apps = (current_user_info.try(:[], 'apps') || []).map do |name, url|
64
+ label = name.sub(/^https?:\/\//, '').sub(/\..+/, '').capitalize
65
+ ["PRX #{label}", url]
66
+ end
67
+
68
+ # only return entire list in development
69
+ if ::Rails.env.production? || ::Rails.env.staging?
70
+ apps.to_h.select { |k, v| v.match?(/\.(org|tech)/) }
71
+ else
72
+ apps.to_h
73
+ end
74
+ end
41
75
 
42
- session[PRX_ACCOUNT_NAME_MAPPING_KEY] ||= {}
76
+ def sign_in_user(token)
77
+ session[PRX_JWT_SESSION_KEY] = token
78
+ accounts_for(current_user.resources)
79
+ end
43
80
 
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
81
+ def after_sign_in_user_redirect
82
+ session[PRX_REFRESH_BACK_KEY]
83
+ end
50
84
 
51
- name = "[#{id}] Unknown Account Name" unless name.present?
85
+ def sign_out_user
86
+ reset_session
87
+ end
52
88
 
53
- name
89
+ def account_name_for(account_id)
90
+ account_for(account_id).try(:[], :name)
54
91
  end
55
92
 
56
- def sign_in_user(token)
57
- session[PRX_TOKEN_SESSION_KEY] = token
93
+ def account_for(account_id)
94
+ lookup_accounts([account_id]).first
58
95
  end
59
96
 
60
- def sign_out_user
61
- session.delete(PRX_TOKEN_SESSION_KEY)
97
+ def accounts_for(account_ids)
98
+ lookup_accounts(account_ids)
62
99
  end
63
100
 
64
101
  private
65
102
 
66
- def lookup_account_name_for(id)
67
- id = id.to_i
103
+ def lookup_accounts(ids)
104
+ session[PRX_ACCOUNT_MAPPING_SESSION_KEY] ||= {}
68
105
 
69
- res = lookup_account_names_mapping([id])
70
- res[id]
106
+ # fetch any accounts we don't have yet
107
+ missing = ids - session[PRX_ACCOUNT_MAPPING_SESSION_KEY].keys
108
+ if missing.present?
109
+ fetch_accounts(missing).each do |account|
110
+ session[PRX_ACCOUNT_MAPPING_SESSION_KEY][account['id']] = account.with_indifferent_access
111
+ end
112
+ end
113
+
114
+ ids.map { |id| session[PRX_ACCOUNT_MAPPING_SESSION_KEY][id] }
71
115
  end
72
116
 
73
- def lookup_account_names_mapping(ids=current_user.resources)
74
- id_host = PrxAuth::Rails.configuration.id_host
117
+ def fetch_accounts(ids)
75
118
  ids_param = ids.map(&:to_s).join(',')
119
+ fetch("/api/v1/accounts?account_ids=#{ids_param}")['accounts']
120
+ end
76
121
 
122
+ def fetch_userinfo
123
+ fetch("/userinfo?scope=apps+email+profile", prx_jwt)
124
+ end
125
+
126
+ def fetch(path, token = nil)
127
+ url = "https://#{PrxAuth::Rails.configuration.id_host}#{path}"
77
128
  options = {}
78
129
  options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if ::Rails.env.development?
130
+ options['Authorization'] = "Bearer #{token}" if token
131
+ JSON.parse(URI.open(url, options).read)
132
+ end
79
133
 
80
- accounts = URI.open("https://#{id_host}/api/v1/accounts?account_ids=#{ids_param}", options).read
134
+ # token from data set by prx_auth rack middleware
135
+ def env_token
136
+ @env_token_data ||= if request.env[PRX_AUTH_ENV_KEY]
137
+ token_data = request.env[PRX_AUTH_ENV_KEY]
138
+ PrxAuth::Rails::Token.new(token_data)
139
+ end
140
+ end
81
141
 
82
- mapping = JSON.parse(accounts)['accounts'].map { |acct| [acct['id'], acct['display_name']] }.to_h
142
+ # token from jwt stored in session
143
+ def session_token
144
+ @session_prx_auth_token ||= if prx_jwt
145
+ # NOTE: we already validated this jwt - so just decode it
146
+ validator = Rack::PrxAuth::AuthValidator.new(prx_jwt)
83
147
 
84
- mapping
85
- end
148
+ # does this jwt need to be refreshed?
149
+ if prx_auth_needs_refresh?(validator.time_to_live)
150
+ raise SessionTokenExpiredError.new
151
+ end
86
152
 
87
- def env_prx_auth_token
88
- if !defined? @_prx_auth_token
89
- @_prx_auth_token = request.env['prx.auth'] && PrxAuth::Rails::Token.new(request.env['prx.auth'])
90
- else
91
- @_prx_auth_token
153
+ # create new data/token from access claims
154
+ token_data = Rack::PrxAuth::TokenData.new(validator.claims)
155
+ PrxAuth::Rails::Token.new(token_data)
92
156
  end
93
157
  end
94
158
  end
@@ -1,5 +1,5 @@
1
1
  module PrxAuth
2
2
  module Rails
3
- VERSION = "1.7.0"
3
+ VERSION = "2.1.0"
4
4
  end
5
5
  end
@@ -8,10 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = PrxAuth::Rails::VERSION
9
9
  spec.authors = ["Chris Rhoden"]
10
10
  spec.email = ["carhoden@gmail.com"]
11
- spec.description = %q{Rails integration for next generation PRX Authorization system.
12
- }
13
- spec.summary = %q{Rails integration for next generation PRX Authorization system.
14
- }
11
+ spec.description = "Rails integration for next generation PRX Authorization system."
12
+ spec.summary = "Rails integration for next generation PRX Authorization system."
15
13
  spec.homepage = "https://github.com/PRX/prx_auth-rails"
16
14
  spec.license = "MIT"
17
15
 
@@ -29,11 +27,11 @@ Gem::Specification.new do |spec|
29
27
  spec.add_development_dependency 'coveralls', '~> 0'
30
28
  spec.add_development_dependency 'guard'
31
29
  spec.add_development_dependency 'guard-minitest'
30
+ spec.add_development_dependency 'm', '~> 1.5'
32
31
  spec.add_development_dependency "rails", "~> 6.1.0"
33
32
  spec.add_development_dependency 'pry'
34
33
  spec.add_development_dependency 'sqlite3'
34
+ spec.add_development_dependency 'webmock'
35
35
 
36
-
37
-
38
- spec.add_runtime_dependency 'prx_auth', "~> 1.2"
36
+ spec.add_runtime_dependency 'prx_auth', ">= 1.7.0"
39
37
  end
@@ -2,6 +2,8 @@ class ApplicationController < ActionController::Base
2
2
 
3
3
  before_action :authenticate!
4
4
 
5
+ def index; end
6
+
5
7
  def after_sign_in_path_for(_resource)
6
8
  '/after-sign-in-path'
7
9
  end
@@ -0,0 +1 @@
1
+ the controller index!
@@ -1,3 +1,5 @@
1
1
  Rails.application.routes.draw do
2
+ get 'index', to: 'application#index'
3
+ put 'index', to: 'application#index'
2
4
  mount PrxAuth::Rails::Engine => "/prx_auth-rails"
3
5
  end
@@ -0,0 +1,165 @@
1
+ require 'test_helper'
2
+
3
+ module PrxAuth::Rails::Ext
4
+ class ControllerTest < ActionController::TestCase
5
+
6
+ setup do
7
+ @controller = ApplicationController.new
8
+ @jwt_session_key = ApplicationController::PRX_JWT_SESSION_KEY
9
+ @user_info_key = ApplicationController::PRX_USER_INFO_SESSION_KEY
10
+ @account_mapping_key = ApplicationController::PRX_ACCOUNT_MAPPING_SESSION_KEY
11
+ @stub_claims = {'iat' => Time.now.to_i, 'exp' => Time.now.to_i + 3600}
12
+ end
13
+
14
+ # stub auth and init controller+session by getting a page
15
+ def with_stubbed_auth(jwt)
16
+ session[@jwt_session_key] = 'some-jwt'
17
+ @controller.stub(:prx_auth_needs_refresh?, false) do
18
+ get :index
19
+ assert_equal response.code, '200'
20
+ yield
21
+ end
22
+ end
23
+
24
+ test 'redirects unless you are authenticated' do
25
+ get :index
26
+ assert_equal response.code, '302'
27
+ assert response.headers['Location'].ends_with?('/sessions/new')
28
+ end
29
+
30
+ test 'uses a valid session token' do
31
+ session[@jwt_session_key] = 'some-jwt'
32
+ JSON::JWT.stub(:decode, @stub_claims) do
33
+ get :index
34
+ assert_equal response.code, '200'
35
+ assert response.body.include?('the controller index!')
36
+ assert @controller.current_user.is_a?(PrxAuth::Rails::Token)
37
+ end
38
+ end
39
+
40
+ test 'redirects if your token is nearing expiration' do
41
+ session[@jwt_session_key] = 'some-jwt'
42
+ @stub_claims['exp'] = Time.now.to_i + 10
43
+ JSON::JWT.stub(:decode, @stub_claims) do
44
+ get :index
45
+ assert_equal response.code, '302'
46
+ assert response.headers['Location'].ends_with?('/sessions/new')
47
+ end
48
+ end
49
+
50
+ test 'does not redirect if your token has expired on a non-GET request' do
51
+ session[@jwt_session_key] = 'some-jwt'
52
+ @stub_claims['exp'] = Time.now.to_i + 10
53
+ JSON::JWT.stub(:decode, @stub_claims) do
54
+ put :index
55
+ assert_equal response.code, '200'
56
+ assert response.body.include?('the controller index!')
57
+ end
58
+ end
59
+
60
+ test 'fetches current user info' do
61
+ with_stubbed_auth('some-jwt') do
62
+ body = {
63
+ 'name' => 'Some Username',
64
+ 'apps' => {'publish.prx.test' => 'https://publish.prx.test'},
65
+ 'other' => 'stuff'
66
+ }
67
+
68
+ id_host = PrxAuth::Rails.configuration.id_host
69
+ stub_request(:get, "https://#{id_host}/userinfo?scope=apps%20email%20profile").
70
+ with(headers: {'Authorization' => 'Bearer some-jwt'}).
71
+ to_return(status: 200, body: JSON.generate(body))
72
+
73
+ assert session[@user_info_key] == nil
74
+ assert_equal @controller.current_user_info, body
75
+ refute session[@user_info_key] == nil
76
+ assert_equal @controller.current_user_name, 'Some Username'
77
+ assert_equal @controller.current_user_apps, {'PRX Publish' => 'https://publish.prx.test'}
78
+ end
79
+ end
80
+
81
+ test 'has user name fallbacks' do
82
+ with_stubbed_auth('some-jwt') do
83
+ session[@user_info_key] = {'name' => 'one', 'preferred_username' => 'two', 'email' => 'three'}
84
+ assert_equal @controller.current_user_name, 'one'
85
+
86
+ session[@user_info_key] = {'preferred_username' => 'two', 'email' => 'three'}
87
+ assert_equal @controller.current_user_name, 'two'
88
+
89
+ session[@user_info_key] = {'email' => 'three'}
90
+ assert_equal @controller.current_user_name, 'three'
91
+ end
92
+ end
93
+
94
+ test 'filters apps displayed in production' do
95
+ with_stubbed_auth('some-jwt') do
96
+ Rails.env.stub(:production?, true) do
97
+ session[@user_info_key] = {
98
+ 'apps' => {
99
+ 'localhost stuff' => 'http://localhost:4000/path1',
100
+ 'publish.prx.test' => 'https://publish.prx.test/path2',
101
+ 'metrics.prx.tech' => 'https://metrics.prx.tech/path3',
102
+ 'augury.prx.org' => 'https://augury.prx.org/path4',
103
+ }
104
+ }
105
+
106
+ assert_equal @controller.current_user_apps, {
107
+ 'PRX Metrics' => 'https://metrics.prx.tech/path3',
108
+ 'PRX Augury' => 'https://augury.prx.org/path4',
109
+ }
110
+ end
111
+ end
112
+ end
113
+
114
+ test 'fetches accounts' do
115
+ with_stubbed_auth('some-jwt') do
116
+ one = {'id' => 1, 'type' => 'IndividualAccount', 'name' => 'One'}
117
+ three = {'id' => 3, 'type' => 'GroupAccount', 'name' => 'Three'}
118
+ body = {'accounts' => [one, three]}
119
+
120
+ id_host = PrxAuth::Rails.configuration.id_host
121
+ stub_request(:get, "https://#{id_host}/api/v1/accounts?account_ids=1,2,3").
122
+ to_return(status: 200, body: JSON.generate(body))
123
+
124
+ assert_nil session[@account_mapping_key]
125
+ assert_equal @controller.accounts_for([1, 2, 3]), [one, nil, three]
126
+ refute_nil session[@account_mapping_key]
127
+ assert_equal @controller.account_for(1), one
128
+ assert_equal @controller.account_for(3), three
129
+ assert_equal @controller.account_name_for(1), 'One'
130
+ assert_equal @controller.account_name_for(3), 'Three'
131
+ end
132
+ end
133
+
134
+ test 'handles unknown account ids' do
135
+ with_stubbed_auth('some-jwt') do
136
+ id_host = PrxAuth::Rails.configuration.id_host
137
+ stub_request(:get, "https://#{id_host}/api/v1/accounts?account_ids=2").
138
+ to_return(status: 200, body: JSON.generate({accounts: []})).
139
+ times(3)
140
+
141
+ assert_equal @controller.accounts_for([2]), [nil]
142
+ assert_nil @controller.account_for(2)
143
+ assert_nil @controller.account_name_for(2)
144
+ end
145
+ end
146
+
147
+ test 'only fetches only missing accounts' do
148
+ with_stubbed_auth('some-jwt') do
149
+ one = {'name' => 'One'}
150
+ two = {'id' => 2, 'type' => 'StationAccount', 'name' => 'Two'}
151
+ three = {'name' => 'Three'}
152
+ session[@account_mapping_key] = {1 => one, 3 => three}
153
+ body = {'accounts' => [two]}
154
+
155
+ id_host = PrxAuth::Rails.configuration.id_host
156
+ stub_request(:get, "https://#{id_host}/api/v1/accounts?account_ids=2").
157
+ to_return(status: 200, body: JSON.generate(body))
158
+
159
+ assert_equal @controller.accounts_for([1, 2, 3]), [one, two, three]
160
+ assert_equal @controller.account_for(2), two
161
+ assert_equal @controller.account_name_for(2), 'Two'
162
+ end
163
+ end
164
+ end
165
+ end
@@ -6,8 +6,10 @@ module PrxAuth::Rails
6
6
  setup do
7
7
  @routes = PrxAuth::Rails::Engine.routes
8
8
  @nonce_session_key = SessionsController::ID_NONCE_SESSION_KEY
9
- @token_params = {id_token: 'sometok', access_token: 'othertok'}
9
+ @refresh_back_key = SessionsController::PRX_REFRESH_BACK_KEY
10
+ @token_params = {id_token: 'idtok', access_token: 'accesstok'}
10
11
  @stub_claims = {'nonce' => '123', 'sub' => '1'}
12
+ @stub_token = PrxAuth::Rails::Token.new(Rack::PrxAuth::TokenData.new())
11
13
  end
12
14
 
13
15
  test "new creates nonce" do
@@ -31,11 +33,12 @@ module PrxAuth::Rails
31
33
  end
32
34
 
33
35
  test 'create should validate a token and set the session variable' do
36
+ session[SessionsController::PRX_JWT_SESSION_KEY] = nil
34
37
  @controller.stub(:validate_token, @stub_claims) do
35
- @controller.stub(:lookup_and_register_accounts_names, nil) do
38
+ @controller.stub(:session_token, @stub_token) do
36
39
  session[@nonce_session_key] = '123'
37
40
  post :create, params: @token_params, format: :json
38
- assert session['prx.auth']['id_token']['nonce'] == '123'
41
+ assert session[SessionsController::PRX_JWT_SESSION_KEY] == 'accesstok'
39
42
  end
40
43
  end
41
44
  end
@@ -50,7 +53,7 @@ module PrxAuth::Rails
50
53
 
51
54
  test 'create should reset the nonce after consumed' do
52
55
  @controller.stub(:validate_token, @stub_claims) do
53
- @controller.stub(:lookup_and_register_accounts_names, nil) do
56
+ @controller.stub(:session_token, @stub_token) do
54
57
  session[@nonce_session_key] = '123'
55
58
  post :create, params: @token_params, format: :json
56
59
 
@@ -61,6 +64,22 @@ module PrxAuth::Rails
61
64
  end
62
65
  end
63
66
 
67
+ test 'redirects to a back-path after refresh' do
68
+ @controller.stub(:validate_token, @stub_claims) do
69
+ @controller.stub(:session_token, @stub_token) do
70
+ session[@nonce_session_key] = '123'
71
+ session[@refresh_back_key] = '/lets/go/here?okay'
72
+ post :create, params: @token_params, format: :json
73
+
74
+ # A trailing log of the 'last' page
75
+ assert session[@refresh_back_key] == '/lets/go/here?okay'
76
+
77
+ assert response.code == '302'
78
+ assert response.headers['Location'].ends_with?('/lets/go/here?okay')
79
+ end
80
+ end
81
+ end
82
+
64
83
  test 'should respond with redirect to the auth error page / code if the nonce does not match' do
65
84
  @controller.stub(:validate_token, @stub_claims) do
66
85
  session[@nonce_session_key] = 'nonce-does-not-match'
@@ -96,9 +115,9 @@ module PrxAuth::Rails
96
115
  end
97
116
 
98
117
  test 'should clear the user token on sign out' do
99
- session[PrxAuth::Rails::Controller::PRX_TOKEN_SESSION_KEY] = 'some-token'
118
+ session[SessionsController::PRX_JWT_SESSION_KEY] = 'some-token'
100
119
  post :destroy
101
- assert session[PrxAuth::Rails::Controller::PRX_TOKEN_SESSION_KEY] == nil
120
+ assert session[SessionsController::PRX_JWT_SESSION_KEY] == nil
102
121
  end
103
122
  end
104
123
  end
data/test/test_helper.rb CHANGED
@@ -5,6 +5,7 @@ Coveralls.wear!
5
5
  require 'minitest/autorun'
6
6
  require 'minitest/spec'
7
7
  require 'minitest/pride'
8
+ require 'webmock/minitest'
8
9
  require 'action_pack'
9
10
  require 'action_controller'
10
11
  require 'action_view'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prx_auth-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Rhoden
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-01 00:00:00.000000000 Z
11
+ date: 2022-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: m
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.5'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rails
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -136,23 +150,35 @@ dependencies:
136
150
  - - ">="
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
139
167
  - !ruby/object:Gem::Dependency
140
168
  name: prx_auth
141
169
  requirement: !ruby/object:Gem::Requirement
142
170
  requirements:
143
- - - "~>"
171
+ - - ">="
144
172
  - !ruby/object:Gem::Version
145
- version: '1.2'
173
+ version: 1.7.0
146
174
  type: :runtime
147
175
  prerelease: false
148
176
  version_requirements: !ruby/object:Gem::Requirement
149
177
  requirements:
150
- - - "~>"
178
+ - - ">="
151
179
  - !ruby/object:Gem::Version
152
- version: '1.2'
153
- description: 'Rails integration for next generation PRX Authorization system.
154
-
155
- '
180
+ version: 1.7.0
181
+ description: Rails integration for next generation PRX Authorization system.
156
182
  email:
157
183
  - carhoden@gmail.com
158
184
  executables: []
@@ -165,10 +191,6 @@ files:
165
191
  - LICENSE.txt
166
192
  - README.md
167
193
  - Rakefile
168
- - app/assets/config/prx_auth-rails_manifest.js
169
- - app/assets/images/prx_auth-rails/user.svg
170
- - app/assets/javascripts/prx_auth-rails/user_widget.js.erb
171
- - app/assets/stylesheets/prx_auth-rails/user_widget.css
172
194
  - app/controllers/prx_auth/rails/sessions_controller.rb
173
195
  - app/views/prx_auth/rails/sessions/auth_error.html.erb
174
196
  - app/views/prx_auth/rails/sessions/show.html.erb
@@ -196,6 +218,7 @@ files:
196
218
  - test/dummy/app/mailers/application_mailer.rb
197
219
  - test/dummy/app/models/application_record.rb
198
220
  - test/dummy/app/models/concerns/.keep
221
+ - test/dummy/app/views/application/index.html.erb
199
222
  - test/dummy/app/views/layouts/application.html.erb
200
223
  - test/dummy/app/views/layouts/mailer.html.erb
201
224
  - test/dummy/app/views/layouts/mailer.text.erb
@@ -239,6 +262,7 @@ files:
239
262
  - test/dummy/storage/.keep
240
263
  - test/log/development.log
241
264
  - test/prx_auth/rails/configuration_test.rb
265
+ - test/prx_auth/rails/ext/controller_test.rb
242
266
  - test/prx_auth/rails/sessions_controller_test.rb
243
267
  - test/prx_auth/rails/token_test.rb
244
268
  - test/test_helper.rb
@@ -281,6 +305,7 @@ test_files:
281
305
  - test/dummy/app/mailers/application_mailer.rb
282
306
  - test/dummy/app/models/application_record.rb
283
307
  - test/dummy/app/models/concerns/.keep
308
+ - test/dummy/app/views/application/index.html.erb
284
309
  - test/dummy/app/views/layouts/application.html.erb
285
310
  - test/dummy/app/views/layouts/mailer.html.erb
286
311
  - test/dummy/app/views/layouts/mailer.text.erb
@@ -324,6 +349,7 @@ test_files:
324
349
  - test/dummy/storage/.keep
325
350
  - test/log/development.log
326
351
  - test/prx_auth/rails/configuration_test.rb
352
+ - test/prx_auth/rails/ext/controller_test.rb
327
353
  - test/prx_auth/rails/sessions_controller_test.rb
328
354
  - test/prx_auth/rails/token_test.rb
329
355
  - test/test_helper.rb
@@ -1,3 +0,0 @@
1
- //= link_directory ../javascripts .js
2
- //= link_directory ../stylesheets .css
3
- //= link_tree ../images
@@ -1,5 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
- <svg width="100%" height="100%" viewBox="0 0 51 51" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
4
- <path d="M51,25.5C51,11.44 39.56,0 25.5,0C11.44,0 0,11.44 0,25.5C0,32.927 3.194,39.621 8.277,44.285L8.253,44.306L9.08,45.003C9.134,45.049 9.192,45.086 9.246,45.13C9.685,45.495 10.141,45.841 10.604,46.175C10.755,46.284 10.905,46.392 11.058,46.498C11.553,46.839 12.061,47.163 12.58,47.47C12.693,47.537 12.807,47.602 12.922,47.666C13.49,47.99 14.07,48.295 14.665,48.575C14.708,48.596 14.753,48.614 14.796,48.635C16.734,49.535 18.801,50.196 20.964,50.586C21.02,50.597 21.077,50.607 21.134,50.617C21.806,50.733 22.485,50.826 23.172,50.888C23.255,50.895 23.339,50.9 23.423,50.907C24.107,50.964 24.799,51 25.5,51C26.195,51 26.88,50.964 27.56,50.909C27.647,50.902 27.733,50.897 27.819,50.89C28.501,50.828 29.174,50.738 29.839,50.624C29.896,50.613 29.955,50.603 30.012,50.592C32.142,50.21 34.18,49.564 36.092,48.686C36.163,48.654 36.234,48.623 36.305,48.59C36.877,48.321 37.436,48.031 37.984,47.722C38.12,47.645 38.256,47.567 38.391,47.487C38.89,47.194 39.38,46.887 39.857,46.56C40.029,46.443 40.196,46.32 40.366,46.198C40.773,45.905 41.173,45.602 41.561,45.286C41.648,45.217 41.74,45.156 41.825,45.085L42.673,44.376L42.648,44.355C47.776,39.689 51,32.965 51,25.5ZM1.855,25.5C1.855,12.462 12.462,1.855 25.5,1.855C38.538,1.855 49.145,12.462 49.145,25.5C49.145,32.526 46.062,38.843 41.181,43.177C40.908,42.988 40.634,42.82 40.353,42.679L32.502,38.754C31.797,38.401 31.359,37.693 31.359,36.905L31.359,34.164C31.541,33.939 31.733,33.685 31.932,33.406C32.948,31.971 33.763,30.374 34.357,28.656C35.532,28.097 36.291,26.927 36.291,25.606L36.291,22.319C36.291,21.515 35.996,20.735 35.468,20.122L35.468,15.794C35.516,15.312 35.687,12.597 33.722,10.357C32.013,8.406 29.247,7.418 25.5,7.418C21.753,7.418 18.987,8.406 17.278,10.356C15.313,12.596 15.484,15.313 15.532,15.793L15.532,20.121C15.005,20.734 14.709,21.514 14.709,22.318L14.709,25.605C14.709,26.626 15.167,27.578 15.952,28.221C16.703,31.163 18.249,33.39 18.82,34.145L18.82,36.828C18.82,37.585 18.407,38.281 17.742,38.644L10.41,42.643C10.177,42.77 9.945,42.919 9.713,43.085C4.892,38.753 1.855,32.475 1.855,25.5Z" style="fill:white;fill-rule:nonzero;"/>
5
- </svg>
@@ -1,46 +0,0 @@
1
- // https://stackoverflow.com/questions/8578617/inject-a-script-tag-with-remote-src-and-wait-for-it-to-execute
2
- function prxInjectScript(src, callback) {
3
- const script = document.createElement('script');
4
-
5
- script.type = 'text/javascript';
6
- script.async = false;
7
- script.src = src;
8
-
9
- script.onload = function () { script.onload = null; callback(); }
10
-
11
- document.getElementsByTagName('head')[0].appendChild(script);
12
- }
13
-
14
- document.addEventListener('DOMContentLoaded', function () {
15
-
16
- const widget = document.getElementById('prx-user-widget');
17
- const account = document.getElementById('prx-user-widget-menu-account');
18
-
19
- const idHost = 'https://' + widget.getAttribute('data-id-host');;
20
- const scriptUrl = idHost + '/widget.js';
21
-
22
- prxInjectScript(scriptUrl, function () {
23
- const signIn = new PRXSignIn(idHost);
24
-
25
- signIn.signedIn(function (prx) {
26
-
27
- if (!prx.userinfo) {
28
- // Not logged in
29
- widget.classList.add('no-user-info');
30
-
31
- const url = idHost + '/session?return_to=' + encodeURIComponent(window.location);
32
-
33
- account.innerHTML = '<a class=sign-in href="' + url + '">Sign in</a>';
34
- } else {
35
- // Logged in
36
- widget.classList.add('user-info');
37
-
38
- const account = document.getElementById('prx-user-widget-menu-account');
39
- account.innerText = prx.userinfo.email;
40
-
41
- signIn.listApps('prx-user-widget-menu-apps');
42
- }
43
- });
44
- });
45
- });
46
-
@@ -1,69 +0,0 @@
1
- #prx-user-widget {
2
- display: flex;
3
- flex-direction: column;
4
- height: 100%;
5
- justify-content: center;
6
- padding: 0 20px;
7
- position: absolute;
8
- right: 0;
9
- transition-property: opacity;
10
- transition-duration: 0.2s;
11
- }
12
- @media (max-width: ) {
13
- #prx-user-widget {
14
- height: auto;
15
- top: 0;
16
- }
17
- }
18
- #prx-user-widget:hover {
19
- }
20
- #prx-user-widget:hover .user-icon {
21
- opacity: 1;
22
- }
23
- #prx-user-widget:hover #prx-user-widget-menu {
24
- display: block;
25
- }
26
- #prx-user-widget .user-icon {
27
- cursor: pointer;
28
- height: 2em;
29
- opacity: 0.7;
30
- width: 2em;
31
- }
32
- #prx-user-widget #prx-user-widget-menu {
33
- background-color: #1a1a1a;
34
- display: none;
35
- right: 0;
36
- padding: 10px 20px;
37
- position: absolute;
38
- top: 100%;
39
- z-index: 999;
40
- display: none;
41
- }
42
-
43
- #prx-user-widget #prx-user-widget-menu h1 {
44
- color: white;
45
- font-size: .9em;
46
- font-weight: 700;
47
- }
48
-
49
- #prx-user-widget #prx-user-widget-menu-apps {
50
- padding: 0;
51
- }
52
- #prx-user-widget #prx-user-widget-menu-apps ul {
53
- border-top: 1px solid #ddd;
54
- padding: 15px 0 0;
55
- }
56
-
57
- #prx-user-widget #prx-user-widget-menu-apps ul li a {
58
- display: block;
59
- font-weight: normal;
60
- opacity: 0.7;
61
- padding: 5px 0;
62
- text-transform: none;
63
- }
64
- #prx-user-widget #prx-user-widget-menu-apps ul li a:hover {
65
- opacity: 1;
66
- }
67
- .prx-home #prx-user-widget.loaded:hover {
68
- background: transparent;
69
- }