From 3b8bc1c242919289d1351f91b46235f3d1109c63 Mon Sep 17 00:00:00 2001 From: Kenan Kigunda Date: Wed, 18 Jul 2018 17:29:13 -0700 Subject: [PATCH 1/8] Added sample for token administration --- ...crosoft.TeamServices.Samples.Client.csproj | 9 +- .../FromFutureWebApi/TokenAdminContracts.cs | 69 ++++++++ .../FromFutureWebApi/TokenAdminHttpClient.cs | 154 ++++++++++++++++ .../TokenAdmin/TokenAdminSample.cs | 166 ++++++++++++++++++ 4 files changed, 395 insertions(+), 3 deletions(-) create mode 100644 ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminContracts.cs create mode 100644 ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminHttpClient.cs create mode 100644 ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/Microsoft.TeamServices.Samples.Client.csproj b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/Microsoft.TeamServices.Samples.Client.csproj index b953f18d..c333f0ec 100644 --- a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/Microsoft.TeamServices.Samples.Client.csproj +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/Microsoft.TeamServices.Samples.Client.csproj @@ -154,13 +154,14 @@ + + + - - @@ -171,6 +172,9 @@ + + + @@ -190,7 +194,6 @@ - Designer diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminContracts.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminContracts.cs new file mode 100644 index 00000000..0f13afa0 --- /dev/null +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminContracts.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Microsoft.VisualStudio.Services.DelegatedAuthorization; + +namespace Microsoft.VisualStudio.Services.TokenAdmin.Client +{ + public static class TokenAdminResourceIds + { + public const string AreaName = "TokenAdmin"; + public const string AreaId = "af68438b-ed04-4407-9eb6-f1dbae3f922e"; + + public const string PersonalAccessTokensResource = "PersonalAccessTokens"; + public static readonly Guid PersonalAccessTokensLocationId = new Guid("{af68438b-ed04-4407-9eb6-f1dbae3f922e}"); + + public const string RevocationsResource = "Revocations"; + public static readonly Guid RevocationsLocationId = new Guid("{a9c08b2c-5466-4e22-8626-1ff304ffdf0f}"); + + public const string RevocationRulesResource = "RevocationRules"; + public static readonly Guid RevocationRulesLocationId = new Guid("{ee4afb16-e7ab-4ed8-9d4b-4ef3e78f97e4}"); + } + + /// + /// A paginatated list of session tokens. + /// Session tokens correspond to OAuth credentials such as personal access tokens (PATs) + /// and other OAuth authorizations. + /// + [DataContract] + public class TokenAdminPagedSessionTokens + { + /// + /// The list of all session tokens in the current page. + /// + [DataMember(Name = "Value")] + public IEnumerable SessionTokens { get; set; } + + /// + /// The continuation token that can be used to retrieve the next page of session tokens, + /// or null if there is no next page. + /// + [DataMember] + public Guid? ContinuationToken { get; set; } + } + + /// + /// A rule which is applied to disable any incoming delegated authorization + /// which matches the given properties. + /// + public class TokenAdminRevocationRule + { + /// + /// The OAuth scope for which matching authorizations should be rejected. + /// For a list of all OAuth scopes supported by VSTS, see: + /// https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/oauth?view=vsts#scopes + /// + public string Scope { get; set; } + } + + /// + /// A request to revoke a particular delegated authorization. + /// + public class TokenAdminRevocation + { + /// + /// The authorization ID of the OAuth authorization to revoke. + /// + public Guid AuthorizationId { get; set; } + } +} diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminHttpClient.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminHttpClient.cs new file mode 100644 index 00000000..4ab8f3cb --- /dev/null +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminHttpClient.cs @@ -0,0 +1,154 @@ +/* + * --------------------------------------------------------- + * Copyright(C) Microsoft Corporation. All rights reserved. + * --------------------------------------------------------- + * + * --------------------------------------------------------- + * Generated file, DO NOT EDIT + * --------------------------------------------------------- + * + * See following wiki page for instructions on how to regenerate: + * https://vsowiki.com/index.php?title=Rest_Client_Generation + * + * Configuration file: + * vssf\client\webapi\httpclients\clientgeneratorconfigs\tokenadmin.genclient.json + */ + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Services.Common; +using Microsoft.VisualStudio.Services.WebApi; + +namespace Microsoft.VisualStudio.Services.TokenAdmin.Client +{ + [ResourceArea(TokenAdminResourceIds.AreaId)] + public class TokenAdminHttpClient : VssHttpClientBase + { + public TokenAdminHttpClient(Uri baseUrl, VssCredentials credentials) + : base(baseUrl, credentials) + { + } + + public TokenAdminHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings) + : base(baseUrl, credentials, settings) + { + } + + public TokenAdminHttpClient(Uri baseUrl, VssCredentials credentials, params DelegatingHandler[] handlers) + : base(baseUrl, credentials, handlers) + { + } + + public TokenAdminHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings, params DelegatingHandler[] handlers) + : base(baseUrl, credentials, settings, handlers) + { + } + + public TokenAdminHttpClient(Uri baseUrl, HttpMessageHandler pipeline, bool disposeHandler) + : base(baseUrl, pipeline, disposeHandler) + { + } + + /// + /// [Preview API] Lists of all the session token details of the personal access tokens (PATs) for a particular user. + /// + /// The descriptor of the target user. + /// The maximum number of results to return on each page. + /// An opaque data blob that allows the next page of data to resume immediately after where the previous page ended. The only reliable way to know if there is more data left is the presence of a continuation token. + /// + /// The cancellation token to cancel operation. + public Task ListPersonalAccessTokensAsync( + SubjectDescriptor subjectDescriptor, + int? pageSize = null, + string continuationToken = null, + object userState = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + HttpMethod httpMethod = new HttpMethod("GET"); + Guid locationId = new Guid("af68438b-ed04-4407-9eb6-f1dbae3f922e"); + object routeValues = new { subjectDescriptor = subjectDescriptor }; + + List> queryParams = new List>(); + if (pageSize != null) + { + queryParams.Add("pageSize", pageSize.Value.ToString(CultureInfo.InvariantCulture)); + } + if (!string.IsNullOrEmpty(continuationToken)) + { + queryParams.Add("continuationToken", continuationToken); + } + + return SendAsync( + httpMethod, + locationId, + routeValues: routeValues, + version: new ApiResourceVersion("5.0-preview.1"), + queryParameters: queryParams, + userState: userState, + cancellationToken: cancellationToken); + } + + /// + /// [Preview API] Creates a revocation rule to prevent the further usage of any OAuth authorizations that were created before the current point in time and which match the conditions in the rule. + /// + /// The revocation rule to create. The rule must specify a scope, after which preexisting OAuth authorizations that match that scope will be rejected. For a list of all OAuth scopes supported by VSTS, see: https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/oauth?view=vsts#scopes + /// + /// The cancellation token to cancel operation. + public async Task CreateRevocationRuleAsync( + TokenAdminRevocationRule revocationRule, + object userState = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + HttpMethod httpMethod = new HttpMethod("POST"); + Guid locationId = new Guid("ee4afb16-e7ab-4ed8-9d4b-4ef3e78f97e4"); + HttpContent content = new ObjectContent(revocationRule, new VssJsonMediaTypeFormatter(true)); + + using (HttpResponseMessage response = await SendAsync( + httpMethod, + locationId, + version: new ApiResourceVersion("5.0-preview.1"), + userState: userState, + cancellationToken: cancellationToken, + content: content).ConfigureAwait(false)) + { + return; + } + } + + /// + /// [Preview API] Revokes the listed OAuth authorizations. + /// + /// The list of objects containing the authorization IDs of the OAuth authorizations, such as session tokens retrieved by listed a users PATs, that should be revoked. + /// + /// The cancellation token to cancel operation. + public async Task RevokeAuthorizationsAsync( + IEnumerable revocations, + object userState = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + HttpMethod httpMethod = new HttpMethod("POST"); + Guid locationId = new Guid("a9c08b2c-5466-4e22-8626-1ff304ffdf0f"); + HttpContent content = new ObjectContent>(revocations, new VssJsonMediaTypeFormatter(true)); + + using (HttpResponseMessage response = await SendAsync( + httpMethod, + locationId, + version: new ApiResourceVersion("5.0-preview.1"), + userState: userState, + cancellationToken: cancellationToken, + content: content).ConfigureAwait(false)) + { + return; + } + } + } +} diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs new file mode 100644 index 00000000..34b4a917 --- /dev/null +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.Services.Graph.Client; +using Microsoft.VisualStudio.Services.TokenAdmin.Client; +using Microsoft.VisualStudio.Services.WebApi; + +namespace Microsoft.TeamServices.Samples.Client.TokenAdmin +{ + /// + /// This sample shows how you can use the VSTS REST APIs to find and revoke + /// personal access tokens (PATs) for users in your organization. + /// It also shows how to create revocation rules that prevent access through other OAuth credentials, + /// such as self-describing session tokens. + /// The sample is written using our C# client libraries, + /// but is commented with the HTTP calls that you can make to perform these same operations directly over the wire. + /// + /// + /// - NOTE ON REQUIRED PERMISSIONS: To be able to call of all the endpoints in this sample, + /// you will need to be an administrator of the target organization. + /// Non-administrators will receive an authorization error. + /// - NOTE ON CONNECTION DETAILS: For this sample, we will be using the connection from our current client context, + /// which includes our base URL and our authentication credentials through the parameters passed in through the runner. + /// If you are calling these endpoints directly over the wire, + /// your base URL will be => https://{yourOrganization}.vssps.visualstudio.com + /// and the recommended approach for authentication is to acquire an ADAL access token and pass it in the header => Authorization: Bearer {yourADALAccessToken} + /// For further guidance on how to acquire an ADAL access token, see: + /// https://github.com/Microsoft/vsts-auth-samples/tree/master/ManagedClientConsoleAppSample + /// For further guidance on other authentication options, see: + /// https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/authentication-guidance?view=vsts + /// + [ClientSample(TokenAdminResourceIds.AreaName)] + public class TokenAdminSample : ClientSample + { + // The areas of the VSTS REST API / HTTP clients that we will be using for these administration tasks are: + // - the Graph endpoints, corresponding to the path pattern: /_apis/graph/* + // - the TokenAdmin endpoints, corresponding to the path pattern: /_apis/tokenAdmin/* + // The C# client for this area may not be available until the next release of our client libraries, + // but the REST endpoints themselves should be available at the time of publishing. + + [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource)] + public List GetPersonalAccessTokenDetailsForUsersInYourOrganization() + { + List authorizationIds = new List(); + + var graphHttpClient = Context.Connection.GetClient(); + var tokenAdminHttpClient = Context.Connection.GetClient(); + + // 1. RETRIEVING USERS + + // First, we will use the Graph endpoints to find all of the users in the organization. + // This is a paginated API, so you may need to keep track of the continuation token + // and send multiple requests depending on the number of users in your organization. + + string userContinuationToken = null; + + do + { + // Make a call to retrieve the next page of users: + // HTTP: GET /_apis/graph/users[?continuationToken={userContinuationToken}] + // => 200 OK { + // "count":n, + // "value":[{ + // "descriptor":"{user.Descriptor}", + // … + // },…] + var pageOfUsers = graphHttpClient.ListUsersAsync(continuationToken: userContinuationToken).SyncResult(); + + // The continuation token, if any, is returned in the X-MS-ContinuationToken response header: + userContinuationToken = pageOfUsers.ContinuationToken?.SingleOrDefault(); + + // 2. RETRIEVING AUTHORIZATION IDS FOR A USER'S PERSONAL ACCESS TOKENS + + // For each user, you can retrieve the authorization IDs corresponding to their personal access tokens as shown below. + // This is a paginated API, so you may need to keep track of the continuation token + // and send multiple requests depending on the number of PATs associated with the user. + // The page size is client-configurable up to a server-side limit. + // If you pass too high a value, you will receive an error. + var patPageSize = 20; + + foreach (var user in pageOfUsers.GraphUsers) + { + Guid? patContinuationToken = null; + + do + { + // Make a call to retrieve the next page of PATs: + // HTTP: GET /_apis/tokenAdmin/personalAccessTokens/{user.Descriptor}[&continuationToken={patContinuationToken}] + // => 200 OK { + // "count":n, + // "value":[{ + // "authorizationId":"{pat.AuthorizationId}", + // … + // },…], + // "continuationToken":"{patContinuationToken}" + // } + var pageOfPats = tokenAdminHttpClient.ListPersonalAccessTokensAsync( + user.Descriptor, pageSize: patPageSize, continuationToken: patContinuationToken?.ToString()).SyncResult(); + + // In this case, the continuation token is returned in the response body as shown above. + patContinuationToken = pageOfPats.ContinuationToken; + + // Track the authorization IDs from the objects returned in the response: + authorizationIds.AddRange(pageOfPats.SessionTokens.Select(pat => pat.AuthorizationId)); + } + // Repeat the above while we still have more pages of PATs: + while (patContinuationToken != null && patContinuationToken != Guid.Empty); + } + } + // Repeat the above while we still have more pages of users: + while (!string.IsNullOrEmpty(userContinuationToken)); + + return authorizationIds; + } + + [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] + public void RevokePersonalAccessTokensForUsersInYourOrganization() + { + var tokenAdminHttpClient = Context.Connection.GetClient(); + + var authorizationIds = GetPersonalAccessTokenDetailsForUsersInYourOrganization(); + + // 3. REVOKING THE AUTHORIZATIONS + + // Once you have the list of authorization IDs you want to revoke, + // you can use the following token administration endpoints to revoke them in batch, up to a server-side limit. + // Just as we've structure it here, you can revoke a list of authorizations that correspond to multiple users in one call, + // but keep in mind that you will receive an error if you pass too many revocations at once. + + var revocations = authorizationIds.Select(id => new TokenAdminRevocation { AuthorizationId = id }); + + // HTTP: POST /_apis/tokenAdmin/revocations + // [{ + // "authorizationId":"{authorizationId}" + // },…] + // => 204 No Content + tokenAdminHttpClient.RevokeAuthorizationsAsync(revocations).SyncResult(); + } + + [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationRulesResource)] + public void RevokeSelfDescribingSessionTokensForUsersInYourOrganization() + { + var tokenAdminHttpClient = Context.Connection.GetClient(); + + ////// CREATING OAUTH REVOCATION RULES + + // Not all kinds of OAuth authorizations can be revoked directly. + // Some, such as self-describing session tokens, must instead by revoked by creating a rule + // which will be evaluated and used to reject matching OAuth credentials at authentication time. + // Revocation rules as shown here will apply to all credentials that were issued before the datetime + // at which the rule was created, and which match a particular OAuth scope. + // For a list of all OAuth scopes supported by VSTS, see: + // https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/oauth?view=vsts#scopes + + var scopeToRevoke = "vso.code"; + var revocationRule = new TokenAdminRevocationRule { Scope = scopeToRevoke }; + + // HTTP: POST /_apis/tokenAdmin/revocationRules + // { + // "scope":"{scopeToRevoke}" + // } + // => 204 No Content + tokenAdminHttpClient.CreateRevocationRuleAsync(revocationRule).SyncResult(); + } + } +} \ No newline at end of file From b330c5c89f6103a275a00b0e9a3ae0bc68b4c04b Mon Sep 17 00:00:00 2001 From: Kenan Kigunda Date: Wed, 18 Jul 2018 22:11:23 -0700 Subject: [PATCH 2/8] Updated with changes to TokenAdminRevocationRule (renaming of "Scope" to "Scopes", inclusion of "CreatedBefore") --- .../FromFutureWebApi/TokenAdminContracts.cs | 16 ++++++++++++---- .../TokenAdmin/TokenAdminSample.cs | 16 ++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminContracts.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminContracts.cs index 0f13afa0..4f1836eb 100644 --- a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminContracts.cs +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/FromFutureWebApi/TokenAdminContracts.cs @@ -41,7 +41,7 @@ public class TokenAdminPagedSessionTokens [DataMember] public Guid? ContinuationToken { get; set; } } - + /// /// A rule which is applied to disable any incoming delegated authorization /// which matches the given properties. @@ -49,11 +49,19 @@ public class TokenAdminPagedSessionTokens public class TokenAdminRevocationRule { /// - /// The OAuth scope for which matching authorizations should be rejected. + /// A string containing a space-delimited list of OAuth scopes. + /// A token matching any one of the scopes will be rejected. /// For a list of all OAuth scopes supported by VSTS, see: - /// https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/oauth?view=vsts#scopes + /// https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/oauth?view=vsts#scopes. + /// This is a mandatory parameter. + /// + public string Scopes { get; set; } + + /// + /// A datetime cutoff. Tokens created before this time will be rejected. + /// This is an optional paramter. If omitted, defaults to the time at which the rule was created. /// - public string Scope { get; set; } + public DateTime? CreatedBefore { get; set; } } /// diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs index 34b4a917..808e650d 100644 --- a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs @@ -42,7 +42,7 @@ public class TokenAdminSample : ClientSample public List GetPersonalAccessTokenDetailsForUsersInYourOrganization() { List authorizationIds = new List(); - + var graphHttpClient = Context.Connection.GetClient(); var tokenAdminHttpClient = Context.Connection.GetClient(); @@ -147,17 +147,21 @@ public void RevokeSelfDescribingSessionTokensForUsersInYourOrganization() // Not all kinds of OAuth authorizations can be revoked directly. // Some, such as self-describing session tokens, must instead by revoked by creating a rule // which will be evaluated and used to reject matching OAuth credentials at authentication time. - // Revocation rules as shown here will apply to all credentials that were issued before the datetime - // at which the rule was created, and which match a particular OAuth scope. + // Revocation rules as shown here will apply to all credentials that were issued before the supplied datetime + // (if omitted, at the time at which the rule is created), and which match a particular set of OAuth scopes (must be provided). // For a list of all OAuth scopes supported by VSTS, see: // https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/oauth?view=vsts#scopes - var scopeToRevoke = "vso.code"; - var revocationRule = new TokenAdminRevocationRule { Scope = scopeToRevoke }; + var revocationRule = new TokenAdminRevocationRule + { + Scopes = "vso.code vso.packaging", // reject any token presented which matches EITHER the Code OR the Packaging scope + CreatedBefore = DateTime.Now - TimeSpan.FromDays(1) // reject any token that was created more than a day ago + }; // HTTP: POST /_apis/tokenAdmin/revocationRules // { - // "scope":"{scopeToRevoke}" + // "scopes":"{scopeToRevoke}", + // "createdBefore":"{dateTimeToStopRevokingAt}" // } // => 204 No Content tokenAdminHttpClient.CreateRevocationRuleAsync(revocationRule).SyncResult(); From c1dab555a3092566100eb84b0db86254d47414ae Mon Sep 17 00:00:00 2001 From: Kenan Kigunda Date: Wed, 18 Jul 2018 23:53:26 -0700 Subject: [PATCH 3/8] Updated samples to show proper usage of (1) VSID conversion (2) nested pagination + improved crossreferencing of shared code snippets --- .../TokenAdmin/TokenAdminSample.cs | 200 ++++++++++++------ 1 file changed, 139 insertions(+), 61 deletions(-) diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs index 808e650d..8947bfc1 100644 --- a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.Graph.Client; using Microsoft.VisualStudio.Services.TokenAdmin.Client; using Microsoft.VisualStudio.Services.WebApi; @@ -16,10 +17,13 @@ namespace Microsoft.TeamServices.Samples.Client.TokenAdmin /// but is commented with the HTTP calls that you can make to perform these same operations directly over the wire. /// /// - /// - NOTE ON REQUIRED PERMISSIONS: To be able to call of all the endpoints in this sample, + ///

+ /// NOTE ON REQUIRED PERMISSIONS: To be able to call of all the endpoints in this sample, /// you will need to be an administrator of the target organization. /// Non-administrators will receive an authorization error. - /// - NOTE ON CONNECTION DETAILS: For this sample, we will be using the connection from our current client context, + ///

+ ///

+ /// NOTE ON CONNECTION DETAILS: For this sample, we will be using the connection from our current client context, /// which includes our base URL and our authentication credentials through the parameters passed in through the runner. /// If you are calling these endpoints directly over the wire, /// your base URL will be => https://{yourOrganization}.vssps.visualstudio.com @@ -28,6 +32,7 @@ namespace Microsoft.TeamServices.Samples.Client.TokenAdmin /// https://github.com/Microsoft/vsts-auth-samples/tree/master/ManagedClientConsoleAppSample /// For further guidance on other authentication options, see: /// https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/authentication-guidance?view=vsts + ///

///
[ClientSample(TokenAdminResourceIds.AreaName)] public class TokenAdminSample : ClientSample @@ -35,77 +40,85 @@ public class TokenAdminSample : ClientSample // The areas of the VSTS REST API / HTTP clients that we will be using for these administration tasks are: // - the Graph endpoints, corresponding to the path pattern: /_apis/graph/* // - the TokenAdmin endpoints, corresponding to the path pattern: /_apis/tokenAdmin/* - // The C# client for this area may not be available until the next release of our client libraries, + // The C# client for this latter area may not be available until the next release of our client libraries, // but the REST endpoints themselves should be available at the time of publishing. [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource)] - public List GetPersonalAccessTokenDetailsForUsersInYourOrganization() + public List GetPersonalAccessTokenAuthorizationIdsForSpecificUsersInYourOrganization() { - List authorizationIds = new List(); + var graphHttpClient = Context.Connection.GetClient(); + + // In this example, we assume that you already have a set of VSIDs or subject descriptors + // for the users whose PAT tokens you want to retrieve. If this is not the case, see the sample for all users further below. + // If you have VSIDs, you will need to first convert them to subject descriptors as shown below. + // If you have subject descriptors, you can go directly to the code in GetPersonalAccessTokenDetailsForASetOfUsers. + + // (1) CONVERTING VSIDS to SUBJECT DESCRIPTORS + + var vsids = new[] + { + new Guid("e3b2f97f-8fd8-4086-82b4-e7d878e89c37"), + new Guid("1b90428b-d3f0-4703-812b-e497f08f183a"), + }; + + // For each VSID, we ask Graph for the corresponding subject descriptor: + // HTTP: GET /_apis/graph/descriptors/{vsid} + // => 200 OK { + // "value":"{subjectDescriptor}", + // … + // } + var subjectDescriptors = vsids.Select(vsid => graphHttpClient.GetDescriptorAsync(vsid).SyncResult()).Select(result => result.Value); + + // (2) RETRIEVING PERSONAL ACCESS TOKEN AUTHORIZATION IDS FOR A SET OF SUBJECT DESCRIPTORS => see (4) below. + return GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(subjectDescriptors); + } + + [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource)] + public List GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization() + => GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization(thenForEachPage: null); + private List GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization(Action> thenForEachPage) + { var graphHttpClient = Context.Connection.GetClient(); - var tokenAdminHttpClient = Context.Connection.GetClient(); - // 1. RETRIEVING USERS + // (3) RETRIEVING SUBJECT DESCRIPTORS FOR ALL USERS - // First, we will use the Graph endpoints to find all of the users in the organization. + // First, we will use the Graph endpoints to find the subject descriptors of all users in the organization. // This is a paginated API, so you may need to keep track of the continuation token // and send multiple requests depending on the number of users in your organization. - string userContinuationToken = null; + var authorizationIds = new List(); + string userContinuationToken = null; do { // Make a call to retrieve the next page of users: // HTTP: GET /_apis/graph/users[?continuationToken={userContinuationToken}] - // => 200 OK { - // "count":n, - // "value":[{ - // "descriptor":"{user.Descriptor}", - // … - // },…] + // => 200 OK + // X-MS-ContinuationToken: {userContinuationToken} + // { + // "count":n, + // "value":[{ + // "descriptor":"{user.Descriptor}", + // … + // },…] var pageOfUsers = graphHttpClient.ListUsersAsync(continuationToken: userContinuationToken).SyncResult(); - // The continuation token, if any, is returned in the X-MS-ContinuationToken response header: + // Over the wire, the continuation token (if any) is returned in the X-MS-ContinuationToken response header as shown above. + // In C#, this is parsed for us as a property of the result object: userContinuationToken = pageOfUsers.ContinuationToken?.SingleOrDefault(); - // 2. RETRIEVING AUTHORIZATION IDS FOR A USER'S PERSONAL ACCESS TOKENS + // Now that we have a page of users, we use their descriptors to get the authorization IDs for all of their personal access tokens: + var authorizationIdsForOnePageOfUsers = GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(pageOfUsers.GraphUsers.Select(user => user.Descriptor)); - // For each user, you can retrieve the authorization IDs corresponding to their personal access tokens as shown below. - // This is a paginated API, so you may need to keep track of the continuation token - // and send multiple requests depending on the number of PATs associated with the user. - // The page size is client-configurable up to a server-side limit. - // If you pass too high a value, you will receive an error. - var patPageSize = 20; + // And once we have the authorization IDs for the current page, + // we can chain in another operation, such as revoking the authorizations (see below), in a paginated way. + // This pattern is recommended over pulling down the full list of authorization IDs + // and trying to pass them all back up later on to help callers remain with resource governance restrictions. + thenForEachPage?.Invoke(authorizationIdsForOnePageOfUsers); - foreach (var user in pageOfUsers.GraphUsers) - { - Guid? patContinuationToken = null; - - do - { - // Make a call to retrieve the next page of PATs: - // HTTP: GET /_apis/tokenAdmin/personalAccessTokens/{user.Descriptor}[&continuationToken={patContinuationToken}] - // => 200 OK { - // "count":n, - // "value":[{ - // "authorizationId":"{pat.AuthorizationId}", - // … - // },…], - // "continuationToken":"{patContinuationToken}" - // } - var pageOfPats = tokenAdminHttpClient.ListPersonalAccessTokensAsync( - user.Descriptor, pageSize: patPageSize, continuationToken: patContinuationToken?.ToString()).SyncResult(); - - // In this case, the continuation token is returned in the response body as shown above. - patContinuationToken = pageOfPats.ContinuationToken; - - // Track the authorization IDs from the objects returned in the response: - authorizationIds.AddRange(pageOfPats.SessionTokens.Select(pat => pat.AuthorizationId)); - } - // Repeat the above while we still have more pages of PATs: - while (patContinuationToken != null && patContinuationToken != Guid.Empty); - } + // If desired, you may also want to track the full list of authorization IDs for local usage only. + authorizationIds.AddRange(authorizationIdsForOnePageOfUsers); } // Repeat the above while we still have more pages of users: while (!string.IsNullOrEmpty(userContinuationToken)); @@ -113,18 +126,83 @@ public List GetPersonalAccessTokenDetailsForUsersInYourOrganization() return authorizationIds; } - [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] - public void RevokePersonalAccessTokensForUsersInYourOrganization() + private List GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(IEnumerable userDescriptors) { var tokenAdminHttpClient = Context.Connection.GetClient(); - var authorizationIds = GetPersonalAccessTokenDetailsForUsersInYourOrganization(); + // (4) RETRIEVING PERSONAL ACCESS TOKEN AUTHORIZATION IDS FOR A SET OF SUBJECT DESCRIPTORS + + // Given a user's subject descriptor, you can retrieve the authorization IDs that correspond + // to their personal access tokens as shown below. + // This is a paginated API, so you may need to keep track of the continuation token + // and send multiple requests depending on the number of PATs associated with each user. + // The page size is client-configurable up to a server-side limit. + // We pass null below to use the default (recommended), but you can adjust it if desired. + // However, keep in mind that you will receive an error if you pass too high a value. + int? patPageSize = null; + + var authorizationIds = new List(); + + foreach (var userDescriptor in userDescriptors) + { + Guid? patContinuationToken = null; + do + { + // Make a call to retrieve the next page of PATs: + // HTTP: GET /_apis/tokenAdmin/personalAccessTokens/{userDescriptor}[&continuationToken={patContinuationToken}] + // => 200 OK { + // "count":n, + // "value":[{ + // "authorizationId":"{pat.AuthorizationId}", + // … + // },…], + // "continuationToken":"{patContinuationToken}" + // } + var pageOfPats = tokenAdminHttpClient.ListPersonalAccessTokensAsync( + userDescriptor, pageSize: patPageSize, continuationToken: patContinuationToken?.ToString()).SyncResult(); + + // In this case the continuation token (if any) is returned in the response body as shown above, + // which maps directly in C# to a property of the result object: + patContinuationToken = pageOfPats.ContinuationToken; + + // Track the authorization IDs from the objects returned in the response: + authorizationIds.AddRange(pageOfPats.SessionTokens.Select(pat => pat.AuthorizationId)); + } + // Repeat the above while we still have more pages of PATs: + while (patContinuationToken != null && patContinuationToken != Guid.Empty); + } + + return authorizationIds; + } + + [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] + public void RevokePersonalAccessTokensForSpecificUsersInYourOrganization() + { + // (5) REVOKING PERSONAL ACCESS TOKENS FOR SPECIFIC USERS + // - Get the authorization IDs corresponding to those specific users' personal access tokens, as in either (4) or (1+4) above. + // - For those specific authorization IDs, create and send the corresponding revocations, as in (7) below + var authorizationIds = GetPersonalAccessTokenAuthorizationIdsForSpecificUsersInYourOrganization(); + RevokeAuthorizationsForASetOfUsers(authorizationIds); + } + + [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] + public void RevokePersonalAccessTokensForAllUsersInYourOrganization() + { + // (6) REVOKING PERSONAL ACCESS TOKENS FOR ALL USERS + // - In pages, get the authorization IDs corresponding to the users' personal access tokens, as in (3+4) above. + // - For each page of authorization IDs, create and send the corresponding page of revocations, as in (7) below. + GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization(thenForEachPage: RevokeAuthorizationsForASetOfUsers); + } + + private void RevokeAuthorizationsForASetOfUsers(IEnumerable authorizationIds) + { + var tokenAdminHttpClient = Context.Connection.GetClient(); - // 3. REVOKING THE AUTHORIZATIONS + // (7) REVOKING SPECIFIC AUTHORIZATIONS // Once you have the list of authorization IDs you want to revoke, - // you can use the following token administration endpoints to revoke them in batch, up to a server-side limit. - // Just as we've structure it here, you can revoke a list of authorizations that correspond to multiple users in one call, + // you can use the following token administration endpoint to revoke them in batch, up to a server-side limit. + // Just as we've structured it here, you can revoke a list of authorizations that correspond to multiple users in one call, // but keep in mind that you will receive an error if you pass too many revocations at once. var revocations = authorizationIds.Select(id => new TokenAdminRevocation { AuthorizationId = id }); @@ -142,20 +220,20 @@ public void RevokeSelfDescribingSessionTokensForUsersInYourOrganization() { var tokenAdminHttpClient = Context.Connection.GetClient(); - ////// CREATING OAUTH REVOCATION RULES + // (8) CREATING OAUTH REVOCATION RULES // Not all kinds of OAuth authorizations can be revoked directly. // Some, such as self-describing session tokens, must instead by revoked by creating a rule // which will be evaluated and used to reject matching OAuth credentials at authentication time. // Revocation rules as shown here will apply to all credentials that were issued before the supplied datetime - // (if omitted, at the time at which the rule is created), and which match a particular set of OAuth scopes (must be provided). + // (or if omitted, at the time at which the rule is created), and which match a particular set of OAuth scopes (which must be provided). // For a list of all OAuth scopes supported by VSTS, see: // https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/oauth?view=vsts#scopes var revocationRule = new TokenAdminRevocationRule { - Scopes = "vso.code vso.packaging", // reject any token presented which matches EITHER the Code OR the Packaging scope - CreatedBefore = DateTime.Now - TimeSpan.FromDays(1) // reject any token that was created more than a day ago + Scopes = "vso.code vso.packaging", // rejects any token presented which matches EITHER the Code OR the Packaging scope + CreatedBefore = DateTime.Now - TimeSpan.FromDays(1) // rejects any token that was created more than a day ago }; // HTTP: POST /_apis/tokenAdmin/revocationRules From 7c151d16b63e2b1cf4d16569b75532646876fb6f Mon Sep 17 00:00:00 2001 From: Kenan Kigunda Date: Thu, 19 Jul 2018 00:02:15 -0700 Subject: [PATCH 4/8] Cleaned up summary in TokenAdminSample class doc --- .../TokenAdmin/TokenAdminSample.cs | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs index 8947bfc1..e046e9c4 100644 --- a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs @@ -17,32 +17,39 @@ namespace Microsoft.TeamServices.Samples.Client.TokenAdmin /// but is commented with the HTTP calls that you can make to perform these same operations directly over the wire. ///
/// - ///

- /// NOTE ON REQUIRED PERMISSIONS: To be able to call of all the endpoints in this sample, + /// + /// NOTE ON REQUIRED PERMISSIONS: + /// To be able to call of all the endpoints in this sample, /// you will need to be an administrator of the target organization. /// Non-administrators will receive an authorization error. - ///

- ///

- /// NOTE ON CONNECTION DETAILS: For this sample, we will be using the connection from our current client context, - /// which includes our base URL and our authentication credentials through the parameters passed in through the runner. - /// If you are calling these endpoints directly over the wire, - /// your base URL will be => https://{yourOrganization}.vssps.visualstudio.com - /// and the recommended approach for authentication is to acquire an ADAL access token and pass it in the header => Authorization: Bearer {yourADALAccessToken} - /// For further guidance on how to acquire an ADAL access token, see: - /// https://github.com/Microsoft/vsts-auth-samples/tree/master/ManagedClientConsoleAppSample - /// For further guidance on other authentication options, see: - /// https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/authentication-guidance?view=vsts - ///

+ /// + /// + /// NOTE ON CONNECTION DETAILS: + /// For this sample, we will be using the Context.Connection variable from our enclosing context, + /// which forwards our base URL and authentication credentials through the parameters passed in through the runner. + /// If instead you are calling these endpoints directly over the wire: + /// - your base URL will be + /// => https://{yourOrganization}.vssps.visualstudio.com + /// - and the recommended approach for authentication + /// is to acquire an ADAL access token and pass it in the header, i.e. + /// => Authorization: Bearer {yourADALAccessToken} + /// For further guidance on how to acquire an ADAL access token, see: + /// https://github.com/Microsoft/vsts-auth-samples/tree/master/ManagedClientConsoleAppSample + /// For further guidance on other authentication options, see: + /// https://docs.microsoft.com/en-us/vsts/integrate/get-started/authentication/authentication-guidance?view=vsts + /// + /// + /// NOTE ON TARGET ENDPOINTS: + /// The areas of the VSTS REST API / HTTP clients that we will be using for these administration tasks are: + /// - the Graph endpoints, corresponding to the path pattern: /_apis/graph/* + /// - the TokenAdmin endpoints, corresponding to the path pattern: /_apis/tokenAdmin/* + /// The C# client for this latter area may not be available until the next release of our client libraries, + /// but the REST endpoints themselves should be available at the time of publishing. + /// ///
[ClientSample(TokenAdminResourceIds.AreaName)] public class TokenAdminSample : ClientSample { - // The areas of the VSTS REST API / HTTP clients that we will be using for these administration tasks are: - // - the Graph endpoints, corresponding to the path pattern: /_apis/graph/* - // - the TokenAdmin endpoints, corresponding to the path pattern: /_apis/tokenAdmin/* - // The C# client for this latter area may not be available until the next release of our client libraries, - // but the REST endpoints themselves should be available at the time of publishing. - [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource)] public List GetPersonalAccessTokenAuthorizationIdsForSpecificUsersInYourOrganization() { From 463b64e0a5d5351ccecf02e9198a74ead972b733 Mon Sep 17 00:00:00 2001 From: Kenan Kigunda Date: Thu, 19 Jul 2018 01:27:19 -0700 Subject: [PATCH 5/8] Futher cleanup of crossrefencing / return types --- .../TokenAdmin/TokenAdminSample.cs | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs index e046e9c4..6d191a5f 100644 --- a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.TeamFoundation.Common; using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.Graph.Client; using Microsoft.VisualStudio.Services.TokenAdmin.Client; @@ -51,21 +52,22 @@ namespace Microsoft.TeamServices.Samples.Client.TokenAdmin public class TokenAdminSample : ClientSample { [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource)] - public List GetPersonalAccessTokenAuthorizationIdsForSpecificUsersInYourOrganization() + public IEnumerable GetPersonalAccessTokenAuthorizationIdsForSpecificUsersInYourOrganization() { var graphHttpClient = Context.Connection.GetClient(); - // In this example, we assume that you already have a set of VSIDs or subject descriptors - // for the users whose PAT tokens you want to retrieve. If this is not the case, see the sample for all users further below. - // If you have VSIDs, you will need to first convert them to subject descriptors as shown below. - // If you have subject descriptors, you can go directly to the code in GetPersonalAccessTokenDetailsForASetOfUsers. + // In this example, we assume that you already have a set of + // VSIDs or subject descriptors for the users whose PAT tokens you want to retrieve. + // If have neither of these, go to (2) to see how to enumerate your organization's users. + // If you have VSIDs, continue below to see how to first convert them to subject descriptors. + // If you have subject descriptors, go directly to (3) to retrieve the authorization details. // (1) CONVERTING VSIDS to SUBJECT DESCRIPTORS var vsids = new[] { - new Guid("e3b2f97f-8fd8-4086-82b4-e7d878e89c37"), - new Guid("1b90428b-d3f0-4703-812b-e497f08f183a"), + new Guid("372b07fb-6d3a-69d2-bffc-3e141cdefa7e"), + new Guid("3cdead07-1a99-46f6-80cb-2d247ad68606"), }; // For each VSID, we ask Graph for the corresponding subject descriptor: @@ -76,19 +78,19 @@ public List GetPersonalAccessTokenAuthorizationIdsForSpecificUsersInYourOr // } var subjectDescriptors = vsids.Select(vsid => graphHttpClient.GetDescriptorAsync(vsid).SyncResult()).Select(result => result.Value); - // (2) RETRIEVING PERSONAL ACCESS TOKEN AUTHORIZATION IDS FOR A SET OF SUBJECT DESCRIPTORS => see (4) below. + // Once we have the subject descriptors, we can query for the personal access token details: return GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(subjectDescriptors); } [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource)] - public List GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization() + public IEnumerable GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization() => GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization(thenForEachPage: null); - private List GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization(Action> thenForEachPage) + private IEnumerable GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization(Action> thenForEachPage) { var graphHttpClient = Context.Connection.GetClient(); - // (3) RETRIEVING SUBJECT DESCRIPTORS FOR ALL USERS + // (2) RETRIEVING SUBJECT DESCRIPTORS FOR ALL USERS // First, we will use the Graph endpoints to find the subject descriptors of all users in the organization. // This is a paginated API, so you may need to keep track of the continuation token @@ -133,11 +135,16 @@ private List GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrgani return authorizationIds; } - private List GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(IEnumerable userDescriptors) + private IEnumerable GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(IEnumerable userDescriptors) { + if (userDescriptors.IsNullOrEmpty()) + { + return Enumerable.Empty(); + } + var tokenAdminHttpClient = Context.Connection.GetClient(); - // (4) RETRIEVING PERSONAL ACCESS TOKEN AUTHORIZATION IDS FOR A SET OF SUBJECT DESCRIPTORS + // (3) RETRIEVING PERSONAL ACCESS TOKEN AUTHORIZATION IDS FOR A SET OF SUBJECT DESCRIPTORS // Given a user's subject descriptor, you can retrieve the authorization IDs that correspond // to their personal access tokens as shown below. @@ -185,9 +192,9 @@ private List GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(IEnumera [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] public void RevokePersonalAccessTokensForSpecificUsersInYourOrganization() { - // (5) REVOKING PERSONAL ACCESS TOKENS FOR SPECIFIC USERS - // - Get the authorization IDs corresponding to those specific users' personal access tokens, as in either (4) or (1+4) above. - // - For those specific authorization IDs, create and send the corresponding revocations, as in (7) below + // (4) REVOKING PERSONAL ACCESS TOKENS FOR SPECIFIC USERS + // - Get the authorization IDs corresponding to those specific users' personal access tokens, as in either (3) or (1+3) above. + // - For those specific authorization IDs, create and send the corresponding revocations, as in (6) below var authorizationIds = GetPersonalAccessTokenAuthorizationIdsForSpecificUsersInYourOrganization(); RevokeAuthorizationsForASetOfUsers(authorizationIds); } @@ -195,17 +202,22 @@ public void RevokePersonalAccessTokensForSpecificUsersInYourOrganization() [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] public void RevokePersonalAccessTokensForAllUsersInYourOrganization() { - // (6) REVOKING PERSONAL ACCESS TOKENS FOR ALL USERS - // - In pages, get the authorization IDs corresponding to the users' personal access tokens, as in (3+4) above. - // - For each page of authorization IDs, create and send the corresponding page of revocations, as in (7) below. + // (5) REVOKING PERSONAL ACCESS TOKENS FOR ALL USERS + // - In pages, get the authorization IDs corresponding to the users' personal access tokens, as in (2+3) above. + // - For each page of authorization IDs, create and send the corresponding page of revocations, as in (6) below. GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization(thenForEachPage: RevokeAuthorizationsForASetOfUsers); } private void RevokeAuthorizationsForASetOfUsers(IEnumerable authorizationIds) { + if (authorizationIds.IsNullOrEmpty()) + { + return; + } + var tokenAdminHttpClient = Context.Connection.GetClient(); - // (7) REVOKING SPECIFIC AUTHORIZATIONS + // (6) REVOKING SPECIFIC AUTHORIZATIONS // Once you have the list of authorization IDs you want to revoke, // you can use the following token administration endpoint to revoke them in batch, up to a server-side limit. @@ -227,7 +239,7 @@ public void RevokeSelfDescribingSessionTokensForUsersInYourOrganization() { var tokenAdminHttpClient = Context.Connection.GetClient(); - // (8) CREATING OAUTH REVOCATION RULES + // (7) CREATING OAUTH REVOCATION RULES // Not all kinds of OAuth authorizations can be revoked directly. // Some, such as self-describing session tokens, must instead by revoked by creating a rule From 49dc79efdc3eb3817ccfcbeadb740cfe8bfed8cd Mon Sep 17 00:00:00 2001 From: Kenan Kigunda Date: Thu, 19 Jul 2018 21:27:04 -0700 Subject: [PATCH 6/8] Updated to filter out non-user descriptors --- .../TokenAdmin/TokenAdminSample.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs index 6d191a5f..1e6cfbfd 100644 --- a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs @@ -137,11 +137,6 @@ private IEnumerable GetPersonalAccessTokenAuthorizationIdsForAllUsersInYou private IEnumerable GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(IEnumerable userDescriptors) { - if (userDescriptors.IsNullOrEmpty()) - { - return Enumerable.Empty(); - } - var tokenAdminHttpClient = Context.Connection.GetClient(); // (3) RETRIEVING PERSONAL ACCESS TOKEN AUTHORIZATION IDS FOR A SET OF SUBJECT DESCRIPTORS @@ -155,6 +150,13 @@ private IEnumerable GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(I // However, keep in mind that you will receive an error if you pass too high a value. int? patPageSize = null; + // We can only get personal access tokens for users, so ignore non-user descriptors. + userDescriptors = userDescriptors?.Where(descriptor => descriptor.IsUserType()).ToList(); + if (userDescriptors.IsNullOrEmpty()) + { + return Enumerable.Empty(); + } + var authorizationIds = new List(); foreach (var userDescriptor in userDescriptors) @@ -188,7 +190,7 @@ private IEnumerable GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(I return authorizationIds; } - + [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] public void RevokePersonalAccessTokensForSpecificUsersInYourOrganization() { From abe33c5f6768eaa654dcd0bcd3bc87a4f9f96a44 Mon Sep 17 00:00:00 2001 From: Kenan Kigunda Date: Fri, 20 Jul 2018 14:54:39 -0700 Subject: [PATCH 7/8] Added comments clarifying how to run the samples --- .../TokenAdmin/TokenAdminSample.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs index 1e6cfbfd..a1cf05f5 100644 --- a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs @@ -44,14 +44,24 @@ namespace Microsoft.TeamServices.Samples.Client.TokenAdmin /// The areas of the VSTS REST API / HTTP clients that we will be using for these administration tasks are: /// - the Graph endpoints, corresponding to the path pattern: /_apis/graph/* /// - the TokenAdmin endpoints, corresponding to the path pattern: /_apis/tokenAdmin/* - /// The C# client for this latter area may not be available until the next release of our client libraries, - /// but the REST endpoints themselves should be available at the time of publishing. + /// The C# client for this latter area will not be available until the next release of our client libraries + /// and so are included in this branch (under the folder FromFutureWebApi). + /// The REST endpoints themselves are presently available. /// /// [ClientSample(TokenAdminResourceIds.AreaName)] public class TokenAdminSample : ClientSample { - [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource)] + // To run these samples: + // 1) clone this repo and checkout this branch + // 2) build the Microsoft.TeamServices.Samples.Client solution + // 3) find the location of Microsoft.TeamServices.Samples.Client.Runner.exe in the bin directory + // 4) run the lines above each method marked with ">" from that folder in the cmd prompt + // - make sure to replace "{yourOrganization}" in the /url parameter with the appropriate value + // - the runner will pop up a window prompting you to authenticate, where you will need to sign in as one of the organization's administrators + + // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:PersonalAccessTokensSubset /outputPath:C:\Temp + [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource + "Subset")] public IEnumerable GetPersonalAccessTokenAuthorizationIdsForSpecificUsersInYourOrganization() { var graphHttpClient = Context.Connection.GetClient(); @@ -82,6 +92,7 @@ public IEnumerable GetPersonalAccessTokenAuthorizationIdsForSpecificUsersI return GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(subjectDescriptors); } + // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:PersonalAccessTokens /outputPath:C:\Temp [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource)] public IEnumerable GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization() => GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization(thenForEachPage: null); @@ -191,7 +202,8 @@ private IEnumerable GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(I return authorizationIds; } - [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] + // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:RevocationsSubset /outputPath:C:\Temp + [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource + "Subset")] public void RevokePersonalAccessTokensForSpecificUsersInYourOrganization() { // (4) REVOKING PERSONAL ACCESS TOKENS FOR SPECIFIC USERS @@ -201,6 +213,7 @@ public void RevokePersonalAccessTokensForSpecificUsersInYourOrganization() RevokeAuthorizationsForASetOfUsers(authorizationIds); } + // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:Revocations /outputPath:C:\Temp [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] public void RevokePersonalAccessTokensForAllUsersInYourOrganization() { @@ -236,6 +249,7 @@ private void RevokeAuthorizationsForASetOfUsers(IEnumerable authorizationI tokenAdminHttpClient.RevokeAuthorizationsAsync(revocations).SyncResult(); } + // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:RevocationRules /outputPath:C:\Temp [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationRulesResource)] public void RevokeSelfDescribingSessionTokensForUsersInYourOrganization() { From 43fd79275b3548534b49420ced2549b5ca27d047 Mon Sep 17 00:00:00 2001 From: Kenan Kigunda Date: Fri, 20 Jul 2018 14:59:58 -0700 Subject: [PATCH 8/8] Added title for each runnable sample --- .../TokenAdmin/TokenAdminSample.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs index a1cf05f5..f5c803fb 100644 --- a/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs +++ b/ClientLibrary/Snippets/Microsoft.TeamServices.Samples.Client/TokenAdmin/TokenAdminSample.cs @@ -60,6 +60,7 @@ public class TokenAdminSample : ClientSample // - make sure to replace "{yourOrganization}" in the /url parameter with the appropriate value // - the runner will pop up a window prompting you to authenticate, where you will need to sign in as one of the organization's administrators + // TO GET THE PERSONAL ACCESS TOKEN AUTHORIZATION DETAILS FOR A SUBSET OF USERS IN YOUR ORGANIZATION: // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:PersonalAccessTokensSubset /outputPath:C:\Temp [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource + "Subset")] public IEnumerable GetPersonalAccessTokenAuthorizationIdsForSpecificUsersInYourOrganization() @@ -92,6 +93,7 @@ public IEnumerable GetPersonalAccessTokenAuthorizationIdsForSpecificUsersI return GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(subjectDescriptors); } + // TO GET THE PERSONAL ACCESS TOKEN AUTHORIZATION DETAILS FOR ALL THE USERS IN YOUR ORGANIZATION: // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:PersonalAccessTokens /outputPath:C:\Temp [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.PersonalAccessTokensResource)] public IEnumerable GetPersonalAccessTokenAuthorizationIdsForAllUsersInYourOrganization() @@ -202,6 +204,7 @@ private IEnumerable GetPersonalAccessTokenAuthorizationIdsForASetOfUsers(I return authorizationIds; } + // TO REVOKE THE PERSONAL ACCESS TOKENS OF A SUBSET OF USERS IN YOUR ORGANIZATION: // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:RevocationsSubset /outputPath:C:\Temp [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource + "Subset")] public void RevokePersonalAccessTokensForSpecificUsersInYourOrganization() @@ -213,6 +216,7 @@ public void RevokePersonalAccessTokensForSpecificUsersInYourOrganization() RevokeAuthorizationsForASetOfUsers(authorizationIds); } + // TO REVOKE THE PERSONAL ACCESS TOKENS OF ALL USERS IN YOUR ORGANIZATION: // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:Revocations /outputPath:C:\Temp [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationsResource)] public void RevokePersonalAccessTokensForAllUsersInYourOrganization() @@ -249,6 +253,7 @@ private void RevokeAuthorizationsForASetOfUsers(IEnumerable authorizationI tokenAdminHttpClient.RevokeAuthorizationsAsync(revocations).SyncResult(); } + // TO REVOKE THE SELF-DESCRIBING SESSION TOKENS ISSUED FOR A PARTICULAR SCOPE AND BEFORE A CERTAIN DATE FOR ALL USERS IN YOUR ORGANIZATION: // > Microsoft.TeamServices.Samples.Client.Runner.exe /url:https://{yourOrganization}.visualstudio.com /area:TokenAdmin /resource:RevocationRules /outputPath:C:\Temp [ClientSampleMethod(TokenAdminResourceIds.AreaName, TokenAdminResourceIds.RevocationRulesResource)] public void RevokeSelfDescribingSessionTokensForUsersInYourOrganization()