def test_is_proxy_login(self): for campaign in self.campaign_lists: proxy = campaigns.is_proxy_login(campaign) if campaign.endswith('-preprints'): assert_true(proxy) else: assert_false(proxy) proxy = campaigns.is_proxy_login(self.invalid_campaign) assert_true(proxy is None)
def test_get_service_provider(self): for campaign in self.campaign_lists: provider = campaigns.get_service_provider(campaign) if campaigns.is_proxy_login(campaign): assert_true(provider is not None) else: assert_true(provider is None) provider = campaigns.get_service_provider(self.invalid_campaign) assert_true(provider is None)
def test_is_native_login(self): for campaign in self.campaign_lists: native = campaigns.is_native_login(campaign) if campaign == 'prereg' or campaign == 'erpc': assert_true(native) else: assert_false(native) native = campaigns.is_proxy_login(self.invalid_campaign) assert_true(native is None)
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 send_confirm_email(user, email, renew=False, external_id_provider=None, external_id=None, destination=None): """ Sends `user` a confirmation to the given `email`. :param user: the user :param email: the email :param renew: refresh the token :param external_id_provider: user's external id provider :param external_id: user's external id :param destination: the destination page to redirect after confirmation :return: :raises: KeyError if user does not have a confirmation token for the given email. """ confirmation_url = user.get_confirmation_url( email, external=True, force=True, renew=renew, external_id_provider=external_id_provider, destination=destination) try: merge_target = OSFUser.objects.get(emails__address=email) except OSFUser.DoesNotExist: merge_target = None campaign = campaigns.campaign_for_user(user) branded_preprints_provider = None # Choose the appropriate email template to use and add existing_user flag if a merge or adding an email. if external_id_provider and external_id: # First time login through external identity provider, link or create an OSF account confirmation if user.external_identity[external_id_provider][ external_id] == 'CREATE': mail_template = mails.EXTERNAL_LOGIN_CONFIRM_EMAIL_CREATE elif user.external_identity[external_id_provider][ external_id] == 'LINK': mail_template = mails.EXTERNAL_LOGIN_CONFIRM_EMAIL_LINK elif merge_target: # Merge account confirmation mail_template = mails.CONFIRM_MERGE confirmation_url = '{}?logout=1'.format(confirmation_url) elif user.is_active: # Add email confirmation mail_template = mails.CONFIRM_EMAIL confirmation_url = '{}?logout=1'.format(confirmation_url) elif campaign: # Account creation confirmation: from campaign mail_template = campaigns.email_template_for_campaign(campaign) if campaigns.is_proxy_login( campaign ) and campaigns.get_service_provider(campaign) != 'OSF': branded_preprints_provider = campaigns.get_service_provider( campaign) else: # Account creation confirmation: from OSF mail_template = mails.INITIAL_CONFIRM_EMAIL mails.send_mail(email, mail_template, 'plain', user=user, confirmation_url=confirmation_url, email=email, merge_target=merge_target, external_id_provider=external_id_provider, branded_preprints_provider=branded_preprints_provider, osf_support_email=settings.OSF_SUPPORT_EMAIL)
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 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 send_confirm_email(user, email, renew=False, external_id_provider=None, external_id=None, destination=None): """ Sends `user` a confirmation to the given `email`. :param user: the user :param email: the email :param renew: refresh the token :param external_id_provider: user's external id provider :param external_id: user's external id :param destination: the destination page to redirect after confirmation :return: :raises: KeyError if user does not have a confirmation token for the given email. """ confirmation_url = user.get_confirmation_url( email, external=True, force=True, renew=renew, external_id_provider=external_id_provider, destination=destination ) try: merge_target = User.find_one(Q('emails', 'eq', email)) except NoResultsFound: merge_target = None campaign = campaigns.campaign_for_user(user) branded_preprints_provider = None # Choose the appropriate email template to use and add existing_user flag if a merge or adding an email. if external_id_provider and external_id: # First time login through external identity provider, link or create an OSF account confirmation if user.external_identity[external_id_provider][external_id] == 'CREATE': mail_template = mails.EXTERNAL_LOGIN_CONFIRM_EMAIL_CREATE elif user.external_identity[external_id_provider][external_id] == 'LINK': mail_template = mails.EXTERNAL_LOGIN_CONFIRM_EMAIL_LINK elif merge_target: # Merge account confirmation mail_template = mails.CONFIRM_MERGE confirmation_url = '{}?logout=1'.format(confirmation_url) elif user.is_active: # Add email confirmation mail_template = mails.CONFIRM_EMAIL confirmation_url = '{}?logout=1'.format(confirmation_url) elif campaign: # Account creation confirmation: from campaign mail_template = campaigns.email_template_for_campaign(campaign) if campaigns.is_proxy_login(campaign) and campaigns.get_service_provider(campaign) != 'OSF': branded_preprints_provider = campaigns.get_service_provider(campaign) else: # Account creation confirmation: from OSF mail_template = mails.INITIAL_CONFIRM_EMAIL mails.send_mail( email, mail_template, 'plain', user=user, confirmation_url=confirmation_url, email=email, merge_target=merge_target, external_id_provider=external_id_provider, branded_preprints_provider=branded_preprints_provider )
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 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 }