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 712889073 feat(portal): de-Thrift the auth IAM user-management to gRPC 
(Track D, D5) (#180)
712889073 is described below

commit 7128890734fa914cb85e0e906dfb87884e7897de
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Mon Jun 8 21:46:05 2026 -0400

    feat(portal): de-Thrift the auth IAM user-management to gRPC (Track D, D5) 
(#180)
    
    Repoint the auth app's user-management path from the legacy Thrift clients 
to
    the new gRPC `iam` facade, using direct proto types (no Thrift-name 
roundtrip).
    
    - `iam_admin_client.py`: the IAM admin operations (username availability, 
user
      registration, enable, reset password, lookup) ran on the Thrift
      `iamadmin_client_pool`. They now build a short-lived 
service-account-scoped
      `AiravataClient` and call the gRPC `iam` facade. These run in 
unauthenticated
      contexts (account creation, email verification, password reset), so the
      client carries the Keycloak service-account token plus a `gatewayID` claim
      (the IAM admin service resolves the Keycloak realm from the gateway 
claim).
      `update_user`/`update_username` are unchanged — they already talk to the
      Keycloak admin REST API directly, not Thrift.
    - `serializers.py` / `views.py`: the user-profile updates 
(`UserSerializer.update`,
      `UserViewSet.verify_email_change`) used the Thrift `profile_service`
      user-profile client. They now use `request.airavata.iam` (does_user_exist 
/
      get_user_profile_by_id / update_user_profile) in the authenticated request
      context.
    - Consumers read the returned protobuf `UserProfile` directly
      (`first_name`/`last_name`/`emails`/`user_id`) instead of the Thrift 
attribute
      names.
---
 .../django_airavata/apps/auth/iam_admin_client.py  | 77 ++++++++++++++--------
 .../django_airavata/apps/auth/serializers.py       | 16 ++---
 .../django_airavata/apps/auth/views.py             | 26 ++++----
 3 files changed, 68 insertions(+), 51 deletions(-)

diff --git 
a/airavata-django-portal/django_airavata/apps/auth/iam_admin_client.py 
b/airavata-django-portal/django_airavata/apps/auth/iam_admin_client.py
index 6106626ae..c080409b6 100644
--- a/airavata-django-portal/django_airavata/apps/auth/iam_admin_client.py
+++ b/airavata-django-portal/django_airavata/apps/auth/iam_admin_client.py
@@ -1,70 +1,91 @@
-"""
-Wrapper around the IAM Admin Services client.
+"""IAM admin operations via the gRPC ``iam`` facade.
+
+These operations (username availability, user registration, enable/reset) run 
in
+unauthenticated contexts — account creation, email verification, password 
reset —
+so they use a Keycloak **service-account** token rather than a logged-in user's
+token. Each call builds a short-lived ``AiravataClient`` scoped to that token 
and
+talks to the gRPC ``iam`` facade; callers consume the returned protobuf
+``UserProfile`` directly (``user_id``/``first_name``/``last_name``/``emails``).
+
+``update_user``/``update_username`` talk to the Keycloak admin REST API 
directly
+(not the gRPC backend) and are unchanged.
 """
 
 import logging
+from contextlib import contextmanager
 from urllib.parse import urlparse
 
 import requests
 from django.conf import settings
 
-from django_airavata.utils import iamadmin_client_pool
+from django_airavata.airavata_grpc import build_airavata_client
 
 from . import utils
 
 logger = logging.getLogger(__name__)
 
 
+@contextmanager
+def _iam():
+    """Yield the gRPC ``iam`` facade scoped to the Keycloak service account.
+
+    The IAM admin operations resolve the Keycloak realm from the request's
+    gateway claim, so the service-account client carries ``gatewayID`` in its
+    ``x-claims`` metadata (mirroring the legacy service-account 
``AuthzToken``).
+    """
+    access_token = utils.get_service_account_authz_token().accessToken
+    client = build_airavata_client(
+        access_token, claims={'gatewayID': settings.GATEWAY_ID})
+    try:
+        yield client.iam
+    finally:
+        client.close()
+
+
 def is_username_available(username):
-    authz_token = utils.get_service_account_authz_token()
-    return iamadmin_client_pool.isUsernameAvailable(authz_token, username)
+    with _iam() as iam:
+        return iam.is_username_available(username)
 
 
 def register_user(username, email_address, first_name, last_name, password):
-    authz_token = utils.get_service_account_authz_token()
-    return iamadmin_client_pool.registerUser(
-        authz_token,
-        username,
-        email_address,
-        first_name,
-        last_name,
-        password)
+    with _iam() as iam:
+        return iam.register_user(
+            username, email_address, first_name, last_name, password)
 
 
 def is_user_enabled(username):
-    authz_token = utils.get_service_account_authz_token()
-    return iamadmin_client_pool.isUserEnabled(authz_token, username)
+    with _iam() as iam:
+        return iam.is_user_enabled(username)
 
 
 def enable_user(username):
-    authz_token = utils.get_service_account_authz_token()
-    return iamadmin_client_pool.enableUser(authz_token, username)
+    with _iam() as iam:
+        return iam.enable_user(username)
 
 
 def delete_user(username):
-    authz_token = utils.get_service_account_authz_token()
-    return iamadmin_client_pool.deleteUser(authz_token, username)
+    with _iam() as iam:
+        return iam.delete_iam_user(username)
 
 
 def is_user_exist(username):
-    authz_token = utils.get_service_account_authz_token()
-    return iamadmin_client_pool.isUserExist(authz_token, username)
+    with _iam() as iam:
+        return iam.is_user_exist(username)
 
 
 def get_user(username):
-    authz_token = utils.get_service_account_authz_token()
-    return iamadmin_client_pool.getUser(authz_token, username)
+    with _iam() as iam:
+        return iam.get_iam_user(username)
 
 
 def get_users(offset, limit, search=None):
-    authz_token = utils.get_service_account_authz_token()
-    return iamadmin_client_pool.getUsers(authz_token, offset, limit, search)
+    with _iam() as iam:
+        return iam.get_iam_users(offset, limit, search or "")
 
 
 def reset_user_password(username, new_password):
-    authz_token = utils.get_service_account_authz_token()
-    return iamadmin_client_pool.resetUserPassword(
-        authz_token, username, new_password)
+    with _iam() as iam:
+        return iam.reset_user_password(username, new_password)
 
 
 def update_username(username, new_username):
diff --git a/airavata-django-portal/django_airavata/apps/auth/serializers.py 
b/airavata-django-portal/django_airavata/apps/auth/serializers.py
index 4b35c111f..48a5a80b3 100644
--- a/airavata-django-portal/django_airavata/apps/auth/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/auth/serializers.py
@@ -68,17 +68,15 @@ class UserSerializer(serializers.ModelSerializer):
             self._send_email_verification_link(request, pending_email_change)
         instance.save()
         # save in the user profile service too
-        user_profile_client = request.profile_service['user_profile']
+        iam = request.airavata.iam
 
         # update the Airavata profile if it exists
-        if user_profile_client.doesUserExist(request.authz_token,
-                                             request.user.username,
-                                             settings.GATEWAY_ID):
-            airavata_user_profile = user_profile_client.getUserProfileById(
-                request.authz_token, request.user.username, 
settings.GATEWAY_ID)
-            airavata_user_profile.firstName = instance.first_name
-            airavata_user_profile.lastName = instance.last_name
-            user_profile_client.updateUserProfile(request.authz_token, 
airavata_user_profile)
+        if iam.does_user_exist(request.user.username, settings.GATEWAY_ID):
+            airavata_user_profile = iam.get_user_profile_by_id(
+                request.user.username, settings.GATEWAY_ID)
+            airavata_user_profile.first_name = instance.first_name
+            airavata_user_profile.last_name = instance.last_name
+            iam.update_user_profile(airavata_user_profile)
         # otherwise, update in Keycloak user store
         else:
             iam_admin_client.update_user(request.user.username,
diff --git a/airavata-django-portal/django_airavata/apps/auth/views.py 
b/airavata-django-portal/django_airavata/apps/auth/views.py
index a05b2480e..0a27cb40b 100644
--- a/airavata-django-portal/django_airavata/apps/auth/views.py
+++ b/airavata-django-portal/django_airavata/apps/auth/views.py
@@ -260,8 +260,8 @@ def verify_email(request, code):
             iam_admin_client.enable_user(username)
             user_profile = iam_admin_client.get_user(username)
             email_address = user_profile.emails[0]
-            first_name = user_profile.firstName
-            last_name = user_profile.lastName
+            first_name = user_profile.first_name
+            last_name = user_profile.last_name
             utils.send_new_user_email(request,
                                       username,
                                       email_address,
@@ -305,8 +305,8 @@ def resend_email_link(request):
                         request,
                         username,
                         email_address,
-                        user_profile.firstName,
-                        user_profile.lastName)
+                        user_profile.first_name,
+                        user_profile.last_name)
                     messages.success(
                         request,
                         "Email verification link sent successfully. Please "
@@ -414,8 +414,8 @@ def _create_and_send_password_reset_request_link(request, 
username):
     context = Context({
         "username": username,
         "email": user.emails[0],
-        "first_name": user.firstName,
-        "last_name": user.lastName,
+        "first_name": user.first_name,
+        "last_name": user.last_name,
         "portal_title": settings.PORTAL_TITLE,
         "url": verification_uri,
     })
@@ -622,14 +622,12 @@ class UserViewSet(viewsets.ModelViewSet):
 
         try:
             # only update the airavata profile if it exists
-            user_profile_client = request.profile_service['user_profile']
-            if user_profile_client.doesUserExist(request.authz_token,
-                                                 request.user.username,
-                                                 settings.GATEWAY_ID):
-                airavata_user_profile = user_profile_client.getUserProfileById(
-                    request.authz_token, user.username, settings.GATEWAY_ID)
-                airavata_user_profile.emails = 
[pending_email_change.email_address]
-                user_profile_client.updateUserProfile(request.authz_token, 
airavata_user_profile)
+            iam = request.airavata.iam
+            if iam.does_user_exist(request.user.username, settings.GATEWAY_ID):
+                airavata_user_profile = iam.get_user_profile_by_id(
+                    user.username, settings.GATEWAY_ID)
+                airavata_user_profile.emails[:] = 
[pending_email_change.email_address]
+                iam.update_user_profile(airavata_user_profile)
             # otherwise, update the user's email in the Keycloak user store
             else:
                 iam_admin_client.update_user(request.user.username,

Reply via email to