我正在开发一个 azure devops 扩展,它向 UI 添加了一个新的中心。扩展中的 JavaScript 代码将应用程序令牌(通过
SDK.getAppToken()
检索)和访问令牌(通过 SDK.getAccessToken()
检索)传递到后端。我正在尝试使用访问令牌通过 Memberships - List API 检索已登录用户的组成员身份。然而我收到了未经授权的 401。
我正在Postman中做这个实验。因此,首先,当扩展程序将请求发送到后端时,我使用 Chrome 开发人员工具检索应用程序令牌和访问令牌。然后我在 Postman 中使用访问令牌作为不记名令牌。
GET https://vssps.dev.azure.com/{organization}/_apis/graph/Memberships/{my subject descriptor}?api-version=6.0-preview.1
这将返回 401。
<div class="title">401 - Uh-oh, you do not have access.</div>
<div class="details">The request requires authentication.</div>
我知道访问令牌是正确的,因为我能够成功使用它来访问:
GET https://vssps.dev.azure.com/{organization}/_apis/connectionData
我知道主题描述符是正确的,因为我正在使用上面的 connectionData API 返回的值。
你知道我是否犯了一个明显的错误吗? 我的主要目标是能够检查使用我的扩展程序的用户是否属于特定组(也就是说,我不需要他们的成员资格列表,而只需要他们是否属于我关心的这个组)。我还尝试了 检查会员资格存在 API ,结果相同 401。
我终于想通了,留在这里以供将来其他人使用。
TL;DR: 我需要将 vso.graph 范围添加到我的扩展的清单的范围属性中。
会员 API 的 API 参考提到每次调用需要哪些范围。
Scopes
Name Description
vso.graph Grants the ability to read user, group, scope and group membership information
扩展所需的范围可以在其清单中定义,如此处所述。在我的清单中,我根本没有范围属性,因此它默认为 vso.extension.default 的范围,这不足以访问成员资格 API。所以我添加了以下几行:
"scopes": [
"vso.graph"
],
然后我重新发布了扩展。另一方面,安装扩展程序的组织方面,我必须接受扩展程序现在要求的附加权限。这不会自动发生。此处解释:更改已发布扩展的范围。
这获取组的主体名称
async function getGroupPrincipalName(groupApiURL, myBearerToken) {
let groupName = '';
await fetch(groupApiURL, {
method: 'GET',
headers: {
'Content-Type': 'text/plain',
'Authorization': myBearerToken
}
}).then(function (response) {
return response.arrayBuffer();
}).then(function (buffer) {
const decoder = new TextDecoder('iso-8859-1');
memberJsonInfo = decoder.decode(buffer);
groupName = JSON.parse(memberJsonInfo).principalName;
});
return groupName;
}
这将获取当前用户所属的所有集合 URI 组
async function getUserGroupMemberships() {
var jsonURLGroups = [];
var userDescriptor = 'aad.' + btoa(VSS.getWebContext().user.subjectId);
var myOrganization = VSS.getWebContext().collection.name;
var myAccessToken = await VSS.getAccessToken();
var myBearerToken = "Bearer " + myAccessToken.token;
var apiURL = 'https://vssps.dev.azure.com/' + myOrganization + '/_apis/Graph/Memberships/' + userDescriptor;
await fetch(apiURL, {
method: 'GET',
headers: {
'Content-Type': 'text/plain',
'Authorization': myBearerToken
}
}).then(function (response) {
return response.arrayBuffer();
}).then(function (buffer) {
const decoder = new TextDecoder('iso-8859-1');
jsonString = decoder.decode(buffer);
JSON.parse(jsonString).value.forEach(item => {
if (item._links && item._links.container && item._links.container.href) {
jsonURLGroups.push(item._links.container.href);
}
});
});
return jsonURLGroups;
}
此功能允许每次生成新的组条目时返回组,建议具有许多组的大型组织使用此实现,这样您就不需要等待所有组都被读取。
async function* getGroupNames(groupApiURLs, myBearerToken) {
for (const groupURL of groupApiURLs) {
const groupName = await getGroupPrincipalName(groupURL, myBearerToken);
yield groupName;
}
}
这个包装器封装了逻辑,以便您可以请求特定的组名称
async function isMemberOfGroup(groupNameToCheck) {
const jsonURLGroups = await getUserGroupMemberships();
const myAccessToken = await VSS.getAccessToken();
const myBearerToken = "Bearer " + myAccessToken.token;
const groupNamesIterator = getGroupNames(jsonURLGroups, myBearerToken);
for await (const groupName of groupNamesIterator) {
if (groupName.toLowerCase().includes(groupNameToCheck.toLowerCase())) {
return true;
}
}
return false;
}
最后,您可以调用这样的逻辑...请记住,这仅适用于 azdo 扩展的前面,并且需要范围“vso.graph”
await isMemberOfGroup('Project Collection Administrators')