From df5ea578ac64e16ca6ea6f1416e2fa2330f4336e Mon Sep 17 00:00:00 2001 From: JSyro Date: Tue, 21 Mar 2023 15:20:48 -0700 Subject: [PATCH 1/8] dynamic instantiation and client config in mongo Signed-off-by: JSyro --- .../api/clientConfigurations/crud.py | 63 +++++++++++++++++++ .../api/clientConfigurations/models.py | 37 +++++++++++ oidc-controller/api/core/oidc/provider.py | 45 +++++++------ oidc-controller/api/db/collections.py | 1 + oidc-controller/api/routers/oidc.py | 2 - 5 files changed, 123 insertions(+), 25 deletions(-) create mode 100644 oidc-controller/api/clientConfigurations/crud.py create mode 100644 oidc-controller/api/clientConfigurations/models.py diff --git a/oidc-controller/api/clientConfigurations/crud.py b/oidc-controller/api/clientConfigurations/crud.py new file mode 100644 index 00000000..4f730cf1 --- /dev/null +++ b/oidc-controller/api/clientConfigurations/crud.py @@ -0,0 +1,63 @@ +import logging + +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.models import PyObjectId +from .models import ( + ClientConfiguration, + ClientConfigurationCreate, + ClientConfigurationPatch, +) +from ..db.session import COLLECTION_NAMES + + +logger = logging.getLogger(__name__) + + +class ClientConfigurationCRUD: + def __init__(self, db: Database): + self._db = db + + async def create( + self, client_config: ClientConfigurationCreate + ) -> ClientConfiguration: + col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) + result = col.insert_one(jsonable_encoder(client_config)) + return ClientConfiguration(**col.find_one({"_id": result.inserted_id})) + + async def get(self, id: str) -> ClientConfiguration: + if not PyObjectId.is_valid(id): + raise HTTPException( + status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" + ) + col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) + auth_sess = col.find_one({"_id": PyObjectId(id)}) + + if auth_sess is None: + raise HTTPException( + status_code=http_status.HTTP_404_NOT_FOUND, + detail="The auth_session hasn't been found!", + ) + + return ClientConfiguration(**auth_sess) + + async def patch( + self, id: str, data: ClientConfigurationPatch + ) -> ClientConfiguration: + col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) + auth_sess = col.find_one_and_update( + {"_id": id}, + {"$set": data.dict(exclude_unset=True)}, + return_document=ReturnDocument.AFTER, + ) + + return auth_sess + + async def delete(self, auth_session_id: str) -> bool: + col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) + auth_sess = col.find_one_and_delete({"_id": auth_session_id}) + return bool(auth_sess) diff --git a/oidc-controller/api/clientConfigurations/models.py b/oidc-controller/api/clientConfigurations/models.py new file mode 100644 index 00000000..39b4eee0 --- /dev/null +++ b/oidc-controller/api/clientConfigurations/models.py @@ -0,0 +1,37 @@ +from enum import Enum +from typing import List +from pydantic import BaseModel, Field + +from api.core.models import UUIDModel +from api.core.config import settings + + +class TOKENENDPOINTAUTHMETHODS(str, Enum): + client_secret_basic = "client_secret_basic" + + +class ClientConfigurationBase(BaseModel): + client_id: str = Field(default=settings.KEYCLOAK_CLIENT_ID) + client_name: str = Field(default=settings.KEYCLOAK_CLIENT_NAME) + response_types: List[str] = Field(default=["code", "id_token", "token"]) + redirect_uris: List[str] = Field(default=[settings.KEYCLOAK_REDIRECT_URI]) + token_endpoint_auth_method: TOKENENDPOINTAUTHMETHODS = Field( + default=TOKENENDPOINTAUTHMETHODS.client_secret_basic + ) + + client_secret: str = Field(default=settings.KEYCLOAK_CLIENT_SECRET) + + class Config: + allow_population_by_field_name = True + + +class ClientConfiguration(ClientConfigurationBase, UUIDModel): + pass + + +class ClientConfigurationCreate(ClientConfigurationBase): + pass + + +class ClientConfigurationPatch(ClientConfigurationBase): + pass diff --git a/oidc-controller/api/core/oidc/provider.py b/oidc-controller/api/core/oidc/provider.py index f32ed35b..19335538 100644 --- a/oidc-controller/api/core/oidc/provider.py +++ b/oidc-controller/api/core/oidc/provider.py @@ -5,17 +5,18 @@ from pyop.authz_state import AuthorizationState from pyop.provider import Provider +from pyop.storage import RedisWrapper from pyop.subject_identifier import HashBasedSubjectIdentifierFactory from pyop.userinfo import Userinfo +from api.clientConfigurations.models import ClientConfiguration +from api.clientConfigurations.crud import ClientConfigurationCRUD from ..config import settings logger = logging.getLogger(__name__) -db_uri = settings.REDISDB_URL issuer_url = settings.CONTROLLER_URL - if urlparse(issuer_url).scheme != "https": logger.error("CONTROLLER_URL is not HTTPS. changing openid-config for development") issuer_url = issuer_url[:4] + "s" + issuer_url[4:] @@ -51,25 +52,23 @@ subject_id_factory = HashBasedSubjectIdentifierFactory(settings.SUBJECT_ID_HASH_SALT) -kc_client = { - "enabled": True, - "client_id": settings.KEYCLOAK_CLIENT_ID, - "client_name": settings.KEYCLOAK_CLIENT_NAME, - "allowed_grant_types": ["implicit", "code"], - "allowed_scopes": ["openid", "profile", "vc_authn"], - "response_types": ["code", "id_token", "token"], - "redirect_uris": [settings.KEYCLOAK_REDIRECT_URI], - "require_consent": False, - "token_endpoint_auth_method": "client_secret_basic", - "require_client_secret": True, - "client_secret": settings.KEYCLOAK_CLIENT_SECRET, -} +# unclear what is required for clients without using handle_client_registration_request +# JSyro best guess at minimum required fields +default_kc_client = ClientConfiguration() + +print(default_kc_client.dict()) + + +def init_provider(): + # all_kc_configs = ClientConfigurationCRUD(db).get_all() + # client_configs = {d.name : d for d in all_kc_configs} + return Provider( + signing_key, + configuration_information, + AuthorizationState(subject_id_factory), + {"keycloak": default_kc_client.dict()}, + Userinfo({"Jason": {"sub": "Jason"}}), + ) + -print(kc_client) -provider = Provider( - signing_key, - configuration_information, - AuthorizationState(subject_id_factory), - {"keycloak": kc_client}, - Userinfo({"Jason": {"sub": "Jason"}}), -) +provider = init_provider() diff --git a/oidc-controller/api/db/collections.py b/oidc-controller/api/db/collections.py index fa6da9a4..57ab94f0 100644 --- a/oidc-controller/api/db/collections.py +++ b/oidc-controller/api/db/collections.py @@ -4,3 +4,4 @@ class COLLECTION_NAMES(str, Enum): VER_CONFIGS = "verification_configuration" AUTH_SESSION = "auth_session" + CLIENT_CONFiGURATIONS = "client_configuration" diff --git a/oidc-controller/api/routers/oidc.py b/oidc-controller/api/routers/oidc.py index 21accb90..239e0520 100644 --- a/oidc-controller/api/routers/oidc.py +++ b/oidc-controller/api/routers/oidc.py @@ -47,12 +47,10 @@ async def get_authorize(request: Request, db: Database = Depends(get_db)): model = AuthorizationRequest().from_dict(request.query_params._dict) model.verify() - # pyop provider auth_req = provider.provider.parse_authentication_request( urlencode(request.query_params._dict), request.headers ) authn_response = provider.provider.authorize(model, "Jason") - # pyop provider END # retrieve presentation_request config. client = AcapyClient() From b3d37712058f9a86e565de0b1c8bceb9b6d8c8e7 Mon Sep 17 00:00:00 2001 From: JSyro Date: Tue, 21 Mar 2023 16:18:33 -0700 Subject: [PATCH 2/8] add crud and load from collection Signed-off-by: JSyro --- oidc-controller/api/authSessions/crud.py | 9 +- .../api/clientConfigurations/crud.py | 34 +++++--- .../api/clientConfigurations/examples.py | 10 +++ .../api/clientConfigurations/models.py | 11 ++- .../api/clientConfigurations/router.py | 84 +++++++++++++++++++ oidc-controller/api/core/oidc/provider.py | 24 +++--- oidc-controller/api/main.py | 4 + 7 files changed, 150 insertions(+), 26 deletions(-) create mode 100644 oidc-controller/api/clientConfigurations/examples.py create mode 100644 oidc-controller/api/clientConfigurations/router.py diff --git a/oidc-controller/api/authSessions/crud.py b/oidc-controller/api/authSessions/crud.py index a1597658..03a602a5 100644 --- a/oidc-controller/api/authSessions/crud.py +++ b/oidc-controller/api/authSessions/crud.py @@ -44,9 +44,14 @@ async def get(self, auth_session_id: str) -> AuthSession: return AuthSession(**auth_sess) async def patch(self, auth_session_id: str, data: AuthSessionPatch) -> AuthSession: + if not PyObjectId.is_valid(id): + raise HTTPException( + status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" + ) + col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) auth_sess = col.find_one_and_update( - {"_id": auth_session_id}, + {"_id": PyObjectId(auth_session_id)}, {"$set": data.dict(exclude_unset=True)}, return_document=ReturnDocument.AFTER, ) @@ -55,7 +60,7 @@ async def patch(self, auth_session_id: str, data: AuthSessionPatch) -> AuthSessi async def delete(self, auth_session_id: str) -> bool: col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) - auth_sess = col.find_one_and_delete({"_id": auth_session_id}) + auth_sess = col.find_one_and_delete({"_id": PyObjectId(auth_session_id)}) return bool(auth_sess) async def get_by_pres_exch_id(self, pres_exch_id: str) -> AuthSession: diff --git a/oidc-controller/api/clientConfigurations/crud.py b/oidc-controller/api/clientConfigurations/crud.py index 4f730cf1..b770db36 100644 --- a/oidc-controller/api/clientConfigurations/crud.py +++ b/oidc-controller/api/clientConfigurations/crud.py @@ -1,5 +1,6 @@ import logging +from typing import List from pymongo import ReturnDocument from pymongo.database import Database from fastapi import HTTPException @@ -11,6 +12,7 @@ ClientConfiguration, ClientConfigurationCreate, ClientConfigurationPatch, + ClientConfigurationRead, ) from ..db.session import COLLECTION_NAMES @@ -29,35 +31,47 @@ async def create( result = col.insert_one(jsonable_encoder(client_config)) return ClientConfiguration(**col.find_one({"_id": result.inserted_id})) - async def get(self, id: str) -> ClientConfiguration: + async def get(self, id: str) -> ClientConfigurationRead: if not PyObjectId.is_valid(id): raise HTTPException( status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" ) col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) - auth_sess = col.find_one({"_id": PyObjectId(id)}) + obj = col.find_one({"_id": PyObjectId(id)}) - if auth_sess is None: + if obj is None: raise HTTPException( status_code=http_status.HTTP_404_NOT_FOUND, detail="The auth_session hasn't been found!", ) - return ClientConfiguration(**auth_sess) + return ClientConfiguration(**obj) + + async def get_all(self) -> List[ClientConfigurationRead]: + col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) + return [ClientConfigurationRead(**cc) for cc in col.find()] async def patch( self, id: str, data: ClientConfigurationPatch ) -> ClientConfiguration: + if not PyObjectId.is_valid(id): + raise HTTPException( + status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" + ) col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) - auth_sess = col.find_one_and_update( - {"_id": id}, + obj = col.find_one_and_update( + {"_id": PyObjectId(id)}, {"$set": data.dict(exclude_unset=True)}, return_document=ReturnDocument.AFTER, ) - return auth_sess + return obj - async def delete(self, auth_session_id: str) -> bool: + async def delete(self, id: str) -> bool: + if not PyObjectId.is_valid(id): + raise HTTPException( + status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" + ) col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) - auth_sess = col.find_one_and_delete({"_id": auth_session_id}) - return bool(auth_sess) + obj = col.find_one_and_delete({"_id": PyObjectId(id)}) + return bool(obj) diff --git a/oidc-controller/api/clientConfigurations/examples.py b/oidc-controller/api/clientConfigurations/examples.py new file mode 100644 index 00000000..16a91450 --- /dev/null +++ b/oidc-controller/api/clientConfigurations/examples.py @@ -0,0 +1,10 @@ +from api.core.config import settings + +ex_client_config_create = { + "client_id": settings.KEYCLOAK_CLIENT_ID, + "client_name": settings.KEYCLOAK_CLIENT_NAME, + "client_secret": "**********", + "response_types": ["code", "id_token", "token"], + "token_endpoint_auth_method": "client_secret_basic", + "redirect_uris": [settings.KEYCLOAK_REDIRECT_URI], +} diff --git a/oidc-controller/api/clientConfigurations/models.py b/oidc-controller/api/clientConfigurations/models.py index 39b4eee0..8227f907 100644 --- a/oidc-controller/api/clientConfigurations/models.py +++ b/oidc-controller/api/clientConfigurations/models.py @@ -5,6 +5,8 @@ from api.core.models import UUIDModel from api.core.config import settings +from .examples import ex_client_config_create + class TOKENENDPOINTAUTHMETHODS(str, Enum): client_secret_basic = "client_secret_basic" @@ -14,7 +16,7 @@ class ClientConfigurationBase(BaseModel): client_id: str = Field(default=settings.KEYCLOAK_CLIENT_ID) client_name: str = Field(default=settings.KEYCLOAK_CLIENT_NAME) response_types: List[str] = Field(default=["code", "id_token", "token"]) - redirect_uris: List[str] = Field(default=[settings.KEYCLOAK_REDIRECT_URI]) + redirect_uris: List[str] token_endpoint_auth_method: TOKENENDPOINTAUTHMETHODS = Field( default=TOKENENDPOINTAUTHMETHODS.client_secret_basic ) @@ -29,9 +31,14 @@ class ClientConfiguration(ClientConfigurationBase, UUIDModel): pass -class ClientConfigurationCreate(ClientConfigurationBase): +class ClientConfigurationRead(ClientConfigurationBase, UUIDModel): pass +class ClientConfigurationCreate(ClientConfigurationBase): + class Config: + schema_extra = {"example": ex_client_config_create} + + class ClientConfigurationPatch(ClientConfigurationBase): pass diff --git a/oidc-controller/api/clientConfigurations/router.py b/oidc-controller/api/clientConfigurations/router.py new file mode 100644 index 00000000..63ea83f2 --- /dev/null +++ b/oidc-controller/api/clientConfigurations/router.py @@ -0,0 +1,84 @@ +from pymongo.database import Database + +from fastapi import APIRouter, HTTPException, Depends +from fastapi import status as http_status + +from ..core.models import StatusMessage + +from .crud import ClientConfigurationCRUD +from .models import ( + ClientConfigurationRead, + ClientConfigurationPatch, + ClientConfigurationCreate, +) +from ..core.auth import get_api_key +from ..db.session import get_db + +router = APIRouter() + + +@router.post( + "/", + response_description="Add new verification configuration", + status_code=http_status.HTTP_201_CREATED, + response_model=ClientConfigurationCreate, + response_model_exclude_unset=True, + dependencies=[Depends(get_api_key)], +) +async def create_client_config( + ver_config: ClientConfigurationCreate, db: Database = Depends(get_db) +): + return await ClientConfigurationCRUD(db).create(ver_config) + + +@router.get( + "/", + status_code=http_status.HTTP_200_OK, + response_model_exclude_unset=True, + dependencies=[Depends(get_api_key)], +) +async def get_all_client_configs(db: Database = Depends(get_db)): + return await ClientConfigurationCRUD(db).get_all() + + +@router.get( + "/{client_config_id}", + status_code=http_status.HTTP_200_OK, + response_model=ClientConfigurationRead, + 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) + + +@router.patch( + "/{client_config_id}", + status_code=http_status.HTTP_200_OK, + response_model=ClientConfigurationRead, + response_model_exclude_unset=True, + dependencies=[Depends(get_api_key)], +) +async def patch_client_config( + client_config_id: str, + data: ClientConfigurationPatch, + db: Database = Depends(get_db), +): + return await ClientConfigurationCRUD(db).patch(id=client_config_id, data=data) + + +@router.delete( + "/{client_config_id}", + status_code=http_status.HTTP_200_OK, + response_model=StatusMessage, + 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") diff --git a/oidc-controller/api/core/oidc/provider.py b/oidc-controller/api/core/oidc/provider.py index 19335538..cc6fc12a 100644 --- a/oidc-controller/api/core/oidc/provider.py +++ b/oidc-controller/api/core/oidc/provider.py @@ -5,14 +5,14 @@ from pyop.authz_state import AuthorizationState from pyop.provider import Provider -from pyop.storage import RedisWrapper from pyop.subject_identifier import HashBasedSubjectIdentifierFactory from pyop.userinfo import Userinfo from api.clientConfigurations.models import ClientConfiguration from api.clientConfigurations.crud import ClientConfigurationCRUD -from ..config import settings +from api.core.config import settings +from api.db.session import get_db logger = logging.getLogger(__name__) @@ -54,21 +54,21 @@ # unclear what is required for clients without using handle_client_registration_request # JSyro best guess at minimum required fields -default_kc_client = ClientConfiguration() +default_kc_client = ClientConfiguration(redirect_uris=[settings.KEYCLOAK_REDIRECT_URI]) -print(default_kc_client.dict()) +provider = None -def init_provider(): - # all_kc_configs = ClientConfigurationCRUD(db).get_all() - # client_configs = {d.name : d for d in all_kc_configs} - return Provider( +async def init_provider(): + all_kc_configs = await ClientConfigurationCRUD(await get_db()).get_all() + client_configs = {d.client_name: d.dict() for d in all_kc_configs} + + global provider + + provider = Provider( signing_key, configuration_information, AuthorizationState(subject_id_factory), - {"keycloak": default_kc_client.dict()}, + client_configs, Userinfo({"Jason": {"sub": "Jason"}}), ) - - -provider = init_provider() diff --git a/oidc-controller/api/main.py b/oidc-controller/api/main.py index c40eed7a..87dd0f82 100644 --- a/oidc-controller/api/main.py +++ b/oidc-controller/api/main.py @@ -10,7 +10,9 @@ from .routers import acapy_handler, oidc, presentation_request, well_known_oid_config from .verificationConfigs.router import router as ver_configs_router +from .clientConfigurations.router import router as client_config_router from .db.session import init_db +from api.core.oidc.provider import init_provider # setup loggers # TODO: set config via env parameters... @@ -35,6 +37,7 @@ def get_application() -> FastAPI: app = get_application() app.include_router(ver_configs_router, prefix="/ver_configs", tags=["ver_configs"]) +app.include_router(client_config_router, prefix="/clients", tags=["oidc_clients"]) app.include_router(well_known_oid_config.router, tags=[".well-known"]) app.include_router( oidc.router, tags=["OpenID Connect Provider"], include_in_schema=False @@ -64,6 +67,7 @@ def get_application() -> FastAPI: async def on_tenant_startup(): """Register any events we need to respond to.""" await init_db() + await init_provider() logger.warning(">>> Starting up app ...") From ba34a06d1bbcb4b2cde57bd1d0b2eaffa3999d98 Mon Sep 17 00:00:00 2001 From: JSyro Date: Tue, 21 Mar 2023 16:34:28 -0700 Subject: [PATCH 3/8] client works.... but auth session is being lost now? Signed-off-by: JSyro --- oidc-controller/api/authSessions/crud.py | 16 ++++------------ oidc-controller/api/clientConfigurations/crud.py | 11 ++++++++++- oidc-controller/api/core/oidc/provider.py | 12 ++++-------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/oidc-controller/api/authSessions/crud.py b/oidc-controller/api/authSessions/crud.py index 03a602a5..5b294b09 100644 --- a/oidc-controller/api/authSessions/crud.py +++ b/oidc-controller/api/authSessions/crud.py @@ -12,7 +12,8 @@ AuthSessionCreate, AuthSessionPatch, ) -from ..db.session import COLLECTION_NAMES +from api.db.session import COLLECTION_NAMES +from api.core.oidc.provider import init_provider logger = logging.getLogger(__name__) @@ -28,12 +29,8 @@ async def create(self, auth_session: AuthSessionCreate) -> AuthSession: return AuthSession(**col.find_one({"_id": result.inserted_id})) async def get(self, auth_session_id: str) -> AuthSession: - if not PyObjectId.is_valid(auth_session_id): - raise HTTPException( - status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" - ) col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) - auth_sess = col.find_one({"_id": PyObjectId(auth_session_id)}) + auth_sess = col.find_one({"_id": auth_session_id}) if auth_sess is None: raise HTTPException( @@ -44,14 +41,9 @@ async def get(self, auth_session_id: str) -> AuthSession: return AuthSession(**auth_sess) async def patch(self, auth_session_id: str, data: AuthSessionPatch) -> AuthSession: - if not PyObjectId.is_valid(id): - raise HTTPException( - status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" - ) - col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) auth_sess = col.find_one_and_update( - {"_id": PyObjectId(auth_session_id)}, + {"_id": auth_session_id}, {"$set": data.dict(exclude_unset=True)}, return_document=ReturnDocument.AFTER, ) diff --git a/oidc-controller/api/clientConfigurations/crud.py b/oidc-controller/api/clientConfigurations/crud.py index b770db36..db1cfbde 100644 --- a/oidc-controller/api/clientConfigurations/crud.py +++ b/oidc-controller/api/clientConfigurations/crud.py @@ -15,6 +15,7 @@ ClientConfigurationRead, ) from ..db.session import COLLECTION_NAMES +from api.core.oidc.provider import init_provider logger = logging.getLogger(__name__) @@ -29,6 +30,9 @@ async def create( ) -> ClientConfiguration: col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) result = col.insert_one(jsonable_encoder(client_config)) + + # remake provider instance to refresh provider client + await init_provider() return ClientConfiguration(**col.find_one({"_id": result.inserted_id})) async def get(self, id: str) -> ClientConfigurationRead: @@ -64,7 +68,8 @@ async def patch( {"$set": data.dict(exclude_unset=True)}, return_document=ReturnDocument.AFTER, ) - + # remake provider instance to refresh provider client + await init_provider() return obj async def delete(self, id: str) -> bool: @@ -74,4 +79,8 @@ async def delete(self, id: str) -> bool: ) col = self._db.get_collection(COLLECTION_NAMES.CLIENT_CONFiGURATIONS) obj = col.find_one_and_delete({"_id": PyObjectId(id)}) + + # remake provider instance to refresh provider client + await init_provider() + return bool(obj) diff --git a/oidc-controller/api/core/oidc/provider.py b/oidc-controller/api/core/oidc/provider.py index cc6fc12a..2a5c9c73 100644 --- a/oidc-controller/api/core/oidc/provider.py +++ b/oidc-controller/api/core/oidc/provider.py @@ -8,8 +8,6 @@ from pyop.subject_identifier import HashBasedSubjectIdentifierFactory from pyop.userinfo import Userinfo -from api.clientConfigurations.models import ClientConfiguration -from api.clientConfigurations.crud import ClientConfigurationCRUD from api.core.config import settings from api.db.session import get_db @@ -52,19 +50,17 @@ subject_id_factory = HashBasedSubjectIdentifierFactory(settings.SUBJECT_ID_HASH_SALT) -# unclear what is required for clients without using handle_client_registration_request -# JSyro best guess at minimum required fields -default_kc_client = ClientConfiguration(redirect_uris=[settings.KEYCLOAK_REDIRECT_URI]) - +# placeholder that gets set on app_start and write operations to ClientConfigurationCRUD provider = None async def init_provider(): + global provider + from api.clientConfigurations.crud import ClientConfigurationCRUD + all_kc_configs = await ClientConfigurationCRUD(await get_db()).get_all() client_configs = {d.client_name: d.dict() for d in all_kc_configs} - global provider - provider = Provider( signing_key, configuration_information, From e20af0345fe1fbbb8041cc23a98fc9172d44804d Mon Sep 17 00:00:00 2001 From: JSyro Date: Wed, 22 Mar 2023 09:21:22 -0700 Subject: [PATCH 4/8] need the type casting Signed-off-by: JSyro --- oidc-controller/api/authSessions/crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oidc-controller/api/authSessions/crud.py b/oidc-controller/api/authSessions/crud.py index 5b294b09..15379d1e 100644 --- a/oidc-controller/api/authSessions/crud.py +++ b/oidc-controller/api/authSessions/crud.py @@ -30,7 +30,7 @@ async def create(self, auth_session: AuthSessionCreate) -> AuthSession: async def get(self, auth_session_id: str) -> AuthSession: col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) - auth_sess = col.find_one({"_id": auth_session_id}) + auth_sess = col.find_one({"_id": PyObjectId(auth_session_id)}) if auth_sess is None: raise HTTPException( From c536fcbb6c4c84687da36bfac5d0feed96327caa Mon Sep 17 00:00:00 2001 From: JSyro Date: Wed, 22 Mar 2023 09:29:58 -0700 Subject: [PATCH 5/8] check validity. Signed-off-by: JSyro --- oidc-controller/api/authSessions/crud.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/oidc-controller/api/authSessions/crud.py b/oidc-controller/api/authSessions/crud.py index 15379d1e..663ac491 100644 --- a/oidc-controller/api/authSessions/crud.py +++ b/oidc-controller/api/authSessions/crud.py @@ -29,6 +29,10 @@ async def create(self, auth_session: AuthSessionCreate) -> AuthSession: return AuthSession(**col.find_one({"_id": result.inserted_id})) async def get(self, auth_session_id: str) -> AuthSession: + if not PyObjectId.is_valid(id): + raise HTTPException( + status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" + ) col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) auth_sess = col.find_one({"_id": PyObjectId(auth_session_id)}) @@ -51,6 +55,10 @@ async def patch(self, auth_session_id: str, data: AuthSessionPatch) -> AuthSessi return auth_sess async def delete(self, auth_session_id: str) -> bool: + if not PyObjectId.is_valid(id): + raise HTTPException( + status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" + ) col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) auth_sess = col.find_one_and_delete({"_id": PyObjectId(auth_session_id)}) return bool(auth_sess) From 3e0d573240bc3a727b9dc67acc494723c3001b32 Mon Sep 17 00:00:00 2001 From: JSyro Date: Wed, 22 Mar 2023 10:02:59 -0700 Subject: [PATCH 6/8] wrong var name Signed-off-by: JSyro --- oidc-controller/api/authSessions/crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oidc-controller/api/authSessions/crud.py b/oidc-controller/api/authSessions/crud.py index 663ac491..ce26bd20 100644 --- a/oidc-controller/api/authSessions/crud.py +++ b/oidc-controller/api/authSessions/crud.py @@ -29,7 +29,7 @@ async def create(self, auth_session: AuthSessionCreate) -> AuthSession: return AuthSession(**col.find_one({"_id": result.inserted_id})) async def get(self, auth_session_id: str) -> AuthSession: - if not PyObjectId.is_valid(id): + if not PyObjectId.is_valid(auth_session_id): raise HTTPException( status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" ) From 2272bd1559856979d312d5a23a89f0f614c62bef Mon Sep 17 00:00:00 2001 From: JSyro Date: Wed, 22 Mar 2023 10:10:56 -0700 Subject: [PATCH 7/8] uuid id's are now 'id' as the local var in CRUD methods. Signed-off-by: JSyro --- oidc-controller/api/authSessions/crud.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/oidc-controller/api/authSessions/crud.py b/oidc-controller/api/authSessions/crud.py index ce26bd20..ad53e983 100644 --- a/oidc-controller/api/authSessions/crud.py +++ b/oidc-controller/api/authSessions/crud.py @@ -13,7 +13,6 @@ AuthSessionPatch, ) from api.db.session import COLLECTION_NAMES -from api.core.oidc.provider import init_provider logger = logging.getLogger(__name__) @@ -28,13 +27,13 @@ async def create(self, auth_session: AuthSessionCreate) -> AuthSession: result = col.insert_one(jsonable_encoder(auth_session)) return AuthSession(**col.find_one({"_id": result.inserted_id})) - async def get(self, auth_session_id: str) -> AuthSession: - if not PyObjectId.is_valid(auth_session_id): + async def get(self, id: str) -> AuthSession: + if not PyObjectId.is_valid(id): raise HTTPException( status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" ) col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) - auth_sess = col.find_one({"_id": PyObjectId(auth_session_id)}) + auth_sess = col.find_one({"_id": PyObjectId(id)}) if auth_sess is None: raise HTTPException( @@ -44,23 +43,23 @@ async def get(self, auth_session_id: str) -> AuthSession: return AuthSession(**auth_sess) - async def patch(self, auth_session_id: str, data: AuthSessionPatch) -> AuthSession: + async def patch(self, id: str, data: AuthSessionPatch) -> AuthSession: col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) auth_sess = col.find_one_and_update( - {"_id": auth_session_id}, + {"_id": id}, {"$set": data.dict(exclude_unset=True)}, return_document=ReturnDocument.AFTER, ) return auth_sess - async def delete(self, auth_session_id: str) -> bool: + async def delete(self, id: str) -> bool: if not PyObjectId.is_valid(id): raise HTTPException( status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" ) col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) - auth_sess = col.find_one_and_delete({"_id": PyObjectId(auth_session_id)}) + auth_sess = col.find_one_and_delete({"_id": PyObjectId(id)}) return bool(auth_sess) async def get_by_pres_exch_id(self, pres_exch_id: str) -> AuthSession: From 8adbcf1a249c590527ca0acddcf445cec27834c6 Mon Sep 17 00:00:00 2001 From: JSyro Date: Wed, 22 Mar 2023 13:36:43 -0700 Subject: [PATCH 8/8] crud methods check pyobjectid, but accept str Signed-off-by: JSyro --- oidc-controller/api/authSessions/crud.py | 6 +++++- oidc-controller/api/routers/acapy_handler.py | 2 +- oidc-controller/api/verificationConfigs/models.py | 10 ++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/oidc-controller/api/authSessions/crud.py b/oidc-controller/api/authSessions/crud.py index ad53e983..37e7b343 100644 --- a/oidc-controller/api/authSessions/crud.py +++ b/oidc-controller/api/authSessions/crud.py @@ -44,9 +44,13 @@ async def get(self, id: str) -> AuthSession: return AuthSession(**auth_sess) async def patch(self, id: str, data: AuthSessionPatch) -> AuthSession: + if not PyObjectId.is_valid(id): + raise HTTPException( + status_code=http_status.HTTP_400_BAD_REQUEST, detail=f"Invalid id: {id}" + ) col = self._db.get_collection(COLLECTION_NAMES.AUTH_SESSION) auth_sess = col.find_one_and_update( - {"_id": id}, + {"_id": PyObjectId(id)}, {"$set": data.dict(exclude_unset=True)}, return_document=ReturnDocument.AFTER, ) diff --git a/oidc-controller/api/routers/acapy_handler.py b/oidc-controller/api/routers/acapy_handler.py index 240ed712..c69f4ad5 100644 --- a/oidc-controller/api/routers/acapy_handler.py +++ b/oidc-controller/api/routers/acapy_handler.py @@ -40,7 +40,7 @@ async def post_topic(request: Request, topic: str, db: Database = Depends(get_db # update presentation_exchange record auth_session.verified = True await AuthSessionCRUD(db).patch( - auth_session.id, AuthSessionPatch(**auth_session.dict()) + str(auth_session.id), AuthSessionPatch(**auth_session.dict()) ) pass diff --git a/oidc-controller/api/verificationConfigs/models.py b/oidc-controller/api/verificationConfigs/models.py index 20217b96..c81026f2 100644 --- a/oidc-controller/api/verificationConfigs/models.py +++ b/oidc-controller/api/verificationConfigs/models.py @@ -40,7 +40,6 @@ class VerificationProofRequest(BaseModel): class VerificationConfigBase(BaseModel): - ver_config_id: str = Field() subject_identifier: str = Field() proof_request: VerificationProofRequest = Field() @@ -74,18 +73,25 @@ class Config: class VerificationConfig(VerificationConfigBase): - pass + ver_config_id: str = Field() class VerificationConfigRead(VerificationConfigBase): + ver_config_id: str = Field() + class Config: schema_extra = {"example": ex_ver_config_read} class VerificationConfigCreate(VerificationConfigBase): + ver_config_id: str = Field() + class Config: schema_extra = {"example": ex_ver_config_create} class VerificationConfigPatch(VerificationConfigBase): + subject_identifier: Optional[str] = Field() + proof_request: Optional[VerificationProofRequest] = Field() + pass