diff --git a/config/urls.py b/config/urls.py index c5839529..c911ebbd 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,18 +1,16 @@ from django.contrib import admin from django.urls import include, path -from core.api import protected_apis -from core.api.scim import router as scim_router -from core.api.sso_profile import router as sso_profile_router +from core.api import main_api, people_finder_api, scim_api, sso_profile_api -protected_apis.add_router("/scim/v2/Users", scim_router) -protected_apis.add_router("/sso", sso_profile_router) - urlpatterns = [ - path("", include("core.urls")), - path("api/", protected_apis.urls), + path("api/scim/", scim_api.urls), + path("api/sso/", sso_profile_api.urls), + path("api/peoplefinder/", people_finder_api.urls), + path(route="api/", view=main_api.urls), path("admin/", admin.site.urls), path("auth/", include("authbroker_client.urls")), path("pingdom/", include("pingdom.urls")), + path("", include("core.urls")), ] diff --git a/core/api/__init__.py b/core/api/__init__.py index e4292404..c2b0ea8b 100644 --- a/core/api/__init__.py +++ b/core/api/__init__.py @@ -3,17 +3,57 @@ from django_hawk.utils import DjangoHawkAuthenticationFailed, authenticate_request from ninja import NinjaAPI +from core.api.main import router as main_router +from core.api.people_finder import router as people_finder_router +from core.api.scim import router as scim_router +from core.api.sso_profile import router as sso_profile_router + def do_hawk_auth(request): try: authenticate_request(request) except DjangoHawkAuthenticationFailed: - if settings.APP_ENV == "local": - return True return False -protected_apis = NinjaAPI( - auth=do_hawk_auth, - docs_decorator=staff_member_required, +main_api = NinjaAPI( + title="ID profile API", + version="1.0.0", + description="General API for ID retrieval", + urls_namespace="api", +) +main_api.add_router("", main_router) + +scim_api = NinjaAPI( + title="SCIM User Management API", + version="1.0.0", + description="SSO-limited API for management of User status", + urls_namespace="scim", +) +scim_api.add_router("/v2/Users", scim_router) + +sso_profile_api = NinjaAPI( + title="SSO Fast Profile API", + version="1.0.0", + description="Optimised minimal profile retrieval API for SSO 'hot-path'", + urls_namespace="sso-profile", +) +sso_profile_api.add_router("", sso_profile_router) + +people_finder_api = NinjaAPI( + title="PeopleFinder API", + version="1.0.0", + description="PeopleFinder specific API", + urls_namespace="people-finder", ) +people_finder_api.add_router("", people_finder_router) + +if settings.APP_ENV not in ("local", "test"): + main_api.auth = [do_hawk_auth] + main_api.docs_decorator = staff_member_required + scim_api.auth = [do_hawk_auth] + scim_api.docs_decorator = staff_member_required + sso_profile_api.auth = [do_hawk_auth] + sso_profile_api.docs_decorator = staff_member_required + people_finder_api.auth = [do_hawk_auth] + people_finder_api.docs_decorator = staff_member_required diff --git a/core/api/main.py b/core/api/main.py new file mode 100644 index 00000000..383a618e --- /dev/null +++ b/core/api/main.py @@ -0,0 +1,29 @@ +from ninja import Router + +from core import services as core_services +from core.schemas import Error +from core.schemas.profiles import ProfileMinimal +from profiles.models.combined import Profile + + +router = Router() +identity_router = Router() +router.add_router("identity", identity_router) + + +# NB this is a placeholder to get the router running, it may need editing or deleting etc. +@identity_router.get( + "{id}", + response={ + 200: ProfileMinimal, + 404: Error, + }, +) +def get_user(request, id: str): + """Just a demo, do not build against this.""" + try: + return core_services.get_by_id(id) + except Profile.DoesNotExist: + return 404, { + "message": "Unable to find user", + } diff --git a/core/api/people_finder.py b/core/api/people_finder.py new file mode 100644 index 00000000..63bc126b --- /dev/null +++ b/core/api/people_finder.py @@ -0,0 +1,29 @@ +from ninja import Router + +from core import services as core_services +from core.schemas import Error +from core.schemas.profiles import ProfileMinimal +from profiles.models.combined import Profile + + +router = Router() +profile_router = Router() +router.add_router("person", profile_router) + + +# NB this is a placeholder to get the router running, it may need editing or deleting etc. +@profile_router.get( + "{id}", + response={ + 200: ProfileMinimal, + 404: Error, + }, +) +def get_user(request, id: str): + """Just a demo, do not build against this""" + try: + return core_services.get_by_id(id) + except Profile.DoesNotExist: + return 404, { + "message": "Unable to find user", + } diff --git a/core/api/scim.py b/core/api/scim.py index b2ac7a74..621811f7 100644 --- a/core/api/scim.py +++ b/core/api/scim.py @@ -25,7 +25,7 @@ }, ) def get_user(request, id: str): - """In fact returns the combined Profile""" + """Returns the Identity record (internally: Profile) with the given ID""" try: return core_services.get_by_id(id) except Profile.DoesNotExist: @@ -37,6 +37,7 @@ def get_user(request, id: str): @router.post("", response={201: CreateUserResponse, 409: ScimErrorSchema}) def create_user(request, scim_user: CreateUserRequest) -> tuple[int, User | dict]: + """Creates the given Identity record; will not update""" if not scim_user.active: raise ValueError("Cannot create inactive profile via SCIM") @@ -69,7 +70,7 @@ def create_user(request, scim_user: CreateUserRequest) -> tuple[int, User | dict def update_user( request, id: str, scim_user: UpdateUserRequest ) -> tuple[int, Profile | dict]: - + """Updates the given Identity record; will not create. Use this for status changes e.g. archiving.""" all_emails = [email.value for email in scim_user.emails] primary_email = scim_user.get_primary_email() contact_email = scim_user.get_contact_email() @@ -102,6 +103,7 @@ def delete_user( request, id: str, ) -> int | tuple[int, dict]: + """Deleted the Identity record with the given ID""" profile = core_services.get_by_id(id=id) try: core_services.delete_identity( diff --git a/core/api/sso_profile.py b/core/api/sso_profile.py index f94697c4..ed819f8b 100644 --- a/core/api/sso_profile.py +++ b/core/api/sso_profile.py @@ -17,7 +17,7 @@ }, ) def get_user(request, id: str): - """In fact returns the combined Profile""" + """Optimised, low-flexibility endpoint to return a minimal Identity record (internally: Profile)""" try: return core_services.get_by_id(id) except Profile.DoesNotExist: diff --git a/docs/apis/index.md b/docs/apis/index.md index e0749036..4ae5bf25 100644 --- a/docs/apis/index.md +++ b/docs/apis/index.md @@ -2,10 +2,16 @@ The Identity service exposes different APIs for different purposes, split along lines related to different purposes. +> NB All APIs are self-documenting, at [/api/docs](/api/docs), [/api/sso/docs](/api/sso/docs), [/api/peoplefinder/docs](/api/peoplefinder/docs), and [/api/scim/docs](/api/scim/docs). + The most restricted, highest risk API is for [user management](./user-management.md) and encompasses creation, archiving and merging. The most optimisation-prioritised API is for [Staff SSO profile retrieval](./sso-profile.md); this is on the "hot path" for the Staff SSO auth process during user authentication. +[PeopleFinder has a dedicated API](./people-finder.md) since it needs to retrieve and edit a specific provider profile. + +The [most general use API](./main.md) is for most use cases. + ## Infrastructure "services" and authentication The ID service at runtime will be split into a running "service" (in AWS ECS terminology) per API, allowing infrastructure-level security to be applied per API. diff --git a/docs/apis/main.md b/docs/apis/main.md new file mode 100644 index 00000000..c522b7f5 --- /dev/null +++ b/docs/apis/main.md @@ -0,0 +1,5 @@ +# Main + +> This API is available at `/api/` + +The ID service exposes an API that provides general functionality. diff --git a/docs/apis/people-finder.md b/docs/apis/people-finder.md new file mode 100644 index 00000000..162ac308 --- /dev/null +++ b/docs/apis/people-finder.md @@ -0,0 +1,5 @@ +# People Finder + +> This API is available at `/api/peoplefinder/` + +The ID service exposes an API that provides read and edit functionality designed for the PeopleFinder / Intranet integration. diff --git a/docs/apis/sso-profile.md b/docs/apis/sso-profile.md index ffc2742c..64161afd 100644 --- a/docs/apis/sso-profile.md +++ b/docs/apis/sso-profile.md @@ -1,4 +1,4 @@ -# SSO profile retrieval +# SSO fast profile retrieval > This API is available at `/api/sso/`