You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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()

代码说明

  1. 项目组获取:通过指定项目范围描述符,获取项目下所有安全组。
  2. 成员信息提取:遍历每个组获取成员,区分用户和嵌套组,并提取用户邮箱。
  3. 权限解析:调用安全客户端获取组的项目级权限,映射为可读的角色名称。
  4. 团队成员补充:单独提取项目下的团队信息(团队属于特殊类型的组),完善成员覆盖范围。

内容的提问来源于stack exchange,提问作者xxxx_XXXXXX_xxxxx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 12:25:01