Example #1
0
    def authenticate(self, request, username=None, password=None, **kwargs):
        """Checks user lock out status and raises PermissionDenied if user is not allowed to log in.

        Inserts errors directly to `return_context` that is supplied as a keyword argument.

        Use this on top of your AUTHENTICATION_BACKENDS list to prevent locked out users
        from being authenticated in the standard Django authentication flow.

        Note that this method does not log your user in and delegates login to other backends.

        :param request: see ModelBackend.authenticate
        :param kwargs: see ModelBackend.authenticate
        :keyword response_context: context dict that will be updated with error information
        :raises PermissionDenied: if user is already locked out
        :return: None
        """

        if request is None:
            raise AxesModelBackend.RequestParameterRequired()

        credentials = get_credentials(username=username,
                                      password=password,
                                      **kwargs)

        if is_already_locked(request, credentials):
            # locked out, don't try to authenticate, just update return_context and return
            # Its a bit weird to pass a context and expect a response value but its nice to get a "why" back.
            error_msg = get_lockout_message()
            response_context = kwargs.get('response_context', {})
            response_context['error'] = error_msg
            raise PermissionDenied(error_msg)
    def validate(self, attrs):
        credentials = {
            self.username_field: attrs.get(self.username_field),
            'password': attrs.get('password')
        }

        if all(credentials.values()):
            request = self.context['request']

            if is_already_locked(request):
                raise exceptions.Throttled(
                    600, 'Too many failed password attempts.'
                )

            user = authenticate(request, **credentials)

            if user:
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)

                payload = jwt_payload_handler(user)

                return {
                    'token': jwt_encode_handler(payload),
                    'user': user
                }
            else:
                msg = _('Unable to log in with provided credentials.')
                raise serializers.ValidationError(msg)
        else:
            msg = _('Must include "{username_field}" and "password".')
            msg = msg.format(username_field=self.username_field)
            raise serializers.ValidationError(msg)
    def test_is_already_locked_cache(self, get_cache):
        cache = MagicMock()
        cache.get.return_value = 42
        get_cache.return_value = cache

        self.assertTrue(is_already_locked(self.request, {}))
        self.assertTrue(cache.get.call_count)
    def test_is_already_locked_nolockable(self):
        UserModel = get_user_model()
        user = UserModel.objects.create(username='******')

        with self.subTest('User is marked as nolockout.'):
            with patch.object(UserModel, 'nolockout', True, create=True):
                locked = is_already_locked(self.request, {UserModel.USERNAME_FIELD: user.username})
                self.assertFalse(locked)
Example #5
0
    def authenticate(self, request, username=None, password=None, **kwargs):

        if request is None:
            raise AxesModelBackend.RequestParameterRequired()

        try:
            user = User.objects.get(email__iexact=username)
            if user.check_password(password):
                return
        except User.DoesNotExist:
            if config.ELGG_URL:
                elgg_urls =  config.ELGG_URL.splitlines()

                for url in elgg_urls:
                    # Verify username/password combination
                    valid_user_request = requests.post(
                        url + "/services/api/rest/json/",
                        data={
                            'method': 'pleio.verifyuser',
                            'user': username,
                            'password': password
                        }
                    )
                    valid_user_json = json.loads(valid_user_request.text)
                    valid_user_result = valid_user_json["result"] if 'result' in valid_user_json else []
                    valid_user = valid_user_result["valid"] if 'valid' in valid_user_result else False
                    name = valid_user_result["name"] if 'name' in valid_user_result else username
                    admin = valid_user_result["admin"] if 'admin' in valid_user_result else False

                    # If valid, create new user with Elgg attributes
                    if valid_user is True:
                        user = User.objects.create_user(
                            name=name,
                            email=username.lower(),
                            password=password,
                            accepted_terms=True,
                            receives_newsletter=True
                        )
                        user.is_active = True
                        user.is_admin = admin
                        user.save()
                        service_mesh_message('user.new', json.dumps({
                            'name': user.name,
                            'email': user.email,
                            'gcID': user.id,
                            'isAdmin': user.is_admin
                        }))
                        break

        credentials = get_credentials(username=username, password=password, **kwargs)

        if is_already_locked(request, credentials):
            error_msg = get_lockout_message()
            response_context = kwargs.get('response_context', {})
            response_context['error'] = error_msg
            raise PermissionDenied(error_msg)

        return
Example #6
0
    def inner(request, *args, **kwargs):
        # If the request is currently under lockout, do not proceed to the
        # login function, go directly to lockout url, do not pass go, do not
        # collect messages about this login attempt
        if is_already_locked(request):
            return lockout_response(request)

        # call the login function
        return func(request, *args, **kwargs)
Example #7
0
    def authenticate(self, request, username=None, password=None, **kwargs):
        """
        Check user lock out status and raises PermissionDenied if user is not allowed to log in.

        Inserts errors directly to `return_context` that is supplied as a keyword argument.

        Use this on top of your AUTHENTICATION_BACKENDS list to prevent locked out users
        from being authenticated in the standard Django authentication flow.

        Note that this method does not log your user in and delegates login to other backends.

        :param request: see django.contrib.auth.backends.ModelBackend.authenticate
        :param username: see django.contrib.auth.backends.ModelBackend.authenticate
        :param password: see django.contrib.auth.backends.ModelBackend.authenticate
        :param kwargs: see django.contrib.auth.backends.ModelBackend.authenticate
        :keyword response_context: context dict that will be updated with error information
        :raises AxesBackendRequestParameterRequired: if request parameter is not given correctly
        :raises AxesBackendPermissionDenied: if user is already locked out
        :return: None
        """

        if request is None:
            raise AxesBackendRequestParameterRequired(
                'AxesBackend requires a request as an argument to authenticate'
            )

        credentials = get_credentials(username=username,
                                      password=password,
                                      **kwargs)

        if is_already_locked(request, credentials):
            # locked out, don't try to authenticate, just update return_context and return
            # Its a bit weird to pass a context and expect a response value but its nice to get a "why" back.
            error_msg = get_lockout_message()
            response_context = kwargs.get('response_context', {})
            response_context['error'] = error_msg

            # Raise an error that stops the authentication flows at django.contrib.auth.authenticate.
            # This error stops bubbling up at the authenticate call which catches backend PermissionDenied errors.
            # After this error is caught by authenticate it emits a signal indicating user login failed,
            # which is processed by axes.signals.log_user_login_failed which logs the attempt and raises
            # a second exception which bubbles up the middleware stack and produces a HTTP 403 Forbidden reply
            # in the axes.middleware.AxesMiddleware.process_exception middleware exception handler.

            raise AxesBackendPermissionDenied(
                'AxesBackend detected that the given user is locked out')
Example #8
0
    def authenticate(self, request, username=None, password=None, **kwargs):
        """
        Check user lock out status and raises PermissionDenied if user is not allowed to log in.

        Inserts errors directly to `return_context` that is supplied as a keyword argument.

        Use this on top of your AUTHENTICATION_BACKENDS list to prevent locked out users
        from being authenticated in the standard Django authentication flow.

        Note that this method does not log your user in and delegates login to other backends.

        :param request: see django.contrib.auth.backends.ModelBackend.authenticate
        :param username: see django.contrib.auth.backends.ModelBackend.authenticate
        :param password: see django.contrib.auth.backends.ModelBackend.authenticate
        :param kwargs: see django.contrib.auth.backends.ModelBackend.authenticate
        :keyword response_context: context dict that will be updated with error information
        :raises AxesBackendRequestParameterRequired: if request parameter is not given correctly
        :raises AxesBackendPermissionDenied: if user is already locked out
        :return: None
        """

        if request is None:
            raise AxesBackendRequestParameterRequired('AxesBackend requires a request as an argument to authenticate')

        credentials = get_credentials(username=username, password=password, **kwargs)

        if is_already_locked(request, credentials):
            # locked out, don't try to authenticate, just update return_context and return
            # Its a bit weird to pass a context and expect a response value but its nice to get a "why" back.
            error_msg = get_lockout_message()
            response_context = kwargs.get('response_context', {})
            response_context['error'] = error_msg

            # Raise an error that stops the authentication flows at django.contrib.auth.authenticate.
            # This error stops bubbling up at the authenticate call which catches backend PermissionDenied errors.
            # After this error is caught by authenticate it emits a signal indicating user login failed,
            # which is processed by axes.signals.log_user_login_failed which logs the attempt and raises
            # a second exception which bubbles up the middleware stack and produces a HTTP 403 Forbidden reply
            # in the axes.middleware.AxesMiddleware.process_exception middleware exception handler.

            raise AxesBackendPermissionDenied('AxesBackend detected that the given user is locked out')
Example #9
0
    def authenticate(self, request, username=None, password=None, **kwargs):
        """
        Add django-axes handling and add allow adding errors directly to a passed return_context.
        Will never actually authenticate a user, just blocks locked out uses so don't use this as your only back end.
        :param request: see ModelBackend.authenticate
        :param username: see ModelBackend.authenticate
        :param password: see ModelBackend.authenticate
        :keyword response_context: context dict that will be returned/used in the response template.
            NOTE: will overwrite 'error' field in dict
        :param kwargs: see ModelBackend.authenticate
        :raises PermissionDenied: if user is already locked out.
        :return: Nothing, but will update return_context with lockout message if user is locked out.
        """

        if request is None:
            raise AxesModelBackend.RequestParameterRequired()

        if is_already_locked(request):
            # locked out, don't try to authenticate, just update return_context and return
            # Its a bit weird to pass a context and expect a response value but its nice to get a "why" back.
            error_msg = get_lockout_message()
            response_context = kwargs.get('response_context', {})
            response_context['error'] = error_msg
            raise PermissionDenied(error_msg)
Example #10
0
    def user_login_failed(self, sender, credentials, request, **kwargs):  # pylint: disable=unused-argument
        """
        When user login fails, save AccessAttempt record in database and lock user out if necessary.

        :raises AxesSignalPermissionDenied: if user should is locked out
        """

        if request is None:
            log.warning(
                'AxesHandler.user_login_failed does not function without a request.'
            )
            return

        username = get_client_username(request, credentials)
        ip_address = get_client_ip(request)
        user_agent = get_client_user_agent(request)
        path_info = request.META.get('PATH_INFO', '<unknown>')[:255]
        http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025]
        client_str = get_client_str(username, ip_address, user_agent,
                                    path_info)

        if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(
                ip_address):
            log.info('Login failed from whitelisted IP %s.', ip_address)
            return

        attempts = get_user_attempts(request, credentials)
        cache_key = get_cache_key(request, credentials)
        num_failures_cached = get_axes_cache().get(cache_key)

        if num_failures_cached:
            failures = num_failures_cached
        elif attempts:
            failures = attempts.aggregate(
                Max('failures_since_start'), )['failures_since_start__max']
        else:
            failures = 0

        # add a failed attempt for this user
        failures += 1
        get_axes_cache().set(
            cache_key,
            failures,
            get_cache_timeout(),
        )

        if attempts:
            # Update existing attempt information but do not touch the username, ip_address, or user_agent fields,
            # because attackers can request the site with multiple different usernames, addresses, or programs.
            for attempt in attempts:
                template = '{}\n---------\n{}'

                attempt.get_data = template.format(
                    attempt.get_data,
                    query2str(request.GET),
                )
                attempt.post_data = template.format(attempt.post_data,
                                                    query2str(request.POST))
                attempt.http_accept = http_accept
                attempt.path_info = path_info
                attempt.failures_since_start = failures
                attempt.attempt_time = now()
                attempt.save()

                log.info(
                    'AXES: Repeated login failure by %s. Count = %d of %d',
                    client_str,
                    failures,
                    settings.AXES_FAILURE_LIMIT,
                )
        else:
            # Record failed attempt. Whether or not the IP address or user agent is
            # used in counting failures is handled elsewhere, so we just record # everything here.
            AccessAttempt.objects.create(
                username=username,
                ip_address=ip_address,
                user_agent=user_agent,
                get_data=query2str(request.GET),
                post_data=query2str(request.POST),
                http_accept=http_accept,
                path_info=path_info,
                failures_since_start=failures,
            )

            log.info(
                'AXES: New login failure by %s. Creating access record.',
                client_str,
            )

        if is_already_locked(request, credentials):
            log.warning(
                'AXES: Locked out %s after repeated login failures.',
                client_str,
            )

            user_locked_out.send(
                'axes',
                request=request,
                username=username,
                ip_address=ip_address,
            )

            raise AxesSignalPermissionDenied(
                'Locked out due to repeated login failures.')
Example #11
0
    def inner(request, *args, **kwargs):
        if is_already_locked(request):
            return get_lockout_response(request)

        return func(request, *args, **kwargs)
Example #12
0
    def inner(self, *args, **kwargs):
        if is_already_locked(self.request):
            return lockout_response(self.request)

        return func(self, *args, **kwargs)
 def test_is_already_locked_never_lockout_get(self):
     request = HttpRequest()
     request.method = 'GET'
     self.assertFalse(is_already_locked(request))
    def test_is_already_locked_do_not_lock_out_at_failure(self, get_cache):
        cache = MagicMock()
        cache.get.return_value = 42
        get_cache.return_value = cache

        self.assertFalse(is_already_locked(self.request))
 def test_is_already_locked_ip_not_in_whitelist(self):
     self.assertTrue(is_already_locked(self.request))
 def test_is_already_locked_ip_not_in_blacklist(self):
     self.assertFalse(is_already_locked(self.request))