diff options
Diffstat (limited to 'tests/test_auth_account.py')
-rw-r--r-- | tests/test_auth_account.py | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/tests/test_auth_account.py b/tests/test_auth_account.py new file mode 100644 index 0000000..68a407b --- /dev/null +++ b/tests/test_auth_account.py @@ -0,0 +1,348 @@ +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' + } + +def test_create_exists(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' + } + +def test_login_badcase(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(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' + } + +def test_destroy_badcase(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(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): + (to, body) = mail_server.wait() + assert account.email in to + resp = forgot_token.post_a("/password/forgot/verify_code", { 'code': body.strip() }) + 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(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@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.account2@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, "") |