def test_change_primary_method(active_user_with_many_otp_methods): client = APIClient() first_step = login(active_user_with_many_otp_methods) first_primary_method = \ active_user_with_many_otp_methods.mfa_methods.filter( is_primary=True, )[0] login_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(first_primary_method.secret), }, format='json', ) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) response = client.post( path='/auth/mfa/change-primary-method/', data={ 'method': 'sms', 'code': create_otp_code(first_primary_method.secret), }, format='json', ) new_primary_method = active_user_with_many_otp_methods.mfa_methods.filter( is_primary=True, )[0] assert response.status_code == 200 assert first_primary_method != new_primary_method assert new_primary_method.name == 'sms'
def test_change_primary_method_to_inactive(active_user_with_email_otp): client = APIClient() first_step = login(active_user_with_email_otp) first_primary_method = \ active_user_with_email_otp.mfa_methods.filter( is_primary=True, )[0] login_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(first_primary_method.secret), }, format='json', ) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) response = client.post( path='/auth/mfa/change-primary-method/', data={ 'method': 'sms', 'code': create_otp_code(first_primary_method.secret), }, format='json', ) error_code = 'missing_method' assert response.status_code == 400 assert response.data.get('non_field_errors')[0].code == error_code
def test_backup_codes_regeneration(active_user_with_backup_codes): client = APIClient() first_step = login(active_user_with_backup_codes) first_primary_method = active_user_with_backup_codes.mfa_methods.first() old_backup_codes = first_primary_method.backup_codes login_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(first_primary_method.secret), }, format='json', ) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) response = client.post( path='/auth/email/codes/regenerate/', data={ 'code': create_otp_code(first_primary_method.secret), }, format='json', ) new_backup_codes = \ active_user_with_backup_codes.mfa_methods.first().backup_codes assert response.status_code == 200 assert old_backup_codes != new_backup_codes
def test_2fa_integration(user, logged_in_client, client): mfa_activate_url = reverse("v0:mfa-activate", args=["app"]) # check that url present mfa_confirm = reverse("v0:mfa-activate-confirm", args=["app"]) mfa_backup_codes = reverse("v0:mfa-regenerate-codes", args=["app"]) mfa_deactivate = reverse("v0:mfa-deactivate", args=["app"]) # Step 1: activate MFA response = logged_in_client.post(mfa_activate_url) assert response.status_code == 200 qr_link = response.data.get("qr_link") assert qr_link assert MFAMethod.objects.filter(user=user).count() == 1 secret = create_secret() # Step 2: confirm MFA (mocked step) assert not MFAMethod.objects.filter( user=user, is_active=True, is_primary=True).exists() MFAMethod.objects.filter(user=user).update(secret=secret) response = logged_in_client.post(mfa_confirm, {"code": create_otp_code(secret)}) assert response.status_code == 200 assert response.data["backup_codes"] assert MFAMethod.objects.filter(user=user, is_active=True, is_primary=True).exists() # Step 3: trying to login payload = {"email": user.email, "password": TEST_PASSWORD} response = client.post(GENERATE_CODE_URL, payload) assert response.status_code == 200 ephemeral_token = response.data["ephemeral_token"] payload = { "ephemeral_token": ephemeral_token, "code": create_otp_code(secret) } # Step 4: login with otp data response = client.post(GENERATE_TOKEN_URL, payload) assert response.status_code == 200 assert response.data["access"] client.defaults["HTTP_AUTHORIZATION"] = f"Bearer {response.data['access']}" # Step 5: request backup codes response = client.post(mfa_backup_codes, {"code": create_otp_code(secret)}) assert response.status_code == 200 assert response.data["backup_codes"] # Step 6: deactivate code = response.data["backup_codes"][0] response = client.post(mfa_deactivate, {"code": code}) assert response.status_code == 204 assert MFAMethod.objects.filter(name="app").exists() assert not MFAMethod.objects.get(name="app").is_active
def test_2fa_login_with_2fa(self): """User can log in when 2fa is active and authorised call succeeds""" # Get ephemeral token response = self.client.post('/v1/auth/login/', { "email": '*****@*****.**', "password": "******" }) self.assertEqual(response.status_code, 200) self.assertEqual("ephemeral_token" in response.data, True) # Create a code MFAMethod = apps.get_model('trench.MFAMethod') MFA = MFAMethod.objects.get(user=self.user2, name='email') code = create_otp_code(MFA.secret) # 2FA Login response = self.client.post( '/v1/auth/login/code/', { "ephemeral_token": response.data['ephemeral_token'], "code": code }) self.assertEqual(response.status_code, 200) self.assertEqual("auth_token" in response.data, True) # Check authorised call self.client.credentials(HTTP_AUTHORIZATION="Token " + response.data["auth_token"]) response = self.client.get('/v1/auth/users/me/') self.assertEqual(response.status_code, 200) self.assertEqual(response.data["email"], '*****@*****.**')
def test_new_method_after_deactivation_user_doesnt_have_method( active_user_with_many_otp_methods): client = APIClient() first_step = login(active_user_with_many_otp_methods) first_primary_method = \ active_user_with_many_otp_methods.mfa_methods.filter( is_primary=True, )[0] login_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(first_primary_method.secret), }, format='json', ) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) response = client.post( path='/auth/email/deactivate/', data={'new_primary_method': 'test'}, format='json', ) assert response.status_code == 400 error_code = 'method_not_registered_for_user' assert response.data.get('new_primary_method')[0].code == error_code
def test_user_with_many_methods(active_user_with_many_otp_methods): client = APIClient() initial_active_methods_count = active_user_with_many_otp_methods.mfa_methods.filter( is_active=True).count() first_step = login(active_user_with_many_otp_methods) primary_method = active_user_with_many_otp_methods.mfa_methods.filter( is_primary=True, ) # As user has several methods get first and get sure only 1 is primary assert len(primary_method) == 1 secret = primary_method[0].secret second_step_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(secret), }, format='json', ) # Log in the user in the second step and make sure it is correct assert second_step_response.status_code == 200 client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(second_step_response))) active_methods_response = client.get( path='/auth/mfa/user-active-methods/', ) # This user should have 3 methods, so we check that return has 3 methods assert len(active_methods_response.data) == initial_active_methods_count
def test_backup_codes_regeneration_disabled_method( active_user_with_many_otp_methods, ): client = APIClient() first_step = login(active_user_with_many_otp_methods) first_primary_method = active_user_with_many_otp_methods.mfa_methods.filter( is_primary=True, )[0] sms_method = active_user_with_many_otp_methods.mfa_methods.get( name='sms', ) sms_method.is_active = False sms_method.save() login_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(first_primary_method.secret), }, format='json', ) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) response = client.post( path='/auth/sms/codes/regenerate/', format='json', ) error_msg = 'Method is disabled.' assert response.status_code == 400 assert response.data.get('error') == error_msg
def test_second_method_activation(active_user_with_email_otp): client = APIClient() first_step = login(active_user_with_email_otp) secret = active_user_with_email_otp.mfa_methods.first().secret response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(secret), }, format='json', ) assert response.status_code == 200 client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(response))) # This user should have 1 methods, so we check that it has 1 methods. assert len(active_user_with_email_otp.mfa_methods.all()) == 1 try: response = client.post( path='/auth/sms/activate/', data={ 'phone_number': '555-555-555', }, format='json', ) except (TwilioRestException, TwilioException): # twilio rises this exception in test, but the new mfa_method is # created anyway. pass # Now we check that the user has a new method after the activation. assert len(active_user_with_email_otp.mfa_methods.all()) == 2
def test_request_codes(active_user_with_email_otp): client = APIClient() first_step = login(active_user_with_email_otp) first_primary_method = active_user_with_email_otp.mfa_methods.first() login_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(first_primary_method.secret), }, format='json', ) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) response = client.post( path='/auth/code/request/', data={ 'method': 'email', }, format='json', ) expected_msg = 'Email message with MFA code had been sent.' assert response.status_code == 200 assert response.data.get('message') == expected_msg
def test_confirm_activation_otp(active_user): client = APIClient() login_response = login(active_user) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) client.post( path='/auth/email/activate/', format='json', ) # Until here only make user create a second step confirmation active_user_method = active_user.mfa_methods.first() active_user_method.is_primary = True active_user_method.is_active = True active_user_method.save() # We manually activate the method first_step = login(active_user) secret = active_user.mfa_methods.first().secret code = create_otp_code(secret) response = client.post( path='/auth/email/activate/confirm/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': code, }, format='json', ) # Confirm the response is OK and user gets 5 backup codes assert response.status_code == 200 assert len(response.json().get('backup_codes')) == 5
def test_second_method_activation_already_active(active_user_with_email_otp): client = APIClient() first_step = login(active_user_with_email_otp) secret = active_user_with_email_otp.mfa_methods.first().secret response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(secret), }, format='json', ) assert response.status_code == 200 client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(response))) # This user should have 1 methods, so we check that it has 1 methods. assert len(active_user_with_email_otp.mfa_methods.all()) == 1 response = client.post( path='/auth/email/activate/', format='json', ) error_msg = 'MFA method already active.' assert response.status_code == 400 assert response.data.get('error') == error_msg
def test_deactivation_otp(active_user_with_email_otp): client = APIClient() first_step = login(active_user_with_email_otp) secret = active_user_with_email_otp.mfa_methods.first().secret login_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(secret), }, format='json', ) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) response = client.post( path='/auth/email/deactivate/', data={ 'code': create_otp_code(secret), }, format='json', ) assert response.status_code == 204 assert not active_user_with_email_otp.mfa_methods.first().is_active
def test_wrong_second_step_verification_with_ephemeral_token( active_user_with_email_otp): client = APIClient() first_step = login(active_user_with_email_otp) secret = active_user_with_email_otp.mfa_methods.first().secret response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token') + 'wrong', 'code': create_otp_code(secret), }, format='json', ) assert response.status_code == 400
def test_ephemeral_token_verification_simple_jwt(active_user_with_email_otp): client = APIClient() first_step = login(active_user_with_email_otp) secret = active_user_with_email_otp.mfa_methods.first().secret response = client.post( path='/simplejwt-auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(secret), }, format='json', ) assert response.status_code == 200 assert get_username_from_jwt(response, 'access') == getattr( active_user_with_email_otp, User.USERNAME_FIELD, )
def test_add_user_mfa(active_user): client = APIClient() login_request = login(active_user) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_request))) secret = create_secret() response = client.post( path='/auth/email/activate/', data={ 'secret': secret, 'code': create_otp_code(secret), 'user': getattr( active_user, active_user.USERNAME_FIELD, ) }, format='json', ) assert response.status_code == 200
def test_2fa_activate(self): """User can activate 2fa""" # Login response = self.client.post('/v1/auth/login/', { "email": '*****@*****.**', "password": "******" }) self.client.credentials(HTTP_AUTHORIZATION="Token " + response.data["auth_token"]) # Activate a method response = self.client.post('/v1/auth/email/activate/', {}) self.assertEqual(response.status_code, 200) # Create a code MFAMethod = apps.get_model('trench.MFAMethod') MFA = MFAMethod.objects.get(user=self.user, name='email') code = create_otp_code(MFA.secret) # Confirm the method response = self.client.post('/v1/auth/email/activate/confirm/', {"code": code}) self.assertEqual(response.status_code, 200)
def test_request_code_non_existing_method(active_user_with_email_otp): client = APIClient() first_step = login(active_user_with_email_otp) first_primary_method = active_user_with_email_otp.mfa_methods.first() login_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(first_primary_method.secret), }, format='json', ) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) response = client.post( path='/auth/code/request/', data={ 'method': 'test', }, format='json', ) assert response.status_code == 400
def test_2fa_disable_2fa(self): """User can disable a 2fa method""" # Get ephemeral token response = self.client.post('/v1/auth/login/', { "email": '*****@*****.**', "password": "******" }) # Create a code MFAMethod = apps.get_model('trench.MFAMethod') MFA = MFAMethod.objects.get(user=self.user2, name='email') code = create_otp_code(MFA.secret) # 2FA Login response = self.client.post( '/v1/auth/login/code/', { "ephemeral_token": response.data['ephemeral_token'], "code": code }) self.client.credentials(HTTP_AUTHORIZATION="Token " + response.data["auth_token"]) # Test disable response = self.client.post('/v1/auth/email/deactivate/', {"code": code}) self.assertEqual(response.status_code, 204)
def test_deactivation_otp_already_disabled_method( active_user_with_email_and_inactive_other_methods_otp, ): client = APIClient() first_step = login(active_user_with_email_and_inactive_other_methods_otp) secret = active_user_with_email_and_inactive_other_methods_otp.mfa_methods.first( ).secret # noqa login_response = client.post( path='/auth/login/code/', data={ 'token': first_step.data.get('ephemeral_token'), 'code': create_otp_code(secret), }, format='json', ) client.credentials(HTTP_AUTHORIZATION=header_template.format( get_token_from_response(login_response))) response = client.post( path='/auth/sms/deactivate/', format='json', ) msg_error = 'Method already disabled.' assert response.status_code == 400 assert response.data.get('error') == msg_error
def test_validate_code(active_user_with_email_otp): email_method = active_user_with_email_otp.mfa_methods.get() valid_code = create_otp_code(email_method.secret) assert validate_code("123456", email_method) is False assert validate_code(valid_code, email_method) is True
def create_code(self): return create_otp_code(self.obj.secret)