Beispiel #1
0
    def test_regenerate_auth_token(self):
        self.view = ConnectViewSet.as_view({
            "get": "regenerate_auth_token",
            })
        prev_token = self.user.auth_token
        request = self.factory.get("/", **self.extra)
        response = self.view(request)
        self.assertEqual(response.status_code, 201)
        new_token = Token.objects.get(user=self.user)
        self.assertNotEqual(prev_token, new_token)

        self.view = ConnectViewSet.as_view({
            "get": "list",
            })
        self.extra = {'HTTP_AUTHORIZATION': 'Token %s' % new_token}
        request = self.factory.get('/', **self.extra)
        request.session = self.client.session
        response = self.view(request)
        self.assertEqual(response.status_code, 200)

        self.extra = {'HTTP_AUTHORIZATION': 'Token invalidtoken'}
        request = self.factory.get('/', **self.extra)
        response = self.view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response['www-authenticate'], "Token")
Beispiel #2
0
    def test_regenerate_auth_token(self):
        self.view = ConnectViewSet.as_view({
            "get": "regenerate_auth_token",
            })
        prev_token = self.user.auth_token
        request = self.factory.get("/", **self.extra)
        response = self.view(request)
        self.assertEqual(response.status_code, 201)
        new_token = Token.objects.get(user=self.user)
        self.assertNotEqual(prev_token, new_token)

        self.view = ConnectViewSet.as_view({
            "get": "list",
            })
        self.extra = {'HTTP_AUTHORIZATION': 'Token %s' % new_token}
        request = self.factory.get('/', **self.extra)
        request.session = self.client.session
        response = self.view(request)
        self.assertEqual(response.status_code, 200)

        self.extra = {'HTTP_AUTHORIZATION': 'Token invalidtoken'}
        request = self.factory.get('/', **self.extra)
        response = self.view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response['www-authenticate'], "Token")
Beispiel #3
0
    def test_user_list_with_basic_and_digest(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(
                DigestAuthentication,
                authentication.BasicAuthentication
            ))
        request = self.factory.get('/')
        auth = BasicAuth('bob', 'bob')
        request.META.update(auth(request.META))
        request.session = self.client.session

        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'],
                         u"Invalid username/password.")
        auth = BasicAuth('bob', 'bobbob')
        request.META.update(auth(request.META))
        request.session = self.client.session

        response = view(request)
        temp_token = TempToken.objects.get(user__username='******')
        self.data['temp_token'] = temp_token.key
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, self.data)
Beispiel #4
0
    def setUp(self):
        super().setUp()
        self.view = ConnectViewSet.as_view({
            "get": "list",
        })

        # PostgreSQL behaves differently than SQLite. After each test, table is
        # truncated but PostgreSQL does not reset its sequences but SQLite does.
        # `self.user_profile_data()` does contain the real value of user's id.
        # let's use it.
        user_profile_data = self.user_profile_data()

        self.data = {
            'id': user_profile_data['id'],
            'username': user_profile_data['username'],
            'name': user_profile_data['name'],
            'email': user_profile_data['email'],
            'city': user_profile_data['city'],
            'country': user_profile_data['country'],
            'organization': user_profile_data['organization'],
            'website': user_profile_data['website'],
            'twitter': user_profile_data['twitter'],
            'gravatar': user_profile_data['gravatar'],
            'require_auth': False,
            'api_token': self.user.auth_token.key,
            'temp_token': self.client.session.session_key,
            'metadata': {},
        }
Beispiel #5
0
    def test_get_starred_projects(self):
        self._project_create()

        # add star as bob
        view = ProjectViewSet.as_view({
            'get': 'star',
            'post': 'star'
        })
        request = self.factory.post('/', **self.extra)
        response = view(request, pk=self.project.pk)

        # get starred projects
        view = ConnectViewSet.as_view({
            'get': 'starred',
        })
        request = self.factory.get('/', **self.extra)
        response = view(request, user=self.user)

        self.assertEqual(response.status_code, 200)
        self.project.refresh_from_db()
        request.user = self.user
        self.project_data = ProjectSerializer(
            self.project, context={'request': request}).data
        del self.project_data['date_modified']
        del response.data[0]['date_modified']
        self.assertEqual(len(response.data), 1)
        self.assertDictEqual(dict(response.data[0]), dict(self.project_data))
Beispiel #6
0
    def test_regenerate_odk_token(self):
        """
        Test that ODK Tokens can be regenerated and old tokens
        are set to Inactive after regeneration
        """
        view = ConnectViewSet.as_view({'post': 'odk_token'})
        request = self.factory.post('/', **self.extra)
        request.session = self.client.session
        response = view(request)
        self.assertEqual(response.status_code, 201)
        old_token = response.data['odk_token']

        with self.assertRaises(ODKToken.DoesNotExist):
            ODKToken.objects.get(
                user=self.user, status=ODKToken.INACTIVE)

        request = self.factory.post('/', **self.extra)
        request.session = self.client.session
        response = view(request)
        self.assertEqual(response.status_code, 201)
        self.assertNotEqual(response.data['odk_token'], old_token)

        # Test that the previous token was set to inactive
        inactive_token = ODKToken.objects.get(
            user=self.user, status=ODKToken.INACTIVE)
        self.assertEqual(inactive_token.raw_key, old_token)
    def test_user_list_with_digest(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(DigestAuthentication,))
        request = self.factory.head('/')

        auth = DigestAuth('bob', 'bob')
        response = view(request)
        self.assertTrue(response.has_header('WWW-Authenticate'))
        self.assertTrue(
            response['WWW-Authenticate'].startswith('Digest nonce='))
        request = self.factory.get('/')
        request.META.update(auth(request.META, response))
        request.session = self.client.session

        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'],
                         u"Invalid username/password")
        auth = DigestAuth('bob', 'bobbob')
        request.META.update(auth(request.META, response))
        request.session = self.client.session

        response = view(request)
        temp_token = TempToken.objects.get(user__username='******')
        self.data['temp_token'] = temp_token.key
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, self.data)
Beispiel #8
0
    def test_get_starred_projects(self):
        self._project_create()

        # add star as bob
        view = ProjectViewSet.as_view({
            'get': 'star',
            'post': 'star'
        })
        request = self.factory.post('/', **self.extra)
        response = view(request, pk=self.project.pk)

        # get starred projects
        view = ConnectViewSet.as_view({
            'get': 'starred',
        })
        request = self.factory.get('/', **self.extra)
        response = view(request, user=self.user)

        self.assertEqual(response.status_code, 200)
        self.project.refresh_from_db()
        request.user = self.user
        self.project_data = ProjectSerializer(
            self.project, context={'request': request}).data
        del self.project_data['date_modified']
        del response.data[0]['date_modified']
        self.assertEqual(len(response.data), 1)
        self.assertDictEqual(dict(response.data[0]), dict(self.project_data))
Beispiel #9
0
    def test_user_list_with_digest(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(DigestAuthentication,))
        request = self.factory.head('/')

        auth = DigestAuth('bob', 'bob')
        response = view(request)
        self.assertTrue(response.has_header('WWW-Authenticate'))
        self.assertTrue(response['WWW-Authenticate'].startswith('Digest '))
        self.assertIn('nonce=', response['WWW-Authenticate'])
        request = self.factory.get('/')
        request.META.update(auth(request.META, response))
        request.session = self.client.session

        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 "
                         u"have to wait 30 minutes before trying again.")
        auth = DigestAuth('bob', 'bobbob')
        request.META.update(auth(request.META, response))
        request.session = self.client.session

        response = view(request)
        temp_token = TempToken.objects.get(user__username='******')
        self.data['temp_token'] = temp_token.key
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, self.data)
    def test_user_list_with_basic_and_digest(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(
                DigestAuthentication,
                authentication.BasicAuthentication
            ))
        request = self.factory.get('/')
        auth = BasicAuth('bob', 'bob')
        request.META.update(auth(request.META))
        request.session = self.client.session

        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'],
                         u"Invalid username/password")
        auth = BasicAuth('bob', 'bobbob')
        request.META.update(auth(request.META))
        request.session = self.client.session

        response = view(request)
        temp_token = TempToken.objects.get(user__username='******')
        self.data['temp_token'] = temp_token.key
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, self.data)
Beispiel #11
0
    def test_user_list_with_digest(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'}, authentication_classes=(DigestAuthentication, ))
        request = self.factory.head('/')

        auth = DigestAuth('bob', 'bob')
        response = view(request)
        self.assertTrue(response.has_header('WWW-Authenticate'))
        self.assertTrue(
            response['WWW-Authenticate'].startswith('Digest nonce='))
        request = self.factory.get('/')
        request.META.update(auth(request.META, response))
        request.session = self.client.session

        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'], u"Invalid username/password")
        auth = DigestAuth('bob', 'bobbob')
        request.META.update(auth(request.META, response))
        request.session = self.client.session

        response = view(request)
        temp_token = TempToken.objects.get(user__username='******')
        self.data['temp_token'] = temp_token.key
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, self.data)
Beispiel #12
0
    def test_user_updates_email(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(DigestAuthentication,))

        auth = DigestAuth('*****@*****.**', 'bobbob')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        temp_token = TempToken.objects.get(user__username='******')
        self.data['temp_token'] = temp_token.key
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, self.data)

        self.user.email = "*****@*****.**"
        self.user.save()
        update_partial_digests(self.user, "bobbob")

        auth = DigestAuth('*****@*****.**', 'bobbob')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        temp_token = TempToken.objects.get(user__username='******')
        self.data['temp_token'] = temp_token.key
        self.data['email'] = '*****@*****.**'
        self.assertEqual(response.status_code, 200)
Beispiel #13
0
    def test_user_updates_email(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(DigestAuthentication,))

        auth = DigestAuth('*****@*****.**', 'bobbob')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        temp_token = TempToken.objects.get(user__username='******')
        self.data['temp_token'] = temp_token.key
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, self.data)

        self.user.email = "*****@*****.**"
        self.user.save()
        update_partial_digests(self.user, "bobbob")

        auth = DigestAuth('*****@*****.**', 'bobbob')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        temp_token = TempToken.objects.get(user__username='******')
        self.data['temp_token'] = temp_token.key
        self.data['email'] = '*****@*****.**'
        self.assertEqual(response.status_code, 200)
Beispiel #14
0
 def test_generate_auth_token(self):
     self.view = ConnectViewSet.as_view({
         "post": "create",
         })
     request = self.factory.post("/", **self.extra)
     request.session = self.client.session
     response = self.view(request)
     self.assertEqual(response.status_code, 201)
Beispiel #15
0
 def test_generate_odk_token(self):
     """
     Test that ODK Tokens can be created
     """
     view = ConnectViewSet.as_view({'post': 'odk_token'})
     request = self.factory.post('/', **self.extra)
     request.session = self.client.session
     response = view(request)
     self.assertEqual(response.status_code, 201)
    def test_user_updates_email_wrong_password(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'}, authentication_classes=(DigestAuthentication, ))

        auth = DigestAuth('*****@*****.**', 'bob')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'], u"Invalid username/password")
Beispiel #17
0
    def test_user_updates_email_wrong_password(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(DigestAuthentication,))

        auth = DigestAuth('*****@*****.**', 'bob')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        self.assertEqual(response.status_code, 401)
        self.assertEqual(response.data['detail'],
                         u"Invalid username/password")
    def test_user_has_no_profile_bug(self):
        alice = User.objects.create(username='******')
        alice.set_password('alice')
        update_partial_digests(alice, "alice")
        view = ConnectViewSet.as_view(
            {'get': 'list'}, authentication_classes=(DigestAuthentication, ))

        auth = DigestAuth('alice', 'alice')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        self.assertEqual(response.status_code, 200)
Beispiel #19
0
    def test_user_has_no_profile_bug(self):
        alice = User.objects.create(username='******')
        alice.set_password('alice')
        update_partial_digests(alice, "alice")
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(DigestAuthentication,))

        auth = DigestAuth('alice', 'alice')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        self.assertEqual(response.status_code, 200)
Beispiel #20
0
    def test_deactivates_multiple_active_odk_token(self):
        """
        Test that the viewset deactivates tokens when two or more are
        active at the same time and returns a new token
        """
        view = ConnectViewSet.as_view({
            'post': 'odk_token',
            'get': 'odk_token'
        })

        # Create two active tokens
        token_1 = ODKToken.objects.create(user=self.user)
        token_2 = ODKToken.objects.create(user=self.user)

        self.assertEqual(token_1.status, ODKToken.ACTIVE)
        self.assertEqual(token_2.status, ODKToken.ACTIVE)

        # Test that the GET request deactivates the two active tokens
        # and returns a new active token
        request = self.factory.get('/', **self.extra)
        request.session = self.client.session
        response = view(request)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            len(ODKToken.objects.filter(status=ODKToken.ACTIVE)), 1)
        self.assertNotEqual(response.data['odk_token'], token_1.raw_key)
        self.assertNotEqual(response.data['odk_token'], token_2.raw_key)

        token_1 = ODKToken.objects.get(pk=token_1.pk)
        token_2 = ODKToken.objects.get(pk=token_2.pk)
        token_3_key = response.data['odk_token']

        self.assertEqual(token_1.status, ODKToken.INACTIVE)
        self.assertEqual(token_2.status, ODKToken.INACTIVE)

        # Test that the POST request deactivates two active tokens and returns
        # a new active token

        token_1.status = ODKToken.ACTIVE
        token_1.save()

        self.assertEqual(
            len(ODKToken.objects.filter(status=ODKToken.ACTIVE)), 2)
        request = self.factory.post('/', **self.extra)
        request.session = self.client.session
        response = view(request)
        self.assertEqual(response.status_code, 201)
        self.assertEqual(
            len(ODKToken.objects.filter(status=ODKToken.ACTIVE)), 1)
        self.assertNotEqual(response.data['odk_token'], token_1.raw_key)
        self.assertNotEqual(response.data['odk_token'], token_3_key)
Beispiel #21
0
    def test_user_updates_email_wrong_password(self):
        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(DigestAuthentication,))

        auth = DigestAuth('*****@*****.**', 'bob')
        request = self._get_request_session_with_auth(view, auth)

        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.")
Beispiel #22
0
    def test_update_first_last_name_password_not_affected(self):
        data = {'first_name': 'update_first', 'last_name': 'update_last'}
        request = self.factory.patch('/api/v1/profiles',
                                     data=json.dumps(data),
                                     content_type="application/json",
                                     **self.extra)
        response = self.view(request, user=self.user.username)

        self.assertEqual(response.status_code, 200)

        view = ConnectViewSet.as_view(
            {'get': 'list'}, authentication_classes=(DigestAuthentication, ))

        auth = DigestAuth('*****@*****.**', 'bobbob')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        self.assertEqual(response.status_code, 200)
Beispiel #23
0
 def setUp(self):
     super(self.__class__, self).setUp()
     self.view = ConnectViewSet.as_view({"get": "list", "post": "reset"})
     self.data = {
         'url': 'http://testserver/api/v1/profiles/bob',
         'username': u'bob',
         'name': u'Bob',
         'email': u'*****@*****.**',
         'city': u'Bobville',
         'country': u'US',
         'organization': u'Bob Inc.',
         'website': u'bob.com',
         'twitter': u'boberama',
         'gravatar': self.user.profile.gravatar,
         'require_auth': False,
         'user': '******',
         'api_token': self.user.auth_token.key,
         'temp_token': self.client.session.session_key
     }
    def test_update_first_last_name_password_not_affected(self):
        data = {'first_name': 'update_first',
                'last_name': 'update_last'}
        request = self.factory.patch(
            '/api/v1/profiles', data=json.dumps(data),
            content_type="application/json", **self.extra)
        response = self.view(request, user=self.user.username)

        self.assertEqual(response.status_code, 200)

        view = ConnectViewSet.as_view(
            {'get': 'list'},
            authentication_classes=(DigestAuthentication,))

        auth = DigestAuth('*****@*****.**', 'bobbob')
        request = self._get_request_session_with_auth(view, auth)

        response = view(request)
        self.assertEqual(response.status_code, 200)
 def setUp(self):
     super(self.__class__, self).setUp()
     self.view = ConnectViewSet.as_view({
         "get": "list"
     })
     self.data = {
         'url': 'http://testserver/api/v1/profiles/bob',
         'username': u'bob',
         'name': u'Bob',
         'email': u'*****@*****.**',
         'city': u'Bobville',
         'country': u'US',
         'organization': u'Bob Inc.',
         'website': u'bob.com',
         'twitter': u'boberama',
         'gravatar': self.user.profile.gravatar,
         'require_auth': False,
         'user': '******',
         'api_token': self.user.auth_token.key
     }
Beispiel #26
0
    def test_get_starred_projects(self):
        self._project_create()

        # add star as bob
        view = ProjectViewSet.as_view({
            'get': 'star',
            'post': 'star'
        })
        request = self.factory.post('/', **self.extra)
        response = view(request, pk=self.project.pk)

        # get starred projects
        view = ConnectViewSet.as_view({
            'get': 'starred',
        })
        request = self.factory.get('/', **self.extra)
        response = view(request, user=self.user)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, [self.project_data])
Beispiel #27
0
    def test_retrieve_odk_token(self):
        """
        Test that ODK Tokens can be retrieved
        """
        view = ConnectViewSet.as_view({
            'post': 'odk_token',
            'get': 'odk_token'
        })
        request = self.factory.post('/', **self.extra)
        request.session = self.client.session
        response = view(request)
        self.assertEqual(response.status_code, 201)
        odk_token = response.data['odk_token']
        expires = response.data['expires']

        request = self.factory.get('/', **self.extra)
        request.session = self.client.session
        response = view(request)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data['odk_token'], odk_token)
        self.assertEqual(response.data['expires'], expires)
 def setUp(self):
     super(self.__class__, self).setUp()
     self.view = ConnectViewSet.as_view({
         "get": "list",
     })
     self.data = {
         'id': 1,
         'username': '******',
         'name': 'Bob',
         'email': '*****@*****.**',
         'city': 'Bobville',
         'country': 'US',
         'organization': 'Bob Inc.',
         'website': 'bob.com',
         'twitter': 'boberama',
         'gravatar': self.user.profile.gravatar,
         'require_auth': False,
         'api_token': self.user.auth_token.key,
         'temp_token': self.client.session.session_key,
         'metadata': {},
     }
Beispiel #29
0
 def setUp(self):
     super(self.__class__, self).setUp()
     self.view = ConnectViewSet.as_view({
         "get": "list"
     })
Beispiel #30
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"))