Example #1
0
def login_attempts(request):
    """
    Track number of login attempts made by a specific IP within
    a specified amount of time
    """
    ip, username = check_lockout(request)
    attempts_key = safe_key("{}{}-{}".format(LOGIN_ATTEMPTS, ip, username))
    attempts = cache.get(attempts_key)

    if attempts:
        cache.incr(attempts_key)
        attempts = cache.get(attempts_key)
        if attempts >= getattr(settings, "MAX_LOGIN_ATTEMPTS", 10):
            lockout_key = safe_key("{}{}-{}".format(LOCKOUT_IP, ip, username))
            lockout = cache.get(lockout_key)
            if not lockout:
                send_lockout_email(username, ip)
                cache.set(
                    lockout_key,
                    datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
                    getattr(settings, "LOCKOUT_TIME", 1800),
                )
            check_lockout(request)
            return attempts
        return attempts

    cache.set(attempts_key, 1)

    return cache.get(attempts_key)
Example #2
0
    def test_new_project_set_to_cache(self):
        """
        Test that newly created project is set to cache
        """
        data = {
                'name': u'demo',
                'owner':
                'http://testserver/api/v1/users/%s'
                % self.user,
                'metadata': {'description': 'Some description',
                             'location': 'Naivasha, Kenya',
                             'category': 'governance'},
                'public': False
            }
        # clear cache
        cache.delete(safe_key(f'{PROJ_OWNER_CACHE}1'))
        self.assertIsNone(cache.get(safe_key(f'{PROJ_OWNER_CACHE}1')))

        # Create the project
        self._project_create(data)
        self.assertIsNotNone(self.project_data)

        request = self.factory.get('/', **self.extra)
        request.user = self.user

        serializer = ProjectSerializer(
            self.project, context={'request': request}).data
        self.assertEqual(
             cache.get(f'{PROJ_OWNER_CACHE}{self.project.pk}'), serializer)

        # clear cache
        cache.delete(safe_key(f'{PROJ_OWNER_CACHE}{self.project.pk}'))
Example #3
0
def check_lockout(request) -> Tuple[Optional[str], Optional[str]]:
    """Check request user is not locked out on authentication.

    Returns the username if not locked out, None if request path is in
    LOCKOUT_EXCLUDED_PATHS.
    Raises AuthenticationFailed on lockout.
    """
    uri_path = request.get_full_path()
    if not any(part in LOCKOUT_EXCLUDED_PATHS for part in uri_path.split("/")):
        ip, username = retrieve_user_identification(request)

        if ip and username:
            lockout = cache.get(
                safe_key("{}{}-{}".format(LOCKOUT_IP, ip, username)))
            if lockout:
                time_locked_out = datetime.now() - datetime.strptime(
                    lockout, "%Y-%m-%dT%H:%M:%S")
                remaining_time = round(
                    (getattr(settings, "LOCKOUT_TIME", 1800) -
                     time_locked_out.seconds) / 60)
                raise AuthenticationFailed(
                    _("Locked out. Too many wrong username"
                      "/password attempts. "
                      "Try again in {} minutes.".format(remaining_time)))
            return ip, username
    return None, None
Example #4
0
def check_lockout(request):
    """Check request user is not locked out on authentication.

    Returns the username if not locked out, None if request path is in
    LOCKOUT_EXCLUDED_PATHS.
    Raises AuthenticationFailed on lockout.
    """
    uri_path = request.get_full_path()
    if any(part in LOCKOUT_EXCLUDED_PATHS for part in uri_path.split("/")):
        return None

    try:
        if isinstance(request.META["HTTP_AUTHORIZATION"], bytes):
            username = (request.META["HTTP_AUTHORIZATION"].decode(
                "utf-8").split('"')[1])
        else:
            username = request.META["HTTP_AUTHORIZATION"].split('"')[1]
    except (TypeError, AttributeError, IndexError):
        pass
    else:
        lockout = cache.get(safe_key("{}{}".format(LOCKOUT_USER, username)))
        if lockout:
            time_locked_out = datetime.now() - datetime.strptime(
                lockout, "%Y-%m-%dT%H:%M:%S")
            remaining_time = round((getattr(settings, "LOCKOUT_TIME", 1800) -
                                    time_locked_out.seconds) / 60)
            raise AuthenticationFailed(
                _("Locked out. Too many wrong username/password attempts. "
                  "Try again in {} minutes.".format(remaining_time)))
        return username

    return None
Example #5
0
def check_lockout(request):
    try:
        if isinstance(request.META["HTTP_AUTHORIZATION"], bytes):
            username = (request.META["HTTP_AUTHORIZATION"].decode(
                "utf-8").split('"')[1])
        else:
            username = request.META["HTTP_AUTHORIZATION"].split('"')[1]
    except (TypeError, AttributeError, IndexError):
        return
    else:
        lockout = cache.get(safe_key("{}{}".format(LOCKOUT_USER, username)))
        if lockout:
            time_locked_out = datetime.now() - datetime.strptime(
                lockout, "%Y-%m-%dT%H:%M:%S")
            remaining_time = round((getattr(settings, "LOCKOUT_TIME", 1800) -
                                    time_locked_out.seconds) / 60)
            raise AuthenticationFailed(
                _("Locked out. Too many wrong username/password attempts. "
                  "Try again in {} minutes.".format(remaining_time)))
        return username
Example #6
0
    def test_login_attempts(self, send_account_lockout_email):
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(DigestAuthentication,))
        auth = DigestAuth('bob', 'bob')
        # clear cache
        cache.delete(safe_key("login_attempts-bob"))
        cache.delete(safe_key("lockout_user-bob"))
        self.assertIsNone(cache.get(safe_key('login_attempts-bob')))
        self.assertIsNone(cache.get(safe_key('lockout_user-bob')))

        request = self._get_request_session_with_auth(view, auth)

        # first time it creates a cache
        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'],
                         u"Invalid username/password. For security reasons, "
                         u"after 9 more failed login attempts you'll have to "
                         u"wait 30 minutes before trying again.")
        self.assertEqual(cache.get(safe_key('login_attempts-bob')), 1)

        # cache value increments with subsequent attempts
        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'],
                         u"Invalid username/password. For security reasons, "
                         u"after 8 more failed login attempts you'll have to "
                         u"wait 30 minutes before trying again.")
        self.assertEqual(cache.get(safe_key('login_attempts-bob')), 2)

        # login_attempts doesn't increase with correct login
        auth = DigestAuth('bob', 'bobbob')
        request = self._get_request_session_with_auth(view, auth)
        response = view(request)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(cache.get(safe_key('login_attempts-bob')), 2)

        # lockout_user cache created upon fifth attempt
        auth = DigestAuth('bob', 'bob')
        request = self._get_request_session_with_auth(view, auth)
        self.assertFalse(send_account_lockout_email.called)
        cache.set(safe_key('login_attempts-bob'), 9)
        self.assertIsNone(cache.get(safe_key('lockout_user-bob')))
        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'],
                         u"Locked out. Too many wrong username/password "
                         u"attempts. Try again in 30 minutes.")
        self.assertEqual(cache.get(safe_key('login_attempts-bob')), 10)
        self.assertIsNotNone(cache.get(safe_key('lockout_user-bob')))
        lockout = datetime.strptime(
            cache.get(safe_key('lockout_user-bob')), '%Y-%m-%dT%H:%M:%S')
        self.assertIsInstance(lockout, datetime)

        # email sent upon limit being reached with right arguments
        subject_path = 'account_lockout/lockout_email_subject.txt'
        self.assertTrue(send_account_lockout_email.called)
        email_subject = render_to_string(subject_path)
        self.assertIn(
            email_subject, send_account_lockout_email.call_args[1]['args'])
        self.assertEqual(
            send_account_lockout_email.call_count, 2, "Called twice")

        # subsequent login fails after lockout even with correct credentials
        auth = DigestAuth('bob', 'bobbob')
        request = self._get_request_session_with_auth(view, auth)
        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'],
                         u"Locked out. Too many wrong username/password "
                         u"attempts. Try again in 30 minutes.")
        # clear cache
        cache.delete(safe_key("login_attempts-bob"))
        cache.delete(safe_key("lockout_user-bob"))
Example #7
0
 def test_safe_key(self):
     """Test safe_key() function returns a hashed key"""
     self.assertEqual(
         safe_key("hello world"),
         "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")