Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/pip/dot-github/workflows/nox-2024…
Browse files Browse the repository at this point in the history
….10.9
  • Loading branch information
francbartoli authored Nov 1, 2024
2 parents a9587fc + 659a421 commit e0604e3
Show file tree
Hide file tree
Showing 35 changed files with 2,954 additions and 2,773 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.venv
pygeoapi-openapi.yml
pygeoapi-openapi.json
11 changes: 5 additions & 6 deletions .env
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
ENV_STATE=dev

# base configs
# tiangolo uvicorn-gunicorn-fastapi-docker configs
MODULE_NAME=app.main
VARIABLE_NAME=app
#- GUNICORN_CONF="/app/custom_gunicorn_conf.py"
WORKERS_PER_CORE=1
WEB_CONCURRENCY=2
HOST=0.0.0.0
PORT=5000
LOG_LEVEL=info
#- WORKER_CLASS="uvicorn.workers.UvicornWorker"
LOG_LEVEL=debug
TIMEOUT=120

# aws
Expand Down Expand Up @@ -46,15 +43,16 @@ DEV_OIDC_CLIENT_ID=pygeoapi-client
DEV_OIDC_CLIENT_SECRET=2yholx8r3mqyUJaOoJiZhcqvQDQwmgyD
# oidc-jwks-only
DEV_JWKS_ENABLED=true
DEV_OAUTH2_JWKS_ENDPOINT=https://uat.interop.pagopa.it/.well-known/jwks.json
DEV_OAUTH2_TOKEN_ENDPOINT=https://auth.uat.interop.pagopa.it/token.oauth2
DEV_OAUTH2_JWKS_ENDPOINT=https://76hxgq.logto.app/oidc/jwks
DEV_OAUTH2_TOKEN_ENDPOINT=https://76hxgq.logto.app/oidc/token
# pygeoapi
DEV_PYGEOAPI_BASEURL=http://localhost:5000
DEV_PYGEOAPI_CONFIG=pygeoapi-config.yml
DEV_PYGEOAPI_OPENAPI=pygeoapi-openapi.yml
DEV_PYGEOAPI_SECURITY_SCHEME=http
# fastgeoapi
DEV_FASTGEOAPI_CONTEXT=/geoapi
DEV_FASTGEOAPI_REVERSE_PROXY=false

# prod configs
PROD_ROOT_PATH=
Expand Down Expand Up @@ -89,3 +87,4 @@ PROD_PYGEOAPI_OPENAPI=pygeoapi-openapi.yml
PROD_PYGEOAPI_SECURITY_SCHEME=http
# fastgeoapi
PROD_FASTGEOAPI_CONTEXT=/geoapi
PROD_FASTGEOAPI_REVERSE_PROXY=false
1 change: 1 addition & 0 deletions .github/requirements-docs.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mkdocs-material
termynal
mkdocs-swagger-ui-tag
1 change: 1 addition & 0 deletions .github/workflows/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pip==24.1.2
nox==2024.10.9
nox-poetry==1.0.3
poetry==1.8.3
poetry-plugin-export==1.8.0
virtualenv==20.26.3
2 changes: 1 addition & 1 deletion .github/workflows/contract-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
uses: actions/[email protected]

- name: Set up Python 3.10
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@v5.3.0
with:
python-version: "3.10"

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ jobs:
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- name: Setup Python
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@v5.3.0
with:
python-version: 3.*
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v4.0.2
- uses: actions/cache@v4.1.2
with:
key: mkdocs-material-${{ env.cache_id }}
path: .cache
restore-keys: |
mkdocs-material-
- name: Install dependencies
run: pip install mkdocs mkdocs-material termynal
run: pip install mkdocs mkdocs-material mkdocs-typer termynal mkdocs-swagger-ui-tag
- name: Deploy and publish to GitHub Pages
run: mkdocs gh-deploy --force
8 changes: 4 additions & 4 deletions .github/workflows/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
uses: actions/[email protected]

- name: Set up Python 3.10
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@v5.3.0
with:
python-version: "3.10"

Expand All @@ -28,7 +28,7 @@ jobs:
pipx install --pip-args=--constraint=$GITHUB_WORKSPACE/.github/workflows/constraints.txt poetry
poetry --version
- name: Install fastgeoapi CLI
- name: Create OpenAPI with fastgeoapi CLI
run: |
poetry install
poetry run fastgeoapi openapi
Expand All @@ -53,7 +53,7 @@ jobs:
uses: actions/[email protected]

- name: Set up Python 3.10
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@v5.3.0
with:
python-version: "3.10"

Expand All @@ -67,7 +67,7 @@ jobs:
pipx install --pip-args=--constraint=$GITHUB_WORKSPACE/.github/workflows/constraints.txt poetry
poetry --version
- name: Install fastgeoapi CLI
- name: Create OpenAPI with fastgeoapi CLI
run: |
poetry install
poetry run fastgeoapi openapi
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fetch-depth: 2

- name: Set up Python
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@v5.3.0
with:
python-version: "3.10"

Expand Down Expand Up @@ -57,14 +57,14 @@ jobs:
- name: Publish package on PyPI
if: steps.check-version.outputs.tag
uses: pypa/gh-action-pypi-publish@v1.9.0
uses: pypa/gh-action-pypi-publish@v1.11.0
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}

- name: Publish package on TestPyPI
if: "! steps.check-version.outputs.tag"
uses: pypa/gh-action-pypi-publish@v1.9.0
uses: pypa/gh-action-pypi-publish@v1.11.0
with:
user: __token__
password: ${{ secrets.TEST_PYPI_TOKEN }}
Expand Down
14 changes: 6 additions & 8 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ jobs:
- { python: "3.10", os: "ubuntu-20.04", session: "safety" }
- { python: "3.10", os: "ubuntu-20.04", session: "bandit" }
- { python: "3.10", os: "ubuntu-20.04", session: "mypy" }
- { python: "3.9", os: "ubuntu-20.04", session: "mypy" }
- { python: "3.10", os: "ubuntu-20.04", session: "tests" }
- { python: "3.9", os: "ubuntu-20.04", session: "tests" }
# - { python: "3.10", os: "windows-latest", session: "tests" }
# - { python: "3.10", os: "macos-latest", session: "tests" }
- { python: "3.10", os: "ubuntu-20.04", session: "typeguard" }
Expand All @@ -35,7 +33,7 @@ jobs:
uses: actions/[email protected]

- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python }}

Expand Down Expand Up @@ -86,7 +84,7 @@ jobs:
print("::set-output name=result::{}".format(result))
- name: Restore pre-commit cache
uses: actions/cache@v4.0.2
uses: actions/cache@v4.1.2
if: matrix.session == 'pre-commit'
with:
path: ~/.cache/pre-commit
Expand All @@ -100,14 +98,14 @@ jobs:
- name: Upload coverage data
if: always() && matrix.session == 'tests'
uses: "actions/upload-artifact@v4.3.4"
uses: "actions/upload-artifact@v4.4.3"
with:
name: coverage-data
path: ".coverage.*"

- name: Upload documentation
if: matrix.session == 'docs-build'
uses: actions/upload-artifact@v4.3.4
uses: actions/upload-artifact@v4.4.3
with:
name: docs
path: docs_build/site
Expand All @@ -120,7 +118,7 @@ jobs:
uses: actions/[email protected]

- name: Set up Python
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@v5.3.0
with:
python-version: "3.10"

Expand Down Expand Up @@ -154,4 +152,4 @@ jobs:
nox --force-color --session=coverage -- xml
- name: Upload coverage report
uses: codecov/codecov-action@v4.5.0
uses: codecov/codecov-action@v4.6.0
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Tracker](https://github.com/geobeyond/fastgeoapi/issues).

## How to set up your development environment

You need Python 3.9+ and the following tools:
You need Python 3.10+ and the following tools:

- [Poetry](https://python-poetry.org/)
- [Nox](https://nox.thea.codes/)
Expand Down
37 changes: 33 additions & 4 deletions app/auth/auth_jwks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from app.auth.auth_interface import AuthInterface
from app.auth.exceptions import Oauth2Error
from app.auth.models import OAuth2Claim
from app.config.logging import create_logger

# from cachetools import cached
Expand Down Expand Up @@ -43,7 +44,7 @@ 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:
async with httpx.AsyncClient(timeout=30) as client:
response = await client.get(url)
return JsonWebKey.import_key_set(response.json())

Expand All @@ -54,37 +55,65 @@ async def decode_token(
"""Validate and decode JWT."""
try:
jwks = await self.get_jwks()
claims = JsonWebToken(["RS256"]).decode(
logger.debug(f"JSON Key Set: {jwks.as_json()}")
keys = jwks.as_dict()["keys"]
# Extract algs and remove None
algs = [
item
for item in tuple({key.get("alg") for key in keys})
if item is not None
]
if len(algs) > 1:
logger.error("Multiple algorithms are not supported")
raise Oauth2Error(
"Unable to decode the token with multiple algorithms"
) # noqa
alg = algs[0]
if not alg:
raise Oauth2Error(
"Unable to decode the token with a missing algorithm"
) # noqa
logger.debug(f"Algorithm used for decoding the token: {alg}")
claims = JsonWebToken([alg]).decode(
s=token,
key=jwks,
# claim_options={
# # Example of validating audience to match expected value
# # "aud": {"essential": True, "values": [APP_CLIENT_ID]}
# }
)
logger.debug(f"Decoded claims: {OAuth2Claim(**claims)}")
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 KeyError:
logger.error("Unable to find an algorithm in the key")
raise Oauth2Error( # noqa
"Unable to decode the token with a missing algorithm"
)
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
except Exception as e:
logger.error(f"Generic decode exception: f{e}")
raise e

return claims

async def authenticate(
self,
request: Request,
accepted_methods: typing.Optional[typing.List[str]] = ["access_token"], # noqa
accepted_methods: 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")
raise Oauth2Error("Auth token not found in the incoming request")
access_token = bearer.replace("Bearer ", "")
try:
claims = await self.decode_token(access_token)
Expand Down
23 changes: 23 additions & 0 deletions app/auth/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
"""Authentication models module."""

import typing

import pydantic
from openapi_pydantic.v3.v3_0_3 import Response

unauthorized = {
"401": Response(description="Unauthorized response", message="Unauthenticated")
}


class OAuth2Claim(pydantic.BaseModel):
"""Parse OAuth2 claims."""

jti: str = pydantic.Field(...)
sub: str = pydantic.Field(...)
iat: int = pydantic.Field(...)
exp: int = pydantic.Field(...)
iss: str = pydantic.Field(...)
aud: str = pydantic.Field(...)
client_id: typing.Optional[str] = pydantic.Field(None)


class TokenPayload(pydantic.BaseModel):
"""Parse payload to token endpoint."""

grant_type: str = pydantic.Field(...)
resource: str = pydantic.Field(...)
scope: str = pydantic.Field(...)
7 changes: 4 additions & 3 deletions app/auth/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from abc import abstractmethod
from typing import List
from typing import Optional
from typing import Union

from starlette.requests import Request

Expand Down Expand Up @@ -34,9 +35,9 @@ class Oauth2Provider:

def __init__(
self,
authentication: [AuthInterface, List[AuthInterface]], # type:ignore
authentication: Union[AuthInterface, List[AuthInterface]],
injectables: Optional[List[Injectable]] = None,
accepted_methods: Optional[List[str]] = [ # noqa B006
accepted_methods: List[str] = [ # noqa B006
"id_token",
"access_token",
],
Expand All @@ -46,7 +47,7 @@ def __init__(
PARAMETERS
----------
authentication: [AuthInterface, List[AuthInterface]]
Authentication Implementations to be used for the
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
Expand Down
9 changes: 9 additions & 0 deletions app/config/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ class DevConfig(GlobalConfig):
FASTGEOAPI_CONTEXT: Optional[str] = pydantic.Field(
None, env="DEV_FASTGEOAPI_CONTEXT" # type: ignore
)
FASTGEOAPI_REVERSE_PROXY: Optional[bool] = pydantic.Field(
None, env="DEV_FASTGEOAPI_REVERSE_PROXY" # type: ignore
)

model_config = SettingsConfigDict(env_prefix="DEV_")

Expand Down Expand Up @@ -144,6 +147,9 @@ class ProdConfig(GlobalConfig):
API_KEY_ENABLED: Optional[bool] = pydantic.Field(
None, env="PROD_API_KEY_ENABLED" # type: ignore
)
JWKS_ENABLED: Optional[bool] = pydantic.Field(
None, env="PROD_JWKS_ENABLED" # type: ignore
)
OAUTH2_JWKS_ENDPOINT: Optional[str] = pydantic.Field(
None, env="PROD_OAUTH2_JWKS_ENDPOINT" # type: ignore
)
Expand All @@ -168,6 +174,9 @@ class ProdConfig(GlobalConfig):
FASTGEOAPI_CONTEXT: Optional[str] = pydantic.Field(
None, env="PROD_FASTGEOAPI_CONTEXT" # type: ignore
)
FASTGEOAPI_REVERSE_PROXY: Optional[bool] = pydantic.Field(
None, env="PROD_FASTGEOAPI_REVERSE_PROXY" # type: ignore
)

model_config = SettingsConfigDict(env_prefix="PROD_")

Expand Down
Loading

0 comments on commit e0604e3

Please sign in to comment.