如何通过Python获取Azure DevOps项目的成员及权限信息
Azure DevOps项目组、成员、权限提取问题及认证错误解决办法
一、解决list_groups()认证错误
- 修正组织URL拼写:代码中
organization_url里的organazation是拼写错误,改为organization,错误URL会导致请求地址无效,直接触发认证失败。 - 检查PAT权限:确保个人访问令牌(PAT)至少拥有以下权限:
- 身份与访问管理:读取
- Graph:读取
- 项目和团队:读取
生成PAT时需勾选对应权限范围,同时确认令牌未过期。
- 避免不必要的SSL禁用:公网托管的Azure DevOps实例无需禁用SSL验证,直接移除
no_ssl_verification()上下文;仅内部部署的自签证书环境保留该上下文,并确保其包裹所有请求逻辑。 - 使用兼容的客户端版本:不要强制指定v6_0版本,直接调用
connection.clients.get_graph_client()获取最新版Graph客户端,避免版本兼容性问题。
二、提取项目组、成员、邮箱及角色权限的修正代码
原代码仅获取单个团队的成员信息,无法覆盖所有组和权限需求。以下是完整实现:
import contextlib import requests import warnings from azure.devops.connection import Connection from msrest.authentication import BasicAuthentication # 仅自签证书环境使用,公网实例请删除此上下文 @contextlib.contextmanager def no_ssl_verification(): old_merge_environment_settings = requests.Session.merge_environment_settings opened_adapters = set() def merge_environment_settings(self, url, proxies, stream, verify, cert): opened_adapters.add(self.get_adapter(url)) settings = old_merge_environment_settings(self, url, proxies, stream, verify, cert) settings['verify'] = False return settings requests.Session.merge_environment_settings = merge_environment_settings try: with warnings.catch_warnings(): warnings.simplefilter('ignore', requests.packages.urllib3.exceptions.InsecureRequestWarning) yield finally: requests.Session.merge_environment_settings = old_merge_environment_settings for adapter in opened_adapters: try: adapter.close() except Exception: pass def get_project_id_from_name(connection, project_name): core_client = connection.clients.get_core_client() get_projects_response = core_client.get_projects() while get_projects_response is not None: for project in get_projects_response: if project.name.lower() == project_name.lower(): return project.id # 处理分页,避免遗漏项目 get_projects_response = core_client.get_projects(continuation_token=get_projects_response.continuation_token) return None def get_project_groups_and_members(connection, project_id): graph_client = connection.clients.get_graph_client() core_client = connection.clients.get_core_client() security_client = connection.clients.get_security_client() # 获取项目下所有安全组 project_groups = graph_client.list_groups(scope_descriptor=f"vssgp.U{project_id}") result = [] for group in project_groups: group_info = { "组名称": group.display_name, "组ID": group.descriptor, "成员": [] } # 获取组成员 members = graph_client.list_members(group.descriptor) for member in members: # 提取用户邮箱(组类型无邮箱) email = None if member.subject_kind == "user": user = graph_client.get_user(member.descriptor) email = user.mail_address group_info["成员"].append({ "成员名称": member.display_name, "成员ID": member.descriptor, "邮箱": email, "类型": "用户" if member.subject_kind == "user" else "组" }) # 获取组的项目级权限/角色 namespace_id = "2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87" # 项目级权限命名空间固定ID permissions = security_client.get_permissions(group.descriptor, namespace_id, project_id) group_info["角色/权限"] = [perm.display_name for perm in permissions.permissions] result.append(group_info) # 补充获取项目下所有团队及成员(团队属于特殊组) teams = core_client.get_teams(project_id) for team in teams: team_members = core_client.get_team_members_with_extended_properties(project_id, team.id) team_info = { "组名称": f"团队: {team.name}", "组ID": team.id, "成员": [], "角色/权限": ["团队成员"] } for member in team_members: team_info["成员"].append({ "成员名称": member.identity.display_name, "成员ID": member.identity.id, "邮箱": member.identity.mail_address if hasattr(member.identity, 'mail_address') else None, "类型": "用户" }) result.append(team_info) return result def main(): personal_access_token = "你的PAT令牌" organization_url = "https://azuredevops.**.*****.****/organization" # 修正拼写后的组织URL project_name = "你的项目名称" # 根据环境选择是否启用SSL禁用上下文 with no_ssl_verification(): credentials = BasicAuthentication('', personal_access_token) connection = Connection(base_url=organization_url, creds=credentials) project_id = get_project_id_from_name(connection, project_name) if not project_id: print(f"未找到项目: {project_name}") return project_data = get_project_groups_and_members(connection, project_id) # 打印结果,可按需导出为JSON/CSV for item in project_data: print(f"\n=== {item['组名称']} ===") print(f"组ID: {item['组ID']}") print("角色/权限:", ", ".join(item['角色/权限'])) print("成员列表:") for member in item['成员']: print(f"- {member['成员名称']} ({member['邮箱']}) [{member['类型']}]") if __name__ == "__main__": main()
代码说明
- 项目组获取:通过指定项目范围描述符,获取项目下所有安全组。
- 成员信息提取:遍历每个组获取成员,区分用户和嵌套组,并提取用户邮箱。
- 权限解析:调用安全客户端获取组的项目级权限,映射为可读的角色名称。
- 团队成员补充:单独提取项目下的团队信息(团队属于特殊类型的组),完善成员覆盖范围。
内容的提问来源于stack exchange,提问作者xxxx_XXXXXX_xxxxx
相关产品推荐
相关产品推荐

