import os import pytest from fxa.errors import ClientError from api import * def test_create_no_args(client): with pytest.raises(ClientError) as e: client.post("/account/create") assert e.value.details == { 'code': 400, 'errno': 106, 'error': 'Bad Request', 'message': 'invalid json in request body' } @pytest.mark.parametrize("args", [ {"email": "", "authPW": "", "extra": ""}, {"email": "", "authPW": "00"}, {"email": "a" * 257, "authPW": "0" * 64}, {"email": "a@test", "authPW": "00"}, {"email": "a@test", "authPW": "00" * 32, "style": "foo"}, ]) def test_create_invalid_args(client, args): with pytest.raises(ClientError) as e: client.post("/account/create", args) assert e.value.details == { 'code': 400, 'errno': 107, 'error': 'Bad Request', 'message': 'invalid parameter in request body' } def test_create_nomail(client): with pytest.raises(ClientError) as e: client.create_account("test.account@test-auth", "") assert e.value.details == { 'code': 422, 'errno': 151, 'error': 'Unprocessable Entity', 'message': 'failed to send email' } class TestCreateExists: def test_create_exists(self, account): with pytest.raises(ClientError) as e: account.create_account(account.email, "") assert e.value.details == { 'code': 400, 'errno': 101, 'error': 'Bad Request', 'message': 'account already exists' } @pytest.mark.parametrize("keys", [None, False, True]) @pytest.mark.parametrize("verify", [False, True]) def test_create_destroy(client, keys, verify, mail_server): s = client.create_account("test.create.destroy@test-auth", "", keys=keys) try: if verify: (to, body) = mail_server.wait() assert to == [s.email] data = json.loads(base64.urlsafe_b64decode(body.split("#/verify/", maxsplit=1)[1]).decode('utf8')) s.post_a('/recovery_email/verify_code', { 'uid': data['uid'], 'code': data['code'] }) if keys: k = s.fetch_keys(s.props['keyFetchToken'], "") with pytest.raises(ClientError) as e: s.fetch_keys(s.props['keyFetchToken'], "") assert e.value.details == { 'code': 401, 'errno': 109, 'error': 'Unauthorized', 'message': 'invalid request signature' } finally: client.destroy_account("test.create.destroy@test-auth", "") def test_login_no_args(client): with pytest.raises(ClientError) as e: client.post("/account/login") assert e.value.details == { 'code': 400, 'errno': 106, 'error': 'Bad Request', 'message': 'invalid json in request body' } @pytest.mark.parametrize("args", [ {"email": "", "authPW": "", "extra": ""}, {"email": "", "authPW": "00"}, {"email": "a" * 257, "authPW": "0" * 64}, {"email": "a@test", "authPW": "00"}, ]) def test_login_invalid_args(client, args): with pytest.raises(ClientError) as e: client.post("/account/login", args) assert e.value.details == { 'code': 400, 'errno': 107, 'error': 'Bad Request', 'message': 'invalid parameter in request body' } def test_login_noaccount(client): with pytest.raises(ClientError) as e: client.login("test@test", "") assert e.value.details == { 'code': 400, 'errno': 102, 'error': 'Bad Request', 'message': 'unknown account' } def test_login_unverified(client, unverified_account): with pytest.raises(ClientError) as e: client.login(unverified_account.email, "") assert e.value.details == { 'code': 400, 'errno': 104, 'error': 'Bad Request', 'message': 'unverified account' } class TestBadLogin: def test_login_badcase(self, account): with pytest.raises(ClientError) as e: account.login(account.email.upper(), "") assert e.value.details == { 'code': 400, 'errno': 120, 'error': 'Bad Request', 'message': 'incorrect email case' } def test_login_badpasswd(self, account): with pytest.raises(ClientError) as e: account.login(account.email, "ca0cb780-f114-405a-a5c2-1a4deb933a51") assert e.value.details == { 'code': 400, 'errno': 103, 'error': 'Bad Request', 'message': 'incorrect password' } @pytest.mark.parametrize("keys", [None, False, True]) def test_login(client, account, keys): s = client.login(account.email, "", keys=keys) try: if keys: s.fetch_keys(s.props['keyFetchToken'], "") with pytest.raises(ClientError) as e: s.fetch_keys(s.props['keyFetchToken'], "") assert e.value.details == { 'code': 401, 'errno': 109, 'error': 'Unauthorized', 'message': 'invalid request signature' } finally: s.destroy_session() @pytest.mark.parametrize("args", [ {"email": "", "authPW": "", "extra": ""}, {"email": "", "authPW": "00"}, {"email": "a" * 257, "authPW": "0" * 64}, {"email": "a@test", "authPW": "00"}, ]) def test_destroy_invalid_args(client, args): with pytest.raises(ClientError) as e: client.post("/account/destroy", args) assert e.value.details == { 'code': 400, 'errno': 107, 'error': 'Bad Request', 'message': 'invalid parameter in request body' } def test_destroy_noaccount(client): with pytest.raises(ClientError) as e: client.destroy_account("test@test", "") assert e.value.details == { 'code': 400, 'errno': 102, 'error': 'Bad Request', 'message': 'unknown account' } class TestBadDestroy: def test_destroy_badcase(self, account): with pytest.raises(ClientError) as e: account.destroy_account(account.email.upper(), "") assert e.value.details == { 'code': 400, 'errno': 120, 'error': 'Bad Request', 'message': 'incorrect email case' } def test_destroy_badpasswd(self, account): with pytest.raises(ClientError) as e: account.destroy_account(account.email, "ca0cb780-f114-405a-a5c2-1a4deb933a51") assert e.value.details == { 'code': 400, 'errno': 103, 'error': 'Bad Request', 'message': 'incorrect password' } @pytest.mark.parametrize("auth", [ {}, {"authorization": "bearer invalid"}, {"authorization": "hawk id=invalid"}, ]) def test_keys_invalid(client, auth): with pytest.raises(ClientError) as e: client.get("/account/keys", headers=auth) assert e.value.details == { 'code': 401, 'errno': 109, 'error': 'Unauthorized', 'message': 'invalid request signature' } # create and login do the remaining keyfetch tests. # we don't check whether the bundle is actually *valid* because we'd have to look # into the db to do a full test, so we'll trust it is correct if sync works. @pytest.fixture def reset_token(account, forgot_token, mail_server): resp = forgot_token.post_a("/password/forgot/verify_code", { 'code': forgot_token.code }) return AccountReset(account.client, resp['accountResetToken']) @pytest.mark.parametrize("args", [ { 'authPW': '00', }, { 'authPW': '00' * 32, 'extra': 0, }, ]) def test_reset_invalid(reset_token, args): with pytest.raises(ClientError) as e: reset_token.post_a("/account/reset", args) assert e.value.details == { 'code': 400, 'errno': 107, 'error': 'Bad Request', 'message': 'invalid parameter in request body' } def test_reset(account, reset_token, mail_server, push_server): dev = Device(account, "dev", pcb=push_server.good("d4105515-f4f0-4d26-85c1-f48c5c348edb")) resp = reset_token.post_a("/account/reset", { 'authPW': auth_pw(account.email, "") }) assert resp == {} (to, body) = mail_server.wait() assert account.email in to assert 'account has been reset' in body p = push_server.wait() assert p[0] == "/d4105515-f4f0-4d26-85c1-f48c5c348edb" assert dev.decrypt(p[2]) == {'command': 'fxaccounts:password_reset', 'version': 1} p = push_server.wait() assert p[0] == "/d4105515-f4f0-4d26-85c1-f48c5c348edb" assert dev.decrypt(p[2]) == { 'command': 'fxaccounts:device_disconnected', 'data': {'id': dev.id}, 'version': 1 } assert push_server.done() def test_reset_twice(account, reset_token, mail_server, push_server): reset_token.post_a("/account/reset", { 'authPW': auth_pw(account.email, "") }) with pytest.raises(ClientError) as e: reset_token.post_a("/account/reset", { 'authPW': auth_pw(account.email, "") }) assert e.value.details == { 'code': 401, 'errno': 109, 'error': 'Unauthorized', 'message': 'invalid request signature' } @pytest.mark.invite def test_create_noinvite(client): with pytest.raises(ClientError) as e: client.create_account("test.create.destroy@test-auth", "") assert e.value.details == { 'code': 400, 'errno': -1, 'error': 'Bad Request', 'message': 'invite code required' } @pytest.mark.invite def test_create_badinvite(client): with pytest.raises(ClientError) as e: client.create_account("test.create.destroy@test-auth", "", { 'style': '' }) assert e.value.details == { 'code': 400, 'errno': -1, 'error': 'Bad Request', 'message': 'invite code required' } @pytest.mark.invite def test_create_invite(client, mail_server): # all in one test because restarting the server from python would be a pain c = client.create_account("test.account.invite@test-auth", "", invite=os.environ['INVITE_CODE']) c2 = None try: invite = Invite(c.props['sessionToken']) # unverified sessions fail with pytest.raises(ClientError) as e: code = invite.post_a('/generate', { 'ttl_hours': 1 }) assert e.value.details == { 'code': 400, 'errno': 138, 'error': 'Bad Request', 'message': 'unverified session' } # verified session works (to, body) = mail_server.wait() data = json.loads(base64.urlsafe_b64decode(body.split("#/verify/", maxsplit=1)[1]).decode('utf8')) c.post_a('/recovery_email/verify_code', { 'uid': data['uid'], 'code': data['code'] }) code = invite.post_a('/generate', { 'ttl_hours': 1 }) assert 'url' in code code = code['url'].split('/')[-1] # code allows registration c2 = client.create_account("test.account.invite2@test-auth", "", invite=code) (to, body) = mail_server.wait() data = json.loads(base64.urlsafe_b64decode(body.split("#/verify/", maxsplit=1)[1]).decode('utf8')) c2.post_a('/recovery_email/verify_code', { 'uid': data['uid'], 'code': data['code'] }) # token is consumed with pytest.raises(ClientError) as e: client.create_account("test.account3@test-auth", "", invite=code) assert e.value.details == { 'code': 400, 'errno': -2, 'error': 'Bad Request', 'message': 'invite code not found' } # non-admin accounts can't generate tokens with pytest.raises(ClientError) as e: invite2 = Invite(c2.props['sessionToken']) invite2.post_a('/generate', { 'ttl_hours': 1 }) assert e.value.details == { 'code': 401, 'errno': 110, 'error': 'Unauthorized', 'message': 'invalid authentication token' } finally: c.destroy_account(c.email, "") if c2 is not None: c2.destroy_account(c2.email, "")