From 1977ce379498763f7e90655b5eeaac76710ec14a Mon Sep 17 00:00:00 2001 From: Amith Murthy Date: Mon, 8 Jun 2026 18:07:24 -0700 Subject: [PATCH 1/3] fetches all the gids a user belongs to via a resolver field on User model --- models.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/models.py b/models.py index 0f5d5c4..31db719 100644 --- a/models.py +++ b/models.py @@ -280,6 +280,29 @@ def eppnObjs(self, info) -> List[Eppn]: @strawberry.field def facilities(self, info) -> List[str]: return sorted(list(set([x["facility"] for x in info.context.db.collection("repos").find({"name": "default", "users": self.username}, {"_id": 0, "facility": 1})]))) + + @strawberry.field + def gids(self, info) -> List[int]: + """ A view of the gidNumbers this user belongs to, as nested in each Repo's posixgroup feature. """ + gids = set() + repo_filter = { + "$or": [ + {"users": self.username}, + {"leaders": self.username}, + {"principal": self.username}, + ] + } + for repo in info.context.db.collection("repos").find(repo_filter, {"_id": 0, "features.posixgroup.options": 1}): + for opt in repo.get("features", {}).get("posixgroup", {}).get("options", []): + try: + parsed = json.loads(opt) if isinstance(opt, str) else opt + gid = parsed.get("gidNumber") + if gid is not None: + gids.add(int(gid)) + except (ValueError, TypeError, AttributeError): + continue + return sorted(gids) + @strawberry.field def isAdmin(self, info) -> bool: admins = re.sub(r"\s", "", os.environ.get("ADMIN_USERNAMES",'')).split(',') From cfbe450f56ba9ddae9945daf22b643bd4667185f Mon Sep 17 00:00:00 2001 From: Amith Murthy Date: Wed, 10 Jun 2026 15:26:50 -0700 Subject: [PATCH 2/3] query to get gids from user-lookup service --- .gitignore | 2 ++ main.py | 26 +++++++++++++++++++++++++- schema.py | 4 ++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6589a48..a3b7112 100644 --- a/.gitignore +++ b/.gitignore @@ -154,3 +154,5 @@ ssl/ # vi .*.swp .DS_Store + +dev_middleware.py diff --git a/main.py b/main.py index e75f590..9c39754 100644 --- a/main.py +++ b/main.py @@ -85,6 +85,18 @@ """ ) +lookupUserGids = gql( + """ + query users($filter: UserInput!) { + users(filter: $filter) { + username + gidNumber + secondaryGidNumbers + } + } + """ +) + class CustomContext(BaseContext): LOG = logging.getLogger(__name__) @@ -269,7 +281,19 @@ def lookupUsersFromService(self, filter): except Exception as e: LOG.error("Exception looking up user from service") return [] - + + def lookupUserGidsByUsername(self, username: str) -> list[int]: + resp = self.userlookup.execute(lookupUserGids, variable_values={"filter": {"username": username}}) + users = resp.get("users") or [] + if not users: + return [] + u = users[0] + gids = set() + if u.get("gidNumber") is not None: + gids.add(int(u["gidNumber"])) + for g in u.get("secondaryGidNumbers") or []: + gids.add(int(g)) + return sorted(gids) class DB: LOG = logging.getLogger(__name__) diff --git a/schema.py b/schema.py index cae3de8..da9d203 100644 --- a/schema.py +++ b/schema.py @@ -122,6 +122,10 @@ def usersMatchingUserNames(self, info: Info, regexes: List[str]) -> Optional[Lis @strawberry.field( permission_classes=[ IsAuthenticated ] ) def usersLookupFromService(self, info: Info, filter: UserInput ) -> List[User]: return info.context.lookupUsersFromService( filter ) + + @strawberry.field( permission_classes=[ IsAuthenticated ] ) + def userGids(self, info: Info, username: str) -> List[int]: + return info.context.lookupUserGidsByUsername(username) @strawberry.field( permission_classes=[ IsAuthenticated ] ) def clusters(self, info: Info, filter: Optional[ClusterInput]={} ) -> List[Cluster]: From d40a3d51c4da1f035fa393fd89f1480b20c04e45 Mon Sep 17 00:00:00 2001 From: Amith Murthy Date: Fri, 12 Jun 2026 13:56:35 -0700 Subject: [PATCH 3/3] users can only pull their gid/uid info --- main.py | 20 ++++++++++---------- models.py | 7 +++++++ schema.py | 6 +++--- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/main.py b/main.py index 9c39754..c3e2f0b 100644 --- a/main.py +++ b/main.py @@ -25,7 +25,7 @@ from gql.transport.requests import RequestsHTTPTransport -from models import User, AccessGroup, Repo, Facility, Cluster, CoactRequest, CoactRequestStatus, AuditTrail, AuditTrailObjectType, CoactDatetime +from models import User, AccessGroup, Repo, Facility, Cluster, CoactRequest, CoactRequestStatus, AuditTrail, AuditTrailObjectType, CoactDatetime, UserGidsInfo from schema import Query, Mutation, Subscription, start_change_stream_queues import smtplib @@ -90,6 +90,7 @@ query users($filter: UserInput!) { users(filter: $filter) { username + uidnumber gidNumber secondaryGidNumbers } @@ -112,7 +113,7 @@ class CustomContext(BaseContext): def __init__(self, *args, **kwargs): self.db = DB(mongo,DB_NAME) self.email = Email(EMAIL_SERVER_HOST, EMAIL_SERVER_PORT) - self.userlookup = Client(transport=RequestsHTTPTransport(url=USER_LOOKUP_URL, verify=True, retries=3), fetch_schema_from_transport=True) + self.userlookup = Client(transport=RequestsHTTPTransport(url=USER_LOOKUP_URL, verify=True, retries=3), fetch_schema_from_transport=False) def __str__(self): return f"CustomContext User: {self.username} is_admin {self.is_admin}" @@ -282,18 +283,17 @@ def lookupUsersFromService(self, filter): LOG.error("Exception looking up user from service") return [] - def lookupUserGidsByUsername(self, username: str) -> list[int]: + def lookupUserGidsByUsername(self, username: str) -> Optional[UserGidsInfo]: resp = self.userlookup.execute(lookupUserGids, variable_values={"filter": {"username": username}}) users = resp.get("users") or [] if not users: - return [] + return None u = users[0] - gids = set() - if u.get("gidNumber") is not None: - gids.add(int(u["gidNumber"])) - for g in u.get("secondaryGidNumbers") or []: - gids.add(int(g)) - return sorted(gids) + return UserGidsInfo( + uidnumber=int(u["uidnumber"]), + primaryGid=int(u["gidNumber"]) if u.get("gidNumber") is not None else None, + secondaryGidNumbers=u.get("secondaryGidNumbers") or [] + ) class DB: LOG = logging.getLogger(__name__) diff --git a/models.py b/models.py index 31db719..3e9d639 100644 --- a/models.py +++ b/models.py @@ -262,6 +262,13 @@ class UserInput: publichtml: bool = False isbot: bool = False +@strawberry.type +class UserGidsInfo: + uidnumber: int + primaryGid: int + secondaryGidNumbers: Optional[List[int]] + + @strawberry.type class User(UserInput): # eppnObjs is most likely a call to some external service to get the details of an eppn diff --git a/schema.py b/schema.py index da9d203..6c6d904 100644 --- a/schema.py +++ b/schema.py @@ -41,7 +41,7 @@ AuditTrailObjectType, AuditTrail, AuditTrailInput, \ NotificationInput, Notification, ComputeRequirement, BulkOpsResult, StatusResult, \ CoactDatetime, NormalizedJob, FacillityPastXUsage, RepoPastXUsage, \ - NameDesc, RepoFeature, RepoFeatureInput + NameDesc, RepoFeature, RepoFeatureInput, UserGidsInfo import logging LOG = logging.getLogger(__name__) @@ -124,8 +124,8 @@ def usersLookupFromService(self, info: Info, filter: UserInput ) -> List[User]: return info.context.lookupUsersFromService( filter ) @strawberry.field( permission_classes=[ IsAuthenticated ] ) - def userGids(self, info: Info, username: str) -> List[int]: - return info.context.lookupUserGidsByUsername(username) + def myGids(self, info: Info) -> Optional[UserGidsInfo]: + return info.context.lookupUserGidsByUsername(info.context.username) @strawberry.field( permission_classes=[ IsAuthenticated ] ) def clusters(self, info: Info, filter: Optional[ClusterInput]={} ) -> List[Cluster]: