Skip to content

Commit 1c713fa

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

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
@@ -53,6 +53,10 @@ permissions:
5353
groups: ["super-admin"]
5454
actions: [all]
5555
uri: /m8flow/tenants/*
56+
manage-tenant-realms:
57+
groups: ["super-admin"]
58+
actions: [all]
59+
uri: /m8flow/tenant-realms*
5660
read-all-process-groups:
5761
groups: ["viewer"]
5862
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
@@ -134,15 +150,23 @@ def delete_tenant_realm(realm_id: str) -> tuple[dict, int]:
134150
with ON DELETE RESTRICT. If any rows still reference this tenant, the delete returns
135151
409 and the caller must remove or reassign those references first (or use soft delete).
136152
"""
137-
auth_header = request.headers.get("Authorization")
138-
if not auth_header or not auth_header.startswith("Bearer "):
139-
return {"detail": "Authorization header with Bearer token is required"}, 401
140-
141-
admin_token = auth_header.split(" ")[1]
142-
if not verify_admin_token(admin_token):
143-
return {"detail": "Invalid or unauthorized admin token"}, 401
153+
user = getattr(g, 'user', None)
154+
if not user:
155+
raise ApiError(error_code="not_authenticated", message="User not authenticated", status_code=401)
156+
157+
is_authorized = AuthorizationService.user_has_permission(user, "delete", request.path)
158+
159+
if not is_authorized:
160+
logger.warning(
161+
"User %s (groups: %s) attempted to delete tenant %s without required permissions",
162+
user.username,
163+
[getattr(g, 'identifier', g.name) for g in getattr(user, 'groups', [])],
164+
realm_id
165+
)
166+
raise ApiError(error_code="forbidden", message="Not authorized to delete a tenant.", status_code=403)
144167

145168
try:
169+
admin_token = get_master_admin_token()
146170
# Delete from Keycloak first. If this raises, we do not touch Postgres.
147171
delete_realm(realm_id, admin_token=admin_token)
148172

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)