prx_auth-rails 1.8.0 → 2.0.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: 5a687c733815e035f361fbfd4981e3673480311c6d760206b9b1edd4903e084b
4
- data.tar.gz: 5b04a0584489415a7b0223342c85e638c8c3ca01c141e4ff46db9269c192f6be
3
+ metadata.gz: 76cccc8605691493ace58a976780a69b37bb138d63874eec2a4b1158a316c237
4
+ data.tar.gz: 121c8ac1bfe90d10d4424030c8472f5f443d5a26a62cf4e4480dae5de356c408
5
5
  SHA512:
6
- metadata.gz: 71573dd4f1303e7e2b5fc4ca908c502c74aa986845ddb685613b6e8422c05e136c1952f5bfc2989a607b1a5020f708a06190ebe084a199a46aeefab1f7168706
7
- data.tar.gz: dc5685c9e2908ea56c10f577eb2a457167087345f07b09927e8c36c1b0d2df513e2f2367436fe4f637aac648311c75a44ee3f845c63124be7ab4009c45dccb57
6
+ metadata.gz: 1f6e5d4ca1a8590e1f313791d91f08179eff18217671fbc532f5a6a6dcf365f81519e71192dd80257b35af148b53b8b46933c6fd9d3ce47d678329d91919f38c
7
+ data.tar.gz: 4292c219ad997eea92c2ae81e66db3223e8e4d3cd7f63d521ca0ee59e008e409ba6cf228e97aa5f24765da30dc3defa8e47a9349491d1d5c5885cfb7ca7fad45
@@ -4,6 +4,7 @@ module PrxAuth
4
4
  config.to_prepare do
5
5
  ::ApplicationController.helper_method [
6
6
  :current_user, :prx_jwt,
7
+ :current_user_info, :current_user_name, :current_user_apps,
7
8
  :account_name_for, :account_for, :accounts_for,
8
9
  ]
9
10
  end
@@ -10,12 +10,15 @@ module PrxAuth
10
10
  PRX_JWT_SESSION_KEY = 'prx.auth.jwt'.freeze
11
11
  PRX_JWT_REFRESH_TTL = 300.freeze
12
12
  PRX_ACCOUNT_MAPPING_SESSION_KEY = 'prx.auth.account.mapping'.freeze
13
+ PRX_USER_INFO_SESSION_KEY = 'prx.auth.info'.freeze
13
14
  PRX_REFRESH_BACK_KEY = 'prx.auth.back'.freeze
14
15
 
15
16
  def prx_auth_token
16
17
  env_token || session_token
17
18
  rescue SessionTokenExpiredError
18
- reset_session
19
+ session.delete(PRX_JWT_SESSION_KEY)
20
+ session.delete(PRX_ACCOUNT_MAPPING_SESSION_KEY)
21
+ session.delete(PRX_USER_INFO_SESSION_KEY)
19
22
  session[PRX_REFRESH_BACK_KEY] = request.fullpath
20
23
  redirect_to PrxAuth::Rails::Engine.routes.url_helpers.new_sessions_path
21
24
  end
@@ -34,10 +37,36 @@ module PrxAuth
34
37
  redirect_to PrxAuth::Rails::Engine.routes.url_helpers.new_sessions_path
35
38
  end
36
39
 
40
+ def prx_auth_needs_refresh?(jwt_ttl)
41
+ request.get? && jwt_ttl < PRX_JWT_REFRESH_TTL
42
+ end
43
+
37
44
  def current_user
38
45
  prx_auth_token
39
46
  end
40
47
 
48
+ def current_user_info
49
+ session[PRX_USER_INFO_SESSION_KEY] ||= fetch_userinfo
50
+ end
51
+
52
+ def current_user_name
53
+ current_user_info['name'] || current_user_info['preferred_username'] || current_user_info['email']
54
+ end
55
+
56
+ def current_user_apps
57
+ apps = (current_user_info.try(:[], 'apps') || []).map do |name, url|
58
+ label = name.sub(/^https?:\/\//, '').sub(/\..+/, '').capitalize
59
+ ["PRX #{label}", url]
60
+ end
61
+
62
+ # only return entire list in development
63
+ if ::Rails.env.production? || ::Rails.env.staging?
64
+ apps.to_h.select { |k, v| v.match?(/\.(org|tech)/) }
65
+ else
66
+ apps.to_h
67
+ end
68
+ end
69
+
41
70
  def sign_in_user(token)
42
71
  session[PRX_JWT_SESSION_KEY] = token
43
72
  accounts_for(current_user.resources)
@@ -52,7 +81,7 @@ module PrxAuth
52
81
  end
53
82
 
54
83
  def account_name_for(account_id)
55
- account_for(account_id)[:name]
84
+ account_for(account_id).try(:[], :name)
56
85
  end
57
86
 
58
87
  def account_for(account_id)
@@ -80,15 +109,20 @@ module PrxAuth
80
109
  end
81
110
 
82
111
  def fetch_accounts(ids)
83
- id_host = PrxAuth::Rails.configuration.id_host
84
112
  ids_param = ids.map(&:to_s).join(',')
113
+ fetch("/api/v1/accounts?account_ids=#{ids_param}")['accounts']
114
+ end
85
115
 
116
+ def fetch_userinfo
117
+ fetch("/userinfo?scope=apps+email+profile", prx_jwt)
118
+ end
119
+
120
+ def fetch(path, token = nil)
121
+ url = "https://#{PrxAuth::Rails.configuration.id_host}#{path}"
86
122
  options = {}
87
123
  options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if ::Rails.env.development?
88
-
89
- accounts = URI.open("https://#{id_host}/api/v1/accounts?account_ids=#{ids_param}", options).read
90
-
91
- JSON.parse(accounts)['accounts']
124
+ options['Authorization'] = "Bearer #{token}" if token
125
+ JSON.parse(URI.open(url, options).read)
92
126
  end
93
127
 
94
128
  # token from data set by prx_auth rack middleware
@@ -105,8 +139,8 @@ module PrxAuth
105
139
  # NOTE: we already validated this jwt - so just decode it
106
140
  validator = Rack::PrxAuth::AuthValidator.new(prx_jwt)
107
141
 
108
- # try to refresh auth session on GET requests
109
- if request.get? && validator.time_to_live < PRX_JWT_REFRESH_TTL
142
+ # does this jwt need to be refreshed?
143
+ if prx_auth_needs_refresh?(validator.time_to_live)
110
144
  raise SessionTokenExpiredError.new
111
145
  end
112
146
 
@@ -1,5 +1,5 @@
1
1
  module PrxAuth
2
2
  module Rails
3
- VERSION = "1.8.0"
3
+ VERSION = "2.0.0"
4
4
  end
5
5
  end
@@ -27,9 +27,11 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency 'coveralls', '~> 0'
28
28
  spec.add_development_dependency 'guard'
29
29
  spec.add_development_dependency 'guard-minitest'
30
+ spec.add_development_dependency 'm', '~> 1.5'
30
31
  spec.add_development_dependency "rails", "~> 6.1.0"
31
32
  spec.add_development_dependency 'pry'
32
33
  spec.add_development_dependency 'sqlite3'
34
+ spec.add_development_dependency 'webmock'
33
35
 
34
36
  spec.add_runtime_dependency 'prx_auth', ">= 1.7.0"
35
37
  end
@@ -6,9 +6,21 @@ module PrxAuth::Rails::Ext
6
6
  setup do
7
7
  @controller = ApplicationController.new
8
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
9
11
  @stub_claims = {'iat' => Time.now.to_i, 'exp' => Time.now.to_i + 3600}
10
12
  end
11
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
+
12
24
  test 'redirects unless you are authenticated' do
13
25
  get :index
14
26
  assert_equal response.code, '302'
@@ -45,5 +57,109 @@ module PrxAuth::Rails::Ext
45
57
  end
46
58
  end
47
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
48
164
  end
49
165
  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.8.0
4
+ version: 2.0.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-31 00:00:00.000000000 Z
11
+ date: 2021-05-13 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,6 +150,20 @@ 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
@@ -163,10 +191,6 @@ files:
163
191
  - LICENSE.txt
164
192
  - README.md
165
193
  - Rakefile
166
- - app/assets/config/prx_auth-rails_manifest.js
167
- - app/assets/images/prx_auth-rails/user.svg
168
- - app/assets/javascripts/prx_auth-rails/user_widget.js.erb
169
- - app/assets/stylesheets/prx_auth-rails/user_widget.css
170
194
  - app/controllers/prx_auth/rails/sessions_controller.rb
171
195
  - app/views/prx_auth/rails/sessions/auth_error.html.erb
172
196
  - app/views/prx_auth/rails/sessions/show.html.erb
@@ -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
- }