그룹 멤버십 쿼리

이 가이드에서는 그룹 멤버십을 전이적으로 쿼리하고 구성원의 멤버십 그래프를 검색하는 방법을 보여줍니다.

그룹의 직접 구성원을 나열하는 것 외에도 직접 및 간접 멤버십을 모두 검색하고 특정 구성원의 멤버십 그래프를 볼 수 있습니다. 이러한 기능은 다음의 사용 사례를 다룹니다.

  • 리소스 소유자는 변경사항의 영향을 받는 그룹과 구성원을 파악하여 리소스 ACL 변경에 대해 보다 정보에 입각한 결정을 내릴 수 있습니다.
  • 그룹 소유자는 ACL 제어와 관련된 그룹에서 그룹을 추가하거나 삭제할 경우의 영향을 평가하고 멤버십 문제를 더 쉽게 해결할 수 있습니다.
  • 보안 감사관은 전체 조직의 확장된 멤버십 구조가 표시되기 때문에 액세스 정책을 더 효과적으로 감사할 수 있습니다.
  • 보안 감사관은 직접 및 간접 그룹 멤버십을 모두 보거나 구성원이 특정 그룹에 속하는지 확인하여 구성원의 보안 위험을 평가할 수 있습니다.

그룹 멤버십은 개인, 서비스 계정 또는 다른 그룹에 속할 수 있습니다.

쿼리를 실행하는 사용자나 서비스 계정에는 쿼리에 포함된 모든 그룹의 멤버십을 볼 권한이 있어야 합니다. 그렇지 않으면 요청이 실패합니다. 쿼리가 'PERMISSION_DENIED' 오류를 반환하는 경우, 특히 중첩 그룹 중 하나가 다른 조직이 소유한 그룹인 경우 중첩 그룹 중 하나에 대해 올바른 권한이 없을 수 있습니다.

시작하기 전에

Cloud Identity API 사용 설정

API 사용 설정

그룹의 모든 멤버십 검색

이 코드는 그룹의 모든 멤버십을 반환합니다. 응답에는 각 멤버십의 멤버십 유형(직접, 간접 또는 둘 다)이 포함됩니다.

REST

그룹의 모든 멤버십 목록을 가져오려면 상위 그룹 ID를 사용하여 groups.memberships.searchTransitiveMemberships()를 호출합니다.

Python

Cloud ID에 인증하려면 애플리케이션 기본 사용자 인증 정보를 설정합니다. 자세한 내용은 로컬 개발 환경의 인증 설정을 참조하세요.

import googleapiclient.discovery
from urllib.parse import urlencode

def search_transitive_memberships(service, parent, page_size):
  try:
    memberships = []
    next_page_token = ''
    while True:
      query_params = urlencode(
        {
          "page_size": page_size,
          "page_token": next_page_token
        }
      )
      request = service.groups().memberships().searchTransitiveMemberships(parent=parent)
      request.uri += "&" + query_params
      response = request.execute()

      if 'memberships' in response:
        memberships += response['memberships']

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

      if len(next_page_token) == 0:
        break;

    print(memberships)
  except Exception as e:
    print(e)

def main():

  service = googleapiclient.discovery.build('cloudidentity', 'v1')

  # Return results with a page size of 50
  search_transitive_memberships(service, 'groups/GROUP_ID', 50)

if __name__ == '__main__':
    main()

구성원의 모든 그룹 멤버십 검색

REST

구성원이 속한 모든 그룹을 찾으려면 구성원 키(예: 구성원의 이메일 주소)로 groups.memberships.searchTransitiveGroups()를 호출합니다.

Python

Cloud ID에 인증하려면 애플리케이션 기본 사용자 인증 정보를 설정합니다. 자세한 내용은 로컬 개발 환경의 인증 설정을 참조하세요.

이 코드는 구성원이 직접 또는 간접적으로 속한 모든 그룹(ID 매핑 그룹 제외)을 반환합니다.

import googleapiclient.discovery
from urllib.parse import urlencode

def search_transitive_groups(service, member, page_size):
  try:
    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;

    print(groups)
  except Exception as e:
    print(e)

def main():

  service = googleapiclient.discovery.build('cloudidentity', 'v1')

  # Return results with a page size of 50
  search_transitive_groups(service, 'MEMBER_EMAIL_ADDRESS', 50)

if __name__ == '__main__':
    main()

그룹의 멤버십 확인

REST

구성원이 특정 그룹(직접 또는 간접)에 속하는지 확인하려면 상위 그룹의 ID와 구성원 키(예: 구성원의 이메일 주소)를 사용하여 checkTransitiveMembership()을 호출합니다.

Python

Cloud ID에 인증하려면 애플리케이션 기본 사용자 인증 정보를 설정합니다. 자세한 내용은 로컬 개발 환경의 인증 설정을 참조하세요.

다음 코드는 구성원이 특정 그룹에 속하는지 여부를 확인합니다.

import googleapiclient.discovery
from urllib.parse import urlencode

def check_transitive_membership(service, parent, member):
  try:
    query_params = urlencode(
      {
        "query": "member_key_id == '{}'".format(member)
      }
    )
    request = service.groups().memberships().checkTransitiveMembership(parent=parent)
    request.uri += "&" + query_params
    response = request.execute()
    print(response['hasMembership'])
  except Exception as e:
    print(e)

def main():

  service = googleapiclient.discovery.build('cloudidentity', 'v1')

  check_transitive_membership(service, 'groups/GROUP_ID', 'MEMBER_EMAIL_ADDRESS')

if __name__ == '__main__':
    main()

구성원의 멤버십 그래프 검색

REST

구성원(경로 정보와 함께 구성원에 속한 모든 그룹)의 멤버십 그래프를 가져오려면 상위 그룹의 ID와 구성원 키(예: 구성원의 이메일 주소)를 사용하여 groups.memberships.getMembershipGraph()를 호출합니다. 그래프가 인접 목록으로 반환됩니다.

Python

Cloud ID에 인증하려면 애플리케이션 기본 사용자 인증 정보를 설정합니다. 자세한 내용은 로컬 개발 환경의 인증 설정을 참조하세요.

다음 코드는 Google 그룹에 있는 지정된 구성원의 멤버십 그래프를 반환합니다(이 쿼리는 라벨을 사용하여 그룹 유형별로 필터링됨).

import googleapiclient.discovery
from urllib.parse import urlencode

def get_membership_graph(service, parent, member):
  try:
    query_params = urlencode(
      {
        "query": "member_key_id == '{}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels".format(member)
      }
    )
    request = service.groups().memberships().getMembershipGraph(parent=parent)
    request.uri += "&" + query_params
    response = request.execute()
    print(response['response'])
  except Exception as e:
    print(e)

def main()

  service = googleapiclient.discovery.build('cloudidentity', 'v1')

  # Specify parent group as 'groups/-' to get ALL the groups of a member
  # along with path information
  get_membership_graph(service, 'groups/GROUP_ID', 'MEMBER_KEY')

if __name__ == '__main__':
    main()

멤버십 그래프의 시각적 표현 만들기

다음은 위의 Python 코드의 샘플 응답입니다. 이 예시에서 000, 111, 222 그룹은 000 -> 111 -> 222와 같이 연결되어 있습니다(화살표가 상위에서 하위 항목으로 표시됨). 샘플 코드로 호출하여 그룹 222에 대한 전체 그래프를 검색합니다.

get_membership_graph(service, 'groups/-', 'group-2@example.com')

다음과 같은 응답 결과를 나타냅니다.

{
  "@type": "type.googleapis.com/google.apps.cloudidentity.groups.v1.GetMembershipGraphResponse",
  "adjacencyList": [
    {
      "edges": [
        {
          "name": "groups/000/memberships/111",
          "preferredMemberKey": {
            "id": "group-1@example.com"
          },
          "roles": [
            {
              "name": "MEMBER"
            }
          ]
        }
      ],
      "group": "groups/000"
    },
    {
      "edges": [
        {
          "name": "groups/111/memberships/222",
          "preferredMemberKey": {
            "id": "group-2@example.com"
          },
          "roles": [
            {
              "name": "MEMBER"
            }
          ]
        }
      ],
      "group": "groups/111"
    }
  ],
  "groups": [
    {
      "name": "groups/000",
      "groupKey": {
        "id": "group-0@example.com"
      },
      "displayName": "Group - 0",
      "description": "Group - 0",
      "labels": {
        "cloudidentity.googleapis.com/groups.discussion_forum": ""
      }
    },
    {
      "name": "groups/111",
      "groupKey": {
        "id": "group-1@example.com"
      },
      "displayName": "Group - 1",
      "description": "Group - 1",
      "labels": {
        "cloudidentity.googleapis.com/groups.discussion_forum": ""
      }
    },
    {
      "name": "groups/222",
      "groupKey": {
        "id": "group-2@example.com"
      },
      "displayName": "Group - 2",
      "description": "Group - 2",
      "labels": {
        "cloudidentity.googleapis.com/groups.discussion_forum": ""
      }
    }
  ]
}

인접 목록의 각 항목은 그룹과 직속 구성원(에지)을 나타내며, 응답은 멤버십 그래프에 모든 그룹의 세부정보도 포함합니다. 이는 멤버십 그래프를 시각화하는 데 사용할 수 있는 대체 표현(예: DOT 그래프)을 생성하기 위해 파싱할 수 있습니다.

이 샘플 스크립트를 사용하여 응답을 DOT 그래프로 변환할 수 있습니다.

#
# Generates output in a dot format. Invoke this method using
# response['response'] from get_membership_graph()
#
# Save the output to a .dot file (say graph.dot)
# Use the dot tool to generate a visualization of the graph
# Example:
# dot -Tpng -o graph.png graph.dot
#
# Generates output like below:
#
# digraph {
#   'group0' [label='groups/000 (GROUP 0)'];
#   'group1' [label='groups/111 (GROUP 1)'];
#   'group2' [label='groups/222 (GROUP 2)'];
#   'group3' [label='groups/333 (GROUP 3)'];
#   'group4' [label='groups/444 (GROUP 4)'];
#
#   'group0' -> 'group1' [label='group-1@example.com (MEMBER)'];
#   'group0' -> 'group2' [label='group-2@example.com (MEMBER)'];
#   'group1' -> 'group3' [label='group-3@example.com (MEMBER)'];
#   'group3' -> 'group4' [label='group-4@example.com (MEMBER)'];
#   'group2' -> 'group3' [label='group-3@example.com (MEMBER)'];
# }
#
def convert_to_dot_format(graph):
  output = "digraph {\n"
  try:
    # Generate labels for the group nodes
    for group in graph['groups']:
      if 'displayName' in group:
        label = '{} ({})'.format(group['name'], group['displayName'])
      else:
        label = group['name']
      output += '  "{}" [label="{}"];\n'.format(group['name'].split('/')[1], label)

    output += '\n'

    # Generate edges
    for item in graph['adjacencyList']:
      group_id = item['group'].split('/')[1]
      for edge in item['edges']:
        edge_to = edge['name'].split('/')[3]
        edge_key = edge['preferredMemberKey']['id']
        # Collect the roles
        roles = []
        for role in edge['roles']:
          roles.append(role['name'])
        output += '  "{}" -> "{}" [label="{} ({})"];\n'.format(group_id,
                                                               edge_to,
                                                               edge_key,
                                                               ','.join(roles))

    output += "}\n"
    print(output)
  except Exception as e:
    print(e)

다음은 샘플 응답의 결과로 이루어진 시각적 계층 구조입니다.

DOT 전환의 샘플 멤버십 그래프