그룹 멤버십 쿼리

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

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

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

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

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

그룹의 모든 멤버십 검색

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

REST

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

Python

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)

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

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

REST

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

Python

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

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)

# Get credentials and a handle to the service definition
# Return results with a page size of 50
search_transitive_groups(service, 'joe@example.com', 50)

그룹의 멤버십 확인

REST

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

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

Python

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)

# Get credentials and a handle to the service definition
check_transitive_membership(service, 'groups/{group_id}', 'joe@example.com')

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

REST

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

Python

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

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)

# Get credentials and a handle to the service definition
# 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}')

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

다음은 위의 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 전환의 샘플 멤버십 그래프