summaryrefslogtreecommitdiff
path: root/tests/test_auth_oauth.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_auth_oauth.py')
-rw-r--r--tests/test_auth_oauth.py369
1 files changed, 369 insertions, 0 deletions
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