From 2f8dce44d3f2be74b5c6ec0a2e7f4ceced715328 Mon Sep 17 00:00:00 2001 From: pennae Date: Wed, 13 Jul 2022 10:33:30 +0200 Subject: initial import --- tests/test_auth_oauth.py | 369 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 tests/test_auth_oauth.py (limited to 'tests/test_auth_oauth.py') diff --git a/tests/test_auth_oauth.py b/tests/test_auth_oauth.py new file mode 100644 index 0000000..f8ad201 --- /dev/null +++ b/tests/test_auth_oauth.py @@ -0,0 +1,369 @@ +import copy +import pytest +import time +from fxa.errors import ClientError + +@pytest.mark.parametrize("client_id,scopes", [ + # firefox + ("5882386c6d801776", "profile:write https://identity.mozilla.com/apps/oldsync https://identity.mozilla.com/tokens/session"), + # fenix + ("a2270f727f45f648", "profile https://identity.mozilla.com/apps/oldsync https://identity.mozilla.com/tokens/session"), +]) +def test_oauth_client_scopes(account, client_id, scopes): + body = { + "client_id": client_id, + "ttl": 60, + "grant_type": "fxa-credentials", + "access_type": "online", + "scope": scopes, + } + s = account.post_a("/oauth/token", body)['access_token'] + account.post_a("/oauth/destroy", { "client_id": client_id, "token": s }) + +def test_oauth_authorization_noauth(account): + body = { + "client_id": "5882386c6d801776", + "ttl": 60, + "grant_type": "fxa-credentials", + "access_type": "online", + "scope": "profile", + } + with pytest.raises(ClientError) as e: + account.post("/oauth/authorization", body) + assert e.value.details == { + 'code': 401, + 'errno': 109, + 'error': 'Unauthorized', + 'message': 'invalid request signature' + } + +def test_oauth_authorization_unverified(unverified_account): + body = { + "client_id": "5882386c6d801776", + "ttl": 60, + "grant_type": "fxa-credentials", + "access_type": "online", + "scope": "profile", + } + with pytest.raises(ClientError) as e: + unverified_account.post_a("/oauth/authorization", body) + assert e.value.details == { + 'code': 400, + 'errno': 138, + 'error': 'Bad Request', + 'message': 'unverified session' + } + +@pytest.mark.parametrize("args", [ + { "client_id": "5882386c6d801776", "state": "", "scope": "profile", "access_type": "invalid", + "code_challenge": "", "code_challenge_method": "S256", "response_type": "code" }, + { "client_id": "5882386c6d801776", "state": "", "scope": "profile", "access_type": "online", + "code_challenge": "", "code_challenge_method": "invalid", "response_type": "code" }, + { "client_id": "5882386c6d801776", "state": "", "scope": "profile", "access_type": "online", + "code_challenge": "", "code_challenge_method": "S256", "response_type": "invalid" }, +]) +def test_oauth_authorization_invalid(account, args): + with pytest.raises(ClientError) as e: + account.post_a("/oauth/authorization", args) + assert e.value.details == { + 'code': 400, + 'errno': 107, + 'error': 'Bad Request', + 'message': 'invalid parameter in request body' + } + +def test_oauth_authorization_badclientid(account): + args = { "client_id": "invalid", "state": "", "scope": "profile", "access_type": "online", + "code_challenge": "", "code_challenge_method": "S256", "response_type": "code" } + with pytest.raises(ClientError) as e: + account.post_a("/oauth/authorization", args) + assert e.value.details == { + 'code': 400, + 'errno': 162, + 'error': 'Bad Request', + 'message': 'unknown client_id' + } + +def test_oauth_authorization_badscope(account): + args = { "client_id": "5882386c6d801776", "state": "", "scope": "invalid", "access_type": "online", + "code_challenge": "", "code_challenge_method": "S256", "response_type": "code" } + with pytest.raises(ClientError) as e: + account.post_a("/oauth/authorization", args) + assert e.value.details == { + 'code': 400, + 'errno': 169, + 'error': 'Bad Request', + 'message': 'requested scopes not allowed' + } + +# see below for combined /authorization + unauthed /token + +def test_oauth_destroy_notoken(account): + args = { "client_id": "5882386c6d801776", "token": "00" * 32 } + with pytest.raises(ClientError) as e: + account.post("/oauth/destroy", args) + assert e.value.details == { + 'code': 400, + 'errno': 107, + 'error': 'Bad Request', + 'message': 'invalid parameter in request body' + } + +def test_oauth_destroy_badclient(account, refresh_token): + args = { "client_id": "other", "token": refresh_token.bearer } + with pytest.raises(ClientError) as e: + account.post("/oauth/destroy", args) + assert e.value.details == { + 'code': 400, + 'errno': 107, + 'error': 'Bad Request', + 'message': 'invalid parameter in request body' + } + +def test_oauth_scoped_keys_badclient(account): + with pytest.raises(ClientError) as e: + account.post_a("/account/scoped-key-data", { + "client_id": "invalid", + "scope": "https://identity.mozilla.com/apps/oldsync" + }) + assert e.value.details == { + 'code': 400, + 'errno': 162, + 'error': 'Bad Request', + 'message': 'unknown client_id' + } + +def test_oauth_scoped_keys_badscope(account): + with pytest.raises(ClientError) as e: + account.post_a("/account/scoped-key-data", { + "client_id": "5882386c6d801776", + "scope": "scope" + }) + assert e.value.details == { + 'code': 400, + 'errno': 169, + 'error': 'Bad Request', + 'message': 'requested scopes not allowed' + } + +def test_oauth_scoped_unverified(unverified_account): + with pytest.raises(ClientError) as e: + unverified_account.post_a("/account/scoped-key-data", { + "client_id": "5882386c6d801776", + "scope": "https://identity.mozilla.com/apps/oldsync" + }) + assert e.value.details == { + 'code': 400, + 'errno': 138, + 'error': 'Bad Request', + 'message': 'unverified session' + } + +def test_oauth_scoped_keys(account): + resp = account.post_a("/account/scoped-key-data", { + "client_id": "5882386c6d801776", + "scope": "https://identity.mozilla.com/apps/oldsync" + }) + assert resp == { + "https://identity.mozilla.com/apps/oldsync": { + "identifier": "https://identity.mozilla.com/apps/oldsync", + "keyRotationSecret": "00" * 32, + "keyRotationTimestamp": 0, + }, + } + +@pytest.mark.parametrize("access_type", ["online", "offline"]) +def test_oauth_token_fxa_badclient(account, access_type): + body = { "client_id": "invalid", "ttl": 60, "grant_type": "fxa-credentials", + "access_type": access_type, "scope": "profile" } + with pytest.raises(ClientError) as e: + account.post_a("/oauth/token", body) + assert e.value.details == { + 'code': 400, + 'errno': 162, + 'error': 'Bad Request', + 'message': 'unknown client_id' + } + +@pytest.mark.parametrize("access_type", ["online", "offline"]) +def test_oauth_token_fxa_badscope(account, access_type): + body = { "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "fxa-credentials", + "access_type": access_type, "scope": "scope" } + with pytest.raises(ClientError) as e: + account.post_a("/oauth/token", body) + assert e.value.details == { + 'code': 400, + 'errno': 169, + 'error': 'Bad Request', + 'message': 'requested scopes not allowed' + } + +@pytest.mark.parametrize("grant_type,access_type", [ + ("authorization_code", "online"), + ("refresh_token", "online"), + ("fxa-credentials", "foo"), +]) +def test_oauth_token_fxa_invalid(account, grant_type, access_type): + body = { "client_id": "5882386c6d801776", "ttl": 60, "grant_type": grant_type, + "access_type": access_type, "scope": "scope" } + with pytest.raises(ClientError) as e: + account.post_a("/oauth/token", body) + assert e.value.details == { + 'code': 400, + 'errno': 107, + 'error': 'Bad Request', + 'message': 'invalid parameter in request body' + } + +def test_oauth_token_unverified(unverified_account): + body = { "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "fxa-credentials", + "access_type": "online", "scope": "profile" } + with pytest.raises(ClientError) as e: + unverified_account.post_a("/oauth/token", body) + assert e.value.details == { + 'code': 400, + 'errno': 138, + 'error': 'Bad Request', + 'message': 'unverified session' + } + +@pytest.fixture +def auth_code(account): + body = { + "client_id": "5882386c6d801776", + "state": "test", + "scope": "profile", + "access_type": "online", + "code_challenge": "n4bQgYhMfWWaL-qgxVrQFaO_TxsrC4Is0V1sFbDwCgg", # "test" + "code_challenge_method": "S256", + "response_type": "code", + } + return account.post_a("/oauth/authorization", body)['code'] + +@pytest.mark.parametrize("args,code,error,errno,message", [ + ({ "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "authorization_code", + "code": "invalid", "code_verifier": "test" }, + 400, 'Bad Request', 107, 'invalid parameter in request body'), + ({ "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "authorization_code", + "code_verifier": "test", "extra": 0 }, + 400, 'Bad Request', 107, 'invalid parameter in request body'), + ({ "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "authorization_code", + "code": "00" * 32, "code_verifier": "test" }, + 401, 'Unauthorized', 110, 'invalid authentication token'), + ({ "client_id": "invalid", "ttl": 60, "grant_type": "authorization_code", + "code_verifier": "test" }, + 400, 'Bad Request', 162, 'unknown client_id'), + ({ "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "authorization_code", + "code_verifier": "invalid" }, + 400, 'Bad Request', 107, 'invalid parameter in request body'), +]) +def test_oauth_token_other_invalidcode(account, args, code, error, errno, message, auth_code): + args = copy.deepcopy(args) + if 'code' not in args: args['code'] = auth_code + with pytest.raises(ClientError) as e: + account.post("/oauth/token", args) + assert e.value.details == { 'code': code, 'errno': errno, 'error': error, 'message': message } + +@pytest.mark.parametrize("args,code,error,errno,message", [ + ({ "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "fxa-credentials", + "scope": "profile", "access_type": "online" }, + 400, 'Bad Request', 107, 'invalid parameter in request body'), + ({ "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "refresh_token", + "refresh_token": "invalid", "code_verifier": "test" }, + 400, 'Bad Request', 107, 'invalid parameter in request body'), + ({ "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "refresh_token", + "scope": "profile", "extra": 0 }, + 400, 'Bad Request', 107, 'invalid parameter in request body'), + ({ "client_id": "invalid", "ttl": 60, "grant_type": "refresh_token", + "scope": "profile" }, + 400, 'Bad Request', 162, 'unknown client_id'), + ({ "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "refresh_token", + "scope": "foo" }, + 400, 'Bad Request', 169, 'requested scopes not allowed'), + ({ "client_id": "5882386c6d801776", "ttl": 60, "grant_type": "refresh_token", + "scope": "profile:write" }, + 400, 'Bad Request', 169, 'requested scopes not allowed'), +]) +def test_oauth_token_other_invalidrefresh(account, args, code, error, errno, message, refresh_token): + args = copy.deepcopy(args) + if 'refresh_token' not in args: args['refresh_token'] = refresh_token.bearer + with pytest.raises(ClientError) as e: + account.post("/oauth/token", args) + assert e.value.details == { 'code': code, 'errno': errno, 'error': error, 'message': message } + +@pytest.mark.parametrize("refresh", [False, True]) +def test_oauth_fxa(account, refresh): + body = { + "client_id": "5882386c6d801776", + "ttl": 60, + "grant_type": "fxa-credentials", + "access_type": "offline" if refresh else "online", + "scope": "profile", + } + resp = account.post_a("/oauth/token", body) + + assert 'access_token' in resp + assert ('refresh_token' in resp) == refresh + assert 'session_token' not in resp + assert resp['scope'] == 'profile' + assert resp['token_type'] == 'bearer' + assert resp['expires_in'] <= 60 + assert (resp['auth_at'] - time.time()) < 10 + assert 'keys_jwe' not in resp + +@pytest.mark.parametrize("keys_jwe", [None, "keyskeyskeys"]) +@pytest.mark.parametrize("refresh,session", [ + (False, False), + (True, False), + (True, True), +]) +def test_oauth_auth_code(account, keys_jwe, refresh, session): + scope = "profile" + (" https://identity.mozilla.com/tokens/session" if session else "") + body = { + "client_id": "5882386c6d801776", + "state": "test", + "keys_jwe": keys_jwe, + "scope": scope, + "access_type": "offline" if refresh else "online", + "code_challenge": "n4bQgYhMfWWaL-qgxVrQFaO_TxsrC4Is0V1sFbDwCgg", # "test" + "code_challenge_method": "S256", + "response_type": "code", + } + resp = account.post_a("/oauth/authorization", body) + assert resp['state'] == "test" + + body = { + "client_id": "5882386c6d801776", + "ttl": 60, + "grant_type": "authorization_code", + "code": resp['code'], + "code_verifier": "test", + } + resp = account.post("/oauth/token", body) + assert 'access_token' in resp + assert ('refresh_token' in resp) == refresh + assert ('session_token' in resp) == session + assert resp['scope'] == 'profile' + assert resp['token_type'] == 'bearer' + assert resp['expires_in'] <= 60 + assert (resp['auth_at'] - time.time()) < 10 + assert keys_jwe is None or (resp['keys_jwe'] == keys_jwe) + +def test_oauth_refresh(account, refresh_token): + body = { + "client_id": "5882386c6d801776", + "ttl": 60, + "grant_type": "refresh_token", + "refresh_token": refresh_token.bearer, + "scope": "profile", + } + resp = account.post("/oauth/token", body) + + assert 'access_token' in resp + assert 'refresh_token' not in resp + assert 'session_token' not in resp + assert resp['scope'] == 'profile' + assert resp['token_type'] == 'bearer' + assert resp['expires_in'] <= 60 + assert (resp['auth_at'] - time.time()) < 10 + assert 'keys_jwe' not in resp -- cgit v1.2.3