class ForgotPasswordAPiTests(TestCase): def setUp(self): self.user = BaseUserFactory() self.unknown_user_email = faker.email() self.url = reverse('api:auth:forgot-password-reset') self.data = { 'user': self.user.email } def test_unknown_user_cannot_initiate_password_reset(self): data = { 'user': self.unknown_user_email } response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) def test_inactive_user_cannot_initiate_password_reset(self): response = self.post(self.url, data=self.data) self.assertEqual(response.status_code, 400) @patch('odin.authentication.apis.initiate_reset_user_password') def test_active_user_can_initiate_password_reset(self, mock_object): self.user.is_active = True self.user.save() response = self.post(self.url, data=self.data) self.assertTrue(mock_object.called) self.assertEqual(response.status_code, 202)
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 TestIsStudentOrTeacherPermission(TestCase): def setUp(self): self.user = BaseUserFactory() self.user.is_active = True self.user.save() self.request = Mock() self.request.user = self.user def test_permission_if_user_is_not_student_or_teacher(self): permissions = IsStudentOrTeacherPermission() self.assertFalse(self.user.is_student()) self.assertFalse(self.user.is_teacher()) self.assertFalse(permissions.has_permission(self.request, None)) def test_permission_if_user_is_student(self): student = Student.objects.create_from_user(self.user) student.save() permissions = IsStudentOrTeacherPermission() self.assertTrue(self.user.is_student()) self.assertFalse(self.user.is_teacher()) self.assertTrue(permissions.has_permission(self.request, None)) def test_permission_if_user_is_teacher(self): teacher = Teacher.objects.create_from_user(self.user) teacher.save() permissions = IsStudentOrTeacherPermission() self.assertTrue(self.user.is_teacher()) self.assertFalse(self.user.is_student()) self.assertTrue(permissions.has_permission(self.request, None)) def test_permission_if_user_is_teacher_and_student(self): teacher = Teacher.objects.create_from_user(self.user) teacher.save() student = Student.objects.create_from_user(self.user) student.save() permissions = IsStudentOrTeacherPermission() self.assertTrue(self.user.is_student()) self.assertTrue(self.user.is_teacher()) self.assertTrue(permissions.has_permission(self.request, None))
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)
class InitiateResetUserPasswordTests(TestCase): def setUp(self): self.user = BaseUserFactory() def test_inactive_user_cannot_initiate_password_reset(self): self.user.is_active = False self.user.save() with self.assertRaises(ValidationError): initiate_reset_user_password(user=self.user) @patch('odin.authentication.services.send_mail') def test_active_user_can_initiate_password_reset(self, mock_object): self.user.is_active = True self.user.save() token = initiate_reset_user_password(user=self.user) self.assertTrue(mock_object.called) self.assertEqual(isinstance(token, PasswordResetToken), True) self.assertIsNone(token.voided_at) self.assertIsNone(token.used_at)
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 TestIsStudentOrTeacherInCoursePermission(TestCase): def setUp(self): self.user = BaseUserFactory() self.user.is_active = True self.user.save() self.request = Mock() self.request.user = self.user self.course = CourseFactory() self.view = make_mock_object() self.view.kwargs = {'course_id': self.course.id} def test_permission_if_user_is_not_student_or_teacher_in_course(self): permissions = IsStudentOrTeacherInCoursePermission() self.assertFalse(permissions.has_permission(self.request, self.view)) def test_permission_if_user_is_student_in_course(self): student = Student.objects.create_from_user(self.user) student.save() ca = CourseAssignment() ca.course = self.course ca.student = student ca.save() permissions = IsStudentOrTeacherInCoursePermission() self.assertTrue(self.user.is_student()) self.assertTrue(permissions.has_permission(self.request, self.view)) def test_permission_if_user_is_teacher_in_course(self): teacher = Teacher.objects.create_from_user(self.user) teacher.save() ca = CourseAssignment() ca.course = self.course ca.teacher = teacher ca.save() permissions = IsStudentOrTeacherInCoursePermission() self.assertTrue(self.user.is_teacher()) self.assertTrue(permissions.has_permission(self.request, self.view)) def test_permission_if_user_is_student_and_teacher_in_course(self): teacher = Teacher.objects.create_from_user(self.user) teacher.save() student = Student.objects.create_from_user(self.user) student.save() ca1 = CourseAssignment() ca2 = CourseAssignment() ca1.course = self.course ca1.teacher = teacher ca1.save() ca2.course = self.course ca2.student = student ca2.save() permissions = IsStudentOrTeacherInCoursePermission() self.assertTrue(self.user.is_teacher()) self.assertTrue(self.user.is_student()) self.assertTrue(permissions.has_permission(self.request, self.view))
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)