class LoginApiTest(TestCase): def setUp(self): self.test_password = faker.password() self.test_email = faker.email() self.user = BaseUserFactory(email=self.test_email) self.user.set_password(self.test_password) self.user.is_active = True self.user.save() self.login_url = reverse('api:auth:login') def test_user_cannot_login_with_wrong_email(self): email = faker.email() data = { 'email': email, 'password': self.test_password, } response = client.post(self.login_url, data=data) self.assertEqual(response.status_code, 400) def test_user_cannot_login_with_wrong_password(self): password = faker.password() data = { 'email': self.user.email, 'password': password, } response = client.post(self.login_url, data=data) self.assertEqual(response.status_code, 400) def test_user_cannot_login_if_account_is_inactive(self): self.user.is_active = False self.user.save() data = { 'email': self.user.email, 'password': self.test_password, } response = client.post(self.login_url, data=data) self.assertEqual(response.status_code, 400) @patch('odin.authentication.apis.get_user_data') def test_user_can_login_when_account_is_active(self, mock_object): mock_object.return_value = {} data = { 'email': self.user.email, 'password': self.test_password, } response = self.post(self.login_url, data=data) self.assertTrue(mock_object.called) self.assertEqual(response.status_code, 200)
class LogoutApiTest(TestCase): def setUp(self): self.test_email = faker.email() self.test_password = faker.password() self.user = BaseUserFactory(email=self.test_email) self.user.set_password(self.test_password) self.user.is_active = True self.user.save() self.login_url = reverse('api:auth:login') self.logout_url = reverse('api:auth:logout') self.data = { 'email': self.test_email, 'password': self.test_password, } @patch('odin.authentication.apis.get_user_data') def test_logout_user_with_invalid_token(self, mock_object): mock_object.return_value = {} # performing api login login_response = client.post(self.login_url, data=self.data) token = login_response.data['token'] # that should invalidate all generated tokens until now self.user.rotate_secret_key() # try to perform api logout logout_response = client.post(self.logout_url, **{'HTTP_AUTHORIZATION': f'JWT {token}'}) self.assertEqual(logout_response.status_code, 401) @patch('odin.authentication.apis.get_user_data') @patch('odin.authentication.apis.logout') def test_logout_user_with_valid_token(self, mock1, mock2): mock2.return_value = {} # performing api login login_response = client.post(self.login_url, data=self.data) token = login_response.data['token'] # try to perform api logout logout_response = client.post(self.logout_url, **{'HTTP_AUTHORIZATION': f'JWT {token}'}) self.assertEqual(mock1.called, True) self.assertEqual(mock2.called, True) self.assertEqual(logout_response.status_code, 202)
class TestUserDetailApi(TestCase): def setUp(self): self.test_email = faker.email() self.test_password = faker.password() self.user = BaseUserFactory(email=self.test_email) self.user.set_password(self.test_password) self.user.is_active = True self.user.save() self.login_url = reverse('api:auth:login') self.user_detail_url = reverse('api:auth:user-detail') self.data = { 'email': self.test_email, 'password': self.test_password, } @patch('odin.authentication.apis.get_user_data') def test_cannot_fetch_user_data_with_invalid_token(self, mock_object): mock_object.return_value = {} # perform login login_response = client.post(self.login_url, data=self.data) token = login_response.data['token'] # that should invalidate all previously aquired tokens self.user.rotate_secret_key() me_response = client.get(self.user_detail_url, **{'HTTP_AUTHORIZATION': f'JWT {token}'}) self.assertTrue(mock_object.called) self.assertEqual(me_response.status_code, 401) self.assertEqual(me_response.data['errors'][0]['code'], 'authentication_failed') @patch('odin.authentication.apis.get_user_data') def test_can_fetch_use_data_with_valid_token(self, mock_object): mock_object.return_value = {} # perform login login_response = client.post(self.login_url, data=self.data) token = login_response.data['token'] me_response = client.get(self.user_detail_url, **{'HTTP_AUTHORIZATION': f'JWT {token}'}) self.assertTrue(mock_object.called) self.assertEqual(me_response.status_code, 200)
def test_user_can_decode_only_own_tokens(self): response1 = self.post(self.login_url, data=self.data) user = BaseUserFactory() user.is_active = True user.passwd = faker.password() user.set_password(user.passwd) user.save() data = { 'email': user.email, 'password': user.passwd, } response2 = self.post(self.login_url, data=data) token_user1 = response1.data['token'] token_user2 = response2.data['token'] self.assertNotEqual(token_user1, token_user2) self.assertNotEqual(self.user.secret_key, user.secret_key) with self.assertRaises(InvalidSignatureError): jwt.decode(token_user1, key=str(user.secret_key)) with self.assertRaises(InvalidSignatureError): jwt.decode(token_user2, key=str(self.user.secret_key)) self.assertEqual( self.user.email, jwt.decode(token_user1, key=str(self.user.secret_key))['email'] ) self.assertEqual( user.email, jwt.decode(token_user2, key=str(user.secret_key))['email'] )
class TestJWTSecret(TestCase): def setUp(self): self.test_user_email = faker.email() self.test_password = faker.password() self.user = BaseUserFactory(email=self.test_user_email) self.user.set_password(self.test_password) self.user.is_active = True self.user.save() self.init_secret_key = self.user.secret_key self.login_url = reverse('api:auth:login') self.logout_url = reverse('api:auth:logout') self.user_detail_url = reverse('api:auth:user-detail') self.data = { 'email': self.user.email, 'password': self.test_password, } def test_user_can_decode_only_own_tokens(self): response1 = self.post(self.login_url, data=self.data) user = BaseUserFactory() user.is_active = True user.passwd = faker.password() user.set_password(user.passwd) user.save() data = { 'email': user.email, 'password': user.passwd, } response2 = self.post(self.login_url, data=data) token_user1 = response1.data['token'] token_user2 = response2.data['token'] self.assertNotEqual(token_user1, token_user2) self.assertNotEqual(self.user.secret_key, user.secret_key) with self.assertRaises(InvalidSignatureError): jwt.decode(token_user1, key=str(user.secret_key)) with self.assertRaises(InvalidSignatureError): jwt.decode(token_user2, key=str(self.user.secret_key)) self.assertEqual( self.user.email, jwt.decode(token_user1, key=str(self.user.secret_key))['email'] ) self.assertEqual( user.email, jwt.decode(token_user2, key=str(user.secret_key))['email'] ) def test_user_can_access_urls_with_token_only_after_login(self): self.response = self.post(self.login_url, data=self.data) token = self.response.data['token'] response = client.get(self.user_detail_url, **{'HTTP_AUTHORIZATION': f'JWT {token}'}) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['email'], self.user.email) def test_user_cannot_use_old_token_after_logout(self): self.response = self.post(self.login_url, data=self.data) token = self.response.data['token'] # performs logout with jwt client.post(self.logout_url, **{'HTTP_AUTHORIZATION': f'JWT {token}'}) response = client.get(self.user_detail_url, **{'HTTP_AUTHORIZATION': f'JWT {token}'}) self.assertEqual(response.status_code, 401) def test_user_gets_new_user_secret_key_after_logout(self): self.response = self.post(self.login_url, data=self.data) token = self.response.data['token'] # performs logout with jwt client.post(self.logout_url, **{'HTTP_AUTHORIZATION': f'JWT {token}'}) self.user.refresh_from_db() self.assertNotEqual(self.init_secret_key, self.user.secret_key)
class TestUserChangePasswordApi(TestCase): def setUp(self): self.test_email = faker.email() self.test_password = faker.password() self.user = BaseUserFactory(email=self.test_email) self.user.set_password(self.test_password) self.user.save() self.login_url = reverse('api:auth:login') self.logout_url = reverse('api:auth:logout') self.change_password_url = reverse('api:auth:change-password') self.data = { 'email': self.test_email, 'password': self.test_password, } @patch('odin.authentication.apis.get_user_data') def test_logged_out_user_cannot_change_password(self, mock_object): mock_object.return_value = {} self.user.is_active = True self.user.save() # this should perform login login_response = client.post(self.login_url, data=self.data) token = login_response.data['token'] # perform logout client.post(self.logout_url, **{'HTTP_AUTHORIZATION': f'JWT {token}'}) data = { "old_password": self.test_password, "new_password": faker.password(), } # this should perform change password change_password_response = client.post( self.change_password_url, json.dumps(data), **{ 'HTTP_AUTHORIZATION': f'JWT {token}', 'content_type': 'application/json' }, ) self.assertEqual(change_password_response.status_code, 401) self.assertEqual(change_password_response.data['errors'][0]['code'], 'authentication_failed') @patch('odin.authentication.apis.get_user_data') def test_inactive_user_cannot_change_password(self, mock_object): mock_object.return_value = {} self.user.is_active = True self.user.save() # this should perform login login_response = client.post(self.login_url, data=self.data) token = login_response.data['token'] self.user.is_active = False self.user.save() data = { 'old_password': self.test_password, 'new_password': faker.password(), } # this should perform change password change_password_response = client.post( self.change_password_url, json.dumps(data), **{ 'HTTP_AUTHORIZATION': f'JWT {token}', 'content_type': 'application/json' }, ) self.assertEqual(change_password_response.status_code, 401) self.assertEqual(change_password_response.data['errors'][0]['message'], 'User account is disabled.') @patch('odin.authentication.apis.get_user_data') def test_user_cannot_change_password_with_invalid_token(self, mock_object): mock_object.return_value = {} self.user.is_active = True self.user.save() # this should perform login login_response = client.post(self.login_url, data=self.data) token = login_response.data['token'] self.user.rotate_secret_key() data = { 'old_password': self.test_password, 'new_password': faker.password(), } # this should perform change password change_password_response = client.post( self.change_password_url, json.dumps(data), **{ 'HTTP_AUTHORIZATION': f'JWT {token}', 'content_type': 'application/json' }, ) self.assertEqual(change_password_response.status_code, 401) self.assertEqual(change_password_response.data['errors'][0]['code'], 'authentication_failed') @patch('odin.authentication.apis.get_user_data') def test_user_cannot_change_password_with_wrong_old_password( self, mock_object): mock_object.return_value = {} self.user.is_active = True self.user.save() # this should perform login login_response = client.post(self.login_url, data=self.data) token = login_response.data['token'] data = { "old_password": faker.password(), "new_password": faker.password(), } # this should perform change password change_password_response = client.post( self.change_password_url, json.dumps(data), **{ 'HTTP_AUTHORIZATION': f'JWT {token}', 'content_type': 'application/json' }, ) self.assertEqual(change_password_response.status_code, 400) self.assertEqual(change_password_response.data['errors'][0]['message'], 'Old password is invalid.') @patch('odin.authentication.apis.change_user_password') @patch('odin.authentication.apis.get_user_data') def test_active_user_can_change_password_with_valid_token( self, mock1, mock2): mock1.return_value = {} self.user.is_active = True self.user.save() # this should perform login login_response = client.post(self.login_url, data=self.data) token = login_response.data['token'] data = { "old_password": self.test_password, "new_password": faker.password(), } # this should perform change password change_password_response = client.post( self.change_password_url, json.dumps(data), **{ 'HTTP_AUTHORIZATION': f'JWT {token}', 'content_type': 'application/json' }, ) self.assertTrue(mock2.called) self.assertEqual(change_password_response.status_code, 202)