string Scopes = new System.IdentityModel.Tokens.JwtSecurityToken(Token).Payload["scp"];
There's a sample gist for that.
This came up as I was solving a bigger problem - what exactly is the security context that custom build/release tasks get? When you have a custom Powershell task (or a script in the built-in Powershell task), it gets a global variable called distributedTaskContext. I don't think it's documented anywhere, but you can find references to it in Microsoft's own examples.
Anyway, one can use that variable to call TFS back. You can instantiate .NET client objects and invoke all kinds of API - release, source control, you name it. But the security context is clearly not that of the current service account. Looking at the HTTP traffic, one can see that the HTTP requests back to TFS go with an Authorization: Bearer header with a token. The token, it turns out, can be easily retrieved from the distributedTaskContext:
$Token = $distributedTaskContext.GetEndpoint("SystemVssConnection").Authorization.Parameters.AccessToken
But what is the identity behind the token? Turns out, there's a REST endpoint for that, and invoking it with the token gives you the current user:
$wc = New-Object System.Net.WebClient
$wc.Headers["Authorization"] = "Bearer " + $Token
$wc.DownloadString("http://tfs.example.com:8080/tfs/_api/_common/GetUserProfile?__v=5")
Still, an OAuth client has two parts to its security context: the user, and the scope(s). For the scopes, there's no built-in endpoint that I could find, so I put together my own, see above.
For the record, the user behind the token is an artificial one. The identity record goes:
"IdentityType": "user",
"FriendlyDisplayName": "Project Collection Build Service (TEAM FOUNDATION)",
"DisplayName": "Project Collection Build Service (TEAM FOUNDATION)",
"SubHeader": "Build\\233e4ccc-d129-4ba4-9c5b-ea82c7ae1d15",
"TeamFoundationId": "7a3195ee-870e-4151-ba58-1e522732086c",
"EntityId": "vss.ds.v1.ims.user.7a3195ee870e4151ba581e522732086c",
"Errors": [],
"Warnings": [],
"Domain": "Build",
"AccountName": "233e4ccc-d129-4ba4-9c5b-ea82c7ae1d15",
"IsWindowsUser": false,
"MailAddress": ""
There's one user like this in every team collection. It belongs to a server-level group called "Security Service Group", and also to a collection level group with the same name. The security editing window of TFS, however, has no problem locating this user.
The scope on the token is "app_token". That is a valid scope, and it allows access to all endpoints and all methods in TFS.
Late edit: there is another technique for retrieving the identity behind the token.
This, in turn, came up as I was solving an even bigger problem - how does one make the behavior of a custom Powershell release task conditional upon the identity of the current agent pool (without relying on the pool name)? My answer was - custom capabilities. In order to retrieve the capabilities, I needed to know who should I grant the read access on the pool to, and whether the custom task is allowed to call the pool-related REST endpoints.
This, in turn, came up as I was solving an even bigger problem - how does one make the behavior of a custom Powershell release task conditional upon the identity of the current agent pool (without relying on the pool name)? My answer was - custom capabilities. In order to retrieve the capabilities, I needed to know who should I grant the read access on the pool to, and whether the custom task is allowed to call the pool-related REST endpoints.
This, in turn, came up as I was solving an even bigger problem - how can we set up a scripted deployment system where deploying to pre-prod environments is more or less unrestricted while deployment to production is subject to checks and controls? My answer was - dividing the agent queues into restricted high privilege ones and unrestricted low privilege ones. But then some homemade release tasks would need be aware of the nature of the target environment, and behave differently depending on which kind of queue were they executed within.
No comments:
Post a Comment