Skip to content

Commit

Permalink
IDPF-237 Rework API structure & config (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelkornblum authored Jan 16, 2025
1 parent cb9c23e commit af60fac
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 17 deletions.
14 changes: 6 additions & 8 deletions config/urls.py
Original file line number Diff line number Diff line change
@@ -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")),
]
50 changes: 45 additions & 5 deletions core/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
29 changes: 29 additions & 0 deletions core/api/main.py
Original file line number Diff line number Diff line change
@@ -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",
}
29 changes: 29 additions & 0 deletions core/api/people_finder.py
Original file line number Diff line number Diff line change
@@ -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",
}
6 changes: 4 additions & 2 deletions core/api/scim.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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")

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion core/api/sso_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions docs/apis/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions docs/apis/main.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Main

> This API is available at `/api/`
The ID service exposes an API that provides general functionality.
5 changes: 5 additions & 0 deletions docs/apis/people-finder.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion docs/apis/sso-profile.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SSO profile retrieval
# SSO fast profile retrieval

> This API is available at `/api/sso/`
Expand Down

0 comments on commit af60fac

Please sign in to comment.