Skip to content

Commit e02fae1

Browse files
committed
Ensure ORCID authentication works within spaces
1 parent 4db69de commit e02fae1

File tree

7 files changed

+155
-19
lines changed

7 files changed

+155
-19
lines changed

app/controllers/callbacks_controller.rb

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# The controller for callback actions
22
class CallbacksController < Devise::OmniauthCallbacksController
3+
include SpaceRedirect
34

45
Devise.omniauth_configs.each do |provider, config|
56
define_method(provider) do
@@ -41,18 +42,4 @@ def handle_callback(provider, config)
4142
redirect_to_space(after_sign_in_path_for(@user), space)
4243
end
4344
end
44-
45-
private
46-
47-
def redirect_to_space(path, space)
48-
if space&.is_subdomain?
49-
port_part = ''
50-
port_part = ":#{request.port}" if (request.protocol == "http://" && request.port != 80) ||
51-
(request.protocol == "https://" && request.port != 443)
52-
redirect_to URI.join("#{request.protocol}#{space.host}#{port_part}", path).to_s, allow_other_host: true
53-
else
54-
redirect_to path
55-
end
56-
end
57-
5845
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module SpaceRedirect
2+
extend ActiveSupport::Concern
3+
4+
private
5+
6+
def redirect_to_space(path, space)
7+
if space&.is_subdomain?
8+
port_part = ''
9+
port_part = ":#{request.port}" if (request.protocol == "http://" && request.port != 80) ||
10+
(request.protocol == "https://" && request.port != 443)
11+
redirect_to URI.join("#{request.protocol}#{space.host}#{port_part}", path).to_s, allow_other_host: true
12+
else
13+
redirect_to path
14+
end
15+
end
16+
end

app/controllers/orcid_controller.rb

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
class OrcidController < ApplicationController
2+
include SpaceRedirect
3+
24
before_action :orcid_auth_enabled
35
before_action :authenticate_user!
46
before_action :set_oauth_client, only: [:authenticate, :callback]
@@ -9,12 +11,17 @@ class OrcidController < ApplicationController
911
end
1012

1113
def authenticate
12-
redirect_to @oauth2_client.authorization_uri(scope: '/authenticate'), allow_other_host: true
14+
params = Space.current_space&.default? ? {} : { state: "space_id:#{Space.current_space.id}" }
15+
redirect_to @oauth2_client.authorization_uri(scope: '/authenticate', **params), allow_other_host: true
1316
end
1417

1518
def callback
1619
@oauth2_client.authorization_code = params[:code]
1720
token = Rack::OAuth2::AccessToken::Bearer.new(access_token: @oauth2_client.access_token!)
21+
if params[:state].present?
22+
m = params[:state].match(/space_id:(\d*)/)
23+
space = Space.find_by_id(m[1]) if m
24+
end
1825
orcid = token.access_token&.raw_attributes['orcid']
1926
respond_to do |format|
2027
profile = current_user.profile
@@ -27,18 +34,29 @@ def callback
2734
else
2835
flash[:error] = t('orcid.authentication_failure')
2936
end
30-
format.html { redirect_to current_user }
37+
format.html { redirect_to_space(user_path(current_user), space) }
3138
end
3239
end
3340

3441
private
3542

43+
def redirect_to_space(path, space)
44+
if space&.is_subdomain?
45+
port_part = ''
46+
port_part = ":#{request.port}" if (request.protocol == "http://" && request.port != 80) ||
47+
(request.protocol == "https://" && request.port != 443)
48+
redirect_to URI.join("#{request.protocol}#{space.host}#{port_part}", path).to_s, allow_other_host: true
49+
else
50+
redirect_to path
51+
end
52+
end
53+
3654
def set_oauth_client
3755
config = Rails.application.config.secrets.orcid
3856
@oauth2_client ||= Rack::OAuth2::Client.new(
3957
identifier: config[:client_id],
4058
secret: config[:secret],
41-
redirect_uri: config[:redirect_uri].presence || orcid_callback_url,
59+
redirect_uri: config[:redirect_uri].presence || orcid_callback_url(host: TeSS::Config.base_uri.host),
4260
authorization_endpoint: '/oauth/authorize',
4361
token_endpoint: '/oauth/token',
4462
host: config[:host].presence || (Rails.env.production? ? 'orcid.org' : 'sandbox.orcid.org')

app/views/users/show.html.erb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@
5151
<% else %>
5252
<%= orcid_link(@user.profile) %>
5353
<% end %>
54-
<% if TeSS::Config.orcid_authentication_enabled? && current_user == @user && [email protected]_authenticated? %>
54+
<% if TeSS::Config.orcid_authentication_enabled? &&
55+
current_user == @user &&
56+
[email protected]_authenticated? &&
57+
space_supports_omniauth? %>
5558
<%= button_to t(@user.profile.orcid.blank? ? 'orcid.link' : 'orcid.authenticate'), authenticate_orcid_path, class: 'btn btn-default' %>
5659
<% end %>
5760
</p>

test/controllers/orcid_controller_test.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@ class OrcidControllerTest < ActionController::TestCase
99
post :authenticate
1010

1111
assert_redirected_to /https:\/\/sandbox\.orcid\.org\/oauth\/authorize\?.+/
12+
params = Rack::Utils.parse_query(URI.parse(response.location).query)
13+
assert_equal "#{TeSS::Config.base_url}/orcid/callback", params['redirect_uri']
14+
assert_nil params['state']
15+
end
16+
17+
test 'authenticating orcid in space uses root app redirect URI and sets space state' do
18+
plant_space = spaces(:plants)
19+
with_host(plant_space.host) do
20+
sign_in users(:regular_user)
21+
22+
post :authenticate
23+
24+
assert_redirected_to /https:\/\/sandbox\.orcid\.org\/oauth\/authorize\?.+/
25+
params = Rack::Utils.parse_query(URI.parse(response.location).query)
26+
assert_equal "#{TeSS::Config.base_url}/orcid/callback", params['redirect_uri']
27+
assert_equal "space_id:#{plant_space.id}", params['state']
28+
end
1229
end
1330

1431
test 'do not authenticate orcid if user not logged-in' do
@@ -148,4 +165,61 @@ class OrcidControllerTest < ActionController::TestCase
148165
end
149166
end
150167
end
168+
169+
test 'redirect to subdomain space in callback' do
170+
space = spaces(:astro)
171+
space.update!(host: 'space.example.com')
172+
mock_images
173+
user = users(:regular_user)
174+
assert user.profile.orcid.blank?
175+
sign_in user
176+
177+
VCR.use_cassette('orcid/get_token_free_orcid') do
178+
get :callback, params: { code: '123xyz', state: "space_id:#{space.id}" }
179+
end
180+
181+
profile = user.profile.reload
182+
assert_equal '0009-0006-0987-5702', profile.orcid
183+
assert profile.orcid_authenticated?
184+
assert_redirected_to user_url(user, host: 'space.example.com')
185+
assert response.headers['Location'].starts_with?('http://space.example.com/users/')
186+
assert flash[:error].blank?
187+
end
188+
189+
test 'do not redirect to non-subdomain space in callback' do
190+
space = spaces(:astro)
191+
space.update!(host: 'space.golf.com')
192+
mock_images
193+
user = users(:regular_user)
194+
assert user.profile.orcid.blank?
195+
sign_in user
196+
197+
VCR.use_cassette('orcid/get_token_free_orcid') do
198+
get :callback, params: { code: '123xyz', state: "space_id:#{space.id}" }
199+
end
200+
201+
profile = user.profile.reload
202+
assert_equal '0009-0006-0987-5702', profile.orcid
203+
assert profile.orcid_authenticated?
204+
assert_redirected_to user
205+
refute response.headers['Location'].starts_with?('http://space.golf.com/users/')
206+
assert flash[:error].blank?
207+
end
208+
209+
test 'ignore bad space when redirecting in callback' do
210+
mock_images
211+
user = users(:regular_user)
212+
assert user.profile.orcid.blank?
213+
sign_in user
214+
215+
VCR.use_cassette('orcid/get_token_free_orcid') do
216+
get :callback, params: { code: '123xyz', state: "space_id:banana🍌" }
217+
end
218+
219+
profile = user.profile.reload
220+
assert_equal '0009-0006-0987-5702', profile.orcid
221+
assert profile.orcid_authenticated?
222+
assert_redirected_to user
223+
assert flash[:error].blank?
224+
end
151225
end

test/controllers/users_controller_test.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ class UsersControllerTest < ActionController::TestCase
531531

532532
assert_response :success
533533
assert_select '#sidebar button', text: 'Authenticate your ORCID', count: 0
534+
assert_select '#sidebar button', text: 'Link your ORCID', count: 0
534535
end
535536
end
536537

@@ -543,6 +544,7 @@ class UsersControllerTest < ActionController::TestCase
543544

544545
assert_response :success
545546
assert_select '#sidebar button', text: 'Authenticate your ORCID', count: 0
547+
assert_select '#sidebar button', text: 'Link your ORCID', count: 0
546548
end
547549

548550
test 'should not show authenticate orcid button if already authenticated' do
@@ -554,6 +556,7 @@ class UsersControllerTest < ActionController::TestCase
554556

555557
assert_response :success
556558
assert_select '#sidebar button', text: 'Authenticate your ORCID', count: 0
559+
assert_select '#sidebar button', text: 'Link your ORCID', count: 0
557560
end
558561

559562
test 'should show material and event in trainer page as bioschemas JSON-LD' do
@@ -600,4 +603,39 @@ class UsersControllerTest < ActionController::TestCase
600603
assert_not_nil external_resource
601604
assert_equal external_resource.url, json_event['mentions'].first['url']
602605
end
606+
607+
test 'should show authenticate orcid button on subdomain space' do
608+
space = spaces(:astro)
609+
space.update!(host: 'space.example.com')
610+
with_host(space.host) do
611+
user = users(:private_user)
612+
assert user.profile.orcid.present?
613+
refute user.profile.orcid_authenticated?
614+
615+
sign_in user
616+
617+
get :show, params: { id: user }
618+
619+
assert_response :success
620+
assert_select '#sidebar button', text: 'Authenticate your ORCID'
621+
end
622+
end
623+
624+
test 'should not show authenticate orcid button on non-subdomain space' do
625+
space = spaces(:astro)
626+
space.update!(host: 'space.golf.com')
627+
with_host(space.host) do
628+
user = users(:private_user)
629+
assert user.profile.orcid.present?
630+
refute user.profile.orcid_authenticated?
631+
632+
sign_in user
633+
634+
get :show, params: { id: user }
635+
636+
assert_response :success
637+
assert_select '#sidebar button', text: 'Authenticate your ORCID', count: 0
638+
assert_select '#sidebar button', text: 'Link your ORCID', count: 0
639+
end
640+
end
603641
end

test/integration/omniauth_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ class OmniauthTest < ActionDispatch::IntegrationTest
418418
post user_oidc_omniauth_authorize_url(space_id: space.id)
419419
follow_redirect! # OmniAuth redirect
420420
assert_equal "http://space.example.com/users/aaf_user/edit", response.headers['Location']
421-
end
421+
end
422422

423423
test 'authentication does not redirect user to entirely different domain' do
424424
space = spaces(:astro)

0 commit comments

Comments
 (0)