查詢群組成員資格

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

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

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

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

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

事前準備

如果尚未啟用,請啟用 Cloud Identity API。

啟用 API 時所需的角色

如要啟用 API,您必須具備 serviceusage.services.enable 權限。如果您建立了專案,可能已透過「擁有者」角色 (roles/owner) 取得這項權限。否則,您可以透過「服務使用情形管理員」角色 (roles/serviceusage.serviceUsageAdmin) 取得這項權限。瞭解如何授予角色。

啟用 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 轉換的會員資格圖表範例