def test_with_defective_session(self): """ Test with defective session data """ reg = str(self.pend_reg.uuid) key = Signer(salt=reg).sign(self.pend_reg.key) test_items = ( # pw, otp, reg, key ('reallyC0olPa$$_w0RD!', get_otp(self.user.get_totp_secret()), reg, key), ('reallyC0olPa$$_w0RD!', get_otp(self.user.get_totp_secret()), reg, key + 'x'), ('reallyC0olPa$$_w0RD!', get_otp(self.user.get_totp_secret()), str(uuid4()), key), ) for password, otp, reg, key in test_items: with self.subTest( msg='Testing with password={}, otp={}, reg={}, key={}'. format(password, otp, reg, key)): response = self.client.post('/hub/register/step-2', data={ 'reg': reg, 'key': key, 'password1': password, 'password2': password, 'otp': otp }) self.assertTrue( '<h2>Ooh ooh…</h2>' in response.content.decode('utf-8'))
def test_login_failed(self): """ Tests for the bad cases """ test_items = ( # user, passw, otp, expected ('', '', '', 'This field is required.'), # Empty Strings ('user1', 'password1', 'WRONG1', 'Only numbers allowed.'), # Everything wrong (self.username, 'password2', 'WRONG2', 'Only numbers allowed.'), # Username OK ('user3', self.password, 'WRONG3', 'Only numbers allowed.'), # Password OK ('user4', 'password4', get_otp(self.otp_secret), 'Username or password wrong, or one time password invalid.' ), # OTP ok (self.username, self.password, 'WRONG5', 'Only numbers allowed.'), # OTP invalid (self.username, self.password, get_otp(self.otp_secret, -3), 'Username or password wrong, or one time password invalid.' ), # OTP too old (self.username, self.password, get_otp(self.otp_secret, +3), 'Username or password wrong, or one time password invalid.' ), # OTP too young (self.username, self.password, get_otp(self.otp_secret + b'1'), 'Username or password wrong, or one time password invalid.' ), # OTP not matching to secret ) for username, password, otp, expected in test_items: with self.subTest( msg='Testing user="******", password="******", otp="{}"'.format( username, password, otp)): self.client.logout() response = self.client.get('/hub/auth/login') self.assertEqual(200, response.status_code) response = self.client.post('/hub/auth/login', { 'username': username, 'password': password, 'otp': otp }) self.assertEqual(200, response.status_code) soup = BeautifulSoup(response.content.decode('utf-8'), 'html.parser').find( 'form', attrs={ 'data-ui-relevance': 'main-login' }).find_next('div', attrs={ 'role': 'alert', 'class': 'alert alert-danger' }) self.assertEqual(expected, soup.text.strip())
def test_login_as_superuser_roundtrip(self): """ Roundtrip with superuser """ with firefox_webdriver_factory() as firefox: firefox.get('{}/admin/auth/group/'.format(self.live_server_url)) self.assertEqual( '{}/hub/auth/login?next=/admin/login/%3Fnext%3D/admin/auth/group/' .format(self.live_server_url), firefox.current_url) form = firefox.find_element_by_xpath( '//form[@data-ui-relevance="main-login"]') # Check the next next_url = form.find_element_by_css_selector( 'input[type="hidden"][name="next"]') self.assertEqual('/admin/login/?next=/admin/auth/group/', next_url.get_attribute('value')) # fill out the form form.find_element_by_css_selector( 'input[name="username"]').send_keys( 'superuser_admin_redirect_test') form.find_element_by_css_selector( 'input[name="password"]').send_keys( 'my_super_duper_complex_p455s0RD!') otp = str( get_otp( b'SuperUsersTestSecretForASimpleLoginTestAgainstTheAdminArea!' )) form.find_element_by_css_selector('input[name="otp"]').send_keys( otp) # do the login form.find_element_by_css_selector('button[type="submit"]').click() # Are we there? self.assertEqual('{}/admin/'.format(self.live_server_url), firefox.current_url)
def test_with_next(self): """ Test with existing "next" """ response = self.client.get('/hub/auth/login', data={ 'next': self.next_url }, follow=True) self.assertEqual(200, response.status_code) self._check_next_url_in_source(response.content) response = self.client.post('/hub/auth/login', data={ 'username': '******', 'password': '******', 'otp': '123456', 'next': self.next_url }, follow=True) self.assertEqual(200, response.status_code) self._check_next_url_in_source(response.content) response = self.client.post('/hub/auth/login', data={ 'username': self.username, 'password': self.password, 'otp': get_otp(self.secret), 'next': self.next_url }, follow=False) self.assertEqual(302, response.status_code) self.assertEqual(self.next_url, response.url)
def __login(self, user: HubUser): """ Log a user in """ res = self.client.login(username=user.username, password='******', one_time_pw=get_otp(self.secret)) # nosec self.assertTrue(res)
def test_logged_in(self): """ Test with a Login """ self.client.login(username=self.test_username, password=self.test_password, one_time_pw=get_otp(self.test_secret)) self._check_result(403)
def _check_new_otp(self, link: str): """ Check the new OTP setup """ extracted_link = self.__extract_link(link) user_id = PendingCredentialRecovery.objects.get(uuid=extracted_link['uuid']).user.id self.__open_action(extracted_link) # The bad ones test_items = ( 'doctored-auth', '' ) for auth in test_items: self.client.post( '/hub/auth/forgot-credentials/step-3/{}/reveal-new-otp-secret'.format(extracted_link['uuid']), data={ 'auth': auth, }, follow=True ) otp_ready_response = self.client.post( '/hub/auth/forgot-credentials/step-3/{}/reveal-new-otp-secret'.format(extracted_link['uuid']), data={ 'auth': extracted_link['auth'], }, follow=True ) self.assertEqual(200, otp_ready_response.status_code) internal_session_data = str(b64decode(self.client.session.model.objects.first().session_data).decode('utf-8')) internal_session_data = internal_session_data[internal_session_data.index(':')+1:] internal_session_data = loads(internal_session_data) encrypted_otp_secret = b64decode(internal_session_data['encrypted_temporary_otp_secret']) new_otp_secret = SymmetricCrypt().decrypt(encrypted_otp_secret) # And the bad OTPs fist test_items = ( # auth, otp (extracted_link['auth'], ''), (extracted_link['auth'], '999999'), ) for auth, otp in test_items: self.client.post( '/hub/auth/forgot-credentials/step-3/{}/reveal-new-otp-secret/confirm'.format(extracted_link['uuid']), data={ 'auth': auth, 'otp': otp }, follow=True ) otp_confirm_response = self.client.post( '/hub/auth/forgot-credentials/step-3/{}/reveal-new-otp-secret/confirm'.format(extracted_link['uuid']), data={ 'auth': extracted_link['auth'], 'otp': get_otp(new_otp_secret) }, follow=True ) self.assertEqual(200, otp_confirm_response.status_code) self.assertEqual(0, PendingCredentialRecovery.objects.count()) self.assertEqual(new_otp_secret, HubUser.objects.get(id=user_id).get_totp_secret())
def test_round_trip(self): """ Test the complete round-trip with wrong inputs and finally a good one """ self.client.login(username=self.normal_user.username, password=self.password, one_time_pw=get_otp(self.secret)) self._open() wrong_tries = ( # old, new1, new2 ('', '', ''), (self.password, '', ''), (self.password, 'cool_new!Passw0RD!', ''), ('', 'cool_new!Passw0RD!', ''), ('', 'cool_new!Passw0RD!', 'cool_new!Passw0RD!'), ('wrong_password', 'cool_new!Passw0RD!', 'cool_new!Passw0RD!'), (self.password, 'cool_new!Passw0RD!-1', 'cool_new!Passw0RD!-2'), (self.password, '0123456789', '0123456789'), ('', '0123456789', '0123456789'), (self.password, 'testtest', 'testtest'), ('', 'testtest', 'testtest'), (self.password, self.normal_user.username + '_1!', self.normal_user.username + '_1!'), ) for old, new1, new2 in wrong_tries: with self.subTest( msg='Testing with old="{}", new1="{}", new2="{}"'.format( old, new1, new2)): response = self.client.post(self.url, data={ 'old_password': old, 'new_password1': new1, 'new_password2': new2, }, follow=False) self.assertEqual(200, response.status_code) self.assertTrue( HubUser.objects.get( username=self.normal_user.username).check_password( self.password)) with self.subTest(msg='Testing with good data'): response = self.client.post(self.url, data={ 'old_password': self.password, 'new_password1': 'cool_new!Passw0RD!', 'new_password2': 'cool_new!Passw0RD!', }, follow=False) self.assertEqual(302, response.status_code) self.assertEqual( '/hub/auth/login?next=/hub/my-account/credentials/password', response.url) self.assertTrue( HubUser.objects.get(username=self.normal_user.username). check_password('cool_new!Passw0RD!'))
def test_admin_user(self): """ Test access with a normal user """ self.client.login(username=self.admin_user.username, password=self.password, one_time_pw=get_otp(self.secret)) response = self._open() self.assertEqual(200, response.status_code)
def _login(self): """ Log the client in """ self.assertTrue( self.client.login( username=self.test_superuser_username, password=self.test_superuser_password, one_time_pw=get_otp(self.test_superuser_secret), ))
def test_wrong_credentials_password(self): """ User with wrong credentials is not allowed to create a recovery, but we don't tell """ test_items = ( # email, otp, username ('*****@*****.**', get_otp(self.secret, 1), 'bad_username'), ('*****@*****.**', get_otp(self.secret, -10), 'active_user'), ('*****@*****.**', get_otp(self.secret, 1), 'active_user'), ) for email, otp, username in test_items: with self.subTest(msg='Testing E-Mail="{}", OTP="{}", Username="******"'.format(email, otp, username)): self._start_session('password') response = self._send_partial_credentials('password', { 'email': email, 'otp': otp, 'username': username, }) self._check_no_success(response)
def test_duplicate_email_user_password(self): """ User with a non-unique e-mail address is not allowed to create a recovery, and we tell """ self._start_session('password') response = self._send_partial_credentials('password', { 'email': '*****@*****.**', 'otp': get_otp(self.secret), 'username': '******', }) self._check_no_success_duplicate(response)
def test_pending_registration_user_password(self): """ User with a pending registration is not allowed to create a recovery, but we don't tell """ self._start_session('password') response = self._send_partial_credentials('password', { 'email': '*****@*****.**', 'otp': get_otp(self.secret), 'username': '******', }) self._check_no_success(response)
def test_login_twice(self): """ Try to login if already logged in """ self.assertTrue( self.client.login(username=self.username, password=self.password, one_time_pw=get_otp(self.otp_secret))) response = self.client.get('/hub/auth/login') self.assertEqual(302, response.status_code) self.assertEqual('/hub/auth/login', response.url)
def test_inactive_user_password(self): """ Inactive user is not allowed to create a recovery, but we don't tell """ self._start_session('password') response = self._send_partial_credentials('password', { 'email': '*****@*****.**', 'otp': get_otp(self.secret), 'username': '******', }) self._check_no_success(response)
def test_active_user_username_recovery_happy_path(self): """ Test with an active user a password recovery """ self._start_session('username') response = self._send_partial_credentials('username', { 'email': '*****@*****.**', 'password': self.password, 'otp': get_otp(self.secret) }) link = self._check_success(response, 'Active', '*****@*****.**') self._check_username_reveal(link, 'active_user')
def test_logged_in(self): """ Test with a logged in user """ self.assertTrue( self.client.login( # nosec username='******', password='******', one_time_pw=get_otp( b'MyVerySecretSecretSecretOnlyForThisTestSecretSecret'))) links = self._get_links() self._test_links(links, [settings.MY_ACCOUNT_URL, settings.LOGOUT_URL])
def _login_to_admin(self, webdriver: WebDriver): """ Login to admin """ webdriver.get('{}/admin/'.format(self.live_server_url)) form = webdriver.find_element_by_css_selector( 'form[data-ui-relevance="main-login"]') form.find_element_by_css_selector('input[name="username"]').send_keys( self.admin_username) form.find_element_by_css_selector('input[name="password"]').send_keys( self.admin_password) form.find_element_by_css_selector('input[name="otp"]').send_keys( str(get_otp(self.admin_secret))) form.find_element_by_css_selector('button[type="submit"]').click()
def test_round_trip(self): """ Test a round trip """ self.client.login(username=self.normal_user.username, password=self.password, one_time_pw=get_otp(self.secret)) self._open() current_first = self.normal_user.first_name current_last = self.normal_user.last_name wrong_data = ( # first_name, last_name ('', ''), ('', 'Drebin')) for first, last in wrong_data: with self.subTest( msg='Testing bad first_name="{}", last_name="{}"'.format( first, last)): response = self.client.post(self.url, { 'first_name': first, 'last_name': last }) self.assertEqual(200, response.status_code) self._compare(current_first, current_last) good_data = ( # first_name, last_name ('Frank', 'Drebin'), ('A', 'Men'), ('A', ''), ('Frank', ''), ('Frank', 'Summerland'), ) for first, last in good_data: with self.subTest( msg='Testing good first_name="{}", last_name="{}"'.format( first, last)): response = self.client.post(self.url, { 'first_name': first, 'last_name': last }) self.assertEqual(200, response.status_code) self._compare(first, last)
def test_login_success_and_logout(self): """ Test for the good case """ response = self.client.get('/hub/auth/login') self.assertEqual(200, response.status_code) response = self.client.post( '/hub/auth/login', { 'username': self.username, 'password': self.password, 'otp': get_otp(self.otp_secret) }) self.assertEqual(302, response.status_code) self.assertEqual('/hub/', response.url) self._check_message(response.url, 'Hey Test-Franz, welcome to Passiopeia Hub!') response = self.client.get('/hub/auth/logout') self.assertEqual(302, response.status_code) self.assertEqual('/hub/', response.url) self._check_message(response.url, 'Logout successful.')
def test_round_trip(self): """ Test the complete round trip """ self.client.login(username=self.normal_user.username, password=self.password, one_time_pw=get_otp(self.secret)) self._open() current_email = self.normal_user.email new_email = '*****@*****.**' wrong_data = ( # email '', ' ', 'doo@', '@doo', 'doo@doo@doo', 'doo@@doo.de', '*****@*****.**', 'doo@doo.', '*****@*****.**', '[email protected].', '.doo#[email protected]', '[email protected]') for email in wrong_data: with self.subTest( msg='Testing with bad e-mail "{}"'.format(email)): response = self.client.post(self.url, data={'new_email': email}, follow=False) self.assertEqual(0, PendingEMailChange.objects.count()) self.assertEqual(200, response.status_code) self.assertEqual(0, len(mail.outbox)) self.assertEqual( current_email, HubUser.objects.get(id=self.normal_user.id).email) with self.subTest('Now with a good e-mail address'): response = self.client.post(self.url, data={'new_email': new_email}, follow=False) self.assertEqual(200, response.status_code) self.assertEqual(new_email, PendingEMailChange.objects.first().new_email) self.assertEqual(1, len(mail.outbox)) self.assertEqual(current_email, HubUser.objects.get(id=self.normal_user.id).email) self.assertEqual(1, PendingEMailChange.objects.count()) more_tries = ('*****@*****.**', '*****@*****.**', '*****@*****.**') for good in more_tries: with self.subTest( 'Now more good ones that get rejected: "{}"'.format(good)): self._open() response = self.client.post(self.url, data={'new_email': good}, follow=False) self.assertEqual(1, len(mail.outbox)) self.assertEqual(1, PendingEMailChange.objects.count()) self.assertEqual(200, response.status_code) self.assertEqual( current_email, HubUser.objects.get(id=self.normal_user.id).email) self.assertEqual(new_email, PendingEMailChange.objects.first().new_email) with self.subTest(msg='Checking E-Mail'): confirm_mail = mail.outbox[0] # type: EmailMessage self.assertEqual(settings.EMAIL_VERIFICATION_SUBJECT, confirm_mail.subject) self.assertEqual(settings.EMAIL_VERIFICATION_FROM, confirm_mail.from_email) self.assertEqual([new_email], confirm_mail.to) self.assertTrue('Hi Frank!' in str(confirm_mail.body)) extract_link_regex = re.compile( r'.*(?P<url>http(s)?://.*/personal/email/.*?)\s.*', re.MULTILINE | re.UNICODE | re.DOTALL) match = extract_link_regex.match(str(confirm_mail.body)) self.assertIsNotNone(match) next_link = match.group('url') self.assertIsNotNone(next_link) uuid = str(PendingEMailChange.objects.first().uuid) bad_links = ( reverse('ha:acc:personal.email.verify', kwargs={'change': uuid}) + '?change_key=Moeoeoeb', reverse('ha:acc:personal.email.verify', kwargs={'change': uuid}) + '?change_key=IsVeryLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong' 'LongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong' 'LongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong', reverse('ha:acc:personal.email.verify', kwargs={'change': str(uuid4())}) + '?change_key=IsVeryLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong' 'LongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong' 'LongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong', reverse('ha:acc:personal.email.verify', kwargs={'change': str(uuid4())}) + '?no_change_key=IsVeryLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong' 'LongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong' 'LongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong', ) for bad_link in bad_links: with self.subTest(msg='Call bad link "{}"'.format(bad_link)): response = self.client.get(bad_link, follow=False) self.assertEqual(200, response.status_code) self.assertIn('This link seems to be invalid.', response.content.decode('utf-8')) with self.subTest(msg='Call next link "{}"'.format(next_link)): response = self.client.get(next_link, follow=False) self.assertEqual(200, response.status_code) self.assertIn('click the following button to confirm', response.content.decode('utf-8')) with self.subTest(msg='Check if database is still OK'): self.assertEqual(1, PendingEMailChange.objects.count()) self.assertEqual(new_email, PendingEMailChange.objects.first().new_email) self.assertEqual(1, len(mail.outbox)) self.assertEqual(current_email, HubUser.objects.get(id=self.normal_user.id).email) bad_uuid = str(uuid4()) bad_confirms = ( # uuid, change_key (uuid, ''), (uuid, 'bad_key'), (bad_uuid, ''), (bad_uuid, 'bad_key'), (uuid, Signer(salt=uuid).sign(get_email_key())), (bad_uuid, Signer(salt=bad_uuid).sign(get_email_key())), (uuid, Signer(salt='01').sign(get_email_key())), (bad_uuid, Signer(salt='12').sign(get_email_key())), ) for bad_uuid, bad_change_key in bad_confirms: with self.subTest( 'Testing failing confirmation with UUID="{}" and change_key="{}"' .format(bad_uuid, bad_change_key)): response = self.client.post( reverse('ha:acc:personal.email.verify', kwargs={'change': bad_uuid}), data={'change_key': bad_change_key}, follow=False) self.assertEqual(200, response.status_code) self.assertEqual( current_email, HubUser.objects.get(id=self.normal_user.id).email) self.assertEqual(1, PendingEMailChange.objects.count()) self.assertEqual(new_email, PendingEMailChange.objects.first().new_email) self.assertEqual(1, len(mail.outbox)) with self.subTest(msg='Confirming'): url_parts = parse_url(next_link) end_point = url_parts.path confirm_key = unquote(url_parts.query[11:]) response = self.client.post(end_point, {'change_key': confirm_key}, follow=False) self.assertEqual(200, response.status_code) self.assertIn('Your E-Mail Address has been verified.', response.content.decode('utf-8')) self.assertEqual(0, PendingEMailChange.objects.count()) self.assertEqual(new_email, HubUser.objects.get(id=self.normal_user.id).email)
def test_round_trip(self): """ Test the complete Round Trip with wrong inputs and finally a good one """ self.client.login(username=self.normal_user.username, password=self.password, one_time_pw=get_otp(self.secret)) old_secret = self.normal_user.get_totp_secret() self._open() test_otps = ('', '1', '22', '333', '4444', '55555', '666666', 'aaaaaa', 'a1b2c3') for test_otp in test_otps: with self.subTest( msg='Sending OTP "{}" without creating a secret before'. format(test_otp)): response = self.client.post(self.url, data={'otp_confirm': test_otp}, follow=False) self.assertEqual(200, response.status_code) self.assertEqual( old_secret, HubUser.objects.get( id=self.normal_user.id).get_totp_secret()) for expect in (True, False): with self.subTest( msg='Creating a secret, expecting "{}"'.format(expect)): response = self.client.put(self.url) self.assertEqual(200, response.status_code) self.assertJSONEqual(response.content, {'secret_created': expect}) self.assertEqual( old_secret, HubUser.objects.get( id=self.normal_user.id).get_totp_secret()) session_data = str( b64decode( self.client.session.model.objects.first().session_data).decode( 'utf-8')) session_data = loads(session_data[session_data.index(':') + 1:]) new_otp_secret = SymmetricCrypt().decrypt( b64decode(session_data['encrypted_new_totp_secret'])) test_otps = [ '', '1', '22', '333', '4444', '55555', 'aaaaaa', 'a1b2c3' ] + [get_otp(new_otp_secret, -10)] for test_otp in test_otps: with self.subTest( msg='Sending OTP "{}" which is wrong'.format(test_otp)): response = self.client.post(self.url, data={'otp_confirm': test_otp}, follow=False) self.assertEqual(200, response.status_code) self.assertEqual( old_secret, HubUser.objects.get( id=self.normal_user.id).get_totp_secret()) with self.subTest(msg='Testing with correct OTP'): response = self.client.post( self.url, data={'otp_confirm': get_otp(new_otp_secret)}, follow=False) self.assertEqual(200, response.status_code) self.assertNotEqual( old_secret, HubUser.objects.get(id=self.normal_user.id).get_totp_secret()) self.assertEqual( new_otp_secret, HubUser.objects.get(id=self.normal_user.id).get_totp_secret())
def __get_invalid_otp(): """ Get an invalid TOTP """ new_user = HubUser.objects.get(username='******') return get_otp(new_user.get_totp_secret(), -10)