Skip to content

Commit a7a20ae

Browse files
authored
Merge pull request #11711 from cslzchen/feature/fix-auth-views
[ENG-10872] Fix auth views and session changes introduced by #11531 and #11624
2 parents fa0309e + 88d452e commit a7a20ae

15 files changed

Lines changed: 111 additions & 95 deletions

File tree

framework/auth/utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from framework import sentry
1313
from website import settings
14+
from website.util import web_url_for
1415

1516
logger = logging.getLogger(__name__)
1617

@@ -169,3 +170,16 @@ def generate_csl_given_name(given_name, middle_names='', suffix=''):
169170
if suffix:
170171
given = f'{given}, {suffix}'
171172
return given
173+
174+
def get_default_osf_login_url():
175+
"""Return the default OSF login URL.
176+
"""
177+
next_url = web_url_for(view_name='index', _absolute=True, _angular_route=True)
178+
return web_url_for(view_name='auth_login', _absolute=True, next=next_url)
179+
180+
181+
def get_default_osf_logout_url():
182+
"""Return the default OSF logout URL.
183+
"""
184+
next_url = web_url_for(view_name='index', _absolute=True, _angular_route=True)
185+
return web_url_for(view_name='auth_logout', _absolute=True, next=next_url)

framework/auth/views.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def _reset_password_get(auth, uid=None, token=None, institutional=False):
8484
raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data=error_data)
8585

8686
# override routes.py login_url to redirect to my-projects
87-
service_url = web_url_for('my_projects', _absolute=True)
87+
service_url = web_url_for('dashboard', _absolute=True, _angular_route=True)
8888

8989
return {
9090
'uid': user_obj._id,
@@ -142,7 +142,7 @@ def reset_password_post(uid=None, token=None):
142142
status.push_status_message('Password reset', kind='success', trust=False)
143143
# redirect to CAS and authenticate the user automatically with one-time verification key.
144144
return redirect(cas.get_login_url(
145-
web_url_for('user_account', _absolute=True),
145+
web_url_for('user_account', _absolute=True, _angular_route=True),
146146
username=user_obj.username,
147147
verification_key=user_obj.verification_key
148148
))
@@ -176,7 +176,7 @@ def forgot_password_get(auth):
176176

177177
#overriding the routes.py sign in url to redirect to the my-projects after login
178178
context = {}
179-
context['login_url'] = web_url_for('my_projects', _absolute=True)
179+
context['login_url'] = web_url_for('dashboard', _absolute=True, _angular_route=True)
180180

181181
return context
182182

@@ -324,7 +324,7 @@ def login_and_register_handler(auth, login=True, campaign=None, next_url=None, l
324324
# unlike other campaigns, institution login serves as an alternative for authentication
325325
if campaign == 'institution':
326326
if next_url is None:
327-
next_url = web_url_for('my_projects', _absolute=True)
327+
next_url = web_url_for('dashboard', _absolute=True, _angular_route=True)
328328
data['status_code'] = http_status.HTTP_302_FOUND
329329
if auth.logged_in:
330330
data['next_url'] = next_url
@@ -391,7 +391,7 @@ def login_and_register_handler(auth, login=True, campaign=None, next_url=None, l
391391
# `/login/` or `/register/` without any parameter
392392
if auth.logged_in:
393393
data['status_code'] = http_status.HTTP_302_FOUND
394-
data['next_url'] = web_url_for('my_projects', _absolute=True)
394+
data['next_url'] = web_url_for('dashboard', _absolute=True, _angular_route=True)
395395

396396
return data
397397

@@ -614,7 +614,7 @@ def external_login_confirm_email_get(auth, uid, token):
614614
return redirect(campaign_url)
615615
if new:
616616
status.push_status_message(language.WELCOME_MESSAGE, kind='default', jumbotron=True, trust=True, id='welcome_message')
617-
return redirect(web_url_for('my_projects'))
617+
return redirect(web_url_for('dashboard', _absolute=True, _angular_route=True))
618618

619619
# token is invalid
620620
if token not in user.email_verifications:
@@ -712,10 +712,10 @@ def confirm_email_get(token, auth=None, **kwargs):
712712
status.push_status_message(language.WELCOME_MESSAGE, kind='default', jumbotron=True, trust=True, id='welcome_message')
713713
if token in auth.user.email_verifications:
714714
status.push_status_message(language.CONFIRM_ALTERNATE_EMAIL_ERROR, kind='danger', trust=True, id='alternate_email_error')
715-
return redirect(web_url_for('my_projects'))
715+
return redirect(web_url_for('dashboard', _absolute=True, _angular_route=True))
716716

717717
status.push_status_message(language.MERGE_COMPLETE, kind='success', trust=False)
718-
return redirect(web_url_for('user_account'))
718+
return redirect(web_url_for('user_account', _absolute=True, _angular_route=True))
719719

720720
try:
721721
user.confirm_email(token, merge=is_merge)
@@ -1053,8 +1053,7 @@ def external_login_email_post():
10531053
fullname = session.get('auth_user_fullname', None) or form.name.data
10541054
service_url = session.get('service_url', None)
10551055

1056-
# TODO: @cslzchen use user tags instead of destination
1057-
destination = 'my_projects'
1056+
destination = 'dashboard'
10581057
for campaign in campaigns.get_campaigns():
10591058
if campaign != 'institution':
10601059
# Handle different url encoding schemes between `furl` and `urlparse/urllib`.
@@ -1203,7 +1202,7 @@ def validate_next_url(next_url):
12031202
"""
12041203

12051204
# allow redirection to angular locally
1206-
if settings.LOCAL_ANGULAR_URL in next_url and settings.DEBUG_MODE:
1205+
if settings.LOCAL_MODE and next_url.startswith(settings.LOCAL_ANGULAR_DOMAIN):
12071206
return True
12081207

12091208
# disable external domain using `//`: the browser allows `//` as a shortcut for non-protocol specific requests

framework/sessions/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from osf.utils.fields import ensure_str
1616
from osf.exceptions import InvalidCookieOrSessionError
1717
from website import settings
18-
from website.util import web_url_for
1918

2019
SessionStore = import_module(django_conf_settings.SESSION_ENGINE).SessionStore
2120

@@ -165,7 +164,7 @@ def create_session(response, data=None):
165164
def before_request():
166165
# TODO: Fix circular import
167166
from framework.auth.core import get_user
168-
from framework.auth import cas
167+
from framework.auth import cas, utils
169168
UserSessionMap = apps.get_model('osf.UserSessionMap')
170169

171170
# Request Type 1: Service ticket validation during CAS login.
@@ -216,7 +215,8 @@ def before_request():
216215
try:
217216
user_session = flask_get_session_from_cookie(cookie)
218217
except InvalidCookieOrSessionError:
219-
response = redirect(web_url_for('auth_login'))
218+
# If invalid session/cookie happens, perform a full logout to clear both CAS and OSF Sessions
219+
response = redirect(utils.get_default_osf_logout_url())
220220
response.delete_cookie(settings.COOKIE_NAME, domain=settings.OSF_COOKIE_DOMAIN)
221221
return response
222222
# Case 1: anonymous session that is used for first time external (e.g. ORCiD) login only

osf/models/institution.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,16 @@ def banner_path(self):
216216
def cas_login_url(self):
217217
if self.delegation_protocol == IntegrationType.NONE.value:
218218
return None
219-
if 'localhost' in website_settings.DOMAIN:
220-
next_param = quote(website_settings.PROTOCOL + website_settings.LOCAL_ANGULAR_URL, safe='')
221-
else:
222-
next_param = quote(website_settings.DOMAIN, safe='')
223-
service_url = quote(f'{website_settings.DOMAIN}login?next={next_param}', safe='')
224-
return f'{website_settings.CAS_SERVER_URL}/login?campaign=institution&institutionId={self._id}&service={service_url}'
219+
# Note: admin app can't use `web_url_for()` due to out of context
220+
next_url_param = quote(website_settings.DOMAIN, safe='')
221+
service_url_param = quote(f'{website_settings.DOMAIN}login?next={next_url_param}', safe='')
222+
institution_id_param = quote(self._id, safe='')
223+
return (
224+
f'{website_settings.CAS_SERVER_URL}/login'
225+
f'?campaign=institution'
226+
f'&institutionId={institution_id_param}'
227+
f'&service={service_url_param}'
228+
)
225229

226230
def update_search(self):
227231
from website.search.search import update_institution

tests/test_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def test_confirm_email(self):
9696
res = self.app.resolve_redirect(res)
9797

9898
assert res.status_code == 302
99-
assert '/my-projects/' == urlparse(res.location).path
99+
assert '/dashboard/' == urlparse(res.location).path
100100
# assert len(get_session()['status']) == 1
101101

102102
def test_get_user_by_id(self):

tests/test_auth_basic_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,4 @@ def test_expired_cookie(self):
9999
self.app.set_cookie(settings.COOKIE_NAME, str(cookie))
100100
res = self.app.get(self.reachable_url)
101101
assert res.status_code == 302
102-
assert '/login/' == res.location
102+
assert 'http://localhost:5000/logout/?next=http://localhost:4200/' == res.location

tests/test_auth_views.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -551,32 +551,32 @@ def setUp(self):
551551
self.no_auth = Auth()
552552
self.user_auth = AuthUserFactory()
553553
self.auth = Auth(user=self.user_auth)
554-
self.next_url = web_url_for('my_projects', _absolute=True)
554+
self.next_url = web_url_for('dashboard', _absolute=True, _angular_route=True)
555555
self.invalid_campaign = 'invalid_campaign'
556556

557557
def test_osf_login_with_auth(self):
558558
# login: user with auth
559559
data = login_and_register_handler(self.auth)
560560
assert data.get('status_code') == http_status.HTTP_302_FOUND
561-
assert data.get('next_url') == web_url_for('my_projects', _absolute=True)
561+
assert data.get('next_url') == web_url_for('dashboard', _absolute=True, _angular_route=True)
562562

563563
def test_osf_login_without_auth(self):
564564
# login: user without auth
565565
data = login_and_register_handler(self.no_auth)
566566
assert data.get('status_code') == http_status.HTTP_302_FOUND
567-
assert data.get('next_url') == web_url_for('my_projects', _absolute=True)
567+
assert data.get('next_url') == web_url_for('dashboard', _absolute=True, _angular_route=True)
568568

569569
def test_osf_register_with_auth(self):
570570
# register: user with auth
571571
data = login_and_register_handler(self.auth, login=False)
572572
assert data.get('status_code') == http_status.HTTP_302_FOUND
573-
assert data.get('next_url') == web_url_for('my_projects', _absolute=True)
573+
assert data.get('next_url') == web_url_for('dashboard', _absolute=True, _angular_route=True)
574574

575575
def test_osf_register_without_auth(self):
576576
# register: user without auth
577577
data = login_and_register_handler(self.no_auth, login=False)
578578
assert data.get('status_code') == http_status.HTTP_200_OK
579-
assert data.get('next_url') == web_url_for('my_projects', _absolute=True)
579+
assert data.get('next_url') == web_url_for('dashboard', _absolute=True, _angular_route=True)
580580

581581
def test_next_url_login_with_auth(self):
582582
# next_url login: user with auth
@@ -585,13 +585,13 @@ def test_next_url_login_with_auth(self):
585585
assert data.get('next_url') == self.next_url
586586

587587
def test_next_url_angular_login_with_auth(self):
588-
data = login_and_register_handler(self.auth, next_url=settings.LOCAL_ANGULAR_URL)
588+
data = login_and_register_handler(self.auth, next_url=settings.LOCAL_ANGULAR_DOMAIN)
589589
assert data.get('status_code') == http_status.HTTP_302_FOUND
590-
assert data.get('next_url') == settings.LOCAL_ANGULAR_URL
590+
assert data.get('next_url') == settings.LOCAL_ANGULAR_DOMAIN
591591

592592
def test_next_url_angular_login_without_auth(self):
593-
request.url = web_url_for('auth_login', next=settings.LOCAL_ANGULAR_URL, _absolute=True)
594-
data = login_and_register_handler(self.no_auth, next_url=settings.LOCAL_ANGULAR_URL)
593+
request.url = web_url_for('auth_login', next=settings.LOCAL_ANGULAR_DOMAIN, _absolute=True)
594+
data = login_and_register_handler(self.no_auth, next_url=settings.LOCAL_ANGULAR_DOMAIN)
595595
assert data.get('status_code') == http_status.HTTP_302_FOUND
596596
assert data.get('next_url') == cas.get_login_url(request.url)
597597

@@ -621,14 +621,16 @@ def test_institution_login_with_auth(self):
621621
# institution login: user with auth
622622
data = login_and_register_handler(self.auth, campaign='institution')
623623
assert data.get('status_code') == http_status.HTTP_302_FOUND
624-
assert data.get('next_url') == web_url_for('my_projects', _absolute=True)
624+
assert data.get('next_url') == web_url_for('dashboard', _absolute=True, _angular_route=True)
625625

626626
def test_institution_login_without_auth(self):
627627
# institution login: user without auth
628628
data = login_and_register_handler(self.no_auth, campaign='institution')
629629
assert data.get('status_code') == http_status.HTTP_302_FOUND
630-
assert data.get('next_url') == cas.get_login_url(web_url_for('my_projects', _absolute=True),
631-
campaign='institution')
630+
assert data.get('next_url') == cas.get_login_url(
631+
web_url_for('dashboard', _absolute=True, _angular_route=True),
632+
campaign='institution'
633+
)
632634

633635
def test_institution_login_next_url_with_auth(self):
634636
# institution login: user with auth and next url
@@ -646,13 +648,16 @@ def test_institution_register_with_auth(self):
646648
# institution register: user with auth
647649
data = login_and_register_handler(self.auth, login=False, campaign='institution')
648650
assert data.get('status_code') == http_status.HTTP_302_FOUND
649-
assert data.get('next_url') == web_url_for('my_projects', _absolute=True)
651+
assert data.get('next_url') == web_url_for('dashboard', _absolute=True, _angular_route=True)
650652

651653
def test_institution_register_without_auth(self):
652654
# institution register: user without auth
653655
data = login_and_register_handler(self.no_auth, login=False, campaign='institution')
654656
assert data.get('status_code') == http_status.HTTP_302_FOUND
655-
assert data.get('next_url') == cas.get_login_url(web_url_for('my_projects', _absolute=True), campaign='institution')
657+
assert data.get('next_url') == cas.get_login_url(
658+
web_url_for('dashboard', _absolute=True, _angular_route=True),
659+
campaign='institution'
660+
)
656661

657662
def test_campaign_login_with_auth(self):
658663
for campaign in get_campaigns():
@@ -838,15 +843,13 @@ def test_logout_with_no_parameter(self):
838843
assert resp.status_code == http_status.HTTP_302_FOUND
839844
assert cas.get_logout_url(self.goodbye_url) == resp.headers['Location']
840845

841-
@mock.patch('framework.auth.views.settings.LOCAL_ANGULAR_URL', 'http://localhost:4200')
842846
def test_logout_with_angular_next_url_logged_in(self):
843847
angular_url = 'http://localhost:4200/'
844848
logout_url = web_url_for('auth_logout', _absolute=True, next=angular_url)
845849
resp = self.app.get(logout_url, auth=self.auth_user.auth)
846850
assert resp.status_code == http_status.HTTP_302_FOUND
847851
assert cas.get_logout_url(logout_url) == resp.headers['Location']
848852

849-
@mock.patch('framework.auth.views.settings.LOCAL_ANGULAR_URL', 'http://localhost:4200')
850853
def test_logout_with_angular_next_url_logged_out(self):
851854
angular_url = 'http://localhost:4200/'
852855
logout_url = web_url_for('auth_logout', _absolute=True, next=angular_url)

tests/test_campaigns.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def setUp(self):
238238
super().setUp()
239239
self.url_login = web_url_for('auth_login', campaign='institution')
240240
self.url_register = web_url_for('auth_register', campaign='institution')
241-
self.service_url = web_url_for('my_projects', _absolute=True)
241+
self.service_url = web_url_for('dashboard', _absolute=True, _angular_route=True)
242242

243243
# go to CAS institution login page if not logged in
244244
def test_institution_not_logged_in(self):

tests/test_webtests.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,35 +79,30 @@ def test_can_see_profile_url(self):
7979
res = self.app.get(self.user.url, follow_redirects=True)
8080
assert self.user.url in res.text
8181

82-
# `GET /login/` without parameters is redirected to `/my-projects/` page which has `@must_be_logged_in` decorator
83-
# if user is not logged in, she/he is further redirected to CAS login page
82+
# `GET /login/` (legacy BE endpoint) without parameters is redirected to `/dashboard/` (angular FE endpoint).
83+
# It's impossible to test external redirects in tests, and it is angular's job to redirect correctly to CAS login.
8484
def test_is_redirected_to_cas_if_not_logged_in_at_login_page(self):
85-
res = self.app.resolve_redirect(self.app.get('/login/'))
85+
res = self.app.get('/login/')
8686
assert res.status_code == 302
87-
location = res.headers.get('Location')
88-
assert 'login?service=' in location
87+
assert 'dashboard' in res.headers.get('Location')
8988

90-
def test_is_redirected_to_myprojects_if_already_logged_in_at_login_page(self):
89+
def test_is_redirected_to_dashboard_if_already_logged_in_at_login_page(self):
9190
res = self.app.get('/login/', auth=self.user.auth)
9291
assert res.status_code == 302
93-
assert 'my-projects' in res.headers.get('Location')
92+
assert 'dashboard' in res.headers.get('Location')
9493

9594
def test_register_page(self):
9695
res = self.app.get('/register/')
9796
assert res.status_code == 200
9897

99-
def test_is_redirected_to_myprojects_if_already_logged_in_at_register_page(self):
98+
def test_is_redirected_to_dashboard_if_already_logged_in_at_register_page(self):
10099
res = self.app.get('/register/', auth=self.user.auth)
101100
assert res.status_code == 302
102-
assert 'my-projects' in res.headers.get('Location')
101+
assert 'dashboard' in res.headers.get('Location')
103102

104103
def test_sees_projects_in_her_dashboard(self):
105-
# the user already has a project
106-
project = ProjectFactory(creator=self.user)
107-
project.add_contributor(self.user)
108-
project.save()
109-
res = self.app.get('/my-projects/', auth=self.user.auth)
110-
assert 'Projects' in res.text # Projects heading
104+
# Deprecated test, dashboard and my-projects are angular pages
105+
pass
111106

112107
def test_does_not_see_osffiles_in_user_addon_settings(self):
113108
res = self.app.get('/settings/addons/', auth=self.auth, follow_redirects=True)
@@ -123,10 +118,8 @@ def test_sees_osffiles_in_project_addon_settings(self):
123118
assert 'OSF Storage' in res.text
124119

125120
def test_sees_correct_title_on_dashboard(self):
126-
# User goes to dashboard
127-
res = self.app.get('/my-projects/', auth=self.auth, follow_redirects=True)
128-
title = res.html.title.string
129-
assert 'OSF | My Projects' == title
121+
# Deprecated test, dashboard and my-projects are angular pages
122+
pass
130123

131124
def test_can_see_make_public_button_if_admin(self):
132125
# User is a contributor on a project

website/routes.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,10 @@ def sitemap_file(path):
230230

231231
def goodbye():
232232
# Redirect to dashboard if logged in
233-
redirect_url = util.web_url_for('auth_login')
234233
if _get_current_user():
235-
return redirect(redirect_url)
234+
return redirect(util.web_url_for('dashboard', _absolute=True, _angular_route=True))
236235
else:
237-
return redirect(redirect_url + '?goodbye=true')
236+
return redirect(util.web_url_for('index', _absolute=True, _angular_route=True))
238237

239238
def make_url_map(app):
240239
"""Set up all the routes for the OSF app.
@@ -294,25 +293,15 @@ def make_url_map(app):
294293

295294
process_rules(app, [
296295
Rule('/', 'get', website_views.index, notemplate),
297-
Rule(
298-
'/dashboard/',
299-
'get',
300-
website_views.dashboard,
301-
notemplate
302-
),
296+
Rule('/dashboard/', 'get', website_views.dashboard, notemplate),
297+
Rule('/my-projects/', 'get', website_views.my_projects, notemplate),
303298

304299
Rule(
305300
'/metadata/<guid>/',
306301
'get',
307302
website_views.metadata_download,
308303
notemplate
309304
),
310-
Rule(
311-
'/my-projects/',
312-
'get',
313-
website_views.my_projects,
314-
OsfWebRenderer('my_projects.mako', trust=False)
315-
),
316305

317306
Rule(
318307
'/reproducibility/',

0 commit comments

Comments
 (0)