查询群组成员资格

本指南演示了如何传递群组成员资格和检索成员的成员图。

除了列出群组的直接成员之外,您还可以传递性地搜索直接成员资格和间接成员资格,以及查看特定成员的成员资格图。这些功能可满足以下使用场景:

  • 资源所有者可以通过了解哪些群组和成员受更改影响,从而做出更明智的资源 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

以下代码会返回成员直接或间接所属的所有群组(身份映射群组除外)

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": ""
      }
    }
  ]
}

相邻列表中的每项均代表一个群组及其直接成员(边缘),并且响应中还包含成员资格图中所有群组的详细信息。通过解析可生成替代表示法(例如点图),可用于直观呈现成员资格图。

此示例脚本可用于将响应转换为点图:

#
# 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)

以下是为示例响应生成的可视化层次结构:

从点图转换的成员资格图示例