|
1 | 1 | import asyncio |
2 | 2 | import logging |
3 | | -from datetime import datetime |
| 3 | +from datetime import datetime, timezone |
4 | 4 | from typing import Annotated |
5 | 5 |
|
6 | 6 | from fastapi import APIRouter, Depends, HTTPException, Path |
@@ -96,6 +96,76 @@ def strip_reason(cls, value: str | None) -> str | None: |
96 | 96 | return stripped or None |
97 | 97 |
|
98 | 98 |
|
| 99 | +def _resolve_platform(service_id: str) -> PlatformEnum | None: |
| 100 | + if service_id in PLATFORM_MAPPING: |
| 101 | + return PLATFORM_MAPPING[service_id]["enum"] |
| 102 | + for data in PLATFORM_MAPPING.values(): |
| 103 | + if data["enum"].value == service_id: |
| 104 | + return data["enum"] |
| 105 | + return None |
| 106 | + |
| 107 | + |
| 108 | +def _resolve_group(service_id: str) -> GroupEnum | None: |
| 109 | + if service_id in GROUP_MAPPING: |
| 110 | + return GROUP_MAPPING[service_id]["enum"] |
| 111 | + for data in GROUP_MAPPING.values(): |
| 112 | + if data["enum"].value == service_id: |
| 113 | + return data["enum"] |
| 114 | + return None |
| 115 | + |
| 116 | + |
| 117 | +def _get_or_create_db_user(user_id: str, |
| 118 | + client: Auth0Client, |
| 119 | + db_session: Session) -> BiocommonsUser: |
| 120 | + db_user = db_session.get(BiocommonsUser, user_id) |
| 121 | + if db_user is None: |
| 122 | + db_user = BiocommonsUser.get_or_create( |
| 123 | + auth0_id=user_id, |
| 124 | + db_session=db_session, |
| 125 | + auth0_client=client, |
| 126 | + ) |
| 127 | + return db_user |
| 128 | + |
| 129 | + |
| 130 | +def _get_platform_membership_or_404( |
| 131 | + *, user_id: str, platform_id: PlatformEnum, db_session: Session |
| 132 | +) -> PlatformMembership: |
| 133 | + membership = db_session.exec( |
| 134 | + select(PlatformMembership).where( |
| 135 | + PlatformMembership.user_id == user_id, |
| 136 | + PlatformMembership.platform_id == platform_id, |
| 137 | + ) |
| 138 | + ).one_or_none() |
| 139 | + if membership is None: |
| 140 | + raise HTTPException( |
| 141 | + status_code=404, |
| 142 | + detail=f"Platform membership '{platform_id.value}' not found for user '{user_id}'", |
| 143 | + ) |
| 144 | + return membership |
| 145 | + |
| 146 | + |
| 147 | +def _get_group_membership_or_404( |
| 148 | + *, user_id: str, group_id: str, db_session: Session |
| 149 | +) -> GroupMembership: |
| 150 | + membership = GroupMembership.get_by_user_id( |
| 151 | + user_id=user_id, |
| 152 | + group_id=group_id, |
| 153 | + session=db_session, |
| 154 | + ) |
| 155 | + if membership is None: |
| 156 | + raise HTTPException( |
| 157 | + status_code=404, |
| 158 | + detail=f"Group membership '{group_id}' not found for user '{user_id}'", |
| 159 | + ) |
| 160 | + return membership |
| 161 | + |
| 162 | + |
| 163 | +def _membership_response(kind: str, membership_model) -> dict[str, object]: |
| 164 | + data = membership_model.get_data().model_dump(mode="json") |
| 165 | + data["type"] = kind |
| 166 | + return data |
| 167 | + |
| 168 | + |
99 | 169 | @router.get("/filters") |
100 | 170 | def get_filter_options(): |
101 | 171 | """ |
@@ -299,52 +369,105 @@ def resend_verification_email(user_id: Annotated[str, UserIdParam], |
299 | 369 | def approve_service(user_id: Annotated[str, UserIdParam], |
300 | 370 | service_id: Annotated[str, ServiceIdParam], |
301 | 371 | client: Annotated[Auth0Client, Depends(get_auth0_client)], |
302 | | - approving_user: Annotated[SessionUser, Depends(get_current_user)]): |
303 | | - user = client.get_user(user_id=user_id) |
304 | | - # Need to fetch full user info currently to get email address, not in access token |
305 | | - approving_user_data = client.get_user(user_id=approving_user.access_token.sub) |
306 | | - logger.debug(f"Approving service {service_id} for user {user_id} by {approving_user_data.email}") |
307 | | - user.app_metadata.approve_service(service_id, updated_by=str(approving_user_data.email)) |
308 | | - logger.info("Sending updated metadata to Auth0 API") |
309 | | - # update_user_metadata is async, so run via asyncio |
310 | | - update = update_user_metadata( |
| 372 | + approving_user: Annotated[SessionUser, Depends(get_current_user)], |
| 373 | + db_session: Annotated[Session, Depends(get_db_session)]): |
| 374 | + platform = _resolve_platform(service_id) |
| 375 | + group = _resolve_group(service_id) |
| 376 | + if platform is None and group is None: |
| 377 | + raise HTTPException(status_code=404, detail=f"Service '{service_id}' is not recognised") |
| 378 | + |
| 379 | + admin_record = _get_or_create_db_user( |
| 380 | + user_id=approving_user.access_token.sub, |
| 381 | + client=client, |
| 382 | + db_session=db_session, |
| 383 | + ) |
| 384 | + |
| 385 | + if platform is not None: |
| 386 | + membership = _get_platform_membership_or_404( |
| 387 | + user_id=user_id, |
| 388 | + platform_id=platform, |
| 389 | + db_session=db_session, |
| 390 | + ) |
| 391 | + membership.approval_status = ApprovalStatusEnum.APPROVED |
| 392 | + membership.revocation_reason = None |
| 393 | + membership.updated_at = datetime.now(timezone.utc) |
| 394 | + membership.updated_by = admin_record |
| 395 | + db_session.add(membership) |
| 396 | + membership.save_history(db_session) |
| 397 | + db_session.commit() |
| 398 | + db_session.refresh(membership) |
| 399 | + logger.info("Approved platform %s for user %s", platform.value, user_id) |
| 400 | + return _membership_response("platform", membership) |
| 401 | + |
| 402 | + membership = _get_group_membership_or_404( |
311 | 403 | user_id=user_id, |
312 | | - token=client.management_token, |
313 | | - metadata=user.app_metadata.model_dump(mode="json") |
| 404 | + group_id=group.value, |
| 405 | + db_session=db_session, |
314 | 406 | ) |
315 | | - resp = asyncio.run(update) |
316 | | - logger.info("Metadata updated successfully") |
317 | | - return resp |
| 407 | + membership.approval_status = ApprovalStatusEnum.APPROVED |
| 408 | + membership.revocation_reason = None |
| 409 | + membership.updated_at = datetime.now(timezone.utc) |
| 410 | + membership.updated_by = admin_record |
| 411 | + membership.grant_auth0_role(auth0_client=client) |
| 412 | + membership.save(session=db_session, commit=True) |
| 413 | + db_session.refresh(membership) |
| 414 | + logger.info("Approved group %s for user %s", group.value, user_id) |
| 415 | + return _membership_response("group", membership) |
318 | 416 |
|
319 | 417 |
|
320 | 418 | @router.post("/users/{user_id}/services/{service_id}/revoke") |
321 | 419 | def revoke_service(user_id: Annotated[str, UserIdParam], |
322 | 420 | service_id: Annotated[str, ServiceIdParam], |
323 | 421 | payload: RevokeServiceRequest, |
324 | 422 | client: Annotated[Auth0Client, Depends(get_auth0_client)], |
325 | | - revoking_user: Annotated[SessionUser, Depends(get_current_user)]): |
| 423 | + revoking_user: Annotated[SessionUser, Depends(get_current_user)], |
| 424 | + db_session: Annotated[Session, Depends(get_db_session)]): |
326 | 425 | """ |
327 | | - Revoke a service and all associated resources for a user. |
| 426 | + Revoke a service by updating platform or group membership state in the database. |
328 | 427 | """ |
329 | | - user = client.get_user(user_id=user_id) |
330 | | - revoking_user_data = client.get_user(user_id=revoking_user.access_token.sub) |
331 | | - user.app_metadata.revoke_service( |
332 | | - service_id=service_id, |
333 | | - updated_by=str(revoking_user_data.email), |
334 | | - reason=payload.reason, |
| 428 | + platform = _resolve_platform(service_id) |
| 429 | + group = _resolve_group(service_id) |
| 430 | + if platform is None and group is None: |
| 431 | + raise HTTPException(status_code=404, detail=f"Service '{service_id}' is not recognised") |
| 432 | + |
| 433 | + admin_record = _get_or_create_db_user( |
| 434 | + user_id=revoking_user.access_token.sub, |
| 435 | + client=client, |
| 436 | + db_session=db_session, |
335 | 437 | ) |
336 | | - service = user.app_metadata.get_service_by_id(service_id) |
337 | | - if service is None: |
338 | | - raise HTTPException(status_code=404, detail=f"Service '{service_id}' not found for user '{user_id}'") |
339 | | - for resource in service.resources: |
340 | | - resource.revoke() |
341 | | - update = update_user_metadata( |
| 438 | + |
| 439 | + reason = payload.reason |
| 440 | + |
| 441 | + if platform is not None: |
| 442 | + membership = _get_platform_membership_or_404( |
| 443 | + user_id=user_id, |
| 444 | + platform_id=platform, |
| 445 | + db_session=db_session, |
| 446 | + ) |
| 447 | + membership.approval_status = ApprovalStatusEnum.REVOKED |
| 448 | + membership.revocation_reason = reason |
| 449 | + membership.updated_at = datetime.now(timezone.utc) |
| 450 | + membership.updated_by = admin_record |
| 451 | + db_session.add(membership) |
| 452 | + membership.save_history(db_session) |
| 453 | + db_session.commit() |
| 454 | + db_session.refresh(membership) |
| 455 | + logger.info("Revoked platform %s for user %s", platform.value, user_id) |
| 456 | + return _membership_response("platform", membership) |
| 457 | + |
| 458 | + membership = _get_group_membership_or_404( |
342 | 459 | user_id=user_id, |
343 | | - token=client.management_token, |
344 | | - metadata=user.app_metadata.model_dump(mode="json") |
| 460 | + group_id=group.value, |
| 461 | + db_session=db_session, |
345 | 462 | ) |
346 | | - resp = asyncio.run(update) |
347 | | - return resp |
| 463 | + membership.approval_status = ApprovalStatusEnum.REVOKED |
| 464 | + membership.revocation_reason = reason |
| 465 | + membership.updated_at = datetime.now(timezone.utc) |
| 466 | + membership.updated_by = admin_record |
| 467 | + membership.save(session=db_session, commit=True) |
| 468 | + db_session.refresh(membership) |
| 469 | + logger.info("Revoked group %s for user %s", group.value, user_id) |
| 470 | + return _membership_response("group", membership) |
348 | 471 |
|
349 | 472 |
|
350 | 473 | @router.post("/users/{user_id}/services/{service_id}/resources/{resource_id}/approve") |
|
0 commit comments