Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add eoapi-auth-utils helper #424

Draft
wants to merge 6 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion common/__init__.py

This file was deleted.

17 changes: 0 additions & 17 deletions common/auth/setup.py

This file was deleted.

5 changes: 0 additions & 5 deletions common/auth/veda_auth/__init__.py

This file was deleted.

128 changes: 0 additions & 128 deletions common/auth/veda_auth/main.py

This file was deleted.

4 changes: 4 additions & 0 deletions ingest_api/infrastructure/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class IngestorConfig(BaseSettings):
raster_api_url: str = Field(
description="URL of Raster API used to serve asset tiles"
)
stac_client_id: Optional[str] = Field(description="The Veda Auth Central client ID")
openid_configuration_url: Optional[AnyHttpUrl] = Field(
description="OpenID config url"
)

ingest_root_path: str = Field("", description="Root path for ingest API")
custom_host: Optional[str] = Field(description="Custom host name")
Expand Down
2 changes: 2 additions & 0 deletions ingest_api/infrastructure/construct.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ def __init__(
"USERPOOL_ID": config.userpool_id,
"CLIENT_ID": config.client_id,
"CLIENT_SECRET": config.client_secret,
"STAC_CLIENT_ID": config.stac_client_id,
"STAC_OPENID_CONFIGURATION_URL": config.openid_configuration_url,
"RASTER_URL": config.raster_api_url,
"ROOT_PATH": config.ingest_root_path,
"STAGE": config.stage,
Expand Down
4 changes: 0 additions & 4 deletions ingest_api/runtime/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ RUN echo "Using PGSTAC Version ${PGSTAC_VERSION}"

WORKDIR /tmp

COPY common/auth /tmp/common/auth
RUN pip install /tmp/common/auth -t /asset
RUN rm -rf /tmp/common

COPY ingest_api/runtime/requirements.txt /tmp/ingestor/requirements.txt
RUN pip install -r /tmp/ingestor/requirements.txt pypgstac==${PGSTAC_VERSION} -t /asset --no-binary pydantic uvicorn
RUN rm -rf /tmp/ingestor
Expand Down
1 change: 1 addition & 0 deletions ingest_api/runtime/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ stac-pydantic @ git+https://github.com/ividito/stac-pydantic.git@3f4cb381c85749b
boto3==1.24.59
aws_xray_sdk>=2.6.0,<3
aws-lambda-powertools>=1.18.0
eoapi-auth-utils>=0.4.0
21 changes: 21 additions & 0 deletions ingest_api/runtime/src/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import Any, Dict

from src.config import VedaOpenIdConnectSettings
from typing_extensions import Annotated

from fastapi import Depends

from eoapi.auth_utils import OpenIdConnectAuth

auth_settings = VedaOpenIdConnectSettings()

oidc_auth = OpenIdConnectAuth(
openid_configuration_url=auth_settings.openid_configuration_url,
)


def get_username(
token: Annotated[Dict[Any, Any], Depends(oidc_auth.valid_token_dependency)]
) -> str:
result = token["username"] if "username" in token else str(token.get("sub"))
return result
12 changes: 9 additions & 3 deletions ingest_api/runtime/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from getpass import getuser
from typing import Optional

from eoapi.auth_utils import OpenIdConnectSettings
from pydantic import AnyHttpUrl, BaseSettings, Field, constr
from pydantic_ssm_settings import AwsSsmSourceConfig
from veda_auth import VedaAuth

AwsArn = constr(regex=r"^arn:aws:iam::\d{12}:role/.+")

Expand Down Expand Up @@ -55,6 +55,14 @@ def from_ssm(cls, stack: str):
return cls(_secrets_dir=f"/{stack}")


class VedaOpenIdConnectSettings(OpenIdConnectSettings):
"""eoapi-auth-utils settings subclass needed for Pydantic v1 compatibility"""

class Config:
env_prefix = "STAC_"
extra = "ignore"


settings = (
Settings()
if os.environ.get("NO_PYDANTIC_SSM_SETTINGS")
Expand All @@ -64,5 +72,3 @@ def from_ssm(cls, stack: str):
),
)
)

auth = VedaAuth(settings)
7 changes: 3 additions & 4 deletions ingest_api/runtime/src/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import boto3
import src.services as services
from src.config import auth, settings
from src.auth import get_username
from src.config import settings

from fastapi import Depends, HTTPException, security

logger = logging.getLogger(__name__)

token_scheme = security.HTTPBearer()


def get_table():
client = boto3.resource("dynamodb")
Expand All @@ -23,7 +22,7 @@ def get_db(table=Depends(get_table)) -> services.Database:
def fetch_ingestion(
ingestion_id: str,
db: services.Database = Depends(get_db),
username: str = Depends(auth.get_username),
username: str = Depends(get_username),
):
try:
return db.fetch_one(username=username, ingestion_id=ingestion_id)
Expand Down
37 changes: 28 additions & 9 deletions ingest_api/runtime/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
import src.schemas as schemas
import src.services as services
from aws_lambda_powertools.metrics import MetricUnit
from src.auth import auth_settings, get_username, oidc_auth
from src.collection_publisher import CollectionPublisher, ItemPublisher
from src.config import auth, settings
from src.config import settings
from src.doc import DESCRIPTION
from src.monitoring import LoggerRouteHandler, logger, metrics, tracer

from fastapi import Depends, FastAPI, HTTPException
from fastapi import Depends, FastAPI, HTTPException, Security
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordRequestForm
from starlette.requests import Request

from eoapi.auth_utils import OpenIdConnectAuth

app = FastAPI(
title="VEDA Ingestion API",
description=DESCRIPTION,
Expand All @@ -27,9 +30,10 @@
openapi_url="/openapi.json",
docs_url="/docs",
swagger_ui_init_oauth={
"appName": "Cognito",
"clientId": settings.client_id,
"appName": "Ingest API",
"clientId": auth_settings.client_id,
"usePkceWithAuthorizationCodeGrant": True,
"scopes": "openid stac:item:create stac:item:update stac:item:delete stac:collection:create stac:collection:update stac:collection:delete",
},
)

Expand Down Expand Up @@ -59,10 +63,13 @@ async def list_ingestions(
response_model=schemas.Ingestion,
tags=["Ingestion"],
status_code=201,
dependencies=[
Security(oidc_auth.valid_token_dependency, scopes="stac:item:create")
],
)
async def enqueue_ingestion(
item: schemas.AccessibleItem,
username: str = Depends(auth.get_username),
username: str = Depends(get_username),
db: services.Database = Depends(dependencies.get_db),
) -> schemas.Ingestion:
"""
Expand Down Expand Up @@ -96,6 +103,9 @@ def get_ingestion(
"/ingestions/{ingestion_id}",
response_model=schemas.Ingestion,
tags=["Ingestion"],
dependencies=[
Security(oidc_auth.valid_token_dependency, scopes="stac:item:update")
],
)
def update_ingestion(
update: schemas.UpdateIngestionRequest,
Expand All @@ -113,6 +123,9 @@ def update_ingestion(
"/ingestions/{ingestion_id}",
response_model=schemas.Ingestion,
tags=["Ingestion"],
dependencies=[
Security(oidc_auth.valid_token_dependency, scopes="stac:item:delete")
],
)
def cancel_ingestion(
ingestion: schemas.Ingestion = Depends(dependencies.fetch_ingestion),
Expand All @@ -135,7 +148,9 @@ def cancel_ingestion(
"/collections",
tags=["Collection"],
status_code=201,
dependencies=[Depends(auth.validated_token)],
dependencies=[
Security(oidc_auth.valid_token_dependency, scopes="stac:collection:create")
],
)
def publish_collection(collection: schemas.DashboardCollection):
"""
Expand All @@ -155,7 +170,9 @@ def publish_collection(collection: schemas.DashboardCollection):
@app.delete(
"/collections/{collection_id}",
tags=["Collection"],
dependencies=[Depends(auth.validated_token)],
dependencies=[
Security(oidc_auth.valid_token_dependency, scopes="stac:collection:delete")
],
)
def delete_collection(collection_id: str):
"""
Expand All @@ -173,7 +190,9 @@ def delete_collection(collection_id: str):
"/items",
tags=["Items"],
status_code=201,
dependencies=[Depends(auth.validated_token)],
dependencies=[
Security(oidc_auth.valid_token_dependency, scopes="stac:item:create")
],
)
def publish_item(item: schemas.Item):
"""
Expand Down Expand Up @@ -213,7 +232,7 @@ async def get_token(


@app.get("/auth/me", tags=["Auth"])
def who_am_i(claims=Depends(auth.validated_token)):
def who_am_i(claims=Depends(oidc_auth.valid_token_dependency)):
"""
Return claims for the provided JWT
"""
Expand Down
2 changes: 0 additions & 2 deletions local/Dockerfile.ingest
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ RUN pip install -r /tmp/ingestor/requirements.txt --no-binary pydantic uvicorn
RUN rm -rf /tmp/ingestor
# TODO this is temporary until we use a real packaging system like setup.py or poetry
COPY ingest_api/runtime/src /asset/src
COPY common/auth /tmp/common/auth
RUN pip install /tmp/common/auth

# # Reduce package size and remove useless files
RUN cd /asset && find . -type f -name '*.pyc' | while read f; do n=$(echo $f | sed 's/__pycache__\///' | sed 's/.cpython-[2-3][0-9]//'); cp $f $n; done;
Expand Down
Loading