예제 #1
0
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)
예제 #2
0
class LoadTest(TestCase):

    server_url = 'https://api-accounts.stage.mozaws.net'

    def setUp(self):
        super(LoadTest, self).setUp()
        self.client = Client(APIClient(self.server_url, session=self.session))

    def _pick(self, *choices):
        """Pick one from a list of (item, weighting) options."""
        sum_weights = sum(choice[1] for choice in choices)
        remainder = random.randint(0, sum_weights - 1)
        for choice, weight in choices:
            remainder -= weight
            if remainder < 0:
                return choice
        assert False, "somehow failed to pick from {}".format(choices)

    def _perc(self, percent):
        """Decide whether to do something, given desired percentage of runs."""
        return random.randint(0, 99) < percent

    def test_auth_server(self):
        """Top-level method to run a randomly-secleted auth-server test."""
        which_test = self._pick(
            (self.test_login_session_flow, PERCENT_TEST_LOGIN),
            (self.test_password_reset_flow, PERCENT_TEST_RESET),
            (self.test_password_change_flow, PERCENT_TEST_CHANGE),
            (self.test_support_doc_flow, PERCENT_TEST_SUPPORTDOC),
        )
        which_test()

    def test_login_session_flow(self):
        """Do a full login-flow with cert signing etc."""
        # Login as either a new or existing user.
        if self._perc(PERCENT_LOGIN_CREATE):
            session = self._authenticate_as_new_user()
            can_delete = True
        else:
            session = self._authenticate_as_existing_user()
            can_delete = False
        try:
            # Do a whole lot of account status polling.
            n_poll_reqs = random.randint(LOGIN_POLL_REQS_MIN,
                                         LOGIN_POLL_REQS_MAX)
            for i in xrange(n_poll_reqs):
                session.get_email_status()
            # Always fetch the keys.
            session.fetch_keys()
            # Sometimes check the session status.
            if self._perc(PERCENT_LOGIN_STATUS):
                session.check_session_status()
            # Sometimes get some random bytes.
            if self._perc(PERCENT_LOGIN_RANDBYTES):
                session.get_random_bytes()
            # Always do some number of signing requests.
            n_sign_reqs = random.randint(LOGIN_SIGN_REQS_MIN,
                                         LOGIN_SIGN_REQS_MAX)
            for i in xrange(n_sign_reqs):
                session.sign_certificate(DUMMY_PUBLIC_KEY)
            # Sometimes tear down the session.
            if self._perc(PERCENT_LOGIN_TEARDOWN):
                session.destroy_session()
        except fxa.errors.ClientError as e:
            # There's a small chance this could fail due to concurrent
            # password change destroying the session token.
            if e.errno != ERROR_INVALID_TOKEN:
                raise
        # Sometimes destroy the account.
        if can_delete:
            if self._perc(PERCENT_LOGIN_CREATE_DESTROY):
                self.client.destroy_account(
                    email=session.email,
                    stretchpwd=self._get_stretchpwd(session.email),
                )

    def _get_stretchpwd(self, email):
        return hashlib.sha256(email).hexdigest()

    def _get_new_user_email(self):
        uid = uniq()
        return "loads-fxa-{}[email protected]".format(uid)

    def _get_existing_user_email(self):
        uid = random.randint(1, 999)
        return "loads-fxa-{}[email protected]".format(uid)

    def _authenticate_as_new_user(self):
        # Authenticate as a brand-new user account.
        # Assume it doesn't exist, and try to create the account.
        # But it's not big deal if it happens to already exist.
        email = self._get_new_user_email()
        kwds = {
            "email": email,
            "stretchpwd": self._get_stretchpwd(email),
            "keys": True,
            "preVerified": True,
        }
        try:
            session = self.client.create_account(**kwds)
        except fxa.errors.ClientError as e:
            if e.errno != ERROR_ACCOUNT_EXISTS:
                raise
            kwds.pop("preVerified")
            session = self.client.login(**kwds)
        # Sometimes resend the confirmation email.
        if self._perc(PERCENT_LOGIN_CREATE_RESEND):
            session.resend_email_code()
        # Sometimes (pretend to) verify the confirmation code.
        if self._perc(PERCENT_LOGIN_CREATE_VERIFY):
            try:
                session.verify_email_code(uniq(32))
            except fxa.errors.ClientError as e:
                if e.errno != ERROR_INVALID_CODE:
                    raise
        return session

    def _authenticate_as_existing_user(self):
        # Authenticate as an existing user account.
        # We select from a small pool of known accounts, creating it
        # if it does not exist.  This should mean that all the accounts
        # are created quickly at the start of the loadtest run.
        email = self._get_existing_user_email()
        kwds = {
            "email": email,
            "stretchpwd": self._get_stretchpwd(email),
            "keys": True,
        }
        try:
            return self.client.login(**kwds)
        except fxa.errors.ClientError as e:
            if e.errno != ERROR_UNKNOWN_ACCOUNT:
                raise
            kwds["preVerified"] = True
            # Account creation might likewise fail due to a race.
            try:
                return self.client.create_account(**kwds)
            except fxa.errors.ClientError as e:
                if e.errno != ERROR_ACCOUNT_EXISTS:
                    raise
                # Assume a normal login will now succeed.
                kwds.pop("preVerified")
                return self.client.login(**kwds)

    def test_password_reset_flow(self):
        email = self._get_existing_user_email()
        pft = self.client.send_reset_code(email)
        # XXX TODO: how to get the reset code?
        # I don't want to actually poll restmail during a loadtest...
        pft.get_status()
        try:
            pft.verify_code("0" * 32)
        except fxa.errors.ClientError as e:
            if e.errno != ERROR_INVALID_CODE:
                raise
        pft.get_status()

    def test_password_change_flow(self):
        email = self._get_existing_user_email()
        stretchpwd = self._get_stretchpwd(email)
        try:
            self.client.change_password(
                email,
                oldstretchpwd=stretchpwd,
                newstretchpwd=stretchpwd,
            )
        except fxa.errors.ClientError as e:
            if e.errno != ERROR_UNKNOWN_ACCOUNT:
                raise
            # Create the "existing" account if it doens't yet exist.
            kwds = {
                "email": email,
                "stretchpwd": stretchpwd,
                "preVerified": True,
            }
            try:
                self.client.create_account(**kwds)
            except fxa.errors.ClientError as e:
                if e.errno != ERROR_UNKNOWN_ACCOUNT:
                    raise
            else:
                self.client.change_password(
                    email,
                    oldstretchpwd=stretchpwd,
                    newstretchpwd=stretchpwd,
                )

    def test_support_doc_flow(self):
        base_url = self.server_url[:-3]
        self.session.get(base_url + "/.well-known/browserid")
예제 #3
0
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)
예제 #4
0
파일: test_core.py 프로젝트: mozilla/PyFxA
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
        )
예제 #5
0
class LoadTest(TestCase):

    server_url = 'https://api-accounts.stage.mozaws.net'

    def setUp(self):
        super(LoadTest, self).setUp()
        self.client = Client(APIClient(self.server_url, session=self.session))

    def _pick(self, *choices):
        """Pick one from a list of (item, weighting) options."""
        sum_weights = sum(choice[1] for choice in choices)
        remainder = random.randint(0, sum_weights - 1)
        for choice, weight in choices:
            remainder -= weight
            if remainder < 0:
                return choice
        assert False, "somehow failed to pick from {}".format(choices)

    def _perc(self, percent):
        """Decide whether to do something, given desired percentage of runs."""
        return random.randint(0, 99) < percent

    def test_auth_server(self):
        """Top-level method to run a randomly-secleted auth-server test."""
        which_test = self._pick(
            (self.test_login_session_flow, PERCENT_TEST_LOGIN),
            (self.test_password_reset_flow, PERCENT_TEST_RESET),
            (self.test_password_change_flow, PERCENT_TEST_CHANGE),
            (self.test_support_doc_flow, PERCENT_TEST_SUPPORTDOC),
        )
        which_test()

    def test_login_session_flow(self):
        """Do a full login-flow with cert signing etc."""
        # Login as either a new or existing user.
        if self._perc(PERCENT_LOGIN_CREATE):
            session = self._authenticate_as_new_user()
            can_delete = True
        else:
            session = self._authenticate_as_existing_user()
            can_delete = False
        try:
            # Do a whole lot of account status polling.
            n_poll_reqs = random.randint(LOGIN_POLL_REQS_MIN,
                                         LOGIN_POLL_REQS_MAX)
            for i in xrange(n_poll_reqs):
                session.get_email_status()
            # Always fetch the keys.
            session.fetch_keys()
            # Sometimes check the session status.
            if self._perc(PERCENT_LOGIN_STATUS):
                session.check_session_status()
            # Sometimes get some random bytes.
            if self._perc(PERCENT_LOGIN_RANDBYTES):
                session.get_random_bytes()
            # Always do some number of signing requests.
            n_sign_reqs = random.randint(LOGIN_SIGN_REQS_MIN,
                                         LOGIN_SIGN_REQS_MAX)
            for i in xrange(n_sign_reqs):
                session.sign_certificate(DUMMY_PUBLIC_KEY)
            # Sometimes tear down the session.
            if self._perc(PERCENT_LOGIN_TEARDOWN):
                session.destroy_session()
        except fxa.errors.ClientError as e:
            # There's a small chance this could fail due to concurrent
            # password change destroying the session token.
            if e.errno != ERROR_INVALID_TOKEN:
                raise
        # Sometimes destroy the account.
        if can_delete:
            if self._perc(PERCENT_LOGIN_CREATE_DESTROY):
                self.client.destroy_account(
                    email=session.email,
                    stretchpwd=self._get_stretchpwd(session.email),
                )

    def _get_stretchpwd(self, email):
        return hashlib.sha256(email).hexdigest()

    def _get_new_user_email(self):
        uid = uniq()
        return "loads-fxa-{}[email protected]".format(uid)

    def _get_existing_user_email(self):
        uid = random.randint(1, 999)
        return "loads-fxa-{}[email protected]".format(uid)

    def _authenticate_as_new_user(self):
        # Authenticate as a brand-new user account.
        # Assume it doesn't exist, and try to create the account.
        # But it's not big deal if it happens to already exist.
        email = self._get_new_user_email()
        kwds = {
            "email": email,
            "stretchpwd": self._get_stretchpwd(email),
            "keys": True,
            "preVerified": True,
        }
        try:
            session = self.client.create_account(**kwds)
        except fxa.errors.ClientError as e:
            if e.errno != ERROR_ACCOUNT_EXISTS:
                raise
            kwds.pop("preVerified")
            session = self.client.login(**kwds)
        # Sometimes resend the confirmation email.
        if self._perc(PERCENT_LOGIN_CREATE_RESEND):
            session.resend_email_code()
        # Sometimes (pretend to) verify the confirmation code.
        if self._perc(PERCENT_LOGIN_CREATE_VERIFY):
            try:
                session.verify_email_code(uniq(32))
            except fxa.errors.ClientError as e:
                if e.errno != ERROR_INVALID_CODE:
                    raise
        return session

    def _authenticate_as_existing_user(self):
        # Authenticate as an existing user account.
        # We select from a small pool of known accounts, creating it
        # if it does not exist.  This should mean that all the accounts
        # are created quickly at the start of the loadtest run.
        email = self._get_existing_user_email()
        kwds = {
            "email": email,
            "stretchpwd": self._get_stretchpwd(email),
            "keys": True,
        }
        try:
            return self.client.login(**kwds)
        except fxa.errors.ClientError as e:
            if e.errno != ERROR_UNKNOWN_ACCOUNT:
                raise
            kwds["preVerified"] = True
            # Account creation might likewise fail due to a race.
            try:
                return self.client.create_account(**kwds)
            except fxa.errors.ClientError as e:
                if e.errno != ERROR_ACCOUNT_EXISTS:
                    raise
                # Assume a normal login will now succeed.
                kwds.pop("preVerified")
                return self.client.login(**kwds)

    def test_password_reset_flow(self):
        email = self._get_existing_user_email()
        pft = self.client.send_reset_code(email)
        # XXX TODO: how to get the reset code?
        # I don't want to actually poll restmail during a loadtest...
        pft.get_status()
        try:
            pft.verify_code("0" * 32)
        except fxa.errors.ClientError as e:
            if e.errno != ERROR_INVALID_CODE:
                raise
        pft.get_status()

    def test_password_change_flow(self):
        email = self._get_existing_user_email()
        stretchpwd = self._get_stretchpwd(email)
        try:
            self.client.change_password(
                email,
                oldstretchpwd=stretchpwd,
                newstretchpwd=stretchpwd,
            )
        except fxa.errors.ClientError as e:
            if e.errno != ERROR_UNKNOWN_ACCOUNT:
                raise
            # Create the "existing" account if it doens't yet exist.
            kwds = {
                "email": email,
                "stretchpwd": stretchpwd,
                "preVerified": True,
            }
            try:
                self.client.create_account(**kwds)
            except fxa.errors.ClientError as e:
                if e.errno != ERROR_UNKNOWN_ACCOUNT:
                    raise
            else:
                self.client.change_password(
                    email,
                    oldstretchpwd=stretchpwd,
                    newstretchpwd=stretchpwd,
                )

    def test_support_doc_flow(self):
        base_url = self.server_url[:-3]
        self.session.get(base_url + "/.well-known/browserid")