/// <summary> /// Get a scope description string. /// /// Group the categories together so as to generate the most minimal string. Some of this is done via /// the flags enum minimization step, but there are some disjoint roots that require an additional step. /// <param name="scopes">Scopes</param> /// <returns>Description string.</returns> /// <exception cref="Exception">Exception if an attribute is missing from the enum.</exception> public static string GetScopeString(this AzureDevOpsPATScopes scopes) { var minimalScopes = scopes.GetMinimizedScopeList(); var azdoScopeType = typeof(AzureDevOpsPATScopes); var shortHandType = typeof(ScopeDescriptionAttribute); Dictionary <string, string> minimalScopesStrings = new Dictionary <string, string>(); // Build up a dictionary of strings with common category roots. foreach (var scope in minimalScopes) { var memberInfo = azdoScopeType.GetMember(scope.ToString()); var attribute = Attribute.GetCustomAttribute(memberInfo[0], shortHandType); if (attribute == null) { throw new Exception($"{scope.ToString()} should have a 'ScopeShortHand' attribute."); } var shortHandTypeAttribute = (ScopeDescriptionAttribute)attribute; if (minimalScopesStrings.ContainsKey(shortHandTypeAttribute.Resource)) { minimalScopesStrings[shortHandTypeAttribute.Resource] += shortHandTypeAttribute.Permissions; } else { minimalScopesStrings.Add(shortHandTypeAttribute.Resource, shortHandTypeAttribute.Permissions); } } // Join their values together. return(string.Join('-', minimalScopesStrings.Select(kv => $"{kv.Key}-{kv.Value}"))); }
/// <summary> /// Generate an azure devops PAT with a given name, target organization set, and scopes. /// </summary> /// <param name="name"></param> /// <param name="targetScopes"></param> /// <param name="targetOrganizationNames"></param> /// <param name="validTo"></param> /// <returns>New PAT</returns> /// <exception cref="ArgumentException"></exception> /// <exception cref="Exception"></exception> public async Task <SessionToken> GeneratePATAsync( string name, AzureDevOpsPATScopes targetScopes, IEnumerable <string> targetOrganizationNames, DateTime validTo) { ValidateParameters(name, targetScopes, targetOrganizationNames, validTo); var minimalScopesList = targetScopes.GetMinimizedScopeList(); var scopesWithPrefixes = minimalScopesList.Select(scope => $"vso.{scope}"); string scopes = string.Join(" ", scopesWithPrefixes); try { using var tokenConnection = new VssConnection( new Uri("https://vssps.dev.azure.com/"), credentials); using var tokenClient = await tokenConnection.GetClientAsync <TokenHttpClient>(); var organizations = await GetAccountsByNameAsync(targetOrganizationNames); var tokenInfo = new SessionToken() { DisplayName = name, Scope = scopes, TargetAccounts = organizations.Select(account => account.AccountId).ToArray(), ValidFrom = DateTime.UtcNow, ValidTo = validTo, }; SessionToken pat = await tokenClient.CreateSessionTokenAsync( tokenInfo, SessionTokenType.Compact, isPublic : false); return(pat); } catch (Exception ex) { throw new Exception($"Failed to generate a PAT named '{name}' for organizatons '{string.Join(", ", targetOrganizationNames)}' and scopes '{scopes}'", ex); } }