Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,5 @@ ssl/
# vi
.*.swp
.DS_Store

dev_middleware.py
30 changes: 27 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -85,6 +85,19 @@
"""
)

lookupUserGids = gql(
"""
query users($filter: UserInput!) {
users(filter: $filter) {
username
uidnumber
gidNumber
secondaryGidNumbers
}
}
"""
)

class CustomContext(BaseContext):

LOG = logging.getLogger(__name__)
Expand All @@ -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}"
Expand Down Expand Up @@ -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__)
Expand Down
30 changes: 30 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

biggest concern remains this field being 90% duplicate of accessGroupObjs, but not a blocker

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of data type (representation of what the data is), yes. The data source is different, we're going through user-lookup which interfaces both sdf ldap and slac it to provide this data. Agree with you that we will need to revisit this as part of the NIS migration.

""" 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(',')
Expand Down
6 changes: 5 additions & 1 deletion schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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]:
Expand Down