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 41ec59ea3 refactor(portal): remove the last Thrift model-type 
references; the portal is Thrift-free (Track D) (#207)
41ec59ea3 is described below

commit 41ec59ea364cabf183867f5cbd395f8d21fde4c0
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 05:34:48 2026 -0400

    refactor(portal): remove the last Thrift model-type references; the portal 
is Thrift-free (Track D) (#207)
    
    The portal no longer imports any Thrift model type 
(airavata.model.*.ttypes) or
    the thrift library anywhere:
    
    - AuthzToken: replace the Thrift security value type with a plain carrier in
      apps/auth/utils.py (same accessToken / claimsMap attributes, so the gRPC 
client
      factory + IAM REST helpers are unchanged); token_authentication.py 
imports it.
    - exceptions.py: drop the dead Thrift exception handlers 
(AuthorizationException /
      ExperimentNotFoundException / TTransportException / TException) and their 
imports
      — no Thrift call can raise them now; the gRPC RpcError handler covers the 
views.
    - Delete thrift_utils.py (the auto-serializer factory) — every serializer is
      proto-native, so nothing imports it.
    - Test fixtures (apps/auth/tests/test_signals.py, apps/auth + apps/api
      tests/test_views.py) build proto UserProfile / GroupModel / GatewayGroups 
with
      proto field names instead of the Thrift types. test_signals now passes; 
the
      IAMUser/Group view tests remain pre-existing failures (they mock the 
long-removed
      profile_service/airavata_client Thrift surfaces — a separate gRPC-facade
      test-rewrite, unchanged in count vs origin/main).
    
    manage.py check green; the api test failure set is unchanged vs origin/main 
(same
    6 pre-existing). The legacy airavata-python-sdk==2.2.7 + thrift deps can be 
dropped
    once airavata_django_portal_sdk (experiment_util / 
queue_settings_calculators) is
    folded in — the final increment.
---
 .../django_airavata/apps/api/exceptions.py         |  36 +--
 .../django_airavata/apps/api/tests/test_views.py   | 116 ++++-----
 .../django_airavata/apps/api/thrift_utils.py       | 258 ---------------------
 .../apps/auth/tests/test_signals.py                |  22 +-
 .../django_airavata/apps/auth/tests/test_views.py  |  20 +-
 .../apps/auth/token_authentication.py              |   3 +-
 .../django_airavata/apps/auth/utils.py             |  14 +-
 7 files changed, 102 insertions(+), 367 deletions(-)

diff --git a/airavata-django-portal/django_airavata/apps/api/exceptions.py 
b/airavata-django-portal/django_airavata/apps/api/exceptions.py
index b14bce2c7..3b703f469 100644
--- a/airavata-django-portal/django_airavata/apps/api/exceptions.py
+++ b/airavata-django-portal/django_airavata/apps/api/exceptions.py
@@ -2,23 +2,17 @@ import logging
 import sys
 
 import grpc
-from airavata.api.error.ttypes import (
-    AuthorizationException,
-    ExperimentNotFoundException
-)
 from django.core.exceptions import ObjectDoesNotExist
 from django.http import JsonResponse
 from rest_framework import status
 from rest_framework.exceptions import NotAuthenticated
 from rest_framework.response import Response
 from rest_framework.views import exception_handler
-from thrift.Thrift import TException
-from thrift.transport import TTransport
 
 log = logging.getLogger(__name__)
 
-# Track D: map new-stack gRPC status codes to HTTP responses, mirroring the
-# Thrift exception handling below so migrated views behave identically.
+# Map gRPC status codes to HTTP responses (the portal talks only to the gRPC
+# facade now; the legacy Thrift exception handlers are gone).
 GRPC_STATUS_TO_HTTP = {
     grpc.StatusCode.NOT_FOUND: status.HTTP_404_NOT_FOUND,
     grpc.StatusCode.PERMISSION_DENIED: status.HTTP_403_FORBIDDEN,
@@ -52,32 +46,6 @@ def custom_exception_handler(exc, context):
             log.warning("gRPC error %s", code, exc_info=exc)
         return Response({'detail': detail}, status=http_status)
 
-    if isinstance(exc, AuthorizationException):
-        log.warning("AuthorizationException", exc_info=exc)
-        return Response(
-            {'detail': str(exc)},
-            status=status.HTTP_403_FORBIDDEN)
-
-    if isinstance(exc, ExperimentNotFoundException):
-        log.warning("ExperimentNotFoundException", exc_info=exc)
-        return Response(
-            {'detail': str(exc)},
-            status=status.HTTP_404_NOT_FOUND)
-
-    if isinstance(exc, TTransport.TTransportException):
-        log.warning("TTransportException", exc_info=exc)
-        return Response(
-            {'detail': str(exc), 'apiServerDown': True},
-            status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-    # Default TException handler, should come after more specific subclasses of
-    # TException
-    if isinstance(exc, TException):
-        log.error("TException", exc_info=exc, extra={'request': 
context['request']})
-        return Response(
-            {'detail': str(exc)},
-            status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
     if isinstance(exc, ObjectDoesNotExist):
         log.warning("ObjectDoesNotExist", exc_info=exc)
         return Response(
diff --git 
a/airavata-django-portal/django_airavata/apps/api/tests/test_views.py 
b/airavata-django-portal/django_airavata/apps/api/tests/test_views.py
index 201538dc4..067339201 100644
--- a/airavata-django-portal/django_airavata/apps/api/tests/test_views.py
+++ b/airavata-django-portal/django_airavata/apps/api/tests/test_views.py
@@ -1,8 +1,14 @@
 from unittest.mock import MagicMock, call, patch
 
-from airavata.model.appcatalog.gatewaygroups.ttypes import GatewayGroups
-from airavata.model.group.ttypes import GroupModel
-from airavata.model.user.ttypes import UserProfile
+from 
airavata_sdk.generated.org.apache.airavata.model.appcatalog.gatewaygroups.gateway_groups_pb2
 import (  # noqa: E501
+    GatewayGroups,
+)
+from airavata_sdk.generated.org.apache.airavata.model.group.group_manager_pb2 
import (  # noqa: E501
+    GroupModel,
+)
+from airavata_sdk.generated.org.apache.airavata.model.user.user_profile_pb2 
import (
+    UserProfile,
+)
 from django.contrib.auth.models import User
 from django.test import TestCase, override_settings
 from django.urls import reverse
@@ -49,19 +55,19 @@ class GroupViewSetTests(TestCase):
         }
         request.airavata_client = MagicMock(name="airavata_client")
         request.airavata_client.getGatewayGroups.return_value = GatewayGroups(
-            gatewayId=GATEWAY_ID,
-            adminsGroupId="adminsGroupId",
-            readOnlyAdminsGroupId="readOnlyAdminsGroupId",
-            defaultGatewayUsersGroupId="defaultGatewayUsersGroupId"
+            gateway_id=GATEWAY_ID,
+            admins_group_id="adminsGroupId",
+            read_only_admins_group_id="readOnlyAdminsGroupId",
+            default_gateway_users_group_id="defaultGatewayUsersGroupId"
         )
         request.authz_token = "dummy"
         request.session = {}
         group_manager_mock.createGroup.return_value = "abc123"
         user_profile = UserProfile(
-            airavataInternalUserId=f"testuser1@{GATEWAY_ID}",
-            userId="testuser1",
-            firstName="Test",
-            lastName="User1",
+            airavata_internal_user_id=f"testuser1@{GATEWAY_ID}",
+            user_id="testuser1",
+            first_name="Test",
+            last_name="User1",
             emails=["[email protected]"]
         )
         user_profile_mock.getUserProfileById.return_value = user_profile
@@ -105,10 +111,10 @@ class GroupViewSetTests(TestCase):
         }
         request.airavata_client = MagicMock(name="airavata_client")
         request.airavata_client.getGatewayGroups.return_value = GatewayGroups(
-            gatewayId=GATEWAY_ID,
-            adminsGroupId="adminsGroupId",
-            readOnlyAdminsGroupId="readOnlyAdminsGroupId",
-            defaultGatewayUsersGroupId="defaultGatewayUsersGroupId"
+            gateway_id=GATEWAY_ID,
+            admins_group_id="adminsGroupId",
+            read_only_admins_group_id="readOnlyAdminsGroupId",
+            default_gateway_users_group_id="defaultGatewayUsersGroupId"
         )
         request.authz_token = "dummy"
         request.session = {}
@@ -127,10 +133,10 @@ class GroupViewSetTests(TestCase):
         # Only user added is testuser3, so getUserProfileById will be called
         # for that user
         user_profile = UserProfile(
-            airavataInternalUserId=f"testuser3@{GATEWAY_ID}",
-            userId="testuser3",
-            firstName="Test",
-            lastName="User3",
+            airavata_internal_user_id=f"testuser3@{GATEWAY_ID}",
+            user_id="testuser3",
+            first_name="Test",
+            last_name="User3",
             emails=["[email protected]"]
         )
         user_profile_mock.getUserProfileById.return_value = user_profile
@@ -203,10 +209,10 @@ class IAMUserViewSetTests(TestCase):
 
         # Mock api clients
         iam_user_profile = UserProfile(
-            airavataInternalUserId=f"testuser1@{GATEWAY_ID}",
-            userId="testuser1",
-            firstName="Test",
-            lastName="User1",
+            airavata_internal_user_id=f"testuser1@{GATEWAY_ID}",
+            user_id="testuser1",
+            first_name="Test",
+            last_name="User1",
             emails=["[email protected]"]
         )
         iam_admin_client.get_user.return_value = iam_user_profile
@@ -219,10 +225,10 @@ class IAMUserViewSetTests(TestCase):
         request.authz_token = "dummy"
         user_profile_mock.doesUserExist.return_value = True
         user_profile = UserProfile(
-            airavataInternalUserId=f"testuser1@{GATEWAY_ID}",
-            userId="testuser1",
-            firstName="Test",
-            lastName="User1",
+            airavata_internal_user_id=f"testuser1@{GATEWAY_ID}",
+            user_id="testuser1",
+            first_name="Test",
+            last_name="User1",
             emails=["[email protected]"]
         )
         user_profile_mock.getUserProfileById.return_value = user_profile
@@ -234,10 +240,10 @@ class IAMUserViewSetTests(TestCase):
         group_manager_mock.getGroup.return_value = group
         request.airavata_client = MagicMock(name="airavata_client")
         request.airavata_client.getGatewayGroups.return_value = GatewayGroups(
-            gatewayId=GATEWAY_ID,
-            adminsGroupId="adminsGroupId",
-            readOnlyAdminsGroupId="readOnlyAdminsGroupId",
-            defaultGatewayUsersGroupId="defaultGatewayUsersGroupId"
+            gateway_id=GATEWAY_ID,
+            admins_group_id="adminsGroupId",
+            read_only_admins_group_id="readOnlyAdminsGroupId",
+            default_gateway_users_group_id="defaultGatewayUsersGroupId"
         )
         request.session = {}
 
@@ -305,10 +311,10 @@ class IAMUserViewSetTests(TestCase):
 
         # Mock api clients
         iam_user_profile = UserProfile(
-            airavataInternalUserId=f"testuser1@{GATEWAY_ID}",
-            userId="testuser1",
-            firstName="Test",
-            lastName="User1",
+            airavata_internal_user_id=f"testuser1@{GATEWAY_ID}",
+            user_id="testuser1",
+            first_name="Test",
+            last_name="User1",
             emails=["[email protected]"]
         )
         iam_admin_client.get_user.return_value = iam_user_profile
@@ -321,10 +327,10 @@ class IAMUserViewSetTests(TestCase):
         request.authz_token = "dummy"
         user_profile_mock.doesUserExist.return_value = True
         user_profile = UserProfile(
-            airavataInternalUserId=f"testuser1@{GATEWAY_ID}",
-            userId="testuser1",
-            firstName="Test",
-            lastName="User1",
+            airavata_internal_user_id=f"testuser1@{GATEWAY_ID}",
+            user_id="testuser1",
+            first_name="Test",
+            last_name="User1",
             emails=["[email protected]"]
         )
         user_profile_mock.getUserProfileById.return_value = user_profile
@@ -342,10 +348,10 @@ class IAMUserViewSetTests(TestCase):
         group_manager_mock.getGroup.side_effect = side_effect
         request.airavata_client = MagicMock(name="airavata_client")
         request.airavata_client.getGatewayGroups.return_value = GatewayGroups(
-            gatewayId=GATEWAY_ID,
-            adminsGroupId="adminsGroupId",
-            readOnlyAdminsGroupId="readOnlyAdminsGroupId",
-            defaultGatewayUsersGroupId="defaultGatewayUsersGroupId"
+            gateway_id=GATEWAY_ID,
+            admins_group_id="adminsGroupId",
+            read_only_admins_group_id="readOnlyAdminsGroupId",
+            default_gateway_users_group_id="defaultGatewayUsersGroupId"
         )
         request.session = {}
 
@@ -414,10 +420,10 @@ class IAMUserViewSetTests(TestCase):
 
         # Mock api clients
         iam_user_profile = UserProfile(
-            airavataInternalUserId=f"testuser1@{GATEWAY_ID}",
-            userId="testuser1",
-            firstName="Test",
-            lastName="User1",
+            airavata_internal_user_id=f"testuser1@{GATEWAY_ID}",
+            user_id="testuser1",
+            first_name="Test",
+            last_name="User1",
             emails=["[email protected]"]
         )
         iam_admin_client.get_user.return_value = iam_user_profile
@@ -430,10 +436,10 @@ class IAMUserViewSetTests(TestCase):
         request.authz_token = "dummy"
         user_profile_mock.doesUserExist.return_value = True
         user_profile = UserProfile(
-            airavataInternalUserId=f"testuser1@{GATEWAY_ID}",
-            userId="testuser1",
-            firstName="Test",
-            lastName="User1",
+            airavata_internal_user_id=f"testuser1@{GATEWAY_ID}",
+            user_id="testuser1",
+            first_name="Test",
+            last_name="User1",
             emails=["[email protected]"]
         )
         user_profile_mock.getUserProfileById.return_value = user_profile
@@ -442,10 +448,10 @@ class IAMUserViewSetTests(TestCase):
 
         request.airavata_client = MagicMock(name="airavata_client")
         request.airavata_client.getGatewayGroups.return_value = GatewayGroups(
-            gatewayId=GATEWAY_ID,
-            adminsGroupId="adminsGroupId",
-            readOnlyAdminsGroupId="readOnlyAdminsGroupId",
-            defaultGatewayUsersGroupId="defaultGatewayUsersGroupId"
+            gateway_id=GATEWAY_ID,
+            admins_group_id="adminsGroupId",
+            read_only_admins_group_id="readOnlyAdminsGroupId",
+            default_gateway_users_group_id="defaultGatewayUsersGroupId"
         )
         request.session = {}
 
diff --git a/airavata-django-portal/django_airavata/apps/api/thrift_utils.py 
b/airavata-django-portal/django_airavata/apps/api/thrift_utils.py
deleted file mode 100644
index d4e6884f4..000000000
--- a/airavata-django-portal/django_airavata/apps/api/thrift_utils.py
+++ /dev/null
@@ -1,258 +0,0 @@
-"""
-Used to create Django Rest Framework serializers for Apache Thrift Data Types
-"""
-import copy
-import datetime
-import logging
-import enum
-
-from rest_framework.serializers import (
-    BooleanField,
-    CharField,
-    DateTimeField,
-    DecimalField,
-    DictField,
-    Field,
-    IntegerField,
-    ListField,
-    ListSerializer,
-    Serializer,
-    SerializerMetaclass,
-    ValidationError
-)
-from thrift.Thrift import TType
-from airavata.model.experiment.ttypes import ExperimentType
-from airavata.model.status.ttypes import ExperimentState
-from airavata.model.application.io.ttypes import DataType
-from airavata.model.appcatalog.parallelism.ttypes import 
ApplicationParallelismType
-
-logger = logging.getLogger(__name__)
-
-# used to map apache thrift data types to django serializer fields
-mapping = {
-    TType.STRING: CharField,
-    TType.I08: IntegerField,
-    TType.I16: IntegerField,
-    TType.I32: IntegerField,
-    TType.I64: IntegerField,
-    TType.DOUBLE: DecimalField,
-    TType.BOOL: BooleanField,
-    TType.MAP: DictField
-}
-
-
-class UTCPosixTimestampDateTimeField(DateTimeField):
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.default = self.current_time_ms
-        self.initial = self.initial_value
-        self.required = False
-
-    def to_representation(self, obj):
-        # Create datetime instance from milliseconds that is aware of timezon
-        dt = datetime.datetime.fromtimestamp(obj / 1000, datetime.timezone.utc)
-        return super().to_representation(dt)
-
-    def to_internal_value(self, data):
-        dt = super().to_internal_value(data)
-        return int(dt.timestamp() * 1000)
-
-    def initial_value(self):
-        return self.to_representation(self.current_time_ms())
-
-    def current_time_ms(self):
-        return int(datetime.datetime.utcnow().timestamp() * 1000)
-
-
-class ThriftEnumField(Field):
-
-    def __init__(self, enumClass, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.enumClass = enumClass
-
-    def to_representation(self, obj):
-        # For IntEnum, obj is the enum member, its `.name` is the string
-        if obj is None:
-            return None
-        return obj.name
-
-    def to_internal_value(self, data):
-        # Convert string name back into an IntEnum member
-        if self.allow_null and data is None:
-            return None
-        try:
-            return self.enumClass[data]
-        except KeyError:
-            raise ValidationError(f"'{data}' is not a valid name for enum 
{self.enumClass.__name__}")
-
-
-def create_serializer(thrift_data_type, enable_date_time_conversion=False, 
**kwargs):
-    """
-    Create django rest framework serializer based on the thrift data type
-    :param thrift_data_type: Thrift data type
-    :param kwargs: Other Django Framework Serializer initialization parameters
-    :param enable_date_time_conversion: enable conversion of field with name 
ending with time to
-            UTCPosixTimestampDateTimeField instead of IntegerField
-    :return: instance of custom serializer for the given thrift data type
-    """
-    return create_serializer_class(thrift_data_type, 
enable_date_time_conversion)(**kwargs)
-
-
-def create_serializer_class(thrift_data_type, 
enable_date_time_conversion=False):
-    class CustomSerializerMeta(SerializerMetaclass):
-
-        def __new__(cls, name, bases, attrs):
-            meta = attrs.get('Meta', None)
-            thrift_spec = thrift_data_type.thrift_spec
-            for field in thrift_spec:
-                # Don't replace existing attrs to allow subclasses to override
-                if field and field[2] not in attrs:
-                    required = (field[2] in meta.required
-                                if meta and hasattr(meta, 'required')
-                                else False)
-                    read_only = (field[2] in meta.read_only
-                                 if meta and hasattr(meta, 'read_only')
-                                 else False)
-                    allow_null = not required
-                    field_serializer = process_field(
-                        field, enable_date_time_conversion, required=required, 
read_only=read_only,
-                        allow_null=allow_null)
-                    attrs[field[2]] = field_serializer
-            return super().__new__(cls, name, bases, attrs)
-
-    class CustomSerializer(Serializer, metaclass=CustomSerializerMeta):
-        """
-        Custom Serializer which handle the list fields which holds custom 
class objects
-        """
-
-        def process_nested_fields(self, validated_data):
-            fields = self.fields
-            params = copy.deepcopy(validated_data)
-            for field_name, serializer in fields.items():
-                if (isinstance(serializer, ListField) or
-                        isinstance(serializer, ListSerializer)):
-                    if (params.get(field_name, None) is not None or
-                            not serializer.allow_null):
-                        if isinstance(serializer.child, Serializer):
-                            # If this is a list of experiment inputs, need to 
manually
-                            # convert the 'type' field from an integer to a 
DataType enum
-                            if field_name == 'experimentInputs' and 'type' in 
serializer.child.fields:
-                                for item in params[field_name]:
-                                    if 'type' in item and 
isinstance(item['type'], int):
-                                        item['type'] = DataType(item['type'])
-                            elif field_name == 'experimentOutputs' and 'type' 
in serializer.child.fields:
-                                for item in params[field_name]:
-                                    if 'type' in item and 
isinstance(item['type'], int):
-                                        item['type'] = DataType(item['type'])
-                            elif field_name == 'experimentStatus' and 'state' 
in serializer.child.fields:
-                                for item in params[field_name]:
-                                    if 'state' in item and 
isinstance(item['state'], int):
-                                        item['state'] = 
ExperimentState(item['state'])
-                            params[field_name] = [serializer.child.create(
-                                item) for item in params[field_name]]
-                        else:
-                            params[field_name] = serializer.to_representation(
-                                params[field_name])
-                elif isinstance(serializer, Serializer):
-                    if field_name in params and params[field_name] is not None:
-                        params[field_name] = serializer.create(
-                            params[field_name])
-            return params
-
-        def create(self, validated_data):
-            params = self.process_nested_fields(validated_data)
-
-            # The Thrift models expect a mandatory ID but provide a default 
value for creation.
-            # The latest library upgrade is tight and fails if an ID is 
explicitly passed as `None`.
-            # This logic removes such fields allowing the default to be used.
-            thrift_spec = thrift_data_type.thrift_spec
-            for field_spec in thrift_spec:
-                if field_spec:
-                    field_name = field_spec[2]
-                    default_value = field_spec[4]
-                    if default_value is not None:
-                        if field_name in params and params[field_name] is None:
-                            del params[field_name]
-
-            if (thrift_data_type.__name__ == 'ExperimentModel' and
-                'experimentType' in params and 
isinstance(params['experimentType'], int)):
-                params['experimentType'] = 
ExperimentType(params['experimentType'])
-
-            if (thrift_data_type.__name__ == 
'ApplicationDeploymentDescription' and
-                'parallelism' in params and isinstance(params['parallelism'], 
int)):
-                params['parallelism'] = 
ApplicationParallelismType(params['parallelism'])
-
-            return thrift_data_type(**params)
-
-        def update(self, instance, validated_data):
-            return self.create(validated_data)
-
-    return CustomSerializer
-
-
-def process_field(field, enable_date_time_conversion, required=False, 
read_only=False, allow_null=False):
-    """
-    Used to process a thrift data type field
-    :param field:
-    :param required:
-    :param read_only:
-    :param allow_null:
-    :return:
-    """
-    if field[1] in mapping:
-        # handling scenarios when the thrift field type is present in the
-        # mapping
-        field_class = mapping[field[1]]
-        kwargs = dict(required=required, read_only=read_only)
-        # allow_null isn't allowed for BooleanField
-        if field_class not in (BooleanField,):
-            kwargs['allow_null'] = allow_null
-        # allow_null CharField are also allowed to be blank
-        if field_class == CharField:
-            kwargs['allow_blank'] = allow_null
-        thrift_model_class = mapping[field[1]]
-
-        if thrift_model_class == IntegerField and field[3] is not None and 
isinstance(field[3], type) and issubclass(field[3], enum.IntEnum):
-            return ThriftEnumField(field[3], required=required, 
read_only=read_only, allow_null=allow_null)
-
-        if enable_date_time_conversion and thrift_model_class == IntegerField 
and field[2].lower().endswith("time"):
-            thrift_model_class = UTCPosixTimestampDateTimeField
-        return thrift_model_class(**kwargs)
-    elif field[1] == TType.LIST:
-        # handling scenario when the thrift field type is list
-        list_field_serializer = process_list_field(field)
-        return ListField(child=list_field_serializer,
-                         required=required,
-                         read_only=read_only,
-                         allow_null=allow_null)
-    elif field[1] == TType.STRUCT:
-        # handling scenario when the thrift field type is struct
-        return create_serializer(field[3][0],
-                                 required=required,
-                                 read_only=read_only,
-                                 allow_null=allow_null)
-
-
-def process_list_field(field):
-    """
-    Used to process thrift list type field
-    :param field:
-    :return:
-    """
-    list_details = field[3]
-    item_ttype = list_details[0]
-    # For enums, extra type info is the enum Class (e.g. ExperimentState)
-    # For structs, it's a tuple of (StructClass, StructClass.thrift_spec)
-    item_type_info = list_details[1]
-
-    if (item_ttype == TType.I32 and
-        item_type_info is not None and
-        isinstance(item_type_info, type) and
-        issubclass(item_type_info, enum.IntEnum)):
-        return ThriftEnumField(item_type_info)
-
-    if item_ttype in mapping:
-        return mapping[item_ttype]()
-    elif item_ttype == TType.STRUCT:
-        return create_serializer(item_type_info[0])
diff --git 
a/airavata-django-portal/django_airavata/apps/auth/tests/test_signals.py 
b/airavata-django-portal/django_airavata/apps/auth/tests/test_signals.py
index 7ba1111fe..91cc569c4 100644
--- a/airavata-django-portal/django_airavata/apps/auth/tests/test_signals.py
+++ b/airavata-django-portal/django_airavata/apps/auth/tests/test_signals.py
@@ -1,5 +1,9 @@
-from airavata.model.group.ttypes import GroupModel
-from airavata.model.user.ttypes import UserProfile
+from airavata_sdk.generated.org.apache.airavata.model.group.group_manager_pb2 
import (  # noqa: E501
+    GroupModel,
+)
+from airavata_sdk.generated.org.apache.airavata.model.user.user_profile_pb2 
import (
+    UserProfile,
+)
 from django.core import mail
 from django.shortcuts import reverse
 from django.test import RequestFactory, TestCase, override_settings
@@ -25,12 +29,12 @@ class EmailUserAddedToGroupSignalReceiverTests(TestCase):
         factory = RequestFactory()
         self.request = factory.get("/")
         self.user = UserProfile(
-            airavataInternalUserId=f"testuser@{GATEWAY_ID}",
-            userId="testuser",
-            gatewayId=GATEWAY_ID,
+            airavata_internal_user_id=f"testuser@{GATEWAY_ID}",
+            user_id="testuser",
+            gateway_id=GATEWAY_ID,
             emails=["[email protected]"],
-            firstName="Test",
-            lastName="User")
+            first_name="Test",
+            last_name="User")
 
     def test(self):
         group = GroupModel(id="abc123", name="Test Group")
@@ -68,7 +72,7 @@ class EmailUserAddedToGroupSignalReceiverTests(TestCase):
             msg.reply_to,
             [f"\"{PORTAL_ADMINS[0][0]}\" <{PORTAL_ADMINS[0][1]}>"])
         self.assertSequenceEqual(
-            msg.to, [f"\"{self.user.firstName} {self.user.lastName}\" "
+            msg.to, [f"\"{self.user.first_name} {self.user.last_name}\" "
                      f"<{self.user.emails[0]}>"])
         self.assertIn(
             self.request.build_absolute_uri(
@@ -78,4 +82,4 @@ class EmailUserAddedToGroupSignalReceiverTests(TestCase):
             self.request.build_absolute_uri(
                 reverse("django_airavata_workspace:experiments")),
             msg.body)
-        self.assertIn(self.user.userId, msg.body)
+        self.assertIn(self.user.user_id, msg.body)
diff --git 
a/airavata-django-portal/django_airavata/apps/auth/tests/test_views.py 
b/airavata-django-portal/django_airavata/apps/auth/tests/test_views.py
index 384dfcfde..2ad03cf0b 100644
--- a/airavata-django-portal/django_airavata/apps/auth/tests/test_views.py
+++ b/airavata-django-portal/django_airavata/apps/auth/tests/test_views.py
@@ -1,7 +1,9 @@
 from unittest.mock import patch
 from urllib.parse import urlencode
 
-from airavata.model.user.ttypes import UserProfile
+from airavata_sdk.generated.org.apache.airavata.model.user.user_profile_pb2 
import (
+    UserProfile,
+)
 from django.contrib import messages
 from django.contrib.auth.models import AnonymousUser
 from django.contrib.messages.middleware import MessageMiddleware
@@ -223,10 +225,10 @@ class VerifyEmailViewTestCase(TestCase):
         views_iam_admin_client.is_user_enabled.return_value = False
         views_iam_admin_client.enable_user.return_value = True
         user_profile = UserProfile(
-            airavataInternalUserId=f"testuser@{GATEWAY_ID}",
-            userId="testuser",
-            firstName="Test",
-            lastName="User1",
+            airavata_internal_user_id=f"testuser@{GATEWAY_ID}",
+            user_id="testuser",
+            first_name="Test",
+            last_name="User1",
             emails=["[email protected]"]
         )
         views_iam_admin_client.get_user.return_value = user_profile
@@ -270,10 +272,10 @@ class ResendEmailLinkTestCase(TestCase):
 
         views_iam_admin_client.is_user_exist.return_value = True
         user_profile = UserProfile(
-            airavataInternalUserId=f"testuser@{GATEWAY_ID}",
-            userId="testuser",
-            firstName="Test",
-            lastName="User1",
+            airavata_internal_user_id=f"testuser@{GATEWAY_ID}",
+            user_id="testuser",
+            first_name="Test",
+            last_name="User1",
             emails=["[email protected]"]
         )
         views_iam_admin_client.get_user.return_value = user_profile
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 e41ddd60e..8de476760 100644
--- a/airavata-django-portal/django_airavata/apps/auth/token_authentication.py
+++ b/airavata-django-portal/django_airavata/apps/auth/token_authentication.py
@@ -11,10 +11,11 @@ import logging
 import ssl
 
 import jwt
-from airavata.model.security.ttypes import AuthzToken
 from django.conf import settings
 from rest_framework import authentication, exceptions
 
+from .utils import AuthzToken
+
 logger = logging.getLogger(__name__)
 
 _jwks_client = None
diff --git a/airavata-django-portal/django_airavata/apps/auth/utils.py 
b/airavata-django-portal/django_airavata/apps/auth/utils.py
index cfa53c042..5e39acb1f 100644
--- a/airavata-django-portal/django_airavata/apps/auth/utils.py
+++ b/airavata-django-portal/django_airavata/apps/auth/utils.py
@@ -2,7 +2,6 @@
 
 import time
 
-from airavata.model.security.ttypes import AuthzToken
 from django.conf import settings
 from django.contrib.auth import authenticate
 from django.core.mail import EmailMessage
@@ -14,6 +13,19 @@ from requests_oauthlib import OAuth2Session
 from . import models
 
 
+class AuthzToken:
+    """Plain carrier for the Keycloak access token + gateway/user claims.
+
+    Replaces the legacy Thrift ``AuthzToken`` value type; keeps the same
+    ``accessToken`` / ``claimsMap`` attribute names so existing consumers (the
+    gRPC client factory, IAM admin REST helpers, ...) are unchanged.
+    """
+
+    def __init__(self, accessToken, claimsMap=None):
+        self.accessToken = accessToken
+        self.claimsMap = claimsMap or {}
+
+
 def get_authz_token(request, user=None, access_token=None):
     """Construct AuthzToken instance from session; refresh token if needed."""
     if access_token is not None:

Reply via email to