Skip to content

Commit

Permalink
Merge pull request #330 from bcgov/feature/329-fix-and-mature-openapi
Browse files Browse the repository at this point in the history
Fix and refactor open api
  • Loading branch information
esune authored Sep 14, 2023
2 parents 66d86c9 + 79086f3 commit dc1ca7a
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 108 deletions.
36 changes: 17 additions & 19 deletions oidc-controller/api/clientConfigurations/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,16 @@
from typing import List
from pymongo import ReturnDocument
from pymongo.database import Database
from fastapi import HTTPException
from fastapi import status as http_status
from fastapi.encoders import jsonable_encoder

from ..core.http_exception_util import raise_appropriate_http_exception, check_and_raise_not_found_http_exception
from ..core.oidc.provider import init_provider
from ..db.session import COLLECTION_NAMES

from .models import (
ClientConfiguration,
ClientConfigurationCreate,
ClientConfigurationPatch,
ClientConfigurationRead,
)
from ..db.session import COLLECTION_NAMES
from api.core.oidc.provider import init_provider


logger: structlog.typing.FilteringBoundLogger = structlog.getLogger(__name__)

Expand All @@ -25,32 +22,31 @@ def __init__(self, db: Database):
self._db = db

async def create(
self, client_config: ClientConfigurationCreate
self, client_config: ClientConfiguration
) -> ClientConfiguration:
col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFIGURATIONS)
col.insert_one(jsonable_encoder(client_config))
try:
col.insert_one(jsonable_encoder(client_config))
except Exception as err:
raise_appropriate_http_exception(
err, exists_msg="Client configuration already exists")

# remake provider instance to refresh provider client
await init_provider(self._db)
return ClientConfiguration(
**col.find_one({"client_id": client_config.client_id})
)

async def get(self, client_id: str) -> ClientConfigurationRead:
async def get(self, client_id: str) -> ClientConfiguration:
col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFIGURATIONS)
obj = col.find_one({"client_id": client_id})

if obj is None:
raise HTTPException(
status_code=http_status.HTTP_404_NOT_FOUND,
detail="The client_config hasn't been found!",
)
check_and_raise_not_found_http_exception(obj)

return ClientConfiguration(**obj)

async def get_all(self) -> List[ClientConfigurationRead]:
async def get_all(self) -> List[ClientConfiguration]:
col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFIGURATIONS)
return [ClientConfigurationRead(**cc) for cc in col.find()]
return [ClientConfiguration(**cc) for cc in col.find()]

async def patch(
self, client_id: str, data: ClientConfigurationPatch
Expand All @@ -61,15 +57,17 @@ async def patch(
{"$set": data.dict(exclude_unset=True)},
return_document=ReturnDocument.AFTER,
)
check_and_raise_not_found_http_exception(obj)

# remake provider instance to refresh provider client
await init_provider(self._db)
return obj

async def delete(self, client_id: str) -> bool:
col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFIGURATIONS)
obj = col.find_one_and_delete({"client_id": client_id})
check_and_raise_not_found_http_exception(obj)

# remake provider instance to refresh provider client
await init_provider(self._db)

return bool(obj)
2 changes: 1 addition & 1 deletion oidc-controller/api/clientConfigurations/examples.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from api.core.config import settings

ex_client_config_create = {
ex_client_config = {
"client_id": settings.OIDC_CLIENT_ID,
"client_name": settings.OIDC_CLIENT_NAME,
"client_secret": "**********",
Expand Down
16 changes: 6 additions & 10 deletions oidc-controller/api/clientConfigurations/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from enum import Enum
from typing import List, Optional
from typing import Optional, List
from pydantic import BaseModel, Field

from api.core.config import settings

from .examples import ex_client_config_create
from .examples import ex_client_config
from ..core.config import settings


class TOKENENDPOINTAUTHMETHODS(str, Enum):
Expand All @@ -24,6 +23,7 @@ class ClientConfigurationBase(BaseModel):

class Config:
allow_population_by_field_name = True
schema_extra = {"example": ex_client_config}


class ClientConfiguration(ClientConfigurationBase):
Expand All @@ -34,16 +34,12 @@ class ClientConfigurationRead(ClientConfigurationBase):
pass


class ClientConfigurationCreate(ClientConfigurationBase):
class Config:
schema_extra = {"example": ex_client_config_create}


class ClientConfigurationPatch(ClientConfigurationBase):
client_id: Optional[str]
client_name: Optional[str]
response_types: Optional[List[str]]
redirect_uris: Optional[List[str]]
token_endpoint_auth_method: Optional[TOKENENDPOINTAUTHMETHODS]

client_secret: Optional[str]

pass
52 changes: 27 additions & 25 deletions oidc-controller/api/clientConfigurations/router.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,42 @@
from typing import List
from pymongo.database import Database

from fastapi import APIRouter, HTTPException, Depends
from fastapi import APIRouter, Depends
from fastapi import status as http_status

from ..core.models import StatusMessage

from .crud import ClientConfigurationCRUD
from .models import (
ClientConfigurationRead,
ClientConfiguration,
ClientConfigurationPatch,
ClientConfigurationCreate,
ClientConfigurationRead,
)
from ..core.auth import get_api_key
from ..core.models import GenericErrorMessage, StatusMessage
from ..db.session import get_db


router = APIRouter()


@router.post(
"/",
response_description="Add new verification configuration",
response_description="Add new client configuration",
status_code=http_status.HTTP_201_CREATED,
response_model=ClientConfigurationCreate,
response_model=ClientConfiguration,
responses={http_status.HTTP_409_CONFLICT: {"model": GenericErrorMessage}},
response_model_exclude_unset=True,
dependencies=[Depends(get_api_key)],
)
async def create_client_config(
ver_config: ClientConfigurationCreate, db: Database = Depends(get_db)
client_config: ClientConfiguration, db: Database = Depends(get_db)
):
return await ClientConfigurationCRUD(db).create(ver_config)
return await ClientConfigurationCRUD(db).create(client_config)


@router.get(
"/",
status_code=http_status.HTTP_200_OK,
response_model=List[ClientConfigurationRead],
response_model_exclude_unset=True,
dependencies=[Depends(get_api_key)],
)
Expand All @@ -42,43 +45,42 @@ async def get_all_client_configs(db: Database = Depends(get_db)):


@router.get(
"/{client_config_id}",
"/{client_id}",
status_code=http_status.HTTP_200_OK,
response_model=ClientConfigurationRead,
responses={http_status.HTTP_404_NOT_FOUND: {"model": GenericErrorMessage}},
response_model_exclude_unset=True,
dependencies=[Depends(get_api_key)],
)
async def get_client_config(client_config_id: str, db: Database = Depends(get_db)):
return await ClientConfigurationCRUD(db).get(client_config_id)
async def get_client_config(client_id: str, db: Database = Depends(get_db)):
return await ClientConfigurationCRUD(db).get(client_id)


@router.patch(
"/{client_config_id}",
"/{client_id}",
status_code=http_status.HTTP_200_OK,
response_model=ClientConfigurationRead,
responses={http_status.HTTP_404_NOT_FOUND: {"model": GenericErrorMessage}},
response_model_exclude_unset=True,
dependencies=[Depends(get_api_key)],
)
async def patch_client_config(
client_config_id: str,
client_id: str,
data: ClientConfigurationPatch,
db: Database = Depends(get_db),
):
return await ClientConfigurationCRUD(db).patch(id=client_config_id, data=data)
return await ClientConfigurationCRUD(db).patch(
client_id=client_id, data=data
)


@router.delete(
"/{client_config_id}",
"/{client_id}",
status_code=http_status.HTTP_200_OK,
response_model=StatusMessage,
responses={http_status.HTTP_404_NOT_FOUND: {"model": GenericErrorMessage}},
dependencies=[Depends(get_api_key)],
)
async def delete_client_config(client_config_id: str, db: Database = Depends(get_db)):
status = await ClientConfigurationCRUD(db).delete(id=client_config_id)

if not status:
raise HTTPException(
status_code=http_status.HTTP_404_NOT_FOUND,
detail="client_config does not exist",
)
return StatusMessage(status=status, message="The client_config was deleted")
async def delete_client_config(client_id: str, db: Database = Depends(get_db)):
status = await ClientConfigurationCRUD(db).delete(client_id)
return StatusMessage(status=status, message="The client configuration was deleted")
28 changes: 28 additions & 0 deletions oidc-controller/api/core/http_exception_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from pymongo.errors import WriteError
from fastapi import HTTPException
from fastapi import status as http_status
import structlog

logger = structlog.getLogger(__name__)


def raise_appropriate_http_exception(err: WriteError, exists_msg: str = None):
if err.code == 11000:
raise HTTPException(
status_code=http_status.HTTP_409_CONFLICT,
detail=exists_msg,
)
else:
logger.error("Unknown error", err=err)
raise HTTPException(
status_code=http_status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="The server was unable to process the request",
)


def check_and_raise_not_found_http_exception(resp):
if resp is None:
raise HTTPException(
status_code=http_status.HTTP_404_NOT_FOUND,
detail="The requested resource wasn't found",
)
4 changes: 4 additions & 0 deletions oidc-controller/api/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ class Config:
class TimestampModel(BaseModel):
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)


class GenericErrorMessage(BaseModel):
detail: str
30 changes: 19 additions & 11 deletions oidc-controller/api/verificationConfigs/crud.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from fastapi import HTTPException
from fastapi import status as http_status
from typing import List
from fastapi.encoders import jsonable_encoder

from pymongo import ReturnDocument
from pymongo.database import Database

from ..core.http_exception_util import raise_appropriate_http_exception, check_and_raise_not_found_http_exception
from ..db.session import COLLECTION_NAMES

from .models import (
Expand All @@ -21,36 +21,44 @@ def __init__(self, db: Database):

async def create(self, ver_config: VerificationConfig) -> VerificationConfig:
ver_confs = self._db.get_collection(COLLECTION_NAMES.VER_CONFIGS)
ver_confs.insert_one(jsonable_encoder(ver_config))

try:
ver_confs.insert_one(jsonable_encoder(ver_config))
except Exception as err:
raise_appropriate_http_exception(
err, exists_msg="Verification configuration already exists")
return ver_confs.find_one({"ver_config_id": ver_config.ver_config_id})

async def get(self, ver_config_id: str) -> VerificationConfig:
ver_confs = self._db.get_collection(COLLECTION_NAMES.VER_CONFIGS)
ver_conf = ver_confs.find_one({"ver_config_id": ver_config_id})

if ver_conf is None:
raise HTTPException(
status_code=http_status.HTTP_404_NOT_FOUND,
detail="The verification_config hasn't been found!",
)
check_and_raise_not_found_http_exception(ver_conf)

return VerificationConfig(**ver_conf)

async def get_all(self) -> List[VerificationConfig]:
ver_confs = self._db.get_collection(COLLECTION_NAMES.VER_CONFIGS)
return [VerificationConfig(**vc) for vc in ver_confs.find()]

async def patch(
self, ver_config_id: str, data: VerificationConfigPatch
) -> VerificationConfig:
if not isinstance(data, VerificationConfigPatch):
raise Exception("please provide an instance of the <document> PATCH class")
raise Exception(
"please provide an instance of the <document> PATCH class")
ver_confs = self._db.get_collection(COLLECTION_NAMES.VER_CONFIGS)
ver_conf = ver_confs.find_one_and_update(
{"ver_config_id": ver_config_id},
{"$set": data.dict(exclude_unset=True)},
return_document=ReturnDocument.AFTER,
)
check_and_raise_not_found_http_exception(ver_conf)

return ver_conf

async def delete(self, ver_config_id: str) -> bool:
ver_confs = self._db.get_collection(COLLECTION_NAMES.VER_CONFIGS)
ver_conf = ver_confs.find_one_and_delete({"ver_config_id": ver_config_id})
ver_conf = ver_confs.find_one_and_delete(
{"ver_config_id": ver_config_id})
check_and_raise_not_found_http_exception(ver_conf)
return bool(ver_conf)
15 changes: 1 addition & 14 deletions oidc-controller/api/verificationConfigs/examples.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
ex_ver_config_read = {
"ver_config_id": "test-request-config",
"subject_identifier": "first_name",
"proof_request": {
"name": "Basic Proof",
"version": "1.0",
"requested_attributes": [
{"names": ["first_name", "last_name"], "restrictions": []},
],
"requested_predicates": [],
},
}

ex_ver_config_create = {
ex_ver_config = {
"ver_config_id": "test-request-config",
"subject_identifier": "first_name",
"proof_request": {
Expand Down
Loading

0 comments on commit dc1ca7a

Please sign in to comment.