def test_throttle_period_expired_using_timestamp_in_seconds(self): timestamp = int(time.time()) is_expired = throttle_period_expired(timestamp=(timestamp + 29), throttle=30) assert_false(is_expired) is_expired = throttle_period_expired(timestamp=(timestamp - 31), throttle=30) assert_true(is_expired)
def test_throttle_period_expired_using_datetime(self): timestamp = timezone.now() is_expired = throttle_period_expired(timestamp=(timestamp + datetime.timedelta(seconds=29)), throttle=30) assert_false(is_expired) is_expired = throttle_period_expired(timestamp=(timestamp - datetime.timedelta(seconds=31)), throttle=30) assert_true(is_expired)
def user_account_password(auth, **kwargs): user = auth.user old_password = request.form.get('old_password', None) new_password = request.form.get('new_password', None) confirm_password = request.form.get('confirm_password', None) # It has been more than 1 hour since last invalid attempt to change password. Reset the counter for invalid attempts. if throttle_period_expired(user.change_password_last_attempt, settings.TIME_RESET_CHANGE_PASSWORD_ATTEMPTS): user.reset_old_password_invalid_attempts() # There have been more than 3 failed attempts and throttle hasn't expired. if user.old_password_invalid_attempts >= settings.INCORRECT_PASSWORD_ATTEMPTS_ALLOWED and not throttle_period_expired(user.change_password_last_attempt, settings.CHANGE_PASSWORD_THROTTLE): push_status_message( message='Too many failed attempts. Please wait a while before attempting to change your password.', kind='warning', trust=False ) return redirect(web_url_for('user_account')) try: user.change_password(old_password, new_password, confirm_password) except ChangePasswordError as error: for m in error.messages: push_status_message(m, kind='warning', trust=False) else: # We have to logout the user first so all CAS sessions are invalid user.save() osf_logout() return redirect(cas.get_logout_url(cas.get_login_url( web_url_for('user_account', _absolute=True) + '?password_reset=True', username=user.username, verification_key=user.verification_key, ))) user.save() return redirect(web_url_for('user_account'))
def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) user = self.get_user() existing_password = request.data['existing_password'] new_password = request.data['new_password'] # It has been more than 1 hour since last invalid attempt to change password. Reset the counter for invalid attempts. if throttle_period_expired(user.change_password_last_attempt, settings.TIME_RESET_CHANGE_PASSWORD_ATTEMPTS): user.reset_old_password_invalid_attempts() # There have been more than 3 failed attempts and throttle hasn't expired. if user.old_password_invalid_attempts >= settings.INCORRECT_PASSWORD_ATTEMPTS_ALLOWED and not throttle_period_expired( user.change_password_last_attempt, settings.CHANGE_PASSWORD_THROTTLE, ): time_since_throttle = (timezone.now() - user.change_password_last_attempt.replace(tzinfo=pytz.utc)).total_seconds() wait_time = settings.CHANGE_PASSWORD_THROTTLE - time_since_throttle raise Throttled(wait=wait_time) try: # double new password for confirmation because validation is done on the front-end. user.change_password(existing_password, new_password, new_password) except ChangePasswordError as error: # A response object must be returned instead of raising an exception to avoid rolling back the transaction # and losing the incrementation of failed password attempts user.save() return JsonResponse( {'errors': [{'detail': message} for message in error.messages]}, status=400, content_type='application/vnd.api+json; application/json', ) user.save() remove_sessions_for_user(user) return Response(status=status.HTTP_204_NO_CONTENT)
def resend_confirmation(auth): user = auth.user data = request.get_json() validate_user(data, user) if not throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): raise HTTPError(httplib.BAD_REQUEST, data={'message_long': 'Too many requests. Please wait a while before sending another confirmation email.'}) try: primary = data['email']['primary'] confirmed = data['email']['confirmed'] address = data['email']['address'].strip().lower() except KeyError: raise HTTPError(httplib.BAD_REQUEST) if primary or confirmed: raise HTTPError(httplib.BAD_REQUEST, data={'message_long': 'Cannnot resend confirmation for confirmed emails'}) user.add_unconfirmed_email(address) # TODO: This setting is now named incorrectly. if settings.CONFIRM_REGISTRATIONS_BY_EMAIL: send_confirm_email(user, email=address) user.email_last_sent = timezone.now() user.save() return _profile_view(user, is_profile=True)
def user_account_password(auth, **kwargs): user = auth.user old_password = request.form.get('old_password', None) new_password = request.form.get('new_password', None) confirm_password = request.form.get('confirm_password', None) # It has been more than 1 hour since last invalid attempt to change password. Reset the counter for invalid attempts. if throttle_period_expired(user.change_password_last_attempt, settings.TIME_RESET_CHANGE_PASSWORD_ATTEMPTS): user.reset_old_password_invalid_attempts() # There have been more than 3 failed attempts and throttle hasn't expired. if user.old_password_invalid_attempts >= settings.INCORRECT_PASSWORD_ATTEMPTS_ALLOWED and not throttle_period_expired(user.change_password_last_attempt, settings.CHANGE_PASSWORD_THROTTLE): push_status_message( message='Too many failed attempts. Please wait a while before attempting to change your password.', kind='warning', trust=False ) return redirect(web_url_for('user_account')) try: user.change_password(old_password, new_password, confirm_password) except ChangePasswordError as error: for m in error.messages: push_status_message(m, kind='warning', trust=False) else: # We have to logout the user first so all CAS sessions are invalid user.save() osf_logout() return redirect(cas.get_logout_url(cas.get_login_url( web_url_for('user_account', _absolute=True) + '?password_reset=True', username=user.username, verification_key=user.verification_key, ))) user.save() return redirect(web_url_for('user_account'))
def notify_added_node_group_member(group, node, user, permission, auth, throttle=None): throttle = throttle or settings.GROUP_CONNECTED_EMAIL_THROTTLE node_group_record = user.group_connected_email_records.get(group._id, {}) if node_group_record: timestamp = node_group_record.get('last_sent', None) if timestamp: if not throttle_period_expired(timestamp, throttle): return else: user.group_connected_email_records[group._id] = {} if (not auth or auth.user != user) and user.is_registered: email_template = mails.GROUP_ADDED_TO_NODE mails.send_mail( to_addr=user.username, mail=email_template, mimetype='html', user=user, node=node, all_global_subscriptions_none=check_if_all_global_subscriptions_are_none(user), group_name=group.name, permission=permission, referrer_name=auth.user.fullname if auth else '', osf_contact_email=settings.OSF_CONTACT_EMAIL, ) user.group_connected_email_records[group._id]['last_sent'] = get_timestamp() user.save()
def notify_added_group_member(group, user, permission, auth=None, throttle=None, email_template='default', *args, **kwargs): if email_template == 'false': return throttle = throttle or settings.GROUP_MEMBER_ADDED_EMAIL_THROTTLE member_record = user.member_added_email_records.get(group._id, {}) if member_record: timestamp = member_record.get('last_sent', None) if timestamp: if not throttle_period_expired(timestamp, throttle): return else: user.member_added_email_records[group._id] = {} if user.is_registered: email_template = mails.GROUP_MEMBER_ADDED mails.send_mail( to_addr=user.username, mail=email_template, mimetype='html', user=user, group_name=group.name, permission=permission, referrer_name=auth.user.fullname if auth else '', osf_contact_email=settings.OSF_CONTACT_EMAIL, ) user.member_added_email_records[group._id]['last_sent'] = get_timestamp() user.save() else: unreg_member_added.send(group, user=user, permission=permission, auth=auth, throttle=throttle, email_template=email_template)
def resend_confirmation(auth): user = auth.user data = request.get_json() validate_user(data, user) if not throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data={'message_long': 'Too many requests. Please wait a while before sending another confirmation email.'}) try: primary = data['email']['primary'] confirmed = data['email']['confirmed'] address = data['email']['address'].strip().lower() except KeyError: raise HTTPError(http_status.HTTP_400_BAD_REQUEST) if primary or confirmed: raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data={'message_long': 'Cannnot resend confirmation for confirmed emails'}) user.add_unconfirmed_email(address) # TODO: This setting is now named incorrectly. if settings.CONFIRM_REGISTRATIONS_BY_EMAIL: send_confirm_email(user, email=address) user.email_last_sent = timezone.now() user.save() return _profile_view(user, is_profile=True)
def user_account_password(auth, **kwargs): user = auth.user old_password = request.form.get('old_password', None) new_password = request.form.get('new_password', None) confirm_password = request.form.get('confirm_password', None) # It has been more than 1 hour since last invalid attempt to change password. Reset the counter for invalid attempts. if throttle_period_expired(user.change_password_last_attempt, settings.TIME_RESET_CHANGE_PASSWORD_ATTEMPTS): user.reset_old_password_invalid_attempts() # There have been more than 3 failed attempts and throttle hasn't expired. if user.old_password_invalid_attempts >= settings.INCORRECT_PASSWORD_ATTEMPTS_ALLOWED and not throttle_period_expired(user.change_password_last_attempt, settings.CHANGE_PASSWORD_THROTTLE): push_status_message( message='Too many failed attempts. Please wait a while before attempting to change your password.', kind='warning', trust=False ) return redirect(web_url_for('user_account')) try: user.change_password(old_password, new_password, confirm_password) if user.verification_key_v2: user.verification_key_v2['expires'] = timezone.now() user.save() except ChangePasswordError as error: for m in error.messages: push_status_message(m, kind='warning', trust=False) else: push_status_message('Password updated successfully.', kind='success', trust=False) user.save() return redirect(web_url_for('user_account'))
def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) user = self.get_user() existing_password = request.data['existing_password'] new_password = request.data['new_password'] # It has been more than 1 hour since last invalid attempt to change password. Reset the counter for invalid attempts. if throttle_period_expired(user.change_password_last_attempt, settings.TIME_RESET_CHANGE_PASSWORD_ATTEMPTS): user.reset_old_password_invalid_attempts() # There have been more than 3 failed attempts and throttle hasn't expired. if user.old_password_invalid_attempts >= settings.INCORRECT_PASSWORD_ATTEMPTS_ALLOWED and not throttle_period_expired( user.change_password_last_attempt, settings.CHANGE_PASSWORD_THROTTLE, ): time_since_throttle = (timezone.now() - user.change_password_last_attempt.replace(tzinfo=pytz.utc)).total_seconds() wait_time = settings.CHANGE_PASSWORD_THROTTLE - time_since_throttle raise Throttled(wait=wait_time) try: # double new password for confirmation because validation is done on the front-end. user.change_password(existing_password, new_password, new_password) except ChangePasswordError as error: # A response object must be returned instead of raising an exception to avoid rolling back the transaction # and losing the incrementation of failed password attempts user.save() return JsonResponse( {'errors': [{'detail': message} for message in error.messages]}, status=400, content_type='application/vnd.api+json; application/json', ) user.save() remove_sessions_for_user(user) return Response(status=status.HTTP_204_NO_CONTENT)
def notify_added_contributor(node, contributor, auth=None, throttle=None, email_template='default'): if email_template == 'false': return throttle = throttle or settings.CONTRIBUTOR_ADDED_EMAIL_THROTTLE # Email users for projects, or for components where they are not contributors on the parent node. if contributor.is_registered and \ (not node.parent_node or (node.parent_node and not node.parent_node.is_contributor(contributor))): mimetype = 'html' preprint_provider = None logo = None if email_template == 'preprint': email_template, preprint_provider = find_preprint_provider(node) if not email_template or not preprint_provider: return email_template = getattr(mails, 'CONTRIBUTOR_ADDED_PREPRINT')(email_template, preprint_provider) if preprint_provider._id == 'osf': logo = settings.OSF_PREPRINTS_LOGO else: logo = preprint_provider._id elif email_template == 'access_request': mimetype = 'html' email_template = getattr(mails, 'CONTRIBUTOR_ADDED_ACCESS_REQUEST'.format(email_template.upper())) elif node.is_preprint: email_template = getattr(mails, 'CONTRIBUTOR_ADDED_PREPRINT_NODE_FROM_OSF'.format(email_template.upper())) logo = settings.OSF_PREPRINTS_LOGO else: email_template = getattr(mails, 'CONTRIBUTOR_ADDED_DEFAULT'.format(email_template.upper())) contributor_record = contributor.contributor_added_email_records.get(node._id, {}) if contributor_record: timestamp = contributor_record.get('last_sent', None) if timestamp: if not throttle_period_expired(timestamp, throttle): return else: contributor.contributor_added_email_records[node._id] = {} mails.send_mail( contributor.username, email_template, mimetype=mimetype, user=contributor, node=node, referrer_name=auth.user.fullname if auth else '', all_global_subscriptions_none=check_if_all_global_subscriptions_are_none(contributor), branded_service=preprint_provider, can_change_preferences=False, logo=logo if logo else settings.OSF_LOGO, osf_contact_email=settings.OSF_CONTACT_EMAIL ) contributor.contributor_added_email_records[node._id]['last_sent'] = get_timestamp() contributor.save() elif not contributor.is_registered: unreg_contributor_added.send(node, contributor=contributor, auth=auth, email_template=email_template)
def notify_added_contributor(node, contributor, auth=None, throttle=None, email_template='default'): if email_template == 'false': return throttle = throttle or settings.CONTRIBUTOR_ADDED_EMAIL_THROTTLE # Email users for projects, or for components where they are not contributors on the parent node. if contributor.is_registered and \ (not node.parent_node or (node.parent_node and not node.parent_node.is_contributor(contributor))): mimetype = 'html' preprint_provider = None logo = None if email_template == 'preprint': email_template, preprint_provider = find_preprint_provider(node) if not email_template or not preprint_provider: return email_template = getattr(mails, 'CONTRIBUTOR_ADDED_PREPRINT')(email_template, preprint_provider) if preprint_provider._id == 'osf': logo = settings.OSF_PREPRINTS_LOGO else: logo = preprint_provider._id elif email_template == 'access_request': mimetype = 'html' email_template = getattr(mails, 'CONTRIBUTOR_ADDED_ACCESS_REQUEST'.format(email_template.upper())) elif node.is_preprint: email_template = getattr(mails, 'CONTRIBUTOR_ADDED_PREPRINT_NODE_FROM_OSF'.format(email_template.upper())) logo = settings.OSF_PREPRINTS_LOGO else: email_template = getattr(mails, 'CONTRIBUTOR_ADDED_DEFAULT'.format(email_template.upper())) contributor_record = contributor.contributor_added_email_records.get(node._id, {}) if contributor_record: timestamp = contributor_record.get('last_sent', None) if timestamp: if not throttle_period_expired(timestamp, throttle): return else: contributor.contributor_added_email_records[node._id] = {} mails.send_mail( contributor.username, email_template, mimetype=mimetype, user=contributor, node=node, referrer_name=auth.user.fullname if auth else '', all_global_subscriptions_none=check_if_all_global_subscriptions_are_none(contributor), branded_service=preprint_provider, can_change_preferences=False, logo=logo if logo else settings.OSF_LOGO, osf_contact_email=settings.OSF_CONTACT_EMAIL ) contributor.contributor_added_email_records[node._id]['last_sent'] = get_timestamp() contributor.save() elif not contributor.is_registered: unreg_contributor_added.send(node, contributor=contributor, auth=auth, email_template=email_template)
def send_claim_registered_email(claimer, unclaimed_user, node, throttle=24 * 3600): """ A registered user claiming the unclaimed user account as an contributor to a project. Send an email for claiming the account to the referrer and notify the claimer. :param claimer: the claimer :param unclaimed_user: the user account to claim :param node: the project node where the user account is claimed :param throttle: the time period in seconds before another claim for the account can be made :return: :raise: http.BAD_REQUEST """ unclaimed_record = unclaimed_user.get_unclaimed_record(node._primary_key) # check throttle timestamp = unclaimed_record.get('last_sent') if not throttle_period_expired(timestamp, throttle): raise HTTPError(http.BAD_REQUEST, data=dict( message_long='User account can only be claimed with an existing user once every 24 hours' )) # roll the valid token for each email, thus user cannot change email and approve a different email address verification_key = generate_verification_key(verification_type='claim') unclaimed_record['token'] = verification_key['token'] unclaimed_record['expires'] = verification_key['expires'] unclaimed_record['claimer_email'] = claimer.username unclaimed_user.save() referrer = OSFUser.load(unclaimed_record['referrer_id']) claim_url = web_url_for( 'claim_user_registered', uid=unclaimed_user._primary_key, pid=node._primary_key, token=unclaimed_record['token'], _external=True, ) # Send mail to referrer, telling them to forward verification link to claimer mails.send_mail( referrer.username, mails.FORWARD_INVITE_REGISTERED, user=unclaimed_user, referrer=referrer, node=node, claim_url=claim_url, fullname=unclaimed_record['name'], ) unclaimed_record['last_sent'] = get_timestamp() unclaimed_user.save() # Send mail to claimer, telling them to wait for referrer mails.send_mail( claimer.username, mails.PENDING_VERIFICATION_REGISTERED, fullname=claimer.fullname, referrer=referrer, node=node, )
def check_email_throttle(node, contributor, throttle=None): throttle = throttle or settings.CONTRIBUTOR_ADDED_EMAIL_THROTTLE contributor_record = contributor.contributor_added_email_records.get(node._id, {}) if contributor_record: timestamp = contributor_record.get('last_sent', None) if timestamp: if not throttle_period_expired(timestamp, throttle): return True else: contributor.contributor_added_email_records[node._id] = {}
def forgot_password_post(): """ View for user to submit forgot password form. HTTP Method: POST :return {} """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: email = form.email.data status_message = ( 'If there is an OSF account associated with {0}, an email with instructions on how to ' 'reset the OSF password has been sent to {0}. If you do not receive an email and believe ' 'you should have, please contact OSF Support. ').format(email) kind = 'success' # check if the user exists user_obj = get_user(email=email) if user_obj: # rate limit forgot_password_post if not throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE): status_message = 'You have recently requested to change your password. Please wait a few minutes ' \ 'before trying again.' kind = 'error' # TODO [OSF-6673]: Use the feature in [OSF-6998] for user to resend claim email. elif user_obj.is_active: # new random verification key (v2) user_obj.verification_key_v2 = generate_verification_key( verification_type='password') user_obj.email_last_sent = timezone.now() user_obj.save() reset_link = furl.urljoin( settings.DOMAIN, web_url_for('reset_password_get', uid=user_obj._id, token=user_obj.verification_key_v2['token'])) mails.send_mail( to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link, can_change_preferences=False, ) status.push_status_message(status_message, kind=kind, trust=False) return {}
def get_object(self): email_id = self.kwargs['email_id'] user = self.get_user() email = None # check to see if it's a confirmed email with hashed id decoded_id = hashids.decode(email_id) if decoded_id: try: email = user.emails.get(id=decoded_id[0]) except Email.DoesNotExist: email = None else: primary = email.address == user.username address = email.address confirmed = True verified = True is_merge = False # check to see if it's an unconfirmed email with a token elif user.unconfirmed_emails: try: email = user.email_verifications[email_id] address = email['email'] confirmed = email['confirmed'] verified = False primary = False is_merge = Email.objects.filter(address=address).exists() except KeyError: email = None if not email: raise NotFound # check for resend confirmation email query parameter in a GET request if self.request.method == 'GET' and is_truthy( self.request.query_params.get('resend_confirmation')): if not confirmed and settings.CONFIRM_REGISTRATIONS_BY_EMAIL: if throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): send_confirm_email(user, email=address, renew=True) user.email_last_sent = timezone.now() user.save() return UserEmail(email_id=email_id, address=address, confirmed=confirmed, verified=verified, primary=primary, is_merge=is_merge)
def forgot_password_post(): """ View for user to submit forgot password form. HTTP Method: POST :return {} """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: email = form.email.data status_message = ('If there is an OSF account associated with {0}, an email with instructions on how to ' 'reset the OSF password has been sent to {0}. If you do not receive an email and believe ' 'you should have, please contact OSF Support. ').format(email) kind = 'success' # check if the user exists user_obj = get_user(email=email) if user_obj: # rate limit forgot_password_post if not throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE): status_message = 'You have recently requested to change your password. Please wait a few minutes ' \ 'before trying again.' kind = 'error' # TODO [OSF-6673]: Use the feature in [OSF-6998] for user to resend claim email. elif user_obj.is_active: # new random verification key (v2) user_obj.verification_key_v2 = generate_verification_key(verification_type='password') user_obj.email_last_sent = timezone.now() user_obj.save() reset_link = furl.urljoin( settings.DOMAIN, web_url_for( 'reset_password_get', uid=user_obj._id, token=user_obj.verification_key_v2['token'] ) ) mails.send_mail( to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link, can_change_preferences=False, ) status.push_status_message(status_message, kind=kind, trust=False) return {}
def request_export(auth): user = auth.user if not throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): raise HTTPError(httplib.BAD_REQUEST, data={'message_long': 'Too many requests. Please wait a while before sending another account export request.', 'error_type': 'throttle_error'}) mails.send_mail( to_addr=settings.OSF_SUPPORT_EMAIL, mail=mails.REQUEST_EXPORT, user=auth.user, ) user.email_last_sent = timezone.now() user.save() return {'message': 'Sent account export request'}
def request_export(auth): user = auth.user if not throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data={'message_long': 'Too many requests. Please wait a while before sending another account export request.', 'error_type': 'throttle_error'}) mails.send_mail( to_addr=settings.OSF_SUPPORT_EMAIL, mail=mails.REQUEST_EXPORT, user=auth.user, can_change_preferences=False, ) user.email_last_sent = timezone.now() user.save() return {'message': 'Sent account export request'}
def get_object(self): email_id = self.kwargs['email_id'] user = self.get_user() email = None # check to see if it's a confirmed email with hashed id decoded_id = hashids.decode(email_id) if decoded_id: try: email = user.emails.get(id=decoded_id[0]) except Email.DoesNotExist: email = None else: primary = email.address == user.username address = email.address confirmed = True verified = True is_merge = False # check to see if it's an unconfirmed email with a token elif user.unconfirmed_emails: try: email = user.email_verifications[email_id] address = email['email'] confirmed = email['confirmed'] verified = False primary = False is_merge = Email.objects.filter(address=address).exists() except KeyError: email = None if not email: raise NotFound # check for resend confirmation email query parameter in a GET request if self.request.method == 'GET' and is_truthy(self.request.query_params.get('resend_confirmation')): if not confirmed and settings.CONFIRM_REGISTRATIONS_BY_EMAIL: if throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): send_confirm_email(user, email=address, renew=True) user.email_last_sent = timezone.now() user.save() return UserEmail(email_id=email_id, address=address, confirmed=confirmed, verified=verified, primary=primary, is_merge=is_merge)
def request_deactivation(auth): user = auth.user if not throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): raise HTTPError(http.BAD_REQUEST, data={ 'message_long': 'Too many requests. Please wait a while before sending another account deactivation request.', 'error_type': 'throttle_error' }) mails.send_mail( to_addr=settings.OSF_SUPPORT_EMAIL, mail=mails.REQUEST_DEACTIVATION, user=auth.user, can_change_preferences=False, ) user.email_last_sent = timezone.now() user.requested_deactivation = True user.save() return {'message': 'Sent account deactivation request'}
def resend_confirmation_post(auth): """ View for user to submit resend confirmation form. HTTP Method: POST """ # If user is already logged in, log user out if auth.logged_in: return auth_logout(redirect_url=request.url) form = ResendConfirmationForm(request.form) if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) status_message = ( 'If there is an OSF account associated with this unconfirmed email address {0}, ' 'a confirmation email has been resent to it. If you do not receive an email and believe ' 'you should have, please contact OSF Support.').format(clean_email) kind = 'success' if user: if throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): try: send_confirm_email(user, clean_email, renew=True) except KeyError: # already confirmed, redirect to dashboard status_message = 'This email {0} has already been confirmed.'.format( clean_email) kind = 'warning' user.email_last_sent = timezone.now() user.save() else: status_message = ( 'You have recently requested to resend your confirmation email. ' 'Please wait a few minutes before trying again.') kind = 'error' status.push_status_message(status_message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form}
def request_deactivation(auth): user = auth.user if not throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): raise HTTPError( http.BAD_REQUEST, data={ 'message_long': 'Too many requests. Please wait a while before sending another account deactivation request.', 'error_type': 'throttle_error' }) mails.send_mail( to_addr=settings.OSF_SUPPORT_EMAIL, mail=mails.REQUEST_DEACTIVATION, user=auth.user, ) user.email_last_sent = timezone.now() user.requested_deactivation = True user.save() return {'message': 'Sent account deactivation request'}
def resend_confirmation_post(auth): """ View for user to submit resend confirmation form. HTTP Method: POST """ # If user is already logged in, log user out if auth.logged_in: return auth_logout(redirect_url=request.url) form = ResendConfirmationForm(request.form) if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) status_message = ('If there is an OSF account associated with this unconfirmed email address {0}, ' 'a confirmation email has been resent to it. If you do not receive an email and believe ' 'you should have, please contact OSF Support.').format(clean_email) kind = 'success' if user: if throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): try: send_confirm_email(user, clean_email, renew=True) except KeyError: # already confirmed, redirect to dashboard status_message = 'This email {0} has already been confirmed.'.format(clean_email) kind = 'warning' user.email_last_sent = timezone.now() user.save() else: status_message = ('You have recently requested to resend your confirmation email. ' 'Please wait a few minutes before trying again.') kind = 'error' status.push_status_message(status_message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form}
def send_claim_email(email, unclaimed_user, node, notify=True, throttle=24 * 3600, email_template='default'): """ Unregistered user claiming a user account as an contributor to a project. Send an email for claiming the account. Either sends to the given email or the referrer's email, depending on the email address provided. :param str email: The address given in the claim user form :param User unclaimed_user: The User record to claim. :param Node node: The node where the user claimed their account. :param bool notify: If True and an email is sent to the referrer, an email will also be sent to the invited user about their pending verification. :param int throttle: Time period (in seconds) after the referrer is emailed during which the referrer will not be emailed again. :param str email_template: the email template to use :return :raise http.BAD_REQUEST """ claimer_email = email.lower().strip() unclaimed_record = unclaimed_user.get_unclaimed_record(node._primary_key) referrer = OSFUser.load(unclaimed_record['referrer_id']) claim_url = unclaimed_user.get_claim_url(node._primary_key, external=True) # Option 1: # When adding the contributor, the referrer provides both name and email. # The given email is the same provided by user, just send to that email. preprint_provider = None logo = None if unclaimed_record.get('email') == claimer_email: # check email template for branded preprints if email_template == 'preprint': email_template, preprint_provider = find_preprint_provider(node) if not email_template or not preprint_provider: return mail_tpl = getattr(mails, 'INVITE_PREPRINT')(email_template, preprint_provider) if preprint_provider._id == 'osf': logo = settings.OSF_PREPRINTS_LOGO else: logo = preprint_provider._id else: mail_tpl = getattr(mails, 'INVITE_DEFAULT'.format(email_template.upper())) to_addr = claimer_email unclaimed_record['claimer_email'] = claimer_email unclaimed_user.save() # Option 2: # TODO: [new improvement ticket] this option is disabled from preprint but still available on the project page # When adding the contributor, the referred only provides the name. # The account is later claimed by some one who provides the email. # Send email to the referrer and ask her/him to forward the email to the user. else: # check throttle timestamp = unclaimed_record.get('last_sent') if not throttle_period_expired(timestamp, throttle): raise HTTPError(http.BAD_REQUEST, data=dict( message_long='User account can only be claimed with an existing user once every 24 hours' )) # roll the valid token for each email, thus user cannot change email and approve a different email address verification_key = generate_verification_key(verification_type='claim') unclaimed_record['last_sent'] = get_timestamp() unclaimed_record['token'] = verification_key['token'] unclaimed_record['expires'] = verification_key['expires'] unclaimed_record['claimer_email'] = claimer_email unclaimed_user.save() claim_url = unclaimed_user.get_claim_url(node._primary_key, external=True) # send an email to the invited user without `claim_url` if notify: pending_mail = mails.PENDING_VERIFICATION mails.send_mail( claimer_email, pending_mail, user=unclaimed_user, referrer=referrer, fullname=unclaimed_record['name'], node=node, can_change_preferences=False, osf_contact_email=settings.OSF_CONTACT_EMAIL, ) mail_tpl = mails.FORWARD_INVITE to_addr = referrer.username # Send an email to the claimer (Option 1) or to the referrer (Option 2) with `claim_url` mails.send_mail( to_addr, mail_tpl, user=unclaimed_user, referrer=referrer, node=node, claim_url=claim_url, email=claimer_email, fullname=unclaimed_record['name'], branded_service=preprint_provider, can_change_preferences=False, logo=logo if logo else settings.OSF_LOGO, osf_contact_email=settings.OSF_CONTACT_EMAIL, ) return to_addr
def send_claim_email(email, unclaimed_user, node, notify=True, throttle=24 * 3600, email_template='default'): """ Unregistered user claiming a user account as an contributor to a project. Send an email for claiming the account. Either sends to the given email or the referrer's email, depending on the email address provided. :param str email: The address given in the claim user form :param User unclaimed_user: The User record to claim. :param Node node: The node where the user claimed their account. :param bool notify: If True and an email is sent to the referrer, an email will also be sent to the invited user about their pending verification. :param int throttle: Time period (in seconds) after the referrer is emailed during which the referrer will not be emailed again. :param str email_template: the email template to use :return :raise http_status.HTTP_400_BAD_REQUEST """ claimer_email = email.lower().strip() unclaimed_record = unclaimed_user.get_unclaimed_record(node._primary_key) referrer = OSFUser.load(unclaimed_record['referrer_id']) claim_url = unclaimed_user.get_claim_url(node._primary_key, external=True) # Option 1: # When adding the contributor, the referrer provides both name and email. # The given email is the same provided by user, just send to that email. preprint_provider = None logo = None if unclaimed_record.get('email') == claimer_email: # check email template for branded preprints if email_template == 'preprint': email_template, preprint_provider = find_preprint_provider(node) if not email_template or not preprint_provider: return mail_tpl = getattr(mails, 'INVITE_PREPRINT')(email_template, preprint_provider) if preprint_provider._id == 'osf': logo = settings.OSF_PREPRINTS_LOGO else: logo = preprint_provider._id else: mail_tpl = getattr(mails, 'INVITE_DEFAULT'.format(email_template.upper())) to_addr = claimer_email unclaimed_record['claimer_email'] = claimer_email unclaimed_user.save() # Option 2: # TODO: [new improvement ticket] this option is disabled from preprint but still available on the project page # When adding the contributor, the referred only provides the name. # The account is later claimed by some one who provides the email. # Send email to the referrer and ask her/him to forward the email to the user. else: # check throttle timestamp = unclaimed_record.get('last_sent') if not throttle_period_expired(timestamp, throttle): raise HTTPError( http_status.HTTP_400_BAD_REQUEST, data=dict( message_long= 'User account can only be claimed with an existing user once every 24 hours' )) # roll the valid token for each email, thus user cannot change email and approve a different email address verification_key = generate_verification_key(verification_type='claim') unclaimed_record['last_sent'] = get_timestamp() unclaimed_record['token'] = verification_key['token'] unclaimed_record['expires'] = verification_key['expires'] unclaimed_record['claimer_email'] = claimer_email unclaimed_user.save() claim_url = unclaimed_user.get_claim_url(node._primary_key, external=True) # send an email to the invited user without `claim_url` if notify: pending_mail = mails.PENDING_VERIFICATION mails.send_mail( claimer_email, pending_mail, user=unclaimed_user, referrer=referrer, fullname=unclaimed_record['name'], node=node, can_change_preferences=False, osf_contact_email=settings.OSF_CONTACT_EMAIL, ) mail_tpl = mails.FORWARD_INVITE to_addr = referrer.username # Send an email to the claimer (Option 1) or to the referrer (Option 2) with `claim_url` mails.send_mail( to_addr, mail_tpl, user=unclaimed_user, referrer=referrer, node=node, claim_url=claim_url, email=claimer_email, fullname=unclaimed_record['name'], branded_service=preprint_provider, can_change_preferences=False, logo=logo if logo else settings.OSF_LOGO, osf_contact_email=settings.OSF_CONTACT_EMAIL, ) return to_addr
def send_claim_registered_email(claimer, unclaimed_user, node, throttle=24 * 3600): """ A registered user claiming the unclaimed user account as an contributor to a project. Send an email for claiming the account to the referrer and notify the claimer. :param claimer: the claimer :param unclaimed_user: the user account to claim :param node: the project node where the user account is claimed :param throttle: the time period in seconds before another claim for the account can be made :return: :raise: http_status.HTTP_400_BAD_REQUEST """ unclaimed_record = unclaimed_user.get_unclaimed_record(node._primary_key) # check throttle timestamp = unclaimed_record.get('last_sent') if not throttle_period_expired(timestamp, throttle): raise HTTPError( http_status.HTTP_400_BAD_REQUEST, data=dict( message_long= 'User account can only be claimed with an existing user once every 24 hours' )) # roll the valid token for each email, thus user cannot change email and approve a different email address verification_key = generate_verification_key(verification_type='claim') unclaimed_record['token'] = verification_key['token'] unclaimed_record['expires'] = verification_key['expires'] unclaimed_record['claimer_email'] = claimer.username unclaimed_user.save() referrer = OSFUser.load(unclaimed_record['referrer_id']) claim_url = web_url_for( 'claim_user_registered', uid=unclaimed_user._primary_key, pid=node._primary_key, token=unclaimed_record['token'], _absolute=True, ) # Send mail to referrer, telling them to forward verification link to claimer mails.send_mail( referrer.username, mails.FORWARD_INVITE_REGISTERED, user=unclaimed_user, referrer=referrer, node=node, claim_url=claim_url, fullname=unclaimed_record['name'], can_change_preferences=False, osf_contact_email=settings.OSF_CONTACT_EMAIL, ) unclaimed_record['last_sent'] = get_timestamp() unclaimed_user.save() # Send mail to claimer, telling them to wait for referrer mails.send_mail( claimer.username, mails.PENDING_VERIFICATION_REGISTERED, fullname=claimer.fullname, referrer=referrer, node=node, can_change_preferences=False, osf_contact_email=settings.OSF_CONTACT_EMAIL, )
def update_user(auth): """Update the logged-in user's profile.""" # trust the decorator to handle auth user = auth.user data = request.get_json() validate_user(data, user) # TODO: Expand this to support other user attributes ########## # Emails # ########## if 'emails' in data: emails_list = [x['address'].strip().lower() for x in data['emails']] if user.username.strip().lower() not in emails_list: raise HTTPError(httplib.FORBIDDEN) available_emails = [ each.strip().lower() for each in list(user.emails.values_list('address', flat=True)) + user.unconfirmed_emails ] # removals removed_emails = [ each.strip().lower() for each in available_emails if each not in emails_list ] if user.username.strip().lower() in removed_emails: raise HTTPError(httplib.FORBIDDEN) for address in removed_emails: if user.emails.filter(address=address): try: user.remove_email(address) except PermissionsError as e: raise HTTPError(httplib.FORBIDDEN, str(e)) user.remove_unconfirmed_email(address) # additions added_emails = [ each['address'].strip().lower() for each in data['emails'] if each['address'].strip().lower() not in available_emails ] for address in added_emails: try: user.add_unconfirmed_email(address) except (ValidationError, ValueError): raise HTTPError(http.BAD_REQUEST, data=dict(message_long='Invalid Email')) except BlacklistedEmailError: sentry.log_message('User attempted to add a blacklisted email', extra_data={ 'user_id': user.id, 'address': address, }) raise HTTPError( http.BAD_REQUEST, data=dict(message_long=language.BLACKLISTED_EMAIL)) # TODO: This setting is now named incorrectly. if settings.CONFIRM_REGISTRATIONS_BY_EMAIL: if not throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): raise HTTPError( httplib.BAD_REQUEST, data={ 'message_long': 'Too many requests. Please wait a while before adding an email to your account.' }) send_confirm_email(user, email=address) ############ # Username # ############ # get the first email that is set to primary and has an address primary_email = next(( each for each in data['emails'] # email is primary if each.get('primary') and each.get('confirmed') # an address is specified (can't trust those sneaky users!) and each.get('address'))) if primary_email: primary_email_address = primary_email['address'].strip().lower() if primary_email_address not in [ each.strip().lower() for each in user.emails.values_list('address', flat=True) ]: raise HTTPError(httplib.FORBIDDEN) username = primary_email_address # make sure the new username has already been confirmed if username and username != user.username and user.emails.filter( address=username).exists(): mails.send_mail(user.username, mails.PRIMARY_EMAIL_CHANGED, user=user, new_address=username, can_change_preferences=False, osf_contact_email=settings.OSF_CONTACT_EMAIL) # Remove old primary email from subscribed mailing lists for list_name, subscription in user.mailchimp_mailing_lists.items( ): if subscription: mailchimp_utils.unsubscribe_mailchimp_async( list_name, user._id, username=user.username) user.username = username ################### # Timezone/Locale # ################### if 'locale' in data: if data['locale']: locale = data['locale'].replace('-', '_') user.locale = locale # TODO: Refactor to something like: # user.timezone = data.get('timezone', user.timezone) if 'timezone' in data: if data['timezone']: user.timezone = data['timezone'] user.save() # Update subscribed mailing lists with new primary email # TODO: move to user.save() for list_name, subscription in user.mailchimp_mailing_lists.items(): if subscription: mailchimp_utils.subscribe_mailchimp(list_name, user._id) return _profile_view(user, is_profile=True)
def before_request(): # TODO: Fix circular import from framework.auth.core import get_user from framework.auth import cas from framework.utils import throttle_period_expired Session = apps.get_model('osf.Session') # Central Authentication Server Ticket Validation and Authentication ticket = request.args.get('ticket') if ticket: service_url = furl.furl(request.url) service_url.args.pop('ticket') # Attempt to authenticate wih CAS, and return a proper redirect response return cas.make_response_from_ticket(ticket=ticket, service_url=service_url.url) if request.authorization: user = get_user( email=request.authorization.username, password=request.authorization.password ) # Create an empty session # TODO: Shoudn't need to create a session for Basic Auth user_session = Session() set_session(user_session) if user: user_addon = user.get_addon('twofactor') if user_addon and user_addon.is_confirmed: otp = request.headers.get('X-OSF-OTP') if otp is None or not user_addon.verify_code(otp): # Must specify two-factor authentication OTP code or invalid two-factor authentication OTP code. user_session.data['auth_error_code'] = http.UNAUTHORIZED return user_session.data['auth_user_username'] = user.username user_session.data['auth_user_fullname'] = user.fullname if user_session.data.get('auth_user_id', None) != user._primary_key: user_session.data['auth_user_id'] = user._primary_key user_session.save() else: # Invalid key: Not found in database user_session.data['auth_error_code'] = http.UNAUTHORIZED return cookie = request.cookies.get(settings.COOKIE_NAME) if cookie: try: session_id = itsdangerous.Signer(settings.SECRET_KEY).unsign(cookie) user_session = Session.load(session_id) or Session(_id=session_id) except itsdangerous.BadData: return if not throttle_period_expired(user_session.created, settings.OSF_SESSION_TIMEOUT): # Update date last login when making non-api requests if user_session.data.get('auth_user_id') and 'api' not in request.url: OSFUser = apps.get_model('osf.OSFUser') ( OSFUser.objects .filter(guids___id__isnull=False, guids___id=user_session.data['auth_user_id']) # Throttle updates .filter(Q(date_last_login__isnull=True) | Q(date_last_login__lt=timezone.now() - dt.timedelta(seconds=settings.DATE_LAST_LOGIN_THROTTLE))) ).update(date_last_login=timezone.now()) set_session(user_session) else: remove_session(user_session)
def get_campaigns(): global CAMPAIGNS global CAMPAIGNS_LAST_REFRESHED if not CAMPAIGNS or (not mutex.locked() and throttle_period_expired( CAMPAIGNS_LAST_REFRESHED, CAMPAIGN_REFRESH_THRESHOLD)): with mutex: # Native campaigns: PREREG and ERPC newest_campaigns = { 'prereg': { 'system_tag': CampaignSourceTags.Prereg.value, 'redirect_url': furl.furl(DOMAIN).add(path='prereg/').url, 'confirmation_email_template': mails.CONFIRM_EMAIL_PREREG, 'login_type': 'native', 'logo': settings.OSF_PREREG_LOGO }, 'erpc': { 'system_tag': CampaignSourceTags.ErpChallenge.value, 'redirect_url': furl.furl(DOMAIN).add(path='erpc/').url, 'confirmation_email_template': mails.CONFIRM_EMAIL_ERPC, 'login_type': 'native', }, } # Institution Login newest_campaigns.update({ 'institution': { 'system_tag': 'institution_campaign', 'redirect_url': '', 'login_type': 'institution', }, }) # Proxy campaigns: Preprints, both OSF and branded ones preprint_providers = PreprintProvider.objects.all() for provider in preprint_providers: if provider._id == 'osf': template = 'osf' name = 'OSF' url_path = 'preprints/' external_url = None else: template = 'branded' name = provider.name url_path = 'preprints/{}'.format(provider._id) external_url = provider.domain campaign = '{}-preprints'.format(provider._id) system_tag = provider_source_tag(provider._id, 'preprint') newest_campaigns.update({ campaign: { 'system_tag': system_tag, 'redirect_url': furl.furl(DOMAIN).add(path=url_path).url, 'external_url': external_url, 'confirmation_email_template': mails.CONFIRM_EMAIL_PREPRINTS(template, name), 'login_type': 'proxy', 'provider': name, 'logo': provider._id if name != 'OSF' else settings.OSF_PREPRINTS_LOGO, } }) # Proxy campaigns: Registries, OSF only # TODO: refactor for futher branded registries when there is a model for registries providers newest_campaigns.update({ 'osf-registries': { 'system_tag': provider_source_tag('osf', 'registry'), 'redirect_url': furl.furl(DOMAIN).add(path='registries/').url, 'confirmation_email_template': mails.CONFIRM_EMAIL_REGISTRIES_OSF, 'login_type': 'proxy', 'provider': 'osf', 'logo': settings.OSF_REGISTRIES_LOGO } }) newest_campaigns.update({ 'osf-registered-reports': { 'system_tag': CampaignSourceTags.OsfRegisteredReports.value, 'redirect_url': furl.furl(DOMAIN).add(path='rr/').url, 'confirmation_email_template': mails.CONFIRM_EMAIL_REGISTRIES_OSF, 'login_type': 'proxy', 'provider': 'osf', 'logo': settings.OSF_REGISTRIES_LOGO } }) CAMPAIGNS = newest_campaigns CAMPAIGNS_LAST_REFRESHED = timezone.now() return CAMPAIGNS
def _forgot_password_post(mail_template, reset_route, institutional=False): """ View for user to submit forgot password form (standard or institutional). Validates submitted form and sends reset-password link via email if valid. If user has submitted another password reset request recently, declines to create a new one and asks the user to not submit again for awhile. Standard and institutional forgot-password requests behave similarly but use slightly different language and interfaces. When an institution is deactivated, the user should be given the opportunity to reclaim their account. CAS co-ops the forgot-password functionality to send a "set a new password" email link to the institutional user. The language of the email has been adjusted from the standard context, the response html the status message from the reset action is displayed as regular text, and the password form is not shown. HTTP Method: POST :return {} """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: email = form.email.data status_message = ('If there is an OSF account associated with {0}, an email with instructions on how to ' 'reset the OSF password has been sent to {0}. If you do not receive an email and believe ' 'you should have, please contact OSF Support. ').format(email) kind = 'success' # check if the user exists user_obj = get_user(email=email) if user_obj: # rate limit forgot_password_post if not throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE): status_message = 'You have recently requested to change your password. Please wait a few minutes ' \ 'before trying again.' kind = 'error' # TODO [OSF-6673]: Use the feature in [OSF-6998] for user to resend claim email. elif user_obj.is_active: # new random verification key (v2) user_obj.verification_key_v2 = generate_verification_key(verification_type='password') user_obj.email_last_sent = timezone.now() user_obj.save() reset_link = furl.urljoin( settings.DOMAIN, web_url_for( reset_route, uid=user_obj._id, token=user_obj.verification_key_v2['token'] ) ) mails.send_mail( to_addr=email, mail=mail_template, reset_link=reset_link, can_change_preferences=False, ) # institutional forgot password page displays the message as main text, not as an alert if institutional: # pass isError instead of kind to template to decouple python error flag from template's # css class return {'message': status_message, 'isError': (kind == 'error'), 'institutional': institutional} status.push_status_message(status_message, kind=kind, trust=False) return {}
def test_throttle_period_expired_no_timestamp(self): is_expired = throttle_period_expired(timestamp=None, throttle=30) assert_true(is_expired)
def test_throttle_period_expired_no_timestamp(self): is_expired = throttle_period_expired(timestamp=None, throttle=30) assert_true(is_expired)
def get_campaigns(): global CAMPAIGNS global CAMPAIGNS_LAST_REFRESHED if not CAMPAIGNS or (not mutex.locked() and throttle_period_expired(CAMPAIGNS_LAST_REFRESHED, CAMPAIGN_REFRESH_THRESHOLD)): with mutex: # Native campaigns: PREREG and ERPC newest_campaigns = { 'prereg': { 'system_tag': 'prereg_challenge_campaign', 'redirect_url': furl.furl(DOMAIN).add(path='prereg/').url, 'confirmation_email_template': mails.CONFIRM_EMAIL_PREREG, 'login_type': 'native', 'logo': settings.OSF_PREREG_LOGO }, 'erpc': { 'system_tag': 'erp_challenge_campaign', 'redirect_url': furl.furl(DOMAIN).add(path='erpc/').url, 'confirmation_email_template': mails.CONFIRM_EMAIL_ERPC, 'login_type': 'native', }, } # Institution Login newest_campaigns.update({ 'institution': { 'system_tag': 'institution_campaign', 'redirect_url': '', 'login_type': 'institution', }, }) # Proxy campaigns: Preprints, both OSF and branded ones preprint_providers = PreprintProvider.objects.all() for provider in preprint_providers: if provider._id == 'osf': template = 'osf' name = 'OSF' url_path = 'preprints/' external_url = None else: template = 'branded' name = provider.name url_path = 'preprints/{}'.format(provider._id) external_url = provider.domain campaign = '{}-preprints'.format(provider._id) system_tag = '{}_preprints'.format(provider._id) newest_campaigns.update({ campaign: { 'system_tag': system_tag, 'redirect_url': furl.furl(DOMAIN).add(path=url_path).url, 'external_url': external_url, 'confirmation_email_template': mails.CONFIRM_EMAIL_PREPRINTS(template, name), 'login_type': 'proxy', 'provider': name, 'logo': provider._id if name != 'OSF' else settings.OSF_PREPRINTS_LOGO, } }) # Proxy campaigns: Registries, OSF only # TODO: refactor for futher branded registries when there is a model for registries providers newest_campaigns.update({ 'osf-registries': { 'system_tag': 'osf_registries', 'redirect_url': furl.furl(DOMAIN).add(path='registries/').url, 'confirmation_email_template': mails.CONFIRM_EMAIL_REGISTRIES_OSF, 'login_type': 'proxy', 'provider': 'osf', 'logo': settings.OSF_REGISTRIES_LOGO } }) newest_campaigns.update({ 'osf-registered-reports': { 'system_tag': 'osf_registered_reports', 'redirect_url': furl.furl(DOMAIN).add(path='rr/').url, 'confirmation_email_template': mails.CONFIRM_EMAIL_REGISTRIES_OSF, 'login_type': 'proxy', 'provider': 'osf', 'logo': settings.OSF_REGISTRIES_LOGO } }) CAMPAIGNS = newest_campaigns CAMPAIGNS_LAST_REFRESHED = timezone.now() return CAMPAIGNS
def before_request(): # TODO: Fix circular import from framework.auth.core import get_user from framework.auth import cas from framework.utils import throttle_period_expired Session = apps.get_model('osf.Session') # Central Authentication Server Ticket Validation and Authentication ticket = request.args.get('ticket') if ticket: service_url = furl.furl(request.url) service_url.args.pop('ticket') # Attempt to authenticate wih CAS, and return a proper redirect response return cas.make_response_from_ticket(ticket=ticket, service_url=service_url.url) if request.authorization: user = get_user(email=request.authorization.username, password=request.authorization.password) # Create an empty session # TODO: Shoudn't need to create a session for Basic Auth user_session = Session() set_session(user_session) if user: user_addon = user.get_addon('twofactor') if user_addon and user_addon.is_confirmed: otp = request.headers.get('X-OSF-OTP') if otp is None or not user_addon.verify_code(otp): # Must specify two-factor authentication OTP code or invalid two-factor authentication OTP code. user_session.data[ 'auth_error_code'] = http_status.HTTP_401_UNAUTHORIZED return user_session.data['auth_user_username'] = user.username user_session.data['auth_user_fullname'] = user.fullname if user_session.data.get('auth_user_id', None) != user._primary_key: user_session.data['auth_user_id'] = user._primary_key user_session.save() else: # Invalid key: Not found in database user_session.data[ 'auth_error_code'] = http_status.HTTP_401_UNAUTHORIZED return cookie = request.cookies.get(settings.COOKIE_NAME) if cookie: try: session_id = itsdangerous.Signer( settings.SECRET_KEY).unsign(cookie) user_session = Session.load(session_id) or Session(_id=session_id) except itsdangerous.BadData: return if not throttle_period_expired(user_session.created, settings.OSF_SESSION_TIMEOUT): # Update date last login when making non-api requests if user_session.data.get( 'auth_user_id') and 'api' not in request.url: OSFUser = apps.get_model('osf.OSFUser') (OSFUser.objects.filter( guids___id__isnull=False, guids___id=user_session.data['auth_user_id']) # Throttle updates .filter( Q(date_last_login__isnull=True) | Q(date_last_login__lt=timezone.now() - dt.timedelta( seconds=settings.DATE_LAST_LOGIN_THROTTLE)))).update( date_last_login=timezone.now()) set_session(user_session) else: remove_session(user_session)