-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into dependabot/github_actions/actions/download-a…
…rtifact-4.1.2
- Loading branch information
Showing
21 changed files
with
1,378 additions
and
957 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.venv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,7 @@ jobs: | |
uses: actions/[email protected] | ||
|
||
- name: Set up Python ${{ matrix.python }} | ||
uses: actions/setup-python@v4.7.0 | ||
uses: actions/setup-python@v5.0.0 | ||
with: | ||
python-version: ${{ matrix.python }} | ||
|
||
|
@@ -120,7 +120,7 @@ jobs: | |
uses: actions/[email protected] | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v4.7.0 | ||
uses: actions/setup-python@v5.0.0 | ||
with: | ||
python-version: "3.10" | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -130,3 +130,6 @@ dmypy.json | |
|
||
# OS | ||
.DS_Store | ||
.aider* | ||
|
||
pygeoapi-openapi.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Auth package.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
"""Auth interface module.""" | ||
from abc import ABC | ||
from abc import abstractmethod | ||
from typing import Dict | ||
from typing import Union | ||
|
||
from starlette.requests import Request | ||
from starlette.responses import RedirectResponse | ||
|
||
|
||
class AuthInterface(ABC): | ||
"""Define the interface for the authentication instances. | ||
The interface provides necessary methods for the OPAMiddleware | ||
authentication flow. This allows to easily integrate various auth methods. | ||
""" | ||
|
||
@abstractmethod | ||
async def authenticate(self, request: Request) -> Union[RedirectResponse, Dict]: | ||
"""Authenticate the incoming request. | ||
The method returns a dictionary containing the valid and authorized | ||
users information or a redirect since some flows require calling a | ||
identity broker beforehand. | ||
""" | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
"""Auth JWKS module.""" | ||
import typing | ||
from dataclasses import dataclass | ||
from dataclasses import field | ||
|
||
import httpx | ||
from app.auth.auth_interface import AuthInterface | ||
from app.auth.exceptions import Oauth2Error | ||
from app.config.logging import create_logger | ||
from authlib.jose import errors | ||
from authlib.jose import JsonWebKey | ||
from authlib.jose import JsonWebToken | ||
from authlib.jose import JWTClaims | ||
from authlib.jose import KeySet | ||
from starlette.requests import Request | ||
from starlette.responses import RedirectResponse | ||
|
||
# from cachetools import cached | ||
# from cachetools import TTLCache | ||
|
||
|
||
logger = create_logger("app.auth.auth_jwks") | ||
|
||
|
||
@dataclass | ||
class JWKSConfig: | ||
"""JWKS configuration instance.""" | ||
|
||
jwks_uri: str = field(default="") | ||
|
||
|
||
class JWKSAuthentication(AuthInterface): | ||
"""JWKS authentication instance.""" | ||
|
||
def __init__(self, config: JWKSConfig) -> None: | ||
"""Initialize the authentication.""" | ||
self.config = config | ||
|
||
# @cached(TTLCache(maxsize=1, ttl=3600)) | ||
async def get_jwks(self) -> KeySet: | ||
"""Get cached or new JWKS.""" | ||
url = self.config.jwks_uri | ||
logger.info(f"Fetching JSON Web Key Set from {url}") | ||
async with httpx.AsyncClient() as client: | ||
response = await client.get(url) | ||
return JsonWebKey.import_key_set(response.json()) | ||
|
||
async def decode_token( | ||
self, | ||
token: str, | ||
) -> JWTClaims: | ||
"""Validate and decode JWT.""" | ||
try: | ||
jwks = await self.get_jwks() | ||
claims = JsonWebToken(["RS256"]).decode( | ||
s=token, | ||
key=jwks, | ||
# claim_options={ | ||
# # Example of validating audience to match expected value | ||
# # "aud": {"essential": True, "values": [APP_CLIENT_ID]} | ||
# } | ||
) | ||
if "client_id" in claims: | ||
# Insert Cognito's `client_id` into `aud` claim if `aud` claim is unset | ||
claims.setdefault("aud", claims["client_id"]) | ||
claims.validate() | ||
except errors.ExpiredTokenError: | ||
logger.error("Unable to validate an expired token") | ||
raise Oauth2Error("Unable to validate an expired token") # noqa | ||
except errors.JoseError: | ||
logger.error("Unable to decode token") | ||
raise Oauth2Error("Unable to decode token") # noqa | ||
|
||
return claims | ||
|
||
async def authenticate( | ||
self, | ||
request: Request, | ||
accepted_methods: typing.Optional[typing.List[str]] = ["access_token"], # noqa | ||
) -> typing.Union[RedirectResponse, typing.Dict]: | ||
"""Authenticate the caller with the incoming request.""" | ||
bearer = request.headers.get("Authorization") | ||
if not bearer: | ||
logger.exception("Unable to get a token") | ||
raise Oauth2Error("Auth token not found") | ||
access_token = bearer.replace("Bearer ", "") | ||
try: | ||
claims = await self.decode_token(access_token) | ||
if not claims: | ||
pass | ||
return claims | ||
except Exception: | ||
raise Oauth2Error("Authentication error") # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
"""Authentication exceptions module.""" | ||
|
||
|
||
class AuthenticationError(Exception): | ||
"""This is being raised for exceptions within the auth flow.""" | ||
|
||
pass | ||
|
||
|
||
class Oauth2Error(AuthenticationError): | ||
"""Oauth2 authentication flow exception.""" | ||
|
||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
"""OAuth2 provider module.""" | ||
import re | ||
from abc import ABC | ||
from abc import abstractmethod | ||
from typing import List | ||
from typing import Optional | ||
|
||
from app.auth.auth_interface import AuthInterface | ||
from starlette.requests import Request | ||
|
||
|
||
class Injectable(ABC): | ||
"""Define interface for injectables.""" | ||
|
||
def __init__( | ||
self, key: str, skip_endpoints: Optional[List[str]] = [] # noqa B006 | ||
) -> None: | ||
"""Set properties initialization for injectables.""" | ||
self.key = key | ||
self.skip_endpoints = [ | ||
re.compile(skip) for skip in skip_endpoints # type:ignore | ||
] | ||
|
||
@abstractmethod | ||
async def extract(self, request: Request) -> List: | ||
"""Extract the token from the request.""" | ||
pass | ||
|
||
|
||
class Oauth2Provider: | ||
"""OAuth2 middleware.""" | ||
|
||
def __init__( | ||
self, | ||
authentication: [AuthInterface, List[AuthInterface]], # type:ignore | ||
injectables: Optional[List[Injectable]] = None, | ||
accepted_methods: Optional[List[str]] = [ # noqa B006 | ||
"id_token", | ||
"access_token", | ||
], | ||
) -> None: | ||
"""Handle configuration container for the OAuth2 middleware. # noqa D405 | ||
PARAMETERS | ||
---------- | ||
authentication: [AuthInterface, List[AuthInterface]] | ||
Authentication Implementations to be used for the | ||
request authentication. | ||
injectables: List[Injectable], default=None | ||
List of injectables to be used to add information to the | ||
request payload. | ||
accepted_methods: List[str], default=["id_token", "access_token"] | ||
List of accepted authentication methods. | ||
""" | ||
if not isinstance(authentication, list): | ||
authentication = [authentication] | ||
self.authentication = authentication | ||
self.injectables = injectables | ||
self.accepted_methods = accepted_methods |
Oops, something went wrong.