查詢群組成員資格

本指南說明如何遞迴查詢群組成員資格,以及擷取成員的成員資格圖表。

除了列出群組的直接成員,您也可以一併搜尋直接與間接成員,以及查看特定成員的成員資格圖表。這些功能可解決下列用途:

  • 資源擁有者可以瞭解哪些群組和成員會受到變更影響,進而做出更明智的資源 ACL 變更決策。
  • 群組擁有者可以評估從 ACL 控制相關群組新增或移除群組的影響,並更輕鬆地解決成員資格問題。
  • 安全稽核人員可查看整個機構的擴大成員結構,因此能更有效地稽核存取權政策。
  • 安全稽核人員可以查看成員的所有直接和間接群組成員資格,或檢查成員是否屬於特定群組,評估成員的安全風險。

群組成員可以是個人、服務帳戶或其他群組。

提出查詢的使用者或服務帳戶必須有權查看查詢中所有群組的成員資格,否則要求會失敗。如果查詢傳回「PERMISSION_DENIED」錯誤,可能是因為您沒有其中一個巢狀群組的正確權限,尤其是當其中一個群組屬於其他機構時。

事前準備

Enable the Cloud Identity API.

Enable the API

搜尋群組中的所有成員

這段程式碼會傳回群組的所有成員。回應會列出每項成員資格的類型 (直接、間接或兩者皆是)。

REST

如要取得群組中所有成員的清單,請使用父項群組的 ID 呼叫 groups.memberships.searchTransitiveMemberships()

Python

如要向 Cloud Identity 進行驗證,請設定應用程式預設憑證。 詳情請參閱「為本機開發環境設定驗證」。

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 Identity 進行驗證,請設定應用程式預設憑證。 詳情請參閱「為本機開發環境設定驗證」。

這段程式碼會傳回成員所屬的所有群組 (身分對應群組除外),包括直接和間接群組。

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 Identity 進行驗證,請設定應用程式預設憑證。 詳情請參閱「為本機開發環境設定驗證」。

下列程式碼會判斷成員是否屬於特定群組:

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 Identity 進行驗證,請設定應用程式預設憑證。 詳情請參閱「為本機開發環境設定驗證」。

下列程式碼會傳回 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 轉換的成員資格圖表範例