This is an automated email from the ASF dual-hosted git repository.

yasithdev pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git


The following commit(s) were added to refs/heads/main by this push:
     new 0420dc39e auth(portal): derive gateway-admin flags from Keycloak realm 
roles (#222)
0420dc39e is described below

commit 0420dc39e214a5ccc3685cc35ba1d4faf426074c
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Sat Jun 13 22:14:17 2026 -0400

    auth(portal): derive gateway-admin flags from Keycloak realm roles (#222)
    
    Replace the per-request sharing-registry group-membership lookup with local
    parsing of the JWT realm roles: is_gateway_admin from admin-rw,
    is_read_only_gateway_admin from admin-ro. Add KeycloakUser.realm_roles and 
an
    admin_flags_middleware (replacing gateway_groups_middleware) so the session 
and
    bearer auth paths set the flags consistently, removing the fragile gRPC
    round-trip and the GATEWAY_GROUPS session cache.
---
 .../django_airavata/apps/auth/middleware.py        | 64 +++++-----------------
 .../apps/auth/token_authentication.py              |  4 ++
 airavata-django-portal/django_airavata/settings.py |  5 +-
 3 files changed, 21 insertions(+), 52 deletions(-)

diff --git a/airavata-django-portal/django_airavata/apps/auth/middleware.py 
b/airavata-django-portal/django_airavata/apps/auth/middleware.py
index 81c1973d3..bcb943773 100644
--- a/airavata-django-portal/django_airavata/apps/auth/middleware.py
+++ b/airavata-django-portal/django_airavata/apps/auth/middleware.py
@@ -34,55 +34,23 @@ def authz_token_middleware(get_response):
     return middleware
 
 
-def _gateway_groups_dict(request):
-    """Fetch the gateway's admin group ids via the gRPC compute facade."""
-    gg = request.airavata.compute.get_gateway_groups()
-    return {
-        "adminsGroupId": gg.admins_group_id,
-        "readOnlyAdminsGroupId": gg.read_only_admins_group_id,
-        "defaultGatewayUsersGroupId": gg.default_gateway_users_group_id,
-    }
-
-
-def set_admin_group_attributes(request, gateway_groups=None):
-    """Set is_gateway_admin and is_read_only_gateway_admin request attrs."""
-    if gateway_groups is None:
-        gateway_groups = _gateway_groups_dict(request)
-    admins_group_id = gateway_groups["adminsGroupId"]
-    read_only_admins_group_id = gateway_groups["readOnlyAdminsGroupId"]
-    group_memberships = 
request.airavata.sharing.gm_get_all_groups_user_belongs(
-        request.user.username + "@" + settings.GATEWAY_ID
-    )
-    group_ids = [group.id for group in group_memberships]
-    request.is_gateway_admin = admins_group_id in group_ids
-    request.is_read_only_gateway_admin = read_only_admins_group_id in group_ids
-
-
-def gateway_groups_middleware(get_response):
-    """Add 'is_gateway_admin' and 'is_read_only_gateway_admin' to request."""
+# Keycloak realm roles that map to the coarse gateway-admin flags.
+GATEWAY_ADMIN_ROLE = "admin-rw"
+READ_ONLY_ADMIN_ROLE = "admin-ro"
 
-    def middleware(request):
 
-        request.is_gateway_admin = False
-        request.is_read_only_gateway_admin = False
+def set_admin_flags_from_roles(request):
+    """Set is_gateway_admin / is_read_only_gateway_admin from the JWT realm 
roles."""
+    roles = set(getattr(request.user, "realm_roles", []) or [])
+    request.is_gateway_admin = GATEWAY_ADMIN_ROLE in roles
+    request.is_read_only_gateway_admin = READ_ONLY_ADMIN_ROLE in roles
 
-        if not request.user.is_authenticated or not request.authz_token:
-            return get_response(request)
 
-        try:
-            # Load the GatewayGroups and check if user is in the Admins and/or
-            # Read Only Admins groups
-            if not request.session.get("GATEWAY_GROUPS"):
-                request.session["GATEWAY_GROUPS"] = 
_gateway_groups_dict(request)
-            set_admin_group_attributes(
-                request, gateway_groups=request.session.get("GATEWAY_GROUPS")
-            )
-        except Exception as e:
-            log.warning(
-                "Failed to set is_gateway_admin, is_read_only_gateway_admin 
for user",
-                exc_info=e,
-            )
+def admin_flags_middleware(get_response):
+    """Add 'is_gateway_admin' / 'is_read_only_gateway_admin' from the user's 
realm roles."""
 
+    def middleware(request):
+        set_admin_flags_from_roles(request)
         return get_response(request)
 
     return middleware
@@ -195,8 +163,8 @@ def keycloak_bearer_middleware(get_response):
     Reuses ``token_authentication``'s ``_jwks`` / ``KeycloakUser`` / 
``AuthzToken``:
     if ``request.user`` is already
     authenticated (session) this is a no-op; elif a Bearer token is present it 
is
-    validated and ``request.user`` / ``request.authz_token`` (+ the
-    ``is_gateway_admin`` / ``is_read_only_gateway_admin`` defaults) are set; 
else
+    validated and ``request.user`` / ``request.authz_token`` are set (the
+    ``admin_flags_middleware`` then derives the admin flags from realm roles); 
else
     the request is left Anonymous. An invalid token leaves the user Anonymous 
(no
     raise — the permission layer returns 401).
     """
@@ -237,10 +205,6 @@ def keycloak_bearer_middleware(get_response):
         )
         request.user = keycloak_user
         request.authz_token = authz_token
-        # The session-based gateway_groups_middleware sets these; pure-token 
auth
-        # skips it, so default to non-admin.
-        request.is_gateway_admin = False
-        request.is_read_only_gateway_admin = False
 
         return get_response(request)
 
diff --git 
a/airavata-django-portal/django_airavata/apps/auth/token_authentication.py 
b/airavata-django-portal/django_airavata/apps/auth/token_authentication.py
index 25a6be4d2..b3c70e176 100644
--- a/airavata-django-portal/django_airavata/apps/auth/token_authentication.py
+++ b/airavata-django-portal/django_airavata/apps/auth/token_authentication.py
@@ -52,6 +52,10 @@ class KeycloakUser:
     def __str__(self):
         return self.username or "<anonymous>"
 
+    @property
+    def realm_roles(self):
+        return (self.claims.get("realm_access") or {}).get("roles") or []
+
     @property
     def is_staff(self):
         return False
diff --git a/airavata-django-portal/django_airavata/settings.py 
b/airavata-django-portal/django_airavata/settings.py
index 9d9b5f990..3e0c783d2 100644
--- a/airavata-django-portal/django_airavata/settings.py
+++ b/airavata-django-portal/django_airavata/settings.py
@@ -91,8 +91,9 @@ MIDDLEWARE = [
     # gRPC AiravataClient (request.airavata). Must come after 
authz_token_middleware
     # (uses request.authz_token for the access token).
     "django_airavata.middleware.airavata_grpc_client",
-    # Needs to come after authz_token_middleware and airavata_grpc_client.
-    "django_airavata.apps.auth.middleware.gateway_groups_middleware",
+    # Set is_gateway_admin / is_read_only_gateway_admin from the JWT realm 
roles.
+    # Must come after the auth middlewares so request.user is set.
+    "django_airavata.apps.auth.middleware.admin_flags_middleware",
 ]
 
 ROOT_URLCONF = "django_airavata.urls"

Reply via email to