async def test_assign_user_info(auth): """three way to set user info schema 1. pass it to arguments when create instance 2. call `.claim` method and pass it to that arguments 3. assign with `=` statements """ class SubSchema(BaseModel): sub: str class NameSchema(BaseModel): name: str class IatSchema(BaseModel): iat: int # authorized token dummy_http_auth = HTTPAuthorizationCredentials( scheme="a", credentials= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Im5hbWUiLCJpYXQiOjE1MTYyMzkwMjJ9.3ZEDmhWNZWDbJDPDlZX_I3oaalNYXdoT-bKLxIxQK4U", ) user = auth(jwks=JWKS.null(), user_info=IatSchema) assert await user.call(dummy_http_auth) == IatSchema(iat=1516239022) assert await user.claim(SubSchema).call(dummy_http_auth) == SubSchema( sub="1234567890") user.user_info = NameSchema assert await user.call(dummy_http_auth) == NameSchema(name="name")
async def __call__( self, request: Request) -> Optional[HTTPAuthorizationCredentials]: authorization_in_request: str = request.headers.get("Authorization") authorization_in_cookie: str = request.cookies.get("Authorization") if authorization_in_request: scheme, credentials = get_authorization_scheme_param( authorization_in_request) elif authorization_in_cookie: scheme, credentials = get_authorization_scheme_param( authorization_in_cookie) else: scheme, credentials = None, None if not (scheme and credentials): if self.auto_error: raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Not authenticated") else: return None if scheme.lower() != "bearer": if self.auto_error: raise HTTPException( status_code=HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials", ) else: return None return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
async def test_malformed_token_handling(): http_auth_with_malformed_token = HTTPAuthorizationCredentials( scheme="a", credentials="malformed-token", ) verifier = JWKsVerifier(jwks=JWKS.null()) with pytest.raises(HTTPException): await verifier._get_publickey(http_auth_with_malformed_token) with pytest.raises(HTTPException): await verifier.verify_token(http_auth_with_malformed_token) verifier = JWKsVerifier(jwks=JWKS.null(), auto_error=False) assert not await verifier._get_publickey(http_auth_with_malformed_token) assert not await verifier.verify_token(http_auth_with_malformed_token) verifier = ScopedJWKsVerifier(jwks=JWKS.null()) with pytest.raises(HTTPException): verifier._verify_scope(http_auth_with_malformed_token) with pytest.raises(HTTPException): await verifier.verify_token(http_auth_with_malformed_token) verifier = ScopedJWKsVerifier(jwks=JWKS.null(), auto_error=False) assert not verifier._verify_scope(http_auth_with_malformed_token) assert not await verifier.verify_token(http_auth_with_malformed_token)
def test_verify_scope_exeption(mocker): mocker.patch( "fastapi_cloudauth.verification.jwt.get_unverified_claims", return_value={"dummy key": "read:test"}, ) scope_key = "dummy key" http_auth = HTTPAuthorizationCredentials( scheme="a", credentials="dummy-token", ) # trivial scope verifier = ScopedJWKsVerifier(jwks=JWKS.null(), scope_key=scope_key, scope_name=None) assert verifier._verify_scope(http_auth) # invalid incoming scope format mocker.patch( "fastapi_cloudauth.verification.jwt.get_unverified_claims", return_value={"dummy key": 100}, ) verifier = ScopedJWKsVerifier(jwks=JWKS.null(), scope_key=scope_key, scope_name=["read:test"]) with pytest.raises(HTTPException): verifier._verify_scope(http_auth) # auto_error is False verifier = ScopedJWKsVerifier( jwks=JWKS.null(), scope_key=scope_key, scope_name=["read:test"], auto_error=False, ) assert not verifier._verify_scope(http_auth)
async def test_forget_def_user_info(auth): dummy_http_auth = HTTPAuthorizationCredentials( scheme="a", credentials= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Im5hbWUiLCJpYXQiOjE1MTYyMzkwMjJ9.3ZEDmhWNZWDbJDPDlZX_I3oaalNYXdoT-bKLxIxQK4U", ) """If `.user_info` is None, return raw payload""" get_current_user = auth(jwks=JWKS.null()) assert get_current_user.user_info is None res = await get_current_user.call(dummy_http_auth) assert res == {"sub": "1234567890", "name": "name", "iat": 1516239022}
def test_validation_scope(mocker, scopes): mocker.patch( "fastapi_cloudauth.verification.jwt.get_unverified_claims", return_value={"dummy key": scopes}, ) verifier = ScopedAuth(jwks=JWKS.null()) scope_key = "dummy key" verifier.scope_key = scope_key scope_name = "user-assigned-scope" obj = verifier.scope(scope_name) assert obj.verifier._verify_scope( HTTPAuthorizationCredentials(scheme="", credentials="")) scope_name = "user-assigned-scope-invalid" obj = verifier.scope(scope_name) with pytest.raises(HTTPException): obj.verifier._verify_scope( HTTPAuthorizationCredentials(scheme="", credentials="")) obj.verifier.auto_error = False assert not obj.verifier._verify_scope( HTTPAuthorizationCredentials(scheme="", credentials=""))
def test_scope_match_all(mocker, scopes): scope_key = "dummy key" http_auth = HTTPAuthorizationCredentials( scheme="a", credentials="dummy-token", ) # check scope logic mocker.patch( "fastapi_cloudauth.verification.jwt.get_unverified_claims", return_value={"dummy key": scopes}, ) jwks = JWKS.null() # api scope < user scope verifier = ScopedJWKsVerifier( scope_name=["xxx:xxx"], jwks=jwks, scope_key=scope_key, auto_error=False, ) assert verifier._verify_scope(http_auth) # api scope == user scope (in order) verifier = ScopedJWKsVerifier( scope_name=["xxx:xxx", "yyy:yyy"], jwks=jwks, scope_key=scope_key, auto_error=False, ) assert verifier._verify_scope(http_auth) # api scope == user scope (disorder) verifier = ScopedJWKsVerifier( scope_name=["yyy:yyy", "xxx:xxx"], jwks=jwks, scope_key=scope_key, auto_error=False, ) assert verifier._verify_scope(http_auth) # api scope > user scope verifier = ScopedJWKsVerifier( scope_name=["yyy:yyy", "xxx:xxx", "zzz:zzz"], jwks=jwks, scope_key=scope_key, auto_error=False, ) assert not verifier._verify_scope(http_auth)
async def test_extract_raw_user_info(auth): dummy_http_auth = HTTPAuthorizationCredentials( scheme="a", credentials= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Im5hbWUiLCJpYXQiOjE1MTYyMzkwMjJ9.3ZEDmhWNZWDbJDPDlZX_I3oaalNYXdoT-bKLxIxQK4U", ) class NameSchema(BaseModel): name: str get_current_user = auth(jwks=JWKS.null(), user_info=NameSchema) get_current_user.user_info = None res = await get_current_user.call(dummy_http_auth) assert res == {"sub": "1234567890", "name": "name", "iat": 1516239022} get_current_user = auth(jwks=JWKS.null(), user_info=NameSchema) res = await get_current_user.claim(None).call(dummy_http_auth) assert res == {"sub": "1234567890", "name": "name", "iat": 1516239022}
async def test_bearer_auth() -> None: """Check bearer auth return user id from scope.""" request = Request( scope={ "type": "http", "method": "GET", "headers": [], "token_data": { "user_id": str(USER_UUID), } }) credentials: HTTPAuthorizationCredentials = HTTPAuthorizationCredentials( scheme="test", credentials="test", ) user_id: Optional[str] = await bearer_auth(request=request, http_credentials=credentials) AssertThat(user_id).IsEqualTo(str(USER_UUID))
def _assert_verifier_no_error(token, verifier: JWKsVerifier) -> None: http_auth = HTTPAuthorizationCredentials(scheme="a", credentials=token) assert verifier._verify_claims(http_auth) is False
def _assert_verifier(token, verifier: JWKsVerifier) -> HTTPException: http_auth = HTTPAuthorizationCredentials(scheme="a", credentials=token) with pytest.raises(HTTPException) as e: verifier._verify_claims(http_auth) return e.value
def test_verify_token(): verifier = JWKsVerifier(jwks=JWKS.null()) verifier_no_error = JWKsVerifier(jwks=JWKS.null(), auto_error=False) # correct token = jwt.encode( { "sub": "dummy-ID", "exp": datetime.utcnow() + timedelta(hours=10), "iat": datetime.utcnow(), }, "dummy_secret", headers={ "alg": "HS256", "typ": "JWT", "kid": "dummy-kid" }, ) verifier._verify_claims( HTTPAuthorizationCredentials(scheme="a", credentials=token)) verifier_no_error._verify_claims( HTTPAuthorizationCredentials(scheme="a", credentials=token)) # token expired token = jwt.encode( { "sub": "dummy-ID", "exp": datetime.utcnow() - timedelta(hours=10), # 10h before "iat": datetime.utcnow(), }, "dummy_secret", headers={ "alg": "HS256", "typ": "JWT", "kid": "dummy-kid" }, ) e = _assert_verifier(token, verifier) assert e.status_code == HTTP_401_UNAUTHORIZED and e.detail == messages.NOT_VERIFIED _assert_verifier_no_error(token, verifier_no_error) # token created at future token = jwt.encode( { "sub": "dummy-ID", "exp": datetime.utcnow() + timedelta(hours=10), "iat": datetime.utcnow() + timedelta(hours=10), }, "dummy_secret", headers={ "alg": "HS256", "typ": "JWT", "kid": "dummy-kid" }, ) e = _assert_verifier(token, verifier) assert e.status_code == HTTP_401_UNAUTHORIZED and e.detail == messages.NOT_VERIFIED _assert_verifier_no_error(token, verifier_no_error) # invalid format token = jwt.encode( { "sub": "dummy-ID", "exp": datetime.utcnow() + timedelta(hours=10), "iat": datetime.utcnow(), }, "dummy_secret", headers={ "alg": "HS256", "typ": "JWT", "kid": "dummy-kid" }, ) token = token.split(".")[0] e = _assert_verifier(token, verifier) assert (e.status_code == HTTP_401_UNAUTHORIZED and e.detail == messages.NOT_AUTHENTICATED) _assert_verifier_no_error(token, verifier_no_error)
async def test_get_user_id(auth_token): creds = HTTPAuthorizationCredentials(scheme="Bearer", credentials=auth_token) user_id = await auth.get_user_id(creds) assert user_id.startswith("auth0")
async def test_get_user_id_invalid(token): creds = HTTPAuthorizationCredentials(scheme="Bearer", credentials=token) with pytest.raises(HTTPException) as err: await auth.get_user_id(creds) assert err.value.status_code == 401