class TestCoreClient(unittest.TestCase): server_url = TEST_SERVER_URL def setUp(self): self.client = Client(self.server_url) self._accounts_to_delete = [] def tearDown(self): for acct in self._accounts_to_delete: acct.clear() try: stretchpwd = acct.stretchpwd except AttributeError: try: password = acct.password stretchpwd = quick_stretch_password(acct.email, password) except AttributeError: stretchpwd = DUMMY_STRETCHED_PASSWORD self.client.destroy_account(acct.email, stretchpwd=stretchpwd) def test_account_creation(self): acct = TestEmailAccount() acct.password = DUMMY_PASSWORD session = self.client.create_account(acct.email, DUMMY_PASSWORD) self._accounts_to_delete.append(acct) self.assertEqual(session.email, acct.email) self.assertFalse(session.verified) self.assertEqual(session.keys, None) self.assertEqual(session._key_fetch_token, None) with self.assertRaises(Exception): session.fetch_keys() def test_account_creation_with_key_fetch(self): acct = TestEmailAccount() session = self.client.create_account( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, keys=True, ) self._accounts_to_delete.append(acct) self.assertEqual(session.email, acct.email) self.assertFalse(session.verified) self.assertEqual(session.keys, None) self.assertNotEqual(session._key_fetch_token, None) def test_account_login(self): acct = TestEmailAccount() session1 = self.client.create_account( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) self._accounts_to_delete.append(acct) session2 = self.client.login( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) self.assertEqual(session1.email, session2.email) self.assertNotEqual(session1.token, session2.token) def test_get_random_bytes(self): b1 = self.client.get_random_bytes() b2 = self.client.get_random_bytes() self.assertTrue(isinstance(b1, binary_type)) self.assertNotEqual(b1, b2) def test_resend_verify_code(self): acct = TestEmailAccount() session = self.client.create_account( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) self._accounts_to_delete.append(acct) def is_verify_email(m): return "x-verify-code" in m["headers"] m1 = acct.wait_for_email(is_verify_email) code1 = m1["headers"]["x-verify-code"] # NOQA acct.clear() session.resend_email_code() # XXX TODO: this won't work against a live server because we # refuse to send duplicate emails within a short timespan. # m2 = acct.wait_for_email(is_verify_email) # code2 = m2["headers"]["x-verify-code"] # self.assertNotEqual(m1, m2) # self.assertEqual(code1, code2) def test_forgot_password_flow(self): acct = TestEmailAccount() self.client.create_account( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) self._accounts_to_delete.append(acct) # Initiate the password reset flow, and grab the verification code. pftok = self.client.send_reset_code(acct.email, service="foobar") m = acct.wait_for_email(lambda m: "x-recovery-code" in m["headers"]) if not m: raise RuntimeError("Password reset email was not received") acct.clear() code = m["headers"]["x-recovery-code"] # Try with an invalid code to test error handling. tries = pftok.tries_remaining self.assertTrue(tries > 1) with self.assertRaises(Exception): pftok.verify_code(mutate_one_byte(code)) pftok.get_status() self.assertEqual(pftok.tries_remaining, tries - 1) # Re-send the code, as if we've lost the email. pftok.resend_code() m = acct.wait_for_email(lambda m: "x-recovery-code" in m["headers"]) if not m: raise RuntimeError("Password reset email was not received") self.assertEqual(m["headers"]["x-recovery-code"], code) # Now verify with the actual code, and reset the account. artok = pftok.verify_code(code) self.client.reset_account( email=acct.email, token=artok, stretchpwd=DUMMY_STRETCHED_PASSWORD ) def test_email_code_verification(self): self.client = Client(self.server_url) # Create a fresh testing account. self.acct = TestEmailAccount() self.client.create_account( email=self.acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) def wait_for_email(m): return "x-uid" in m["headers"] and "x-verify-code" in m["headers"] m = self.acct.wait_for_email(wait_for_email) if not m: raise RuntimeError("Verification email was not received") # If everything went well, verify_email_code should return an empty json object response = self.client.verify_email_code(m["headers"]["x-uid"], m["headers"]["x-verify-code"]) self.assertEquals(response, {}) def test_send_unblock_code(self): acct = TestEmailAccount(email="block-{uniq}@{hostname}") self.client.create_account( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) self._accounts_to_delete.append(acct) # Initiate sending unblock code response = self.client.send_unblock_code(acct.email) self.assertEquals(response, {}) m = acct.wait_for_email(lambda m: "x-unblock-code" in m["headers"]) if not m: raise RuntimeError("Unblock code email was not received") code = m["headers"]["x-unblock-code"] self.assertTrue(len(code) > 0) self.client.login( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, unblock_code=code )
class TestCoreClient(unittest.TestCase): server_url = TEST_SERVER_URL def setUp(self): self.client = Client(self.server_url) self._accounts_to_delete = [] def tearDown(self): for acct in self._accounts_to_delete: acct.clear() try: stretchpwd = acct.stretchpwd except AttributeError: try: password = acct.password stretchpwd = quick_stretch_password(acct.email, password) except AttributeError: stretchpwd = DUMMY_STRETCHED_PASSWORD self.client.destroy_account(acct.email, stretchpwd=stretchpwd) def test_account_creation(self): acct = TestEmailAccount() acct.password = DUMMY_PASSWORD session = self.client.create_account(acct.email, DUMMY_PASSWORD) self._accounts_to_delete.append(acct) self.assertEqual(session.email, acct.email) self.assertFalse(session.verified) self.assertEqual(session.keys, None) self.assertEqual(session._key_fetch_token, None) with self.assertRaises(Exception): session.fetch_keys() def test_account_creation_with_key_fetch(self): acct = TestEmailAccount() session = self.client.create_account( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, keys=True, ) self._accounts_to_delete.append(acct) self.assertEqual(session.email, acct.email) self.assertFalse(session.verified) self.assertEqual(session.keys, None) self.assertNotEqual(session._key_fetch_token, None) def test_account_login(self): acct = TestEmailAccount() session1 = self.client.create_account( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) self._accounts_to_delete.append(acct) session2 = self.client.login( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) self.assertEqual(session1.email, session2.email) self.assertNotEqual(session1.token, session2.token) def test_get_random_bytes(self): b1 = self.client.get_random_bytes() b2 = self.client.get_random_bytes() self.assertTrue(isinstance(b1, six.binary_type)) self.assertNotEqual(b1, b2) def test_resend_verify_code(self): acct = TestEmailAccount() session = self.client.create_account( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) self._accounts_to_delete.append(acct) is_verify_email = lambda m: "x-verify-code" in m["headers"] m1 = acct.wait_for_email(is_verify_email) code1 = m1["headers"]["x-verify-code"] # NOQA acct.clear() session.resend_email_code() # XXX TODO: this won't work against a live server because we # refuse to send duplicate emails within a short timespan. # m2 = acct.wait_for_email(is_verify_email) # code2 = m2["headers"]["x-verify-code"] # self.assertNotEqual(m1, m2) # self.assertEqual(code1, code2) def test_forgot_password_flow(self): acct = TestEmailAccount() self.client.create_account( email=acct.email, stretchpwd=DUMMY_STRETCHED_PASSWORD, ) self._accounts_to_delete.append(acct) # Initiate the password reset flow, and grab the verification code. pftok = self.client.send_reset_code(acct.email, service="foobar") m = acct.wait_for_email(lambda m: "x-recovery-code" in m["headers"]) if not m: raise RuntimeError("Password reset email was not received") acct.clear() code = m["headers"]["x-recovery-code"] # Try with an invalid code to test error handling. tries = pftok.tries_remaining self.assertTrue(tries > 1) with self.assertRaises(Exception): pftok.verify_code(mutate_one_byte(code)) pftok.get_status() self.assertEqual(pftok.tries_remaining, tries - 1) # Re-send the code, as if we've lost the email. pftok.resend_code() m = acct.wait_for_email(lambda m: "x-recovery-code" in m["headers"]) if not m: raise RuntimeError("Password reset email was not received") self.assertEqual(m["headers"]["x-recovery-code"], code) # Now verify with the actual code, and reset the account. artok = pftok.verify_code(code) self.client.reset_account(email=acct.email, token=artok, stretchpwd=DUMMY_STRETCHED_PASSWORD)
class FxATiming(object): server_url = 'https://api-accounts.stage.mozaws.net/v1' def __init__(self, server_url=None): if server_url is None: server_url = self.server_url self.session = requests.Session() apiclient = APIClient(self.server_url, session=self.session) self.client = Client(apiclient) # setup to capture req/res timings self._patch_send() self._patch_getresponse() self.timing_data = {} self.current_uri = () self.current_start = 0 # hook into http request for timing def _patch_send(self): old_send = httplib.HTTPConnection.send def new_send(context, data): self.current_start = time.time() lines = data.split('\r\n') [method, path, proto] = re.split('\s+', lines.pop(0)) path = urlparse(path).path headers = {} for line in lines: if len(line) == 0: break [key, val] = line.split(': ') headers[key.lower()] = val request = (headers['host'], method, path) self.current_uri = request if self.current_uri not in self.timing_data: self.timing_data[self.current_uri] = { 'times': [], 'codes': [] } log_request(data) req = old_send(context, data) return req httplib.HTTPConnection.send = new_send # hook into http response for timing def _patch_getresponse(self): old_getresponse = httplib.HTTPConnection.getresponse def new_getresponse(context): res = old_getresponse(context) elapsed = time.time() - self.current_start log_response(res) self.timing_data[self.current_uri]['times'].append(elapsed) self.timing_data[self.current_uri]['codes'].append(res.status) self.current_uri = () self.current_start = 0 return res httplib.HTTPConnection.getresponse = new_getresponse def _get_stretchpwd(self, email): return hashlib.sha256(email).hexdigest() def _get_user_email(self): uid = uniq() self.user_email = "fxa-timing-{}@restmail.net".format(uid) return self.user_email def _get_existing_user_email(self): uid = random.randint(1, 999) return "loads-fxa-{}[email protected]".format(uid) def dump_requests(self): for k, v in self.timing_data.items(): if k[0] == 'restmail.net': continue args = (k[0], k[1], k[2], v['codes'][0], v['times'][0]) print "%s %-4s %-32s %3d %.4f" % args def run(self): self.login_session_flow() # self.password_reset_flow() self.dump_requests() def login_session_flow(self): """Do a full login-flow with cert signing etc.""" # Login as a new user. session = self._authenticate_as_new_user() session.get_email_status() session.fetch_keys() session.check_session_status() session.get_random_bytes() session.sign_certificate(DUMMY_PUBLIC_KEY) base_url = self.server_url[:-3] self.session.get(base_url + "/.well-known/browserid") stretchpwd = self._get_stretchpwd(session.email) self.client.change_password( session.email, oldstretchpwd=stretchpwd, newstretchpwd=stretchpwd, ) kwds = { "email": session.email, "stretchpwd": stretchpwd, "keys": True } session = self.client.login(**kwds) pftok = self.client.send_reset_code(session.email) pftok.get_status() # verify the password forgot code. acct = Restmail(email=session.email) mail = acct.wait_for_email(lambda m: "x-recovery-code" in m["headers"]) if not mail: raise RuntimeError("Password reset email was not received") acct.clear() code = mail["headers"]["x-recovery-code"] # Now verify with the actual code, and reset the account. artok = pftok.verify_code(code) self.client.reset_account( email=session.email, token=artok, stretchpwd=stretchpwd ) session = self.client.login(**kwds) session.destroy_session() self.client.destroy_account(email=session.email, stretchpwd=stretchpwd) def _authenticate_as_new_user(self): email = self._get_user_email() stretchpwd = self._get_stretchpwd(email) kwds = { "email": email, "stretchpwd": stretchpwd, "keys": True } session = self.client.create_account(**kwds) # resend the confirmation email. session.resend_email_code() # verify the confirmation code. acct = Restmail(email=email) mail = acct.wait_for_email(lambda m: "x-verify-code" in m["headers"]) if not mail: raise RuntimeError("Verification email was not received") acct.clear() session.verify_email_code(mail["headers"]["x-verify-code"]) return self.client.login(**kwds)