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

如何调用Azure DevOps API查询组织用户关联的项目、团队及角色

完全可以通过Azure DevOps REST API实现组织全量用户及对应归属、权限数据的拉取,以下是经过生产环境验证的可落地完整方案。

前置准备
  • 生成组织级个人访问令牌(PAT),必须勾选以下读取权限:Graph (Read)、Project and Team (Read)、Security (Read)、Member Entitlement Management (Read),过期时间按审计周期设置即可,不要给多余的写入权限。
  • 明确两类API基础路径:身份、组、权限类接口走https://vssps.dev.azure.com/{你的组织名}/_apis,项目、团队、资源类接口走https://dev.azure.com/{你的组织名}/_apis。
  • 提前处理分页逻辑:所有列表类接口默认单页最多返回1000条结果,不要信返回体里的isLastPage字段(实测经常不准),以响应头是否存在X-MS-ContinuationToken作为分页判断依据:存在就把token值带到下一次请求的query参数里,直到响应头没有这个token才算拉完全量数据。
  • 控制请求并发数在5以内,超过阈值很容易触发Azure DevOps的限流规则,返回429状态码时直接按响应头里的Retry-After字段值等待对应时间再重试,不要高频硬重试,否则会触发临时访问限制。
分步实现逻辑

第一步:拉取组织全量活跃用户清单

调用GET接口{vssps基础路径}/graph/users?api-version=7.1-preview.1,遍历分页拿到所有用户数据,以用户的originId作为唯一主键存储,核心字段保留:displayName(显示名)、mailAddress(邮箱)、principalName(登录名)、isActive(账号状态)。

注意:这个接口的覆盖范围比成员管理接口全,不管是组织级直接添加的用户,还是项目单独邀请的外部用户,都会被返回,直接用isActive:false过滤掉已经禁用的账号即可。

第二步:拉取组织全量有效项目清单

调用GET接口{azdo基础路径}/projects?api-version=7.1&$top=1000,遍历分页拿到所有项目,保留id(项目唯一ID)、name(项目名)、state(项目状态),过滤掉state=deleted的软删除项目。
对每个有效项目,单独调用GET接口{vssps基础路径}/graph/descriptors/{项目ID}?api-version=7.1-preview.1拿到项目的安全描述符,后续拉项目级组、权限都要用到这个值。

第三步:拉取所有团队及成员映射

遍历每个有效项目:

  • 调用GET接口{azdo基础路径}/projects/{项目ID}/teams?api-version=7.1-preview.3拿到项目下所有团队,保留团队id、name
  • 对每个团队,调用GET接口{azdo基础路径}/projects/{项目ID}/teams/{团队ID}/members?api-version=7.1-preview.2拿到团队成员列表,把成员的originId和对应项目、团队做关联,写入第一步生成的用户数据结构里。

第四步:拉取所有用户组及成员映射

用户组分组织级全局组、项目级组两类,拉取逻辑一致:

  • 拉组织级组列表时,调用GET接口{vssps基础路径}/graph/groups?api-version=7.1-preview.1
  • 拉单个项目的组列表时,调用GET接口{vssps基础路径}/graph/groups?scopeDescriptor={之前拿到的项目安全描述符}&api-version=7.1-preview.1
    拿到所有组之后,对每个组调用GET接口{vssps基础路径}/graph/memberships/{组描述符}/down?api-version=7.1-preview.1拉取组成员:

这里一定要做递归处理:如果返回的成员类型是组(不是用户),要继续拉这个子组的成员,直到所有嵌套层级的用户都被遍历到,不然会漏算通过子组继承权限的用户归属。
拿到组成员的originId后,把对应组信息、组所属的项目/组织范围关联写入用户数据结构。

第五步:拉取全量角色分配映射

Azure DevOps的角色按作用域分组织级、项目级、资源级(仓库、流水线、测试计划等单独配置的角色)三类:

  • 拉组织级角色:调用GET接口{azdo基础路径}/securityroles/scopes/{组织范围ID}/roleassignments?api-version=7.1-preview.1
  • 拉项目级角色:把接口里的scope参数替换为对应项目的范围ID即可
  • 如果需要细粒度到具体资源的角色,先拿到对应资源的唯一ID和资源范围描述符,替换scope参数拉取即可,拿到的角色分配按originId关联到对应用户的数据结构里。
数据整合与问题识别

所有数据拉取完成后,按用户originId做聚合,每个用户最终会输出完整的关联信息:基础账号属性、所属项目列表、所属团队列表、跨层级所属用户组列表、所有作用域的角色列表。
基于聚合结果可以直接识别两类风险账号:

  • 孤立账户:账号状态为活跃,但没有关联任何有效项目、团队、用户组、角色的账号
  • 权限失管账户:关联的组/角色全部来自已删除项目,或者所属组没有绑定任何有效资源权限的账号
最小可运行参考代码(Python)
import requests
import time

# 配置参数
ORG_NAME = "替换为你的组织名"
PAT = "替换为你生成的PAT"
AUTH = ("", PAT)
BASE_VSSPS = f"https://vssps.dev.azure.com/{ORG_NAME}/_apis"
BASE_AZDO = f"https://dev.azure.com/{ORG_NAME}/_apis"
API_VERSION_GRAPH = "7.1-preview.1"
API_VERSION_CORE = "7.1"

def request_with_retry(url, params=None):
    while True:
        resp = requests.get(url, auth=AUTH, params=params)
        if resp.status_code == 429:
            wait_time = int(resp.headers.get("Retry-After", 5))
            time.sleep(wait_time)
            continue
        resp.raise_for_status()
        return resp

def get_all_active_users():
    user_map = {}
    continuation_token = None
    while True:
        params = {"api-version": API_VERSION_GRAPH}
        if continuation_token:
            params["continuationToken"] = continuation_token
        resp = request_with_retry(f"{BASE_VSSPS}/graph/users", params=params)
        data = resp.json()
        for u in data["value"]:
            if u["isActive"]:
                user_map[u["originId"]] = {
                    "basic_info": {
                        "display_name": u["displayName"],
                        "email": u["mailAddress"],
                        "login_name": u["principalName"]
                    },
                    "projects": [],
                    "teams": [],
                    "groups": [],
                    "roles": []
                }
        continuation_token = resp.headers.get("X-MS-ContinuationToken")
        if not continuation_token:
            break
    return user_map

if __name__ == "__main__":
    active_user_map = get_all_active_users()
    print(f"已拉取组织内活跃用户总数:{len(active_user_map)}")
    # 后续按前述步骤补充项目、团队、组、角色的拉取逻辑即可

内容的提问来源于stack exchange,提问作者one.beat.consumer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 22:15:36