def auth_login(auth, **kwargs): """If GET request, show login page. If POST, attempt to log user in if login form passsed; else send forgot password email. """ campaign = request.args.get('campaign') next_url = request.args.get('next') if campaign: next_url = campaigns.campaign_url_for(campaign) if auth.logged_in: if not request.args.get('logout'): if next_url: return redirect(next_url) return redirect(web_url_for('dashboard')) # redirect user to CAS for logout, return here w/o authentication return auth_logout(redirect_url=request.url) if kwargs.get('first', False): status.push_status_message('You may now log in', 'info') status_message = request.args.get('status', '') if status_message == 'expired': status.push_status_message('The private link you used is expired.') if next_url: status.push_status_message(language.MUST_LOGIN) # set login_url to form action, upon successful authentication specifically w/o logout=True, # allows for next to be followed or a redirect to the dashboard. redirect_url = web_url_for('auth_login', next=next_url, _absolute=True) data = {} if campaign and campaign in campaigns.CAMPAIGNS: data['campaign'] = campaign data['login_url'] = cas.get_login_url(redirect_url, auto=True) return data, http.OK
def confirm_email_get(token, auth=None, **kwargs): """View for email confirmation links. Authenticates and redirects to user settings page if confirmation is successful, otherwise shows an "Expired Link" error. methods: GET """ user = User.load(kwargs['uid']) is_merge = 'confirm_merge' in request.args is_initial_confirmation = not user.date_confirmed if user is None: raise HTTPError(http.NOT_FOUND) if auth and auth.user and (auth.user._id == user._id or auth.user._id == user.merged_by._id): if not is_merge: # determine if the user registered through a campaign campaign = campaigns.campaign_for_user(user) if campaign: return redirect( campaigns.campaign_url_for(campaign) ) status.push_status_message(language.WELCOME_MESSAGE, 'default', jumbotron=True) # Go to dashboard return redirect(web_url_for('dashboard')) status.push_status_message(language.MERGE_COMPLETE, 'success') return redirect(web_url_for('user_account')) try: user.confirm_email(token, merge=is_merge) except exceptions.EmailConfirmTokenError as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': e.message_short, 'message_long': e.message_long }) if is_initial_confirmation: user.date_last_login = datetime.datetime.utcnow() user.save() # Send out our welcome message mails.send_mail( to_addr=user.username, mail=mails.WELCOME, mimetype='html', user=user ) # Redirect to CAS and authenticate the user with a verification key. user.verification_key = security.random_string(20) user.save() return redirect(cas.get_login_url( request.url, auto=True, username=user.username, verification_key=user.verification_key ))
def auth_login(auth, **kwargs): """If GET request, show login page. If POST, attempt to log user in if login form passsed; else send forgot password email. """ campaign = request.args.get('campaign') next_url = request.args.get('next') must_login_warning = True if campaign: next_url = campaigns.campaign_url_for(campaign) if not next_url: next_url = request.args.get('redirect_url') must_login_warning = False if next_url: # Only allow redirects which are relative root or full domain, disallows external redirects. if not (next_url[0] == '/' or next_url.startswith(settings.DOMAIN) or next_url.startswith(settings.CAS_SERVER_URL) or next_url.startswith(settings.MFR_SERVER_URL)): raise HTTPError(http.InvalidURL) if auth.logged_in: if not request.args.get('logout'): if next_url: return redirect(next_url) return redirect('/') # redirect user to CAS for logout, return here w/o authentication return auth_logout(redirect_url=request.url) if kwargs.get('first', False): status.push_status_message('You may now log in', kind='info', trust=False) status_message = request.args.get('status', '') if status_message == 'expired': status.push_status_message('The private link you used is expired.', trust=False) status.push_status_message('The private link you used is expired. Please <a href="/settings/account/">' 'resend email.</a>', trust=False) if next_url and must_login_warning: status.push_status_message(language.MUST_LOGIN, trust=False) # set login_url to form action, upon successful authentication specifically w/o logout=True, # allows for next to be followed or a redirect to the dashboard. redirect_url = web_url_for('auth_login', next=next_url, _absolute=True) data = {} if campaign and campaign in campaigns.CAMPAIGNS: if (campaign == 'institution' and settings.ENABLE_INSTITUTIONS) or campaign != 'institution': data['campaign'] = campaign data['login_url'] = cas.get_login_url(redirect_url, auto=True) data['institution_redirect'] = cas.get_institution_target(redirect_url) data['redirect_url'] = next_url data['sign_up'] = request.args.get('sign_up', False) data['existing_user'] = request.args.get('existing_user', None) return data, http.OK
def confirm_email_get(token, auth=None, **kwargs): """View for email confirmation links. Authenticates and redirects to user settings page if confirmation is successful, otherwise shows an "Expired Link" error. methods: GET """ user = User.load(kwargs['uid']) is_merge = 'confirm_merge' in request.args is_initial_confirmation = not user.date_confirmed if user is None: raise HTTPError(http.NOT_FOUND) if auth and auth.user and (auth.user._id == user._id or auth.user._id == user.merged_by._id): if not is_merge: # determine if the user registered through a campaign campaign = campaigns.campaign_for_user(user) if campaign: return redirect(campaigns.campaign_url_for(campaign)) status.push_status_message(language.WELCOME_MESSAGE, 'default', jumbotron=True) # Go to dashboard return redirect(web_url_for('dashboard')) status.push_status_message(language.MERGE_COMPLETE, 'success') return redirect(web_url_for('user_account')) try: user.confirm_email(token, merge=is_merge) except exceptions.EmailConfirmTokenError as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': e.message_short, 'message_long': e.message_long }) if is_initial_confirmation: user.date_last_login = datetime.datetime.utcnow() user.save() # Send out our welcome message mails.send_mail(to_addr=user.username, mail=mails.WELCOME, mimetype='html', user=user) # Redirect to CAS and authenticate the user with a verification key. user.verification_key = security.random_string(20) user.save() return redirect( cas.get_login_url(request.url, auto=True, username=user.username, verification_key=user.verification_key))
def auth_login(auth, **kwargs): """If GET request, show login page. If POST, attempt to log user in if login form passsed; else send forgot password email. """ campaign = request.args.get('campaign') next_url = request.args.get('next') must_login_warning = True if campaign: next_url = campaigns.campaign_url_for(campaign) if not next_url: next_url = request.args.get('redirect_url') must_login_warning = False if next_url: # Only allow redirects which are relative root or full domain, disallows external redirects. if not (next_url[0] == '/' or next_url.startswith(settings.DOMAIN)): raise HTTPError(http.InvalidURL) if auth.logged_in: if not request.args.get('logout'): if next_url: return redirect(next_url) return redirect('/') # redirect user to CAS for logout, return here w/o authentication return auth_logout(redirect_url=request.url) if kwargs.get('first', False): status.push_status_message('You may now log in', kind='info', trust=False) status_message = request.args.get('status', '') if status_message == 'expired': status.push_status_message('The private link you used is expired.', trust=False) if next_url and must_login_warning: status.push_status_message(language.MUST_LOGIN, trust=False) # set login_url to form action, upon successful authentication specifically w/o logout=True, # allows for next to be followed or a redirect to the dashboard. redirect_url = web_url_for('auth_login', next=next_url, _absolute=True) data = {} if campaign and campaign in campaigns.CAMPAIGNS: if (campaign == 'institution' and settings.ENABLE_INSTITUTIONS) or campaign != 'institution': data['campaign'] = campaign data['login_url'] = cas.get_login_url(redirect_url, auto=True) data['institution_redirect'] = cas.get_institution_target(redirect_url) data['redirect_url'] = next_url data['sign_up'] = request.args.get('sign_up', False) return data, http.OK
def test_confirm_email_get_with_campaign(self): for key, value in campaigns.CAMPAIGNS.items(): user = factories.UnconfirmedUserFactory() user.add_system_tag(value.get('system_tag')) user.save() token = user.get_confirmation_token(user.username) kwargs = { 'uid': user._id, } with self.app.app.test_request_context(), mock_auth(user): res = auth_views.confirm_email_get(token, **kwargs) assert_equal(res.status_code, http.FOUND) assert_equal(res.location, campaigns.campaign_url_for(key))
def setUp(self): super(TestCampaignsAuthViews, self).setUp() self.campaigns = { 'prereg': { 'title_register': 'Preregistration Challenge', 'title_landing': 'Welcome to the Prereg Challenge!' }, 'erpc': { 'title_register': 'Election Research Preacceptance Competition', 'title_landing': 'The Election Research Preacceptance Competition is Now Closed' }, } for key, value in self.campaigns.items(): value.update({'url_login': web_url_for('auth_login', campaign=key)}) value.update({'url_register': web_url_for('auth_register', campaign=key)}) value.update({'url_landing': campaigns.campaign_url_for(key)}) self.user = factories.AuthUserFactory()
def setUp(self): super(TestCampaignsAuthViews, self).setUp() self.campaigns = { 'prereg': { 'title_register': 'Preregistration Challenge', 'title_landing': 'Welcome to the Prereg Challenge!' }, 'erpc': { 'title_register': 'Election Research Preacceptance Competition', 'title_landing': 'Welcome to the Election Research Preacceptance Competition!' }, } for key, value in self.campaigns.items(): value.update({'url_login': web_url_for('auth_login', campaign=key)}) value.update({'url_register': web_url_for('auth_register', campaign=key)}) value.update({'url_landing': campaigns.campaign_url_for(key)}) self.user = AuthUserFactory()
def external_login_email_post(): """ View to handle email submission for first-time oauth-login user. HTTP Method: POST """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] external_id = session.data['auth_user_external_id'] fullname = session.data['auth_user_fullname'] service_url = session.data['service_url'] destination = 'dashboard' for campaign in campaigns.get_campaigns(): if campaign != 'institution': # Handle different url encoding schemes between `furl` and `urlparse/urllib`. # OSF use `furl` to parse service url during service validation with CAS. However, `web_url_for()` uses # `urlparse/urllib` to generate service url. `furl` handles `urlparser/urllib` generated urls while ` but # not vice versa. campaign_url = furl.furl(campaigns.campaign_url_for(campaign)).url if campaigns.is_proxy_login(campaign): # proxy campaigns: OSF Preprints and branded ones if check_service_url_with_proxy_campaign(service_url, campaign_url): destination = campaign # continue to check branded preprints even service url matches osf preprints if campaign != 'osf-preprints': break elif service_url.startswith(campaign_url): # osf campaigns: OSF Prereg and ERPC destination = campaign break if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) external_identity = { external_id_provider: { external_id: None, }, } try: ensure_external_identity_uniqueness(external_id_provider, external_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if user: # 1. update user oauth, with pending status external_identity[external_id_provider][external_id] = 'LINK' if external_id_provider in user.external_identity: user.external_identity[external_id_provider].update(external_identity[external_id_provider]) else: user.external_identity.update(external_identity) # 2. add unconfirmed email and send confirmation email user.add_unconfirmed_email(clean_email, external_identity=external_identity) user.save() send_confirm_email( user, clean_email, external_id_provider=external_id_provider, external_id=external_id, destination=destination ) # 3. notify user message = language.EXTERNAL_LOGIN_EMAIL_LINK_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 4. remove session and osf cookie remove_session(session) else: # 1. create unconfirmed user with pending status external_identity[external_id_provider][external_id] = 'CREATE' user = User.create_unconfirmed( username=clean_email, password=str(uuid.uuid4()), fullname=fullname, external_identity=external_identity, campaign=None ) # TODO: [#OSF-6934] update social fields, verified social fields cannot be modified user.save() # 3. send confirmation email send_confirm_email( user, user.username, external_id_provider=external_id_provider, external_id=external_id, destination=destination ) # 4. notify user message = language.EXTERNAL_LOGIN_EMAIL_CREATE_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 5. remove session remove_session(session) status.push_status_message(message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return { 'form': form, 'external_id_provider': external_id_provider }
def confirm_email_get(token, auth=None, **kwargs): """ View for email confirmation links. Authenticates and redirects to user settings page if confirmation is successful, otherwise shows an "Expired Link" error. HTTP Method: GET """ user = User.load(kwargs['uid']) is_merge = 'confirm_merge' in request.args is_initial_confirmation = not user.date_confirmed log_out = request.args.get('logout', None) if user is None: raise HTTPError(http.NOT_FOUND) # if the user is merging or adding an email (they already are an osf user) if log_out: return auth_email_logout(token, user) if auth and auth.user and (auth.user._id == user._id or auth.user._id == user.merged_by._id): if not is_merge: # determine if the user registered through a campaign campaign = campaigns.campaign_for_user(user) if campaign: return redirect(campaigns.campaign_url_for(campaign)) # go to home page with push notification if len(auth.user.emails) == 1 and len(auth.user.email_verifications) == 0: status.push_status_message(language.WELCOME_MESSAGE, kind='default', jumbotron=True, trust=True) if token in auth.user.email_verifications: status.push_status_message(language.CONFIRM_ALTERNATE_EMAIL_ERROR, kind='danger', trust=True) return redirect(web_url_for('index')) status.push_status_message(language.MERGE_COMPLETE, kind='success', trust=False) return redirect(web_url_for('user_account')) try: user.confirm_email(token, merge=is_merge) except exceptions.EmailConfirmTokenError as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': e.message_short, 'message_long': e.message_long }) if is_initial_confirmation: user.date_last_login = datetime.datetime.utcnow() user.save() # send out our welcome message mails.send_mail( to_addr=user.username, mail=mails.WELCOME, mimetype='html', user=user ) # new random verification key, allows CAS to authenticate the user w/o password one-time only. user.verification_key = generate_verification_key() user.save() # redirect to CAS and authenticate the user with a verification key. return redirect(cas.get_login_url( request.url, username=user.username, verification_key=user.verification_key ))
def external_login_confirm_email_get(auth, uid, token): """ View for email confirmation links when user first login through external identity provider. HTTP Method: GET When users click the confirm link, they are expected not to be logged in. If not, they will be logged out first and redirected back to this view. After OSF verifies the link and performs all actions, they will be automatically logged in through CAS and redirected back to this view again being authenticated. :param auth: the auth context :param uid: the user's primary key :param token: the verification token """ user = User.load(uid) if not user: raise HTTPError(http.BAD_REQUEST) destination = request.args.get('destination') if not destination: raise HTTPError(http.BAD_REQUEST) # if user is already logged in if auth and auth.user: # if it is a wrong user if auth.user._id != user._id: return auth_logout(redirect_url=request.url) # if it is the expected user new = request.args.get('new', None) if destination in campaigns.get_campaigns(): return redirect(campaigns.campaign_url_for(destination)) if new: status.push_status_message(language.WELCOME_MESSAGE, kind='default', jumbotron=True, trust=True) return redirect(web_url_for('dashboard')) # token is invalid if token not in user.email_verifications: raise HTTPError(http.BAD_REQUEST) verification = user.email_verifications[token] email = verification['email'] provider = verification['external_identity'].keys()[0] provider_id = verification['external_identity'][provider].keys()[0] # wrong provider if provider not in user.external_identity: raise HTTPError(http.BAD_REQUEST) external_status = user.external_identity[provider][provider_id] try: ensure_external_identity_uniqueness(provider, provider_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if not user.is_registered: user.set_password(uuid.uuid4(), notify=False) user.register(email) if email.lower() not in user.emails: user.emails.append(email.lower()) user.date_last_logged_in = datetime.datetime.utcnow() user.external_identity[provider][provider_id] = 'VERIFIED' user.social[provider.lower()] = provider_id del user.email_verifications[token] user.verification_key = generate_verification_key() user.save() service_url = request.url if external_status == 'CREATE': mails.send_mail( to_addr=user.username, mail=mails.WELCOME, mimetype='html', user=user ) service_url += '&{}'.format(urllib.urlencode({'new': 'true'})) elif external_status == 'LINK': mails.send_mail( user=user, to_addr=user.username, mail=mails.EXTERNAL_LOGIN_LINK_SUCCESS, external_id_provider=provider, ) # redirect to CAS and authenticate the user with the verification key return redirect(cas.get_login_url( service_url, username=user.username, verification_key=user.verification_key ))
def login_and_register_handler(auth, login=True, campaign=None, next_url=None, logout=None): """ Non-view helper to handle `login` and `register` requests. :param auth: the auth context :param login: `True` if `GET /login`, `False` if `GET /register` :param campaign: a target campaign defined in `auth.campaigns` :param next_url: the service url for CAS login or redirect url for OSF :param logout: used only for `claim_user_registered` :return: data object that contains actions for `auth_register` and `auth_login` :raises: http.BAD_REQUEST """ # Only allow redirects which are relative root or full domain. Disallows external redirects. if next_url and not validate_next_url(next_url): raise HTTPError(http.BAD_REQUEST) data = { 'status_code': http.FOUND if login else http.OK, 'next_url': next_url, 'campaign': None, 'must_login_warning': False, } # login or register with campaign parameter if campaign: if validate_campaign(campaign): # GET `/register` or '/login` with `campaign=institution` # unlike other campaigns, institution login serves as an alternative for authentication if campaign == 'institution': next_url = web_url_for('dashboard', _absolute=True) data['status_code'] = http.FOUND if auth.logged_in: data['next_url'] = next_url else: data['next_url'] = cas.get_login_url(next_url, campaign='institution') # for non-institution campaigns else: destination = next_url if next_url else campaigns.campaign_url_for(campaign) if auth.logged_in: # if user is already logged in, go to the campaign landing page data['status_code'] = http.FOUND data['next_url'] = destination else: # if user is logged out, go to the osf register page with campaign context if login: # `GET /login?campaign=...` data['next_url'] = web_url_for('auth_register', campaign=campaign, next=destination) else: # `GET /register?campaign=...` data['campaign'] = campaign if campaigns.is_proxy_login(campaign): data['next_url'] = web_url_for( 'auth_login', next=destination, _absolute=True ) else: data['next_url'] = destination else: # invalid campaign raise HTTPError(http.BAD_REQUEST) # login or register with next parameter elif next_url: if logout: # handle `claim_user_registered` data['next_url'] = next_url if auth.logged_in: # log user out and come back data['status_code'] = 'auth_logout' else: # after logout, land on the register page with "must_login" warning data['status_code'] = http.OK data['must_login_warning'] = True elif auth.logged_in: # if user is already logged in, redirect to `next_url` data['status_code'] = http.FOUND data['next_url'] = next_url elif login: # `/login?next=next_url`: go to CAS login page with current request url as service url data['status_code'] = http.FOUND data['next_url'] = cas.get_login_url(request.url) else: # `/register?next=next_url`: land on OSF register page with request url as next url data['status_code'] = http.OK data['next_url'] = request.url else: # `/login/` or `/register/` without any parameter if auth.logged_in: data['status_code'] = http.FOUND data['next_url'] = web_url_for('dashboard', _absolute=True) return data
def test_campaign_url_for(self): for campaign in self.campaign_lists: url = campaigns.campaign_url_for(campaign) assert_true(url is not None) url = campaigns.campaign_url_for(self.invalid_campaign) assert_true(url is None)
def confirm_email_get(token, auth=None, **kwargs): """ View for email confirmation links. Authenticates and redirects to user settings page if confirmation is successful, otherwise shows an "Expired Link" error. HTTP Method: GET """ is_merge = 'confirm_merge' in request.args try: if not is_merge or not check_select_for_update(): user = OSFUser.objects.get(guids___id=kwargs['uid']) else: user = OSFUser.objects.filter( guids___id=kwargs['uid']).select_for_update().get() except OSFUser.DoesNotExist: raise HTTPError(http.NOT_FOUND) is_initial_confirmation = not user.date_confirmed log_out = request.args.get('logout', None) # if the user is merging or adding an email (they already are an osf user) if log_out: return auth_email_logout(token, user) if auth and auth.user and (auth.user._id == user._id or auth.user._id == user.merged_by._id): if not is_merge: # determine if the user registered through a campaign campaign = campaigns.campaign_for_user(user) if campaign: return redirect(campaigns.campaign_url_for(campaign)) # go to home page with push notification if auth.user.emails.count() == 1 and len( auth.user.email_verifications) == 0: status.push_status_message(language.WELCOME_MESSAGE, kind='default', jumbotron=True, trust=True) if token in auth.user.email_verifications: status.push_status_message( language.CONFIRM_ALTERNATE_EMAIL_ERROR, kind='danger', trust=True) return redirect(web_url_for('index')) status.push_status_message(language.MERGE_COMPLETE, kind='success', trust=False) return redirect(web_url_for('user_account')) try: user.confirm_email(token, merge=is_merge) except exceptions.EmailConfirmTokenError as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': e.message_short, 'message_long': e.message_long }) if is_initial_confirmation: user.update_date_last_login() user.save() # send out our welcome message mails.send_mail(to_addr=user.username, mail=mails.WELCOME, mimetype='html', user=user) # new random verification key, allows CAS to authenticate the user w/o password one-time only. user.verification_key = generate_verification_key() user.save() # redirect to CAS and authenticate the user with a verification key. return redirect( cas.get_login_url(request.url, username=user.username, verification_key=user.verification_key))
def login_and_register_handler(auth, login=True, campaign=None, next_url=None, logout=None): """ Non-view helper to handle `login` and `register` requests. :param auth: the auth context :param login: `True` if `GET /login`, `False` if `GET /register` :param campaign: a target campaign defined in `auth.campaigns` :param next_url: the service url for CAS login or redirect url for OSF :param logout: used only for `claim_user_registered` :return: data object that contains actions for `auth_register` and `auth_login` :raises: http.BAD_REQUEST """ # Only allow redirects which are relative root or full domain. Disallows external redirects. if next_url and not validate_next_url(next_url): raise HTTPError(http.BAD_REQUEST) data = { 'status_code': http.FOUND if login else http.OK, 'next_url': next_url, 'campaign': None, 'must_login_warning': False, } # login or register with campaign parameter if campaign: if validate_campaign(campaign): # GET `/register` or '/login` with `campaign=institution` # unlike other campaigns, institution login serves as an alternative for authentication if campaign == 'institution': if next_url is None: next_url = web_url_for('dashboard', _absolute=True) data['status_code'] = http.FOUND if auth.logged_in: data['next_url'] = next_url else: data['next_url'] = cas.get_login_url( next_url, campaign='institution') # for non-institution campaigns else: destination = next_url if next_url else campaigns.campaign_url_for( campaign) if auth.logged_in: # if user is already logged in, go to the campaign landing page data['status_code'] = http.FOUND data['next_url'] = destination else: # if user is logged out, go to the osf register page with campaign context if login: # `GET /login?campaign=...` data['next_url'] = web_url_for('auth_register', campaign=campaign, next=destination) else: # `GET /register?campaign=...` data['campaign'] = campaign if campaigns.is_proxy_login(campaign): data['next_url'] = web_url_for('auth_login', next=destination, _absolute=True) else: data['next_url'] = destination else: # invalid campaign, inform sentry and redirect to non-campaign sign up or sign in redirect_view = 'auth_login' if login else 'auth_register' data['status_code'] = http.FOUND data['next_url'] = web_url_for(redirect_view, campaigns=None, next=next_url) data['campaign'] = None sentry.log_message( '{} is not a valid campaign. Please add it if this is a new one' .format(campaign)) # login or register with next parameter elif next_url: if logout: # handle `claim_user_registered` data['next_url'] = next_url if auth.logged_in: # log user out and come back data['status_code'] = 'auth_logout' else: # after logout, land on the register page with "must_login" warning data['status_code'] = http.OK data['must_login_warning'] = True elif auth.logged_in: # if user is already logged in, redirect to `next_url` data['status_code'] = http.FOUND data['next_url'] = next_url elif login: # `/login?next=next_url`: go to CAS login page with current request url as service url data['status_code'] = http.FOUND data['next_url'] = cas.get_login_url(request.url) else: # `/register?next=next_url`: land on OSF register page with request url as next url data['status_code'] = http.OK data['next_url'] = request.url else: # `/login/` or `/register/` without any parameter if auth.logged_in: data['status_code'] = http.FOUND data['next_url'] = web_url_for('dashboard', _absolute=True) return data
def auth_login(auth, **kwargs): """ This view serves as the entry point for OSF login and campaign login. HTTP Method: GET GET '/login/' without any query parameter: redirect to CAS login page with dashboard as target service GET '/login/?logout=true log user out and redirect to CAS login page with redirect_url or next_url as target service GET '/login/?campaign=instituion: if user is logged in, redirect to 'dashboard' show institution login GET '/login/?campaign=prereg: if user is logged in, redirect to prereg home page else show sign up page and notify user to sign in, set next to prereg home page GET '/login/?next=next_url: if user is logged in, redirect to next_url else redirect to CAS login page with next_url as target service """ campaign = request.args.get('campaign') next_url = request.args.get('next') log_out = request.args.get('logout') must_login_warning = True if not campaign and not next_url and not log_out: if auth.logged_in: return redirect(web_url_for('dashboard')) return redirect(cas.get_login_url(web_url_for('dashboard', _absolute=True))) if campaign: must_login_warning = False next_url = campaigns.campaign_url_for(campaign) if not next_url: next_url = request.args.get('redirect_url') must_login_warning = False if not validate_next_url(next_url): raise HTTPError(http.BAD_REQUEST) if auth.logged_in: if not log_out: if next_url: return redirect(next_url) return redirect('dashboard') # redirect user to CAS for logout, return here w/o authentication return auth_logout(redirect_url=request.url) status_message = request.args.get('status', '') if status_message == 'expired': status.push_status_message('The private link you used is expired.', trust=False) status.push_status_message('The private link you used is expired. Please <a href="/settings/account/">' 'resend email.</a>', trust=False) if next_url and must_login_warning: status.push_status_message(language.MUST_LOGIN, trust=False) # set login_url to form action, upon successful authentication specifically w/o logout=True, # allows for next to be followed or a redirect to the dashboard. redirect_url = web_url_for('auth_login', next=next_url, _absolute=True) data = {} if campaign and campaign in campaigns.CAMPAIGNS: if (campaign == 'institution' and settings.ENABLE_INSTITUTIONS) or campaign != 'institution': data['campaign'] = campaign data['login_url'] = cas.get_login_url(redirect_url) data['institution_redirect'] = cas.get_institution_target(redirect_url) data['redirect_url'] = next_url data['sign_up'] = request.args.get('sign_up', False) data['existing_user'] = request.args.get('existing_user', None) return data, http.OK
def external_login_confirm_email_get(auth, uid, token): """ View for email confirmation links when user first login through external identity provider. HTTP Method: GET When users click the confirm link, they are expected not to be logged in. If not, they will be logged out first and redirected back to this view. After OSF verifies the link and performs all actions, they will be automatically logged in through CAS and redirected back to this view again being authenticated. :param auth: the auth context :param uid: the user's primary key :param token: the verification token """ user = OSFUser.load(uid) if not user: raise HTTPError(http.BAD_REQUEST) destination = request.args.get('destination') if not destination: raise HTTPError(http.BAD_REQUEST) # if user is already logged in if auth and auth.user: # if it is a wrong user if auth.user._id != user._id: return auth_logout(redirect_url=request.url) # if it is the expected user new = request.args.get('new', None) if destination in campaigns.get_campaigns(): # external domain takes priority campaign_url = campaigns.external_campaign_url_for(destination) if not campaign_url: campaign_url = campaigns.campaign_url_for(destination) return redirect(campaign_url) if new: status.push_status_message(language.WELCOME_MESSAGE, kind='default', jumbotron=True, trust=True) return redirect(web_url_for('dashboard')) # token is invalid if token not in user.email_verifications: raise HTTPError(http.BAD_REQUEST) verification = user.email_verifications[token] email = verification['email'] provider = verification['external_identity'].keys()[0] provider_id = verification['external_identity'][provider].keys()[0] # wrong provider if provider not in user.external_identity: raise HTTPError(http.BAD_REQUEST) external_status = user.external_identity[provider][provider_id] try: ensure_external_identity_uniqueness(provider, provider_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if not user.is_registered: user.register(email) if not user.emails.filter(address=email.lower()): user.emails.create(address=email.lower()) user.date_last_logged_in = timezone.now() user.external_identity[provider][provider_id] = 'VERIFIED' user.social[provider.lower()] = provider_id del user.email_verifications[token] user.verification_key = generate_verification_key() user.save() service_url = request.url if external_status == 'CREATE': mails.send_mail(to_addr=user.username, mail=mails.WELCOME, mimetype='html', user=user) service_url += '&{}'.format(urllib.urlencode({'new': 'true'})) elif external_status == 'LINK': mails.send_mail( user=user, to_addr=user.username, mail=mails.EXTERNAL_LOGIN_LINK_SUCCESS, external_id_provider=provider, ) # redirect to CAS and authenticate the user with the verification key return redirect( cas.get_login_url(service_url, username=user.username, verification_key=user.verification_key))
def auth_login(auth, **kwargs): """ This view serves as the entry point for OSF login and campaign login. HTTP Method: GET GET '/login/' without any query parameter: redirect to CAS login page with dashboard as target service GET '/login/?logout=true log user out and redirect to CAS login page with redirect_url or next_url as target service GET '/login/?campaign=instituion: if user is logged in, redirect to 'dashboard' show institution login GET '/login/?campaign=prereg: if user is logged in, redirect to prereg home page else show sign up page and notify user to sign in, set next to prereg home page GET '/login/?next=next_url: if user is logged in, redirect to next_url else redirect to CAS login page with next_url as target service """ campaign = request.args.get('campaign') next_url = request.args.get('next') log_out = request.args.get('logout') must_login_warning = True if not campaign and not next_url and not log_out: if auth.logged_in: return redirect(web_url_for('dashboard')) return redirect(cas.get_login_url(web_url_for('dashboard', _absolute=True))) if campaign: next_url = campaigns.campaign_url_for(campaign) if not next_url: next_url = request.args.get('redirect_url') must_login_warning = False if next_url: # Only allow redirects which are relative root or full domain, disallows external redirects. if not (next_url[0] == '/' or next_url.startswith(settings.DOMAIN) or next_url.startswith(settings.CAS_SERVER_URL) or next_url.startswith(settings.MFR_SERVER_URL)): raise HTTPError(http.InvalidURL) if auth.logged_in: if not log_out: if next_url: return redirect(next_url) return redirect('dashboard') # redirect user to CAS for logout, return here w/o authentication return auth_logout(redirect_url=request.url) status_message = request.args.get('status', '') if status_message == 'expired': status.push_status_message('The private link you used is expired.', trust=False) status.push_status_message('The private link you used is expired. Please <a href="/settings/account/">' 'resend email.</a>', trust=False) if next_url and must_login_warning: status.push_status_message(language.MUST_LOGIN, trust=False) # set login_url to form action, upon successful authentication specifically w/o logout=True, # allows for next to be followed or a redirect to the dashboard. redirect_url = web_url_for('auth_login', next=next_url, _absolute=True) data = {} if campaign and campaign in campaigns.CAMPAIGNS: if (campaign == 'institution' and settings.ENABLE_INSTITUTIONS) or campaign != 'institution': data['campaign'] = campaign data['login_url'] = cas.get_login_url(redirect_url) data['institution_redirect'] = cas.get_institution_target(redirect_url) data['redirect_url'] = next_url data['sign_up'] = request.args.get('sign_up', False) data['existing_user'] = request.args.get('existing_user', None) return data, http.OK
def external_login_email_post(): """ View to handle email submission for first-time oauth-login user. HTTP Method: POST """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] external_id = session.data['auth_user_external_id'] fullname = session.data['auth_user_fullname'] service_url = session.data['service_url'] # TODO: @cslzchen use user tags instead of destination destination = 'dashboard' for campaign in campaigns.get_campaigns(): if campaign != 'institution': # Handle different url encoding schemes between `furl` and `urlparse/urllib`. # OSF use `furl` to parse service url during service validation with CAS. However, `web_url_for()` uses # `urlparse/urllib` to generate service url. `furl` handles `urlparser/urllib` generated urls while ` but # not vice versa. campaign_url = furl.furl(campaigns.campaign_url_for(campaign)).url external_campaign_url = furl.furl( campaigns.external_campaign_url_for(campaign)).url if campaigns.is_proxy_login(campaign): # proxy campaigns: OSF Preprints and branded ones if check_service_url_with_proxy_campaign( str(service_url), campaign_url, external_campaign_url): destination = campaign # continue to check branded preprints even service url matches osf preprints if campaign != 'osf-preprints': break elif service_url.startswith(campaign_url): # osf campaigns: OSF Prereg and ERPC destination = campaign break if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) external_identity = { external_id_provider: { external_id: None, }, } try: ensure_external_identity_uniqueness(external_id_provider, external_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if user: # 1. update user oauth, with pending status external_identity[external_id_provider][external_id] = 'LINK' if external_id_provider in user.external_identity: user.external_identity[external_id_provider].update( external_identity[external_id_provider]) else: user.external_identity.update(external_identity) # 2. add unconfirmed email and send confirmation email user.add_unconfirmed_email(clean_email, external_identity=external_identity) user.save() send_confirm_email(user, clean_email, external_id_provider=external_id_provider, external_id=external_id, destination=destination) # 3. notify user message = language.EXTERNAL_LOGIN_EMAIL_LINK_SUCCESS.format( external_id_provider=external_id_provider, email=user.username) kind = 'success' # 4. remove session and osf cookie remove_session(session) else: # 1. create unconfirmed user with pending status external_identity[external_id_provider][external_id] = 'CREATE' user = OSFUser.create_unconfirmed( username=clean_email, password=None, fullname=fullname, external_identity=external_identity, campaign=None) # TODO: [#OSF-6934] update social fields, verified social fields cannot be modified user.save() # 3. send confirmation email send_confirm_email(user, user.username, external_id_provider=external_id_provider, external_id=external_id, destination=destination) # 4. notify user message = language.EXTERNAL_LOGIN_EMAIL_CREATE_SUCCESS.format( external_id_provider=external_id_provider, email=user.username) kind = 'success' # 5. remove session remove_session(session) status.push_status_message(message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form, 'external_id_provider': external_id_provider}
def external_login_email_post(): """ View to handle email submission for first-time oauth-login user. HTTP Method: POST """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] external_id = session.data['auth_user_external_id'] fullname = session.data['auth_user_fullname'] service_url = session.data['service_url'] destination = 'dashboard' for campaign in campaigns.CAMPAIGNS: if campaign != 'institution': campaign_url = furl.furl(campaigns.campaign_url_for(campaign)).url if campaigns.is_proxy_login(campaign): campaign_url = furl.furl(web_url_for('auth_login', next=campaign_url, _absolute=True)).url if service_url.startswith(campaign_url): destination = campaign break if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) external_identity = { external_id_provider: { external_id: None, }, } try: ensure_external_identity_uniqueness(external_id_provider, external_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if user: # 1. update user oauth, with pending status external_identity[external_id_provider][external_id] = 'LINK' if external_id_provider in user.external_identity: user.external_identity[external_id_provider].update(external_identity[external_id_provider]) else: user.external_identity.update(external_identity) # 2. add unconfirmed email and send confirmation email user.add_unconfirmed_email(clean_email, external_identity=external_identity) user.save() send_confirm_email( user, clean_email, external_id_provider=external_id_provider, external_id=external_id, destination=destination ) # 3. notify user message = language.EXTERNAL_LOGIN_EMAIL_LINK_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 4. remove session and osf cookie remove_session(session) else: # 1. create unconfirmed user with pending status external_identity[external_id_provider][external_id] = 'CREATE' user = User.create_unconfirmed( username=clean_email, password=str(uuid.uuid4()), fullname=fullname, external_identity=external_identity, campaign=None ) # TODO: [#OSF-6934] update social fields, verified social fields cannot be modified user.save() # 3. send confirmation email send_confirm_email( user, user.username, external_id_provider=external_id_provider, external_id=external_id, destination=destination ) # 4. notify user message = language.EXTERNAL_LOGIN_EMAIL_CREATE_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 5. remove session remove_session(session) status.push_status_message(message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return { 'form': form, 'external_id_provider': external_id_provider }