Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always search all groups for user memberships #378

Merged
merged 1 commit into from
Nov 27, 2024
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
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
Loading