Skip to content

Commit 0118da0

Browse files
committed
M8F-99 : [ feat ] Implement authorization for Keycloak realm creation and deletion, introducing a new manage-tenant-realms permission.
1 parent 795bb4f commit 0118da0

File tree

3 files changed

+35
-8
lines changed

3 files changed

+35
-8
lines changed

extensions/m8flow-backend/src/m8flow_backend/config/permissions/m8flow.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ permissions:
5757
groups: ["super-admin"]
5858
actions: [all]
5959
uri: /m8flow/tenants/*
60+
manage-tenant-realms:
61+
groups: ["super-admin"]
62+
actions: [all]
63+
uri: /m8flow/tenant-realms*
6064
read-all-process-groups:
6165
groups: ["viewer"]
6266
actions: [read]

extensions/m8flow-backend/src/m8flow_backend/routes/keycloak_controller.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@
3131

3232
def create_realm(body: dict) -> tuple[dict, int]:
3333
"""Create a spoke realm from the spiffworkflow template. Returns (response_dict, status_code)."""
34+
35+
user = getattr(g, 'user', None)
36+
if not user:
37+
raise ApiError(error_code="not_authenticated", message="User not authenticated", status_code=401)
38+
39+
is_authorized = AuthorizationService.user_has_permission(user, "create", request.path)
40+
41+
if not is_authorized:
42+
logger.warning(
43+
"User %s (groups: %s) attempted to create a tenant/realm without required permissions",
44+
user.username,
45+
[getattr(g, 'identifier', g.name) for g in getattr(user, 'groups', [])],
46+
)
47+
raise ApiError(error_code="forbidden", message="Not authorized to create a tenant.", status_code=403)
48+
49+
3450
realm_id = body.get("realm_id")
3551
if not realm_id or not str(realm_id).strip():
3652
return {"detail": "realm_id is required"}, 400
@@ -147,15 +163,23 @@ def delete_tenant_realm(realm_id: str) -> tuple[dict, int]:
147163
with ON DELETE RESTRICT. If any rows still reference this tenant, the delete returns
148164
409 and the caller must remove or reassign those references first (or use soft delete).
149165
"""
150-
auth_header = request.headers.get("Authorization")
151-
if not auth_header or not auth_header.startswith("Bearer "):
152-
return {"detail": "Authorization header with Bearer token is required"}, 401
153-
154-
admin_token = auth_header.split(" ")[1]
155-
if not verify_admin_token(admin_token):
156-
return {"detail": "Invalid or unauthorized admin token"}, 401
166+
user = getattr(g, 'user', None)
167+
if not user:
168+
raise ApiError(error_code="not_authenticated", message="User not authenticated", status_code=401)
169+
170+
is_authorized = AuthorizationService.user_has_permission(user, "delete", request.path)
171+
172+
if not is_authorized:
173+
logger.warning(
174+
"User %s (groups: %s) attempted to delete tenant %s without required permissions",
175+
user.username,
176+
[getattr(g, 'identifier', g.name) for g in getattr(user, 'groups', [])],
177+
realm_id
178+
)
179+
raise ApiError(error_code="forbidden", message="Not authorized to delete a tenant.", status_code=403)
157180

158181
try:
182+
admin_token = get_master_admin_token()
159183
# Delete from Keycloak first. If this raises, we do not touch Postgres.
160184
delete_realm(realm_id, admin_token=admin_token)
161185

extensions/m8flow-backend/src/m8flow_backend/services/authorization_service_patch.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# and bootstrap: create realm / create tenant — no tenant in token yet; Keycloak admin is server-side).
1212
M8FLOW_AUTH_EXCLUSION_ADDITIONS = [
1313
"m8flow_backend.routes.keycloak_controller.get_tenant_login_url",
14-
"m8flow_backend.routes.keycloak_controller.create_realm",
1514
]
1615
M8FLOW_ROLE_GROUP_IDENTIFIERS = frozenset(
1716
{"super-admin", "tenant-admin", "editor", "viewer", "integrator", "reviewer"}

0 commit comments

Comments
 (0)