Skip to content

Commit

Permalink
Merge pull request #378 from WISVCH/fix/google-transative-groups
Browse files Browse the repository at this point in the history
Always search all groups for user memberships
  • Loading branch information
robertdijk authored Nov 27, 2024
2 parents cb446ac + 4f4523d commit 5a28e12
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 140 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ The `master` branch should be automatically deployed to the CH Kubernetes cluste
## Google Serivce Account
Dienst2 requires a Google Service account to access Group and Member data via the directory API. A Google Serivce Account can be created in the [Google Cloud Console](https://console.cloud.google.com/apis/credentials). The service account should be a "Domain-wide Delegation" account with the following scopes:

- https://www.googleapis.com/auth/admin.directory.group.readonly
- https://www.googleapis.com/auth/admin.directory.group.member.readonly
- https://www.googleapis.com/auth/cloud-identity.groups.readonly

The scopes can be defined in Google Admin Console -> Security -> API controls -> Domain-wide delegation.

Expand Down
68 changes: 50 additions & 18 deletions ldb/google.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from urllib.parse import urlencode

from google.oauth2 import service_account
from googleapiclient.discovery import build
from ldb.hardcoded_google_groups import get_indirect_groups

import environ

Expand All @@ -17,19 +18,55 @@ def get_google_service(scopes=[]):
delegated_credentials = credentials.with_subject(
env.str("GOOGLE_SERVICE_ACCOUNT_DELEGATED_USER")
)
return build("admin", "directory_v1", credentials=delegated_credentials)


def get_groups_by_user_key(userKey, domains=["ch.tudelft.nl"], indirect=False) -> list:
return build("cloudidentity", "v1", credentials=delegated_credentials)


# Source: https://cloud.google.com/identity/docs/how-to/
# query-memberships#searching_for_all_group_memberships_of_a_member
def search_transitive_groups(service, member, page_size):
groups = []
next_page_token = ""
while True:
query_params = urlencode(
{
"query": (
"member_key_id == '{}' &&"
" 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
.format(member)
),
"page_size": page_size,
"page_token": next_page_token,
}
)
request = (
service.groups().memberships().searchTransitiveGroups(parent="groups/-")
)
request.uri += "&" + query_params
response = request.execute()

if "memberships" in response:
groups += response["memberships"]

if "nextPageToken" in response:
next_page_token = response["nextPageToken"]
else:
next_page_token = ""

if len(next_page_token) == 0:
break

return groups


def get_groups_by_user_key(userKey) -> list:
"""
Returns all Google Groups that a member is a DIRECT member of
Returns all Google Groups that a member is a direct or indirectmember of
:param userKey: Email or immutable ID of the user if only those groups are
to be listed, the given user is a member of. If it's an ID, it should match
with the ID of the user object.
:param domains: Domains to search for groups. Ensure that these are set to
prevent group name attacks by using other domains.
:param indirect: Whether to include indirect groups
:return: List of group email addresses
Expand All @@ -41,21 +78,16 @@ def get_groups_by_user_key(userKey, domains=["ch.tudelft.nl"], indirect=False) -

service = get_google_service(
[
"https://www.googleapis.com/auth/admin.directory.group.readonly",
"https://www.googleapis.com/auth/admin.directory.group.member.readonly",
"https://www.googleapis.com/auth/cloud-identity.groups.readonly",
]
)

groups: list = []
for domain in domains:
data = service.groups().list(userKey=userKey, domain=domain).execute()
if "groups" in data:
for group in data["groups"]:
groups.append(group["email"])

if indirect:
indirect_groups = get_indirect_groups(groups)
groups.extend(indirect_groups)

transitive_groups = search_transitive_groups(service, userKey, 50)
for group in transitive_groups:
print("group:", group)
groups.append(group["groupKey"]["id"])

# Replace "@ch.tudelft.nl" from the group names
# 1. Replace "[email protected]" with ""
Expand Down
119 changes: 0 additions & 119 deletions ldb/hardcoded_google_groups.py

This file was deleted.

2 changes: 1 addition & 1 deletion ldb/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def google_groups(self, request, pk=None):
# Retrieve the groups from the Directory API
try:
google_groups = get_groups_by_user_key(
person.google_username + "@ch.tudelft.nl", indirect=True
person.google_username + "@ch.tudelft.nl"
)
except HttpError as e:
if e.resp.status == 404:
Expand Down

0 comments on commit 5a28e12

Please sign in to comment.