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..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 @@ -85,6 +85,19 @@ """ ) +lookupUserGids = gql( + """ + query users($filter: UserInput!) { + users(filter: $filter) { + username + uidnumber + gidNumber + secondaryGidNumbers + } + } + """ +) + class CustomContext(BaseContext): LOG = logging.getLogger(__name__) @@ -100,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}" @@ -269,7 +282,18 @@ def lookupUsersFromService(self, filter): except Exception as e: LOG.error("Exception looking up user from service") return [] - + + 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 None + u = users[0] + 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 0f5d5c4..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 @@ -280,6 +287,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(',') diff --git a/schema.py b/schema.py index cae3de8..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__) @@ -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 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]: