TFS 2018u1. I'm building an extension with custom context menu commands for release definitions. I'd like some of them to be conditionally invisible (upon the rights of the current user). Any way to hide them?
Deliberately not calling VSS.register()
doesn't help; the custom commands are still there, just do nothing.
It's not a security measure, it's a usability thing (the menu is getting crowded).
EDIT: in the Contribution data structure there's a property called constraints
. It's not documented, no idea where it comes from. Probably the manifest. The only mention of constraints I could find is in the TFX tool sources. Apparently, constraints
is a valid value in the manifest JSON (probably under the contribution object), and it's supposed to be an array. One assumes, one of ContributionConstraint
objects. The latter is kind of documented.
A constraint object has a name
property that, according to the docs, contains a reference to an IContributionFilter
class. I couldn't find any mentions of that class neither in docs nor in TypeScript sources. However, there's an interface Microsoft.VisualStudio.Services.ExtensionManagement.Sdk.Server.IContributionFilter
in assembly Microsoft.VisualStudio.Services.ExtensionManagement.Sdk.Server.dll
, and it has a Name
property. There are derived classes in bin\Plugins\Microsoft.VisualStudio.Services.ExtensionManagement.Sdk.Plugins.dll
:
Concentrating on the latter. The name is "Security". Looks like it supports the following properties:
If you specify a constraint in the manifest JSON under a contribution object, at the very least it propagates through TFS data structures and shows up under VSS.getContribution()
in the extension's script. Now, on to the details of the security check...
Contribution constraints are the answer. Specifically, the "Security" constraint. It performs a permission check against a securable object in TFS, and hides the command if the current user doesn't hold the desired permission.
In my case, I'd use a certain agent pool as a proxy for the "this user is an admin" condition. Internally, role assignments on pools and queues are treated like ACLs. The namespace GUID for pool actions is 101EAE8C-1709-47F9-B228-0E476C35B3BA ("DistributedTask"), the token format is "AgentPools/{PoolID}/". The access mask 27, which corresponds to Use+Administer Permissions+Manage+View
, is the one that corresponds to the pool administrator role.
The constraint is specified in the manifest, under the contribution object:
{
"contributions": [
{
"id": "mycommand",
"type": "ms.vss-web.action",
"constraints": [
{
"name": "Security",
"properties": {
"namespaceId":"101EAE8C-1709-47F9-B228-0E476C35B3BA",
"namespaceToken":"AgentPools/17/",
"permission": 27
}
}],
// More contribution stuff...
}],
// More extension stuff...
}
The downside of this approach is that I have to hard-code the ID of the pool in the extension. The extension became specific to our particular TFS instance. It works for me though, it's an inhouse extension, I'm not planning to publish or distribute it, and it already has a ton of dependencies on the specifics of our TFS setup.
Naturally, all of this is utterly undocumented and may break anytime. But then again, very little of TFS' API surface is documented.
There's also the restrictedTo
parameter in the manifest, which is a recent addition and is not mentioned in the main contribution manifest doc. That seems to be for limiting access to unauthorized users, somewhat different from my case.
EDIT: I wrote a blog post with some more info. In addition to Security, there are 5 more constraint classes.