示例#1
0
    def save(self, commit=True):
        if self.cleaned_data.get('new_password'):
            self.user.set_password(self.cleaned_data['new_password'])
            self.user.refresh_session_nonce(self.request)

            capture_security_activity(
                account=self.user,
                type='password-changed',
                actor=self.request.user,
                ip_address=self.request.META['REMOTE_ADDR'],
                send_email=True,
            )

        self.user.name = self.cleaned_data['name']

        if self.cleaned_data['email'] != self.user.email:
            new_username = self.user.email == self.user.username
        else:
            new_username = False

        self.user.email = self.cleaned_data['email']

        if self.cleaned_data.get('username'):
            self.user.username = self.cleaned_data['username']
        elif new_username and not User.objects.filter(username__iexact=self.user.email).exists():
            self.user.username = self.user.email

        if commit:
            self.user.save()

        return self.user
    def put(self, request, user):
        # pass some context to serializer otherwise when we create a new serializer instance,
        # user.password gets set to new plaintext password from request and
        # `user.has_usable_password` becomes False
        serializer = UserPasswordSerializer(user,
                                            data=request.DATA,
                                            context={
                                                'is_managed':
                                                user.is_managed,
                                                'has_usable_password':
                                                user.has_usable_password(),
                                            })

        if not serializer.is_valid():
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

        result = serializer.object

        user.set_password(result.passwordNew)
        user.refresh_session_nonce(request._request)
        user.clear_lost_passwords()

        user = serializer.save()

        capture_security_activity(
            account=user,
            type='password-changed',
            actor=request.user,
            ip_address=request.META['REMOTE_ADDR'],
            send_email=True,
        )
        return Response(status=status.HTTP_204_NO_CONTENT)
示例#3
0
    def put(self, request, user, auth_id):
        """
        Modify authenticator interface
        ``````````````````````````````

        Currently, only supports regenerating recovery codes

        :pparam string user_id: user id or 'me' for current user
        :pparam int auth_id: authenticator model id

        :auth required:
        """

        try:
            authenticator = Authenticator.objects.get(user=user, id=auth_id)
        except (ValueError, Authenticator.DoesNotExist):
            return Response(status=status.HTTP_404_NOT_FOUND)

        interface = authenticator.interface

        if interface.interface_id == "recovery":
            interface.regenerate_codes()

            capture_security_activity(
                account=user,
                type="recovery-codes-regenerated",
                actor=request.user,
                ip_address=request.META["REMOTE_ADDR"],
                context={"authenticator": authenticator},
                send_email=True,
            )
        return Response(serialize(interface))
示例#4
0
    def configure(self, request, interface):
        # Try to remove a key handle.  If this returns `False` it means we
        # are about to remove the last key handle.  In that case just
        # bubble through to the configure page which will pick up the
        # 'remove' in the form and bring up the remove screen for the
        # entire authentication method.
        key_handle = request.POST.get('key_handle')
        if key_handle:
            device_name = interface.get_device_name(key_handle)
            if 'remove' in request.POST and interface.remove_u2f_device(
                    key_handle):
                interface.authenticator.save()

                capture_security_activity(
                    account=request.user,
                    type='mfa-removed',
                    actor=request.user,
                    ip_address=request.META['REMOTE_ADDR'],
                    context={
                        'authenticator': interface.authenticator,
                        'device_name': device_name
                    },
                    send_email=True,
                )
                return HttpResponseRedirect(request.path)

        return TwoFactorSettingsView.configure(self, request, interface)
示例#5
0
    def put(self, request: Request, user) -> Response:
        # pass some context to serializer otherwise when we create a new serializer instance,
        # user.password gets set to new plaintext password from request and
        # `user.has_usable_password` becomes False
        serializer = UserPasswordSerializer(data=request.data,
                                            context={"user": user})

        if not serializer.is_valid():
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

        result = serializer.validated_data

        user.set_password(result["passwordNew"])
        user.refresh_session_nonce(request._request)
        user.clear_lost_passwords()
        user.save()

        capture_security_activity(
            account=user,
            type="password-changed",
            actor=request.user,
            ip_address=request.META["REMOTE_ADDR"],
            send_email=True,
        )
        return Response(status=status.HTTP_204_NO_CONTENT)
示例#6
0
文件: accounts.py 项目: yaoqi/sentry
    def save(self, commit=True):
        if self.cleaned_data.get('new_password'):
            self.user.set_password(self.cleaned_data['new_password'])
            self.user.refresh_session_nonce(self.request)

            capture_security_activity(
                account=self.user,
                type='password-changed',
                actor=self.request.user,
                ip_address=self.request.META['REMOTE_ADDR'],
                send_email=True,
            )

        self.user.name = self.cleaned_data['name']

        if self.cleaned_data['email'] != self.user.email:
            new_username = self.user.email == self.user.username
        else:
            new_username = False

        self.user.email = self.cleaned_data['email']

        if self.cleaned_data.get('username'):
            self.user.username = self.cleaned_data['username']
        elif new_username and not User.objects.filter(username__iexact=self.user.email).exists():
            self.user.username = self.user.email

        if commit:
            self.user.save()

        return self.user
    def enroll(self, request, interface, insecure=False):
        next = request.path
        # Only enroll if it's either not an insecure enrollment or we are
        # enrolling a backup interface when we already had a primary one.
        if not insecure \
           or (interface.is_backup_interface and
               Authenticator.objects.user_has_2fa(request.user)):
            try:
                interface.enroll(request.user)
            except Authenticator.AlreadyEnrolled:
                # This can happen in some cases when races occur.  We have
                # seen this when people press the submit button twice.  In
                # that case just go to the overview page of 2fa
                next = reverse('sentry-account-settings-2fa')
            else:
                capture_security_activity(
                    account=request.user,
                    type='mfa-added',
                    actor=request.user,
                    ip_address=request.META['REMOTE_ADDR'],
                    context={
                        'authenticator': interface.authenticator,
                    },
                    send_email=False,
                )

                request.user.refresh_session_nonce(self.request)
                request.user.save()
                if Authenticator.objects.auto_add_recovery_codes(request.user):
                    next = reverse('sentry-account-settings-2fa-recovery')
        return HttpResponseRedirect(next)
示例#8
0
    def put(self, request, user):
        # pass some context to serializer otherwise when we create a new serializer instance,
        # user.password gets set to new plaintext password from request and
        # `user.has_usable_password` becomes False
        serializer = UserPasswordSerializer(user, data=request.DATA, context={
            'is_managed': user.is_managed,
            'has_usable_password': user.has_usable_password(),
        })

        if not serializer.is_valid():
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        result = serializer.object

        user.set_password(result.passwordNew)
        user.refresh_session_nonce(request._request)
        user.clear_lost_passwords()

        user = serializer.save()

        capture_security_activity(
            account=user,
            type='password-changed',
            actor=request.user,
            ip_address=request.META['REMOTE_ADDR'],
            send_email=True,
        )
        return Response(status=status.HTTP_204_NO_CONTENT)
示例#9
0
    def enroll(self, request, interface, insecure=False):
        next = request.path
        # Only enroll if it's either not an insecure enrollment or we are
        # enrolling a backup interface when we already had a primary one.
        if not insecure \
           or (interface.is_backup_interface and
               Authenticator.objects.user_has_2fa(request.user)):
            try:
                interface.enroll(request.user)
            except Authenticator.AlreadyEnrolled:
                # This can happen in some cases when races occur.  We have
                # seen this when people press the submit button twice.  In
                # that case just go to the overview page of 2fa
                next = reverse('sentry-account-settings-2fa')
            else:
                capture_security_activity(
                    account=request.user,
                    type='mfa-added',
                    actor=request.user,
                    ip_address=request.META['REMOTE_ADDR'],
                    context={
                        'authenticator': interface.authenticator,
                    },
                    send_email=True,
                )

                request.user.refresh_session_nonce(self.request)
                request.user.save()
                if Authenticator.objects.auto_add_recovery_codes(request.user):
                    next = reverse('sentry-account-settings-2fa-recovery')
        return HttpResponseRedirect(next)
    def delete(self, request, user, auth_id):
        try:
            authenticator = Authenticator.objects.get(
                user=user,
                id=auth_id,
            )
        except Authenticator.DoesNotExist:
            return Response(status=404)

        with transaction.atomic():
            authenticator.delete()

            # if we delete an actual authenticator and all that
            # remainds are backup interfaces, then we kill them in the
            # process.
            if not authenticator.interface.is_backup_interface:
                interfaces = Authenticator.objects.all_interfaces_for_user(user)
                backup_interfaces = [
                    x for x in interfaces
                    if x.is_backup_interface
                ]
                if len(backup_interfaces) == len(interfaces):
                    for iface in backup_interfaces:
                        iface.authenticator.delete()

                    # wait to generate entries until all pending writes
                    # have been sent to db
                    for iface in backup_interfaces:
                        capture_security_activity(
                            account=request.user,
                            type='mfa-removed',
                            actor=request.user,
                            ip_address=request.META['REMOTE_ADDR'],
                            context={
                                'authenticator': iface.authenticator,
                            },
                            send_email=False,
                        )

            capture_security_activity(
                account=user,
                type='mfa-removed',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': authenticator,
                },
                send_email=not authenticator.interface.is_backup_interface,
            )

        return Response(status=204)
    def delete(self, request, user, auth_id):
        try:
            authenticator = Authenticator.objects.get(
                user=user,
                id=auth_id,
            )
        except Authenticator.DoesNotExist:
            return Response(status=404)

        with transaction.atomic():
            authenticator.delete()

            # if we delete an actual authenticator and all that
            # remainds are backup interfaces, then we kill them in the
            # process.
            if not authenticator.interface.is_backup_interface:
                interfaces = Authenticator.objects.all_interfaces_for_user(
                    user)
                backup_interfaces = [
                    x for x in interfaces if x.is_backup_interface
                ]
                if len(backup_interfaces) == len(interfaces):
                    for iface in backup_interfaces:
                        iface.authenticator.delete()

                    # wait to generate entries until all pending writes
                    # have been sent to db
                    for iface in backup_interfaces:
                        capture_security_activity(
                            account=request.user,
                            type='mfa-removed',
                            actor=request.user,
                            ip_address=request.META['REMOTE_ADDR'],
                            context={
                                'authenticator': iface.authenticator,
                            },
                            send_email=False,
                        )

            capture_security_activity(
                account=user,
                type='mfa-removed',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': authenticator,
                },
                send_email=not authenticator.interface.is_backup_interface,
            )

        return Response(status=204)
示例#12
0
def recover_confirm(request, user_id, hash):
    try:
        password_hash = LostPasswordHash.objects.get(user=user_id, hash=hash)
        if not password_hash.is_valid():
            password_hash.delete()
            raise LostPasswordHash.DoesNotExist
        user = password_hash.user

    except LostPasswordHash.DoesNotExist:
        context = {}
        tpl = 'sentry/account/recover/failure.html'

    else:
        tpl = 'sentry/account/recover/confirm.html'

        if request.method == 'POST':
            form = ChangePasswordRecoverForm(request.POST)
            if form.is_valid():
                with transaction.atomic():
                    user.set_password(form.cleaned_data['password'])
                    user.refresh_session_nonce(request)
                    user.save()

                    # Ugly way of doing this, but Django requires the backend be set
                    user = authenticate(
                        username=user.username,
                        password=form.cleaned_data['password'],
                    )

                    login_user(request, user)

                    password_hash.delete()

                    capture_security_activity(
                        account=user,
                        type='password-changed',
                        actor=request.user,
                        ip_address=request.META['REMOTE_ADDR'],
                        send_email=True,
                    )

                return login_redirect(request)
        else:
            form = ChangePasswordRecoverForm()

        context = {
            'form': form,
        }

    return render_to_response(tpl, context, request)
示例#13
0
def recover_confirm(request, user_id, hash):
    try:
        password_hash = LostPasswordHash.objects.get(user=user_id, hash=hash)
        if not password_hash.is_valid():
            password_hash.delete()
            raise LostPasswordHash.DoesNotExist
        user = password_hash.user

    except LostPasswordHash.DoesNotExist:
        context = {}
        tpl = 'sentry/account/recover/failure.html'

    else:
        tpl = 'sentry/account/recover/confirm.html'

        if request.method == 'POST':
            form = ChangePasswordRecoverForm(request.POST)
            if form.is_valid():
                with transaction.atomic():
                    user.set_password(form.cleaned_data['password'])
                    user.refresh_session_nonce(request)
                    user.save()

                    # Ugly way of doing this, but Django requires the backend be set
                    user = authenticate(
                        username=user.username,
                        password=form.cleaned_data['password'],
                    )

                    login_user(request, user)

                    password_hash.delete()

                    capture_security_activity(
                        account=user,
                        type='password-changed',
                        actor=request.user,
                        ip_address=request.META['REMOTE_ADDR'],
                        send_email=True,
                    )

                return login_redirect(request)
        else:
            form = ChangePasswordRecoverForm()

        context = {
            'form': form,
        }

    return render_to_response(tpl, context, request)
示例#14
0
def recover_confirm(request, user_id, hash, mode='recover'):
    try:
        password_hash = LostPasswordHash.objects.get(user=user_id, hash=hash)
        if not password_hash.is_valid():
            password_hash.delete()
            raise LostPasswordHash.DoesNotExist
        user = password_hash.user

    except LostPasswordHash.DoesNotExist:
        tpl = get_template('failure', mode)
        return render_to_response(tpl, {}, request)

    if request.method == 'POST':
        form = ChangePasswordRecoverForm(request.POST)
        if form.is_valid():
            with transaction.atomic():
                user.set_password(form.cleaned_data['password'])
                user.refresh_session_nonce(request)
                user.save()

                # Ugly way of doing this, but Django requires the backend be set
                user = authenticate(
                    username=user.username,
                    password=form.cleaned_data['password'],
                )

                # Only log the user in if there is no two-factor on the
                # account.
                if not Authenticator.objects.user_has_2fa(user):
                    login_user(request, user)

                password_hash.delete()

                capture_security_activity(
                    account=user,
                    type='password-changed',
                    actor=request.user,
                    ip_address=request.META['REMOTE_ADDR'],
                    send_email=True,
                )

            return login_redirect(request)
    else:
        form = ChangePasswordRecoverForm()

    tpl = get_template('confirm', mode)
    context = {'form': form}

    return render_to_response(tpl, context, request)
    def _regenerate_recovery_code(self, authenticator, request, user):
        interface = authenticator.interface

        if interface.interface_id == "recovery":
            interface.regenerate_codes()

            capture_security_activity(
                account=user,
                type="recovery-codes-regenerated",
                actor=request.user,
                ip_address=request.META["REMOTE_ADDR"],
                context={"authenticator": authenticator},
                send_email=True,
            )
        return Response(serialize(interface))
示例#16
0
 def configure(self, request, interface):
     if 'regenerate' in request.POST:
         interface.regenerate_codes()
         capture_security_activity(
             account=request.user,
             type='recovery-codes-regenerated',
             actor=request.user,
             ip_address=request.META['REMOTE_ADDR'],
             context={
                 'authenticator': interface.authenticator,
             },
             send_email=True,
         )
         return HttpResponseRedirect(request.path)
     return TwoFactorSettingsView.configure(self, request, interface)
示例#17
0
def recover_confirm(request, user_id, hash, mode="recover"):
    try:
        password_hash = LostPasswordHash.objects.get(user=user_id, hash=hash)
        if not password_hash.is_valid():
            password_hash.delete()
            raise LostPasswordHash.DoesNotExist
        user = password_hash.user

    except LostPasswordHash.DoesNotExist:
        return render_to_response(
            u"sentry/account/{}/{}.html".format(mode, "failure"), {}, request)

    if request.method == "POST":
        form = ChangePasswordRecoverForm(request.POST)
        if form.is_valid():
            with transaction.atomic():
                user.set_password(form.cleaned_data["password"])
                user.refresh_session_nonce(request)
                user.save()

                # Ugly way of doing this, but Django requires the backend be set
                user = authenticate(username=user.username,
                                    password=form.cleaned_data["password"])

                # Only log the user in if there is no two-factor on the
                # account.
                if not Authenticator.objects.user_has_2fa(user):
                    login_user(request, user)

                password_hash.delete()

                capture_security_activity(
                    account=user,
                    type="password-changed",
                    actor=request.user,
                    ip_address=request.META["REMOTE_ADDR"],
                    send_email=True,
                )

            return login_redirect(request)
    else:
        form = ChangePasswordRecoverForm()

    return render_to_response(
        u"sentry/account/{}/{}.html".format(mode, "confirm"), {"form": form},
        request)
示例#18
0
    def delete_authenticator(self, request, interface):
        if interface.authenticator is None:
            return

        with transaction.atomic():
            user = interface.authenticator.user

            capture_security_activity(
                account=request.user,
                type='mfa-removed',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': interface.authenticator,
                },
                send_email=True,
            )

            interface.authenticator.delete()

            # If this was an authenticator that was a backup interface we just
            # deleted, then nothing happens.
            if interface.is_backup_interface:
                return

            # If however if we delete an actual authenticator and all that
            # remainds are backup interfaces, then we kill them in the
            # process.
            interfaces = Authenticator.objects.all_interfaces_for_user(user)
            backup_interfaces = [
                x for x in interfaces if x.is_backup_interface
            ]
            if len(backup_interfaces) == len(interfaces):
                for iface in backup_interfaces:
                    iface.authenticator.delete()

                    capture_security_activity(
                        account=request.user,
                        type='mfa-removed',
                        actor=request.user,
                        ip_address=request.META['REMOTE_ADDR'],
                        context={
                            'authenticator': iface.authenticator,
                        },
                        send_email=False,
                    )
示例#19
0
    def delete_authenticator(self, request, interface):
        if interface.authenticator is None:
            return

        with transaction.atomic():
            user = interface.authenticator.user

            capture_security_activity(
                account=request.user,
                type='mfa-removed',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': interface.authenticator,
                },
                send_email=True,
            )

            interface.authenticator.delete()

            # If this was an authenticator that was a backup interface we just
            # deleted, then nothing happens.
            if interface.is_backup_interface:
                return

            # If however if we delete an actual authenticator and all that
            # remainds are backup interfaces, then we kill them in the
            # process.
            interfaces = Authenticator.objects.all_interfaces_for_user(user)
            backup_interfaces = [x for x in interfaces if x.is_backup_interface]
            if len(backup_interfaces) == len(interfaces):
                for iface in backup_interfaces:
                    iface.authenticator.delete()

                    capture_security_activity(
                        account=request.user,
                        type='mfa-removed',
                        actor=request.user,
                        ip_address=request.META['REMOTE_ADDR'],
                        context={
                            'authenticator': iface.authenticator,
                        },
                        send_email=False,
                    )
示例#20
0
    def post(self, request: Request) -> Response:
        serializer = ApiTokenSerializer(data=request.data)

        if serializer.is_valid():
            result = serializer.validated_data

            token = ApiToken.objects.create(
                user=request.user, scope_list=result["scopes"], refresh_token=None, expires_at=None
            )

            capture_security_activity(
                account=request.user,
                type="api-token-generated",
                actor=request.user,
                ip_address=request.META["REMOTE_ADDR"],
                context={},
                send_email=True,
            )

            return Response(serialize(token, request.user), status=201)
        return Response(serializer.errors, status=400)
示例#21
0
    def post(self, request):
        serializer = ApiTokenSerializer(data=request.DATA)

        if serializer.is_valid():
            result = serializer.object

            token = ApiToken.objects.create(
                user=request.user,
                scope_list=result['scopes'],
                refresh_token=None,
                expires_at=None,
            )

            capture_security_activity(account=request.user,
                                      type='api-token-generated',
                                      actor=request.user,
                                      ip_address=request.META['REMOTE_ADDR'],
                                      context={},
                                      send_email=True)

            return Response(serialize(token, request.user), status=201)
        return Response(serializer.errors, status=400)
    def put(self, request, user, auth_id):
        """
        Modify authenticator interface
        ``````````````````````````````

        Currently, only supports regenerating recovery codes

        :pparam string user_id: user id or 'me' for current user
        :pparam int auth_id: authenticator model id

        :auth required:
        """

        try:
            authenticator = Authenticator.objects.get(
                user=user,
                id=auth_id,
            )
        except (ValueError, Authenticator.DoesNotExist):
            return Response(status=status.HTTP_404_NOT_FOUND)

        interface = authenticator.interface

        if interface.interface_id == 'recovery':
            interface.regenerate_codes()

            capture_security_activity(
                account=user,
                type='recovery-codes-regenerated',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': authenticator,
                },
                send_email=True
            )
        return Response(serialize(interface))
示例#23
0
    def post(self, request):
        serializer = ApiTokenSerializer(data=request.DATA)

        if serializer.is_valid():
            result = serializer.object

            token = ApiToken.objects.create(
                user=request.user,
                scope_list=result['scopes'],
                refresh_token=None,
                expires_at=None,
            )

            capture_security_activity(
                account=request.user,
                type='api-token-generated',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={},
                send_email=True
            )

            return Response(serialize(token, request.user), status=201)
        return Response(serializer.errors, status=400)
    def delete(self, request, user, auth_id):
        """
        Remove authenticator
        ````````````````````

        :pparam string user_id: user id or 'me' for current user
        :pparam string auth_id: authenticator model id

        :auth required:
        """

        try:
            authenticator = Authenticator.objects.get(
                user=user,
                id=auth_id,
            )
        except (ValueError, Authenticator.DoesNotExist):
            return Response(status=status.HTTP_404_NOT_FOUND)

        with transaction.atomic():
            authenticator.delete()

            # if we delete an actual authenticator and all that
            # remainds are backup interfaces, then we kill them in the
            # process.
            if not authenticator.interface.is_backup_interface:
                interfaces = Authenticator.objects.all_interfaces_for_user(
                    user)
                backup_interfaces = [
                    x for x in interfaces if x.is_backup_interface
                ]
                if len(backup_interfaces) == len(interfaces):
                    for iface in backup_interfaces:
                        iface.authenticator.delete()

                    # wait to generate entries until all pending writes
                    # have been sent to db
                    for iface in backup_interfaces:
                        capture_security_activity(
                            account=request.user,
                            type='mfa-removed',
                            actor=request.user,
                            ip_address=request.META['REMOTE_ADDR'],
                            context={
                                'authenticator': iface.authenticator,
                            },
                            send_email=False,
                        )

            capture_security_activity(
                account=user,
                type='mfa-removed',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': authenticator,
                },
                send_email=not authenticator.interface.is_backup_interface,
            )

        return Response(status=status.HTTP_204_NO_CONTENT)
示例#25
0
    def delete(self, request, user, auth_id, interface_device_id=None):
        """
        Remove authenticator
        ````````````````````

        :pparam string user_id: user id or 'me' for current user
        :pparam string auth_id: authenticator model id
        :pparam string interface_device_id: some interfaces (u2f) allow multiple devices

        :auth required:
        """

        try:
            authenticator = Authenticator.objects.get(
                user=user,
                id=auth_id,
            )
        except (ValueError, Authenticator.DoesNotExist):
            return Response(status=status.HTTP_404_NOT_FOUND)

        interface = authenticator.interface

        # Remove a single device and not entire authentication method
        if interface.interface_id == 'u2f' and interface_device_id is not None:
            device_name = interface.get_device_name(interface_device_id)
            # Can't remove if this is the last device, will return False if so
            if not interface.remove_u2f_device(interface_device_id):
                return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
            interface.authenticator.save()

            capture_security_activity(account=user,
                                      type='mfa-removed',
                                      actor=request.user,
                                      ip_address=request.META['REMOTE_ADDR'],
                                      context={
                                          'authenticator': authenticator,
                                          'device_name': device_name
                                      },
                                      send_email=True)
            return Response(status=status.HTTP_204_NO_CONTENT)

        with transaction.atomic():
            authenticator.delete()

            # if we delete an actual authenticator and all that
            # remainds are backup interfaces, then we kill them in the
            # process.
            if not interface.is_backup_interface:
                interfaces = Authenticator.objects.all_interfaces_for_user(
                    user)
                backup_interfaces = [
                    x for x in interfaces if x.is_backup_interface
                ]
                if len(backup_interfaces) == len(interfaces):
                    for iface in backup_interfaces:
                        iface.authenticator.delete()

                    # wait to generate entries until all pending writes
                    # have been sent to db
                    for iface in backup_interfaces:
                        capture_security_activity(
                            account=request.user,
                            type='mfa-removed',
                            actor=request.user,
                            ip_address=request.META['REMOTE_ADDR'],
                            context={
                                'authenticator': iface.authenticator,
                            },
                            send_email=False,
                        )

            capture_security_activity(
                account=user,
                type='mfa-removed',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': authenticator,
                },
                send_email=not interface.is_backup_interface,
            )

        return Response(status=status.HTTP_204_NO_CONTENT)
示例#26
0
    def post(self, request, user, interface_id):
        """
        Enroll in authenticator interface
        `````````````````````````````````

        :pparam string user_id: user id or "me" for current user
        :pparam string interface_id: interface id

        :auth: required
        """

        # Using `request.user` here because superuser should not be able to set a user's 2fa

        # start activation
        serializer_cls = serializer_map.get(interface_id, None)

        if serializer_cls is None:
            return Response(status=status.HTTP_404_NOT_FOUND)

        serializer = serializer_cls(data=request.DATA)

        if not serializer.is_valid():
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        interface = Authenticator.objects.get_interface(request.user, interface_id)

        # Not all interfaces allow multi enrollment
        #
        # This is probably un-needed because we catch
        # `Authenticator.AlreadyEnrolled` when attempting to enroll
        if interface.is_enrolled and not interface.allow_multi_enrollment:
            return Response(ALREADY_ENROLLED_ERR, status=status.HTTP_400_BAD_REQUEST)

        try:
            interface.secret = request.DATA['secret']
        except KeyError:
            pass

        # Need to update interface with phone number before validating OTP
        if 'phone' in request.DATA:
            interface.phone_number = serializer.data['phone']

            # Disregarding value of 'otp', if no OTP was provided,
            # send text message to phone number with OTP
            if 'otp' not in request.DATA:
                if interface.send_text(for_enrollment=True, request=request._request):
                    return Response(status=status.HTTP_204_NO_CONTENT)
                else:
                    # Error sending text message
                    return Response(SEND_SMS_ERR, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        # Attempt to validate OTP
        if 'otp' in request.DATA and not interface.validate_otp(serializer.data['otp']):
            return Response(INVALID_OTP_ERR, status=status.HTTP_400_BAD_REQUEST)

        # Try u2f enrollment
        if interface_id == 'u2f':
            # What happens when this fails?
            interface.try_enroll(
                serializer.data['challenge'],
                serializer.data['response'],
                serializer.data['deviceName']
            )

        try:
            interface.enroll(request.user)
        except Authenticator.AlreadyEnrolled:
            return Response(ALREADY_ENROLLED_ERR, status=status.HTTP_400_BAD_REQUEST)
        else:
            capture_security_activity(
                account=request.user,
                type='mfa-added',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': interface.authenticator,
                },
                send_email=False,
            )
            request.user.clear_lost_passwords()
            request.user.refresh_session_nonce(self.request)
            request.user.save()
            Authenticator.objects.auto_add_recovery_codes(request.user)

            return Response(status=status.HTTP_204_NO_CONTENT)
    def post(self, request, user, interface_id):
        """
        Enroll in authenticator interface
        `````````````````````````````````

        :pparam string user_id: user id or "me" for current user
        :pparam string interface_id: interface id

        :auth: required
        """

        # Using `request.user` here because superuser should not be able to set a user's 2fa

        # start activation
        serializer_cls = serializer_map.get(interface_id, None)

        if serializer_cls is None:
            return Response(status=status.HTTP_404_NOT_FOUND)

        serializer = serializer_cls(data=request.data)

        if not serializer.is_valid():
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

        interface = Authenticator.objects.get_interface(
            request.user, interface_id)

        # Not all interfaces allow multi enrollment
        #
        # This is probably un-needed because we catch
        # `Authenticator.AlreadyEnrolled` when attempting to enroll
        if interface.is_enrolled and not interface.allow_multi_enrollment:
            return Response(ALREADY_ENROLLED_ERR,
                            status=status.HTTP_400_BAD_REQUEST)

        try:
            interface.secret = request.data["secret"]
        except KeyError:
            pass

        context = {}
        # Need to update interface with phone number before validating OTP
        if "phone" in request.data:
            interface.phone_number = serializer.data["phone"]

            # Disregarding value of 'otp', if no OTP was provided,
            # send text message to phone number with OTP
            if "otp" not in request.data:
                if interface.send_text(for_enrollment=True,
                                       request=request._request):
                    return Response(status=status.HTTP_204_NO_CONTENT)
                else:
                    # Error sending text message
                    return Response(
                        SEND_SMS_ERR,
                        status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        # Attempt to validate OTP
        if "otp" in request.data and not interface.validate_otp(
                serializer.data["otp"]):
            return Response(INVALID_OTP_ERR,
                            status=status.HTTP_400_BAD_REQUEST)

        # Try u2f enrollment
        if interface_id == "u2f":
            # What happens when this fails?
            interface.try_enroll(
                serializer.data["challenge"],
                serializer.data["response"],
                serializer.data["deviceName"],
            )
            context.update({"device_name": serializer.data["deviceName"]})

        try:
            interface.enroll(request.user)
        except Authenticator.AlreadyEnrolled:
            return Response(ALREADY_ENROLLED_ERR,
                            status=status.HTTP_400_BAD_REQUEST)
        else:
            context.update({"authenticator": interface.authenticator})
            capture_security_activity(
                account=request.user,
                type="mfa-added",
                actor=request.user,
                ip_address=request.META["REMOTE_ADDR"],
                context=context,
                send_email=True,
            )
            request.user.clear_lost_passwords()
            request.user.refresh_session_nonce(self.request)
            request.user.save()
            Authenticator.objects.auto_add_recovery_codes(request.user)

            # Try to accept an org invite pending 2FA enrollment
            member_id = serializer.data.get("memberId")
            token = serializer.data.get("token")

            if member_id and token:
                try:
                    helper = ApiInviteHelper(
                        instance=self,
                        request=request,
                        member_id=member_id,
                        token=token,
                        logger=logger,
                    )
                except OrganizationMember.DoesNotExist:
                    logger.error("Failed to accept pending org invite",
                                 exc_info=True)
                else:
                    if helper.valid_request():
                        helper.accept_invite()

                        response = Response(status=status.HTTP_204_NO_CONTENT)
                        helper.remove_invite_cookie(response)
                        return response

            return Response(status=status.HTTP_204_NO_CONTENT)
    def delete(self,
               request: Request,
               user,
               auth_id,
               interface_device_id=None) -> Response:
        """
        Remove authenticator
        ````````````````````

        :pparam string user_id: user id or 'me' for current user
        :pparam string auth_id: authenticator model id
        :pparam string interface_device_id: some interfaces (u2f) allow multiple devices

        :auth required:
        """
        try:
            authenticator = Authenticator.objects.get(user=user, id=auth_id)
        except (ValueError, Authenticator.DoesNotExist):
            return Response(status=status.HTTP_404_NOT_FOUND)

        interface = authenticator.interface
        # Remove a single device and not entire authentication method
        if interface.interface_id == "u2f" and interface_device_id is not None:
            device_name = interface.get_device_name(interface_device_id)
            # Can't remove if this is the last device, will return False if so
            if not interface.remove_u2f_device(interface_device_id):
                return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
            interface.authenticator.save()

            capture_security_activity(
                account=user,
                type="mfa-removed",
                actor=request.user,
                ip_address=request.META["REMOTE_ADDR"],
                context={
                    "authenticator": authenticator,
                    "device_name": device_name
                },
                send_email=True,
            )
            return Response(status=status.HTTP_204_NO_CONTENT)

        if not is_active_superuser(request):
            # if the user's organization requires 2fa,
            # don't delete the last auth method
            enrolled_methods = Authenticator.objects.all_interfaces_for_user(
                user, ignore_backup=True)
            last_2fa_method = len(enrolled_methods) == 1
            require_2fa = user.get_orgs_require_2fa().exists()

            if require_2fa and last_2fa_method:
                return Response(
                    {
                        "detail":
                        "Cannot delete authenticator because organization requires 2FA"
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

        with transaction.atomic():
            authenticator.delete()

            # if we delete an actual authenticator and all that
            # remains are backup interfaces, then we kill them in the
            # process.
            if not interface.is_backup_interface:
                interfaces = Authenticator.objects.all_interfaces_for_user(
                    user)
                backup_interfaces = [
                    x for x in interfaces if x.is_backup_interface
                ]
                if len(backup_interfaces) == len(interfaces):
                    for iface in backup_interfaces:
                        iface.authenticator.delete()

                    # wait to generate entries until all pending writes
                    # have been sent to db
                    for iface in backup_interfaces:
                        capture_security_activity(
                            account=request.user,
                            type="mfa-removed",
                            actor=request.user,
                            ip_address=request.META["REMOTE_ADDR"],
                            context={"authenticator": iface.authenticator},
                            send_email=False,
                        )

            capture_security_activity(
                account=user,
                type="mfa-removed",
                actor=request.user,
                ip_address=request.META["REMOTE_ADDR"],
                context={"authenticator": authenticator},
                send_email=not interface.is_backup_interface,
            )

        return Response(status=status.HTTP_204_NO_CONTENT)
    def delete(self, request, user, auth_id, interface_device_id=None):
        """
        Remove authenticator
        ````````````````````

        :pparam string user_id: user id or 'me' for current user
        :pparam string auth_id: authenticator model id
        :pparam string interface_device_id: some interfaces (u2f) allow multiple devices

        :auth required:
        """

        try:
            authenticator = Authenticator.objects.get(
                user=user,
                id=auth_id,
            )
        except (ValueError, Authenticator.DoesNotExist):
            return Response(status=status.HTTP_404_NOT_FOUND)

        interface = authenticator.interface

        # Remove a single device and not entire authentication method
        if interface.interface_id == 'u2f' and interface_device_id is not None:
            device_name = interface.get_device_name(interface_device_id)
            # Can't remove if this is the last device, will return False if so
            if not interface.remove_u2f_device(interface_device_id):
                return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
            interface.authenticator.save()

            capture_security_activity(
                account=user,
                type='mfa-removed',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': authenticator,
                    'device_name': device_name
                },
                send_email=True
            )
            return Response(status=status.HTTP_204_NO_CONTENT)

        with transaction.atomic():
            authenticator.delete()

            # if we delete an actual authenticator and all that
            # remainds are backup interfaces, then we kill them in the
            # process.
            if not interface.is_backup_interface:
                interfaces = Authenticator.objects.all_interfaces_for_user(user)
                backup_interfaces = [x for x in interfaces if x.is_backup_interface]
                if len(backup_interfaces) == len(interfaces):
                    for iface in backup_interfaces:
                        iface.authenticator.delete()

                    # wait to generate entries until all pending writes
                    # have been sent to db
                    for iface in backup_interfaces:
                        capture_security_activity(
                            account=request.user,
                            type='mfa-removed',
                            actor=request.user,
                            ip_address=request.META['REMOTE_ADDR'],
                            context={
                                'authenticator': iface.authenticator,
                            },
                            send_email=False,
                        )

            capture_security_activity(
                account=user,
                type='mfa-removed',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context={
                    'authenticator': authenticator,
                },
                send_email=not interface.is_backup_interface,
            )

        return Response(status=status.HTTP_204_NO_CONTENT)
示例#30
0
    def post(self, request, user, interface_id):
        """
        Enroll in authenticator interface
        `````````````````````````````````

        :pparam string user_id: user id or "me" for current user
        :pparam string interface_id: interface id

        :auth: required
        """

        # Using `request.user` here because superuser should not be able to set a user's 2fa

        # start activation
        serializer_cls = serializer_map.get(interface_id, None)

        if serializer_cls is None:
            return Response(status=status.HTTP_404_NOT_FOUND)

        serializer = serializer_cls(data=request.DATA)

        if not serializer.is_valid():
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        interface = Authenticator.objects.get_interface(request.user, interface_id)

        # Not all interfaces allow multi enrollment
        #
        # This is probably un-needed because we catch
        # `Authenticator.AlreadyEnrolled` when attempting to enroll
        if interface.is_enrolled and not interface.allow_multi_enrollment:
            return Response(ALREADY_ENROLLED_ERR, status=status.HTTP_400_BAD_REQUEST)

        try:
            interface.secret = request.DATA['secret']
        except KeyError:
            pass

        context = {}
        # Need to update interface with phone number before validating OTP
        if 'phone' in request.DATA:
            interface.phone_number = serializer.data['phone']

            # Disregarding value of 'otp', if no OTP was provided,
            # send text message to phone number with OTP
            if 'otp' not in request.DATA:
                if interface.send_text(for_enrollment=True, request=request._request):
                    return Response(status=status.HTTP_204_NO_CONTENT)
                else:
                    # Error sending text message
                    return Response(SEND_SMS_ERR, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        # Attempt to validate OTP
        if 'otp' in request.DATA and not interface.validate_otp(serializer.data['otp']):
            return Response(INVALID_OTP_ERR, status=status.HTTP_400_BAD_REQUEST)

        # Try u2f enrollment
        if interface_id == 'u2f':
            # What happens when this fails?
            interface.try_enroll(
                serializer.data['challenge'],
                serializer.data['response'],
                serializer.data['deviceName']
            )
            context.update({
                'device_name': serializer.data['deviceName']
            })

        try:
            interface.enroll(request.user)
        except Authenticator.AlreadyEnrolled:
            return Response(ALREADY_ENROLLED_ERR, status=status.HTTP_400_BAD_REQUEST)
        else:
            context.update({
                'authenticator': interface.authenticator
            })
            capture_security_activity(
                account=request.user,
                type='mfa-added',
                actor=request.user,
                ip_address=request.META['REMOTE_ADDR'],
                context=context,
                send_email=True,
            )
            request.user.clear_lost_passwords()
            request.user.refresh_session_nonce(self.request)
            request.user.save()
            Authenticator.objects.auto_add_recovery_codes(request.user)

            return Response(status=status.HTTP_204_NO_CONTENT)