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 e1bfa3e52 feat(portal): de-Thrift entity sharing to the gRPC sharing
facade (Track D, D5) (#184)
e1bfa3e52 is described below
commit e1bfa3e52ebfe9cbe7cf0071b9b40b0db7b6fb20
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Mon Jun 8 22:19:28 2026 -0400
feat(portal): de-Thrift entity sharing to the gRPC sharing facade (Track D,
D5) (#184)
`SharedEntityViewSet` and `SharedEntitySerializer.get_hasSharingPermission`
ran
on the Thrift sharing-registry / profile-service clients, so opening a
resource's
sharing dialog blocked on the dead Thrift server's read-timeout.
- `SharedEntityViewSet` accessible-users/groups loads ->
`request.airavata.sharing.get_all[_directly]_accessible_{users,groups}`;
share/revoke -> `share_resource_with_{users,groups}` /
`revoke_sharing_of_resource_from_{users,groups}`; `_load_user_profile` ->
`iam.get_user_profile_by_id` (+ new `grpc_adapters.user_profile`);
`_load_group`
-> `sharing.gm_get_group` (+ `grpc_adapters.group`).
- `get_hasSharingPermission` -> the existing gRPC `user_has_access` helper.
- The sharing facade takes the permission as the enum NAME (e.g. "READ");
the
serializer still renders the Thrift int via `permissionType`. So the
viewset
bridges with `ResourcePermissionType(value).name` at the facade boundary
and
keeps the int in the returned dict (frontend contract unchanged).
- New `grpc_adapters.user_profile` maps the protobuf `UserProfile` to the
Thrift
attribute names the serializer reads (incl. the
`State`/`orginationAffiliation`/
`labeledURI` quirks); nested `nsfDemographics`/`customDashboard` render
null.
Verified: `manage.py check` green; a real `UserProfile` renders cleanly
through
`UserProfileSerializer` via the adapter (all 26 fields, no missing-attr
errors);
the sharing facade calls accept the NAME permission and return live results.
---
.../django_airavata/apps/api/grpc_adapters.py | 39 +++++++++++++++++
.../django_airavata/apps/api/serializers.py | 5 +--
.../django_airavata/apps/api/views.py | 51 +++++++++++-----------
3 files changed, 66 insertions(+), 29 deletions(-)
diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
index 057c5a081..b2e24d634 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -38,6 +38,7 @@ from airavata.model.status.ttypes import (
TaskState as _ThriftTaskState,
)
from airavata.model.task.ttypes import TaskTypes as _ThriftTaskTypes
+from airavata.model.user.ttypes import Status as _ThriftStatus
from airavata.model.workspace.ttypes import (
NotificationPriority as _ThriftNotificationPriority,
)
@@ -844,6 +845,44 @@ def group(pb):
)
+def user_profile(pb):
+ """gRPC ``UserProfile`` -> ``UserProfileSerializer`` shape.
+
+ Note the Thrift attribute quirks the serializer expects: ``State``
(capital),
+ ``orginationAffiliation`` (Thrift's spelling), ``labeledURI``. The nested
+ ``nsfDemographics``/``customDashboard`` structs are not surfaced in the
+ sharing UI, so they render null.
+ """
+ return SimpleNamespace(
+ userModelVersion=pb.user_model_version or None,
+ airavataInternalUserId=pb.airavata_internal_user_id,
+ userId=pb.user_id,
+ gatewayId=pb.gateway_id,
+ emails=list(pb.emails),
+ firstName=pb.first_name or None,
+ lastName=pb.last_name or None,
+ middleName=pb.middle_name or None,
+ namePrefix=pb.name_prefix or None,
+ nameSuffix=pb.name_suffix or None,
+ orcidId=pb.orcid_id or None,
+ phones=list(pb.phones),
+ country=pb.country or None,
+ nationality=list(pb.nationality),
+ homeOrganization=pb.home_organization or None,
+ orginationAffiliation=pb.origination_affiliation or None,
+ creationTime=pb.creation_time or None,
+ lastAccessTime=pb.last_access_time or None,
+ validUntil=pb.valid_until or None,
+ State=_thrift_enum_prefixed(pb, 'state', _ThriftStatus, 'STATUS_'),
+ comments=pb.comments or None,
+ labeledURI=pb.labeled_uri or None,
+ gpgKey=pb.gpg_key or None,
+ timeZone=pb.time_zone or None,
+ nsfDemographics=None,
+ customDashboard=None,
+ )
+
+
def notification(pb):
"""gRPC ``Notification`` -> ``NotificationSerializer`` shape."""
return SimpleNamespace(
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 38e0630fb..c6a2e33c3 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -1794,9 +1794,8 @@ class SharedEntitySerializer(serializers.Serializer):
def get_hasSharingPermission(self, shared_entity):
request = self.context['request']
- return request.airavata_client.userHasAccess(
- request.authz_token, shared_entity['entityId'],
- ResourcePermissionType.MANAGE_SHARING)
+ return user_has_access(
+ request, shared_entity['entityId'], "MANAGE_SHARING")
class CredentialSummarySerializer(
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py
b/airavata-django-portal/django_airavata/apps/api/views.py
index ca4ddea64..e7edb4246 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -1196,35 +1196,34 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
'owner': self._load_user_profile(owner_id)}
def _load_accessible_users(self, entity_id, permission_type):
- users = self.request.airavata_client.getAllAccessibleUsers(
- self.authz_token, entity_id, permission_type)
+ users = self.request.airavata.sharing.get_all_accessible_users(
+ entity_id, ResourcePermissionType(permission_type).name)
return {user_id: permission_type for user_id in users}
def _load_directly_accessible_users(self, entity_id, permission_type):
- users = self.request.airavata_client.getAllDirectlyAccessibleUsers(
- self.authz_token, entity_id, permission_type)
+ users =
self.request.airavata.sharing.get_all_directly_accessible_users(
+ entity_id, ResourcePermissionType(permission_type).name)
return {user_id: permission_type for user_id in users}
def _load_user_profile(self, user_id):
- user_profile_client = self.request.profile_service['user_profile']
username = user_id[0:user_id.rindex('@')]
- return user_profile_client.getUserProfileById(self.authz_token,
- username,
- settings.GATEWAY_ID)
+ return grpc_adapters.user_profile(
+ self.request.airavata.iam.get_user_profile_by_id(
+ username, settings.GATEWAY_ID))
def _load_accessible_groups(self, entity_id, permission_type):
- groups = self.request.airavata_client.getAllAccessibleGroups(
- self.authz_token, entity_id, permission_type)
+ groups = self.request.airavata.sharing.get_all_accessible_groups(
+ entity_id, ResourcePermissionType(permission_type).name)
return {group_id: permission_type for group_id in groups}
def _load_directly_accessible_groups(self, entity_id, permission_type):
- groups = self.request.airavata_client.getAllDirectlyAccessibleGroups(
- self.authz_token, entity_id, permission_type)
+ groups =
self.request.airavata.sharing.get_all_directly_accessible_groups(
+ entity_id, ResourcePermissionType(permission_type).name)
return {group_id: permission_type for group_id in groups}
def _load_group(self, group_id):
- group_manager_client = self.request.profile_service['group_manager']
- return group_manager_client.getGroup(self.authz_token, group_id)
+ return grpc_adapters.group(
+ self.request.airavata.sharing.gm_get_group(group_id))
def perform_update(self, serializer):
shared_entity = serializer.save()
@@ -1279,24 +1278,24 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
shared_entity['_group_revoke_manage_sharing_permission'])
def _share_with_users(self, entity_id, permission_type, user_ids):
- self.request.airavata_client.shareResourceWithUsers(
- self.authz_token, entity_id,
- {user_id: permission_type for user_id in user_ids})
+ name = ResourcePermissionType(permission_type).name
+ self.request.airavata.sharing.share_resource_with_users(
+ entity_id, {user_id: name for user_id in user_ids})
def _revoke_from_users(self, entity_id, permission_type, user_ids):
- self.request.airavata_client.revokeSharingOfResourceFromUsers(
- self.authz_token, entity_id,
- {user_id: permission_type for user_id in user_ids})
+ name = ResourcePermissionType(permission_type).name
+ self.request.airavata.sharing.revoke_sharing_of_resource_from_users(
+ entity_id, {user_id: name for user_id in user_ids})
def _share_with_groups(self, entity_id, permission_type, group_ids):
- self.request.airavata_client.shareResourceWithGroups(
- self.authz_token, entity_id,
- {group_id: permission_type for group_id in group_ids})
+ name = ResourcePermissionType(permission_type).name
+ self.request.airavata.sharing.share_resource_with_groups(
+ entity_id, {group_id: name for group_id in group_ids})
def _revoke_from_groups(self, entity_id, permission_type, group_ids):
- self.request.airavata_client.revokeSharingOfResourceFromGroups(
- self.authz_token, entity_id,
- {group_id: permission_type for group_id in group_ids})
+ name = ResourcePermissionType(permission_type).name
+ self.request.airavata.sharing.revoke_sharing_of_resource_from_groups(
+ entity_id, {group_id: name for group_id in group_ids})
@action(methods=['put'], detail=True)
def merge(self, request, entity_id=None):