예제 #1
0
파일: views.py 프로젝트: baylee-d/osf.io
def auth_email_logout(token, user):
    """
    When a user is adding an email or merging an account, add the email to the user and log them out.
    """

    redirect_url = cas.get_logout_url(service_url=cas.get_login_url(service_url=web_url_for('index', _absolute=True)))
    try:
        unconfirmed_email = user.get_unconfirmed_email_for_token(token)
    except InvalidTokenError:
        raise HTTPError(http.BAD_REQUEST, data={
            'message_short': 'Bad token',
            'message_long': 'The provided token is invalid.'
        })
    except ExpiredTokenError:
        status.push_status_message('The private link you used is expired.')
        raise HTTPError(http.BAD_REQUEST, data={
            'message_short': 'Expired link',
            'message_long': 'The private link you used is expired.'
        })
    try:
        user_merge = User.find_one(Q('emails', 'eq', unconfirmed_email))
    except NoResultsFound:
        user_merge = False
    if user_merge:
        remove_sessions_for_user(user_merge)
    user.email_verifications[token]['confirmed'] = True
    user.save()
    remove_sessions_for_user(user)
    resp = redirect(redirect_url)
    resp.delete_cookie(settings.COOKIE_NAME, domain=settings.OSF_COOKIE_DOMAIN)
    return resp
예제 #2
0
def auth_email_logout(token, user):
    """
    When a user is adding an email or merging an account, add the email to the user and log them out.
    """

    redirect_url = cas.get_logout_url(service_url=cas.get_login_url(
        service_url=web_url_for('index', _absolute=True)))
    try:
        unconfirmed_email = user.get_unconfirmed_email_for_token(token)
    except InvalidTokenError:
        raise HTTPError(http.BAD_REQUEST,
                        data={
                            'message_short': 'Bad token',
                            'message_long': 'The provided token is invalid.'
                        })
    except ExpiredTokenError:
        status.push_status_message('The private link you used is expired.')
        raise HTTPError(http.BAD_REQUEST,
                        data={
                            'message_short': 'Expired link',
                            'message_long':
                            'The private link you used is expired.'
                        })
    try:
        user_merge = OSFUser.objects.get(emails__address=unconfirmed_email)
    except OSFUser.DoesNotExist:
        user_merge = False
    if user_merge:
        remove_sessions_for_user(user_merge)
    user.email_verifications[token]['confirmed'] = True
    user.save()
    remove_sessions_for_user(user)
    resp = redirect(redirect_url)
    resp.delete_cookie(settings.COOKIE_NAME, domain=settings.OSF_COOKIE_DOMAIN)
    return resp
예제 #3
0
파일: views.py 프로젝트: aaxelb/osf.io
    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)
예제 #4
0
    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)
예제 #5
0
    def test_remove_session_for_user(self):
        SessionFactory(user=self.user)

        # sanity check
        assert Session.find().count() == 1

        utils.remove_sessions_for_user(self.user)
        assert Session.find().count() == 0

        SessionFactory()
        SessionFactory(user=self.user)

        # sanity check
        assert Session.find().count() == 2

        utils.remove_sessions_for_user(self.user)
        assert Session.find().count() == 1
예제 #6
0
    def test_remove_session_for_user(self):
        SessionFactory(user=self.user)

        # sanity check
        assert Session.find().count() == 1

        utils.remove_sessions_for_user(self.user)
        assert Session.find().count() == 0

        SessionFactory()
        SessionFactory(user=self.user)

        # sanity check
        assert Session.find().count() == 2

        utils.remove_sessions_for_user(self.user)
        assert Session.find().count() == 1
예제 #7
0
    def test_remove_session_for_user(self):
        factories.SessionFactory(user=self.user)

        # sanity check
        assert_equal(1, Session.find().count())

        utils.remove_sessions_for_user(self.user)
        assert_equal(0, Session.find().count())

        factories.SessionFactory()
        factories.SessionFactory(user=self.user)

        # sanity check
        assert_equal(2, Session.find().count())

        utils.remove_sessions_for_user(self.user)
        assert_equal(1, Session.find().count())
예제 #8
0
    def set_password(self, raw_password, notify=True):
        """Set the password for this user to the hash of ``raw_password``.
        If this is a new user, we're done. If this is a password change,
        then email the user about the change and clear all the old sessions
        so that users will have to log in again with the new password.

        :param raw_password: the plaintext value of the new password
        :param notify: Only meant for unit tests to keep extra notifications from being sent
        :rtype: list
        :returns: Changed fields from the user save
        """
        had_existing_password = self.has_usable_password()
        if self.username == raw_password:
            raise ChangePasswordError(
                ['Password cannot be the same as your email address'])
        super(OSFUser, self).set_password(raw_password)
        if had_existing_password and notify:
            mails.send_mail(to_addr=self.username,
                            mail=mails.PASSWORD_RESET,
                            mimetype='plain',
                            user=self)
            remove_sessions_for_user(self)
예제 #9
0
파일: core.py 프로젝트: kwierman/osf.io
    def merge_user(self, user):
        """Merge a registered user into this account. This user will be
        a contributor on any project. if the registered user and this account
        are both contributors of the same project. Then it will remove the
        registered user and set this account to the highest permission of the two
        and set this account to be visible if either of the two are visible on
        the project.

        :param user: A User object to be merged.
        """
        # Fail if the other user has conflicts.
        if not user.can_be_merged:
            raise exceptions.MergeConflictError("Users cannot be merged")
        # Move over the other user's attributes
        # TODO: confirm
        for system_tag in user.system_tags:
            if system_tag not in self.system_tags:
                self.system_tags.append(system_tag)

        self.is_claimed = self.is_claimed or user.is_claimed
        self.is_invited = self.is_invited or user.is_invited

        # copy over profile only if this user has no profile info
        if user.jobs and not self.jobs:
            self.jobs = user.jobs

        if user.schools and not self.schools:
            self.schools = user.schools

        if user.social and not self.social:
            self.social = user.social

        unclaimed = user.unclaimed_records.copy()
        unclaimed.update(self.unclaimed_records)
        self.unclaimed_records = unclaimed
        # - unclaimed records should be connected to only one user
        user.unclaimed_records = {}

        security_messages = user.security_messages.copy()
        security_messages.update(self.security_messages)
        self.security_messages = security_messages

        for key, value in user.mailing_lists.iteritems():
            # subscribe to each list if either user was subscribed
            subscription = value or self.mailing_lists.get(key)
            signals.user_merged.send(self,
                                     list_name=key,
                                     subscription=subscription)

            # clear subscriptions for merged user
            signals.user_merged.send(user, list_name=key, subscription=False)

        for node_id, timestamp in user.comments_viewed_timestamp.iteritems():
            if not self.comments_viewed_timestamp.get(node_id):
                self.comments_viewed_timestamp[node_id] = timestamp
            elif timestamp > self.comments_viewed_timestamp[node_id]:
                self.comments_viewed_timestamp[node_id] = timestamp

        self.emails.extend(user.emails)
        user.emails = []

        for k, v in user.email_verifications.iteritems():
            email_to_confirm = v['email']
            if k not in self.email_verifications and email_to_confirm != user.username:
                self.email_verifications[k] = v
        user.email_verifications = {}

        # FOREIGN FIELDS
        for watched in user.watched:
            if watched not in self.watched:
                self.watched.append(watched)
        user.watched = []

        for account in user.external_accounts:
            if account not in self.external_accounts:
                self.external_accounts.append(account)
        user.external_accounts = []

        # - addons
        # Note: This must occur before the merged user is removed as a
        #       contributor on the nodes, as an event hook is otherwise fired
        #       which removes the credentials.
        for addon in user.get_addons():
            user_settings = self.get_or_add_addon(addon.config.short_name)
            user_settings.merge(addon)
            user_settings.save()

        # - projects where the user was a contributor
        for node in user.node__contributed:
            # Skip dashboard node
            if node.is_dashboard:
                continue
            # if both accounts are contributor of the same project
            if node.is_contributor(self) and node.is_contributor(user):
                if node.permissions[user._id] > node.permissions[self._id]:
                    permissions = node.permissions[user._id]
                else:
                    permissions = node.permissions[self._id]
                node.set_permissions(user=self, permissions=permissions)

                visible1 = self._id in node.visible_contributor_ids
                visible2 = user._id in node.visible_contributor_ids
                if visible1 != visible2:
                    node.set_visible(user=self,
                                     visible=True,
                                     log=True,
                                     auth=Auth(user=self))

            else:
                node.add_contributor(
                    contributor=self,
                    permissions=node.get_permissions(user),
                    visible=node.get_visible(user),
                    log=False,
                )

            try:
                node.remove_contributor(
                    contributor=user,
                    auth=Auth(user=self),
                    log=False,
                )
            except ValueError:
                logger.error('Contributor {0} not in list on node {1}'.format(
                    user._id, node._id))
            node.save()

        # - projects where the user was the creator
        for node in user.node__created:
            node.creator = self
            node.save()

        # finalize the merge

        remove_sessions_for_user(user)

        # - username is set to None so the resultant user can set it primary
        #   in the future.
        user.username = None
        user.password = None
        user.verification_key = None
        user.merged_by = self

        user.save()
예제 #10
0
파일: core.py 프로젝트: keyz182/osf.io
    def merge_user(self, user):
        """Merge a registered user into this account. This user will be
        a contributor on any project. if the registered user and this account
        are both contributors of the same project. Then it will remove the
        registered user and set this account to the highest permission of the two
        and set this account to be visible if either of the two are visible on
        the project.

        :param user: A User object to be merged.
        """
        # Fail if the other user has conflicts.
        if not user.can_be_merged:
            raise MergeConflictError("Users cannot be merged")
        # Move over the other user's attributes
        # TODO: confirm
        for system_tag in user.system_tags:
            if system_tag not in self.system_tags:
                self.system_tags.append(system_tag)

        self.is_claimed = self.is_claimed or user.is_claimed
        self.is_invited = self.is_invited or user.is_invited

        # copy over profile only if this user has no profile info
        if user.jobs and not self.jobs:
            self.jobs = user.jobs

        if user.schools and not self.schools:
            self.schools = user.schools

        if user.social and not self.social:
            self.social = user.social

        unclaimed = user.unclaimed_records.copy()
        unclaimed.update(self.unclaimed_records)
        self.unclaimed_records = unclaimed
        # - unclaimed records should be connected to only one user
        user.unclaimed_records = {}

        security_messages = user.security_messages.copy()
        security_messages.update(self.security_messages)
        self.security_messages = security_messages

        for key, value in user.mailchimp_mailing_lists.iteritems():
            # subscribe to each list if either user was subscribed
            subscription = value or self.mailchimp_mailing_lists.get(key)
            signals.user_merged.send(self, list_name=key, subscription=subscription)

            # clear subscriptions for merged user
            signals.user_merged.send(user, list_name=key, subscription=False)

        for node_id, timestamp in user.comments_viewed_timestamp.iteritems():
            if not self.comments_viewed_timestamp.get(node_id):
                self.comments_viewed_timestamp[node_id] = timestamp
            elif timestamp > self.comments_viewed_timestamp[node_id]:
                self.comments_viewed_timestamp[node_id] = timestamp

        self.emails.extend(user.emails)
        user.emails = []

        for k, v in user.email_verifications.iteritems():
            email_to_confirm = v['email']
            if k not in self.email_verifications and email_to_confirm != user.username:
                self.email_verifications[k] = v
        user.email_verifications = {}

        # FOREIGN FIELDS
        for watched in user.watched:
            if watched not in self.watched:
                self.watched.append(watched)
        user.watched = []

        for account in user.external_accounts:
            if account not in self.external_accounts:
                self.external_accounts.append(account)
        user.external_accounts = []

        # - addons
        # Note: This must occur before the merged user is removed as a
        #       contributor on the nodes, as an event hook is otherwise fired
        #       which removes the credentials.
        for addon in user.get_addons():
            user_settings = self.get_or_add_addon(addon.config.short_name)
            user_settings.merge(addon)
            user_settings.save()

        # - projects where the user was a contributor
        for node in user.node__contributed:
            # Skip dashboard node
            if node.is_dashboard:
                continue
            # if both accounts are contributor of the same project
            if node.is_contributor(self) and node.is_contributor(user):
                if node.permissions[user._id] > node.permissions[self._id]:
                    permissions = node.permissions[user._id]
                else:
                    permissions = node.permissions[self._id]
                node.set_permissions(user=self, permissions=permissions)

                visible1 = self._id in node.visible_contributor_ids
                visible2 = user._id in node.visible_contributor_ids
                if visible1 != visible2:
                    node.set_visible(user=self, visible=True, log=True, auth=Auth(user=self))

            else:
                node.add_contributor(
                    contributor=self,
                    permissions=node.get_permissions(user),
                    visible=node.get_visible(user),
                    log=False,
                )

            try:
                node.remove_contributor(
                    contributor=user,
                    auth=Auth(user=self),
                    log=False,
                )
            except ValueError:
                logger.error('Contributor {0} not in list on node {1}'.format(
                    user._id, node._id
                ))
            node.save()

        # - projects where the user was the creator
        for node in user.node__created:
            node.creator = self
            node.save()

        # finalize the merge

        remove_sessions_for_user(user)

        # - username is set to None so the resultant user can set it primary
        #   in the future.
        user.username = None
        user.password = None
        user.verification_key = None
        user.osf_mailing_lists = {}
        user.merged_by = self

        user.save()
예제 #11
0
    def merge_user(self, user):
        """Merge a registered user into this account. This user will be
        a contributor on any project. if the registered user and this account
        are both contributors of the same project. Then it will remove the
        registered user and set this account to the highest permission of the two
        and set this account to be visible if either of the two are visible on
        the project.

        :param user: A User object to be merged.
        """
        # Fail if the other user has conflicts.
        if not user.can_be_merged:
            raise MergeConflictError('Users cannot be merged')
        # Move over the other user's attributes
        # TODO: confirm
        for system_tag in user.system_tags.all():
            self.add_system_tag(system_tag)

        self.is_claimed = self.is_claimed or user.is_claimed
        self.is_invited = self.is_invited or user.is_invited

        # copy over profile only if this user has no profile info
        if user.jobs and not self.jobs:
            self.jobs = user.jobs

        if user.schools and not self.schools:
            self.schools = user.schools

        if user.social and not self.social:
            self.social = user.social

        unclaimed = user.unclaimed_records.copy()
        unclaimed.update(self.unclaimed_records)
        self.unclaimed_records = unclaimed
        # - unclaimed records should be connected to only one user
        user.unclaimed_records = {}

        security_messages = user.security_messages.copy()
        security_messages.update(self.security_messages)
        self.security_messages = security_messages

        notifications_configured = user.notifications_configured.copy()
        notifications_configured.update(self.notifications_configured)
        self.notifications_configured = notifications_configured
        if not website_settings.RUNNING_MIGRATION:
            for key, value in user.mailchimp_mailing_lists.iteritems():
                # subscribe to each list if either user was subscribed
                subscription = value or self.mailchimp_mailing_lists.get(key)
                signals.user_merged.send(self,
                                         list_name=key,
                                         subscription=subscription)

                # clear subscriptions for merged user
                signals.user_merged.send(user,
                                         list_name=key,
                                         subscription=False,
                                         send_goodbye=False)

        for target_id, timestamp in user.comments_viewed_timestamp.iteritems():
            if not self.comments_viewed_timestamp.get(target_id):
                self.comments_viewed_timestamp[target_id] = timestamp
            elif timestamp > self.comments_viewed_timestamp[target_id]:
                self.comments_viewed_timestamp[target_id] = timestamp

        self.emails.extend(user.emails)
        user.emails = []

        for k, v in user.email_verifications.iteritems():
            email_to_confirm = v['email']
            if k not in self.email_verifications and email_to_confirm != user.username:
                self.email_verifications[k] = v
        user.email_verifications = {}

        self.affiliated_institutions.add(
            *user.affiliated_institutions.values_list('pk', flat=True))

        for service in user.external_identity:
            for service_id in user.external_identity[service].iterkeys():
                if not (service_id in self.external_identity.get(service, '')
                        and self.external_identity[service][service_id]
                        == 'VERIFIED'):
                    # Prevent 'CREATE', merging user has already been created.
                    external = user.external_identity[service][service_id]
                    status = 'VERIFIED' if external == 'VERIFIED' else 'LINK'
                    if self.external_identity.get(service):
                        self.external_identity[service].update(
                            {service_id: status})
                    else:
                        self.external_identity[service] = {service_id: status}
        user.external_identity = {}

        # FOREIGN FIELDS
        WatchConfig.objects.filter(user=user).update(user=self)

        self.external_accounts.add(
            *user.external_accounts.values_list('pk', flat=True))

        # - addons
        # Note: This must occur before the merged user is removed as a
        #       contributor on the nodes, as an event hook is otherwise fired
        #       which removes the credentials.
        for addon in user.get_addons():
            user_settings = self.get_or_add_addon(addon.config.short_name)
            user_settings.merge(addon)
            user_settings.save()

        # - projects where the user was a contributor
        for node in user.contributed:
            # Skip bookmark collection node
            if node.is_bookmark_collection:
                continue
            # if both accounts are contributor of the same project
            if node.is_contributor(self) and node.is_contributor(user):
                user_permissions = node.get_permissions(user)
                self_permissions = node.get_permissions(self)
                permissions = max([user_permissions, self_permissions])
                node.set_permissions(user=self, permissions=permissions)

                visible1 = self._id in node.visible_contributor_ids
                visible2 = user._id in node.visible_contributor_ids
                if visible1 != visible2:
                    node.set_visible(user=self,
                                     visible=True,
                                     log=True,
                                     auth=Auth(user=self))

                node.contributor_set.filter(user=user).delete()
            else:
                node.contributor_set.filter(user=user).update(user=self)

            node.save()

        # - projects where the user was the creator
        user.created.update(creator=self)

        # - file that the user has checked_out, import done here to prevent import error
        # TODO: Uncoment when StoredFileNode is implemented
        # from website.files.models.base import FileNode
        # for file_node in FileNode.files_checked_out(user=user):
        #     file_node.checkout = self
        #     file_node.save()

        # finalize the merge

        remove_sessions_for_user(user)

        # - username is set to the GUID so the merging user can set it primary
        #   in the future (note: it cannot be set to None due to non-null constraint)
        user.set_unusable_username()
        user.set_unusable_password()
        user.verification_key = None
        user.osf_mailing_lists = {}
        user.merged_by = self

        user.save()