Ejemplo n.º 1
0
        /// <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}")));
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Pull out a minimal
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        public static List <AzureDevOpsPATScopes> GetMinimizedScopeList(this AzureDevOpsPATScopes e)
        {
            // Note that in net5.0+ there is a generic version of this, but
            // it's not available in 3.1.
            // Get the list of available values, which are automatically sorted by their magnitude.
            var availableValues = (int[])Enum.GetValues(typeof(AzureDevOpsPATScopes));

            // To figure out a minimal set, rely on the flags setup that means that more permissive scopes
            // include the less permissive ones.
            //
            // We walk the list of avaiable values, checking whether the bit is set. If it is, check whether
            // any existing scopes in the list are contained within this scope. If they are, remove them.
            // Then add the new scope to the list.
            List <AzureDevOpsPATScopes> minimalScopes = new List <AzureDevOpsPATScopes>();

            foreach (var value in availableValues)
            {
                AzureDevOpsPATScopes flag = (AzureDevOpsPATScopes)value;
                if (e.HasFlag(flag))
                {
                    // If this scope includes any of the existing minimal scopes (X & Y != 0) then
                    // remove those existing minimal scopes.
                    var existingMatchingMinimalScopes = minimalScopes.Where(ms => (flag & ms) != 0).ToList();
                    if (existingMatchingMinimalScopes.Any())
                    {
                        existingMatchingMinimalScopes.ForEach(ms => minimalScopes.Remove(ms));
                    }
                    minimalScopes.Add(flag);
                }
            }

            return(minimalScopes);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Determine the desired name of the PAT. It appears that PATs may have really any combination of characters
        /// and they do not have to be unique.
        /// </summary>
        /// <param name="scopes">Desired scopes</param>
        /// <param name="organizations">Organizations</param>
        /// <param name="name">Name provided by the user</param>
        /// <returns>Name of the PAT.</returns>
        private static string GetPatName(AzureDevOpsPATScopes scopes, string[] organizations, string name)
        {
            string patName = name;

            if (string.IsNullOrEmpty(patName))
            {
                string scopeString = scopes.GetScopeString();
                patName = $"{string.Join("-", organizations)}-{scopeString}";
            }

            return(patName);
        }
Ejemplo n.º 4
0
        private static async Task <int> Go(List <AzureDevOpsPATScopes> scopes, string[] organizations, string name, int?expiresIn, DateTime?expiration, string user, string password, IConsole console)
        {
            AzureDevOpsPATScopes scopeFlags = 0;

            foreach (var scope in scopes)
            {
                scopeFlags |= scope;
            }

            string patName = GetPatName(scopeFlags, organizations, name);

            if (expiresIn.HasValue && expiration.HasValue)
            {
                Console.WriteLine("May not specify both --expires-in and --expiration.");
                return(1);
            }

            if (string.IsNullOrEmpty(user) != string.IsNullOrEmpty(password))
            {
                Console.WriteLine("Must specify both user + password, or neither.");
                return(1);
            }

            DateTime credentialExpiration = GetExpirationDate(expiration, expiresIn);

            VssCredentials credentials;

            if (!string.IsNullOrEmpty(user))
            {
                credentials = new VssAadCredential(user, password);
            }
            else
            {
                credentials = await GetInteractiveUserCredentials();
            }

            var patGenerator = new AzureDevOpsPATGenerator(credentials);
            var pat          = await patGenerator.GeneratePATAsync(patName, scopeFlags, organizations, credentialExpiration);

            Console.WriteLine($"{patName} (Valid Until: {credentialExpiration}): {pat.Token}");
            return(0);
        }
        /// <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);
            }
        }
        private static void ValidateParameters(string name, AzureDevOpsPATScopes targetScopes, IEnumerable <string> targetOrganizationNames, DateTime validTo)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("PAT must have a name");
            }

            if (targetScopes == 0)
            {
                throw new ArgumentException("PAT must have one or more desired scopes.");
            }

            if (!targetOrganizationNames.Any())
            {
                throw new ArgumentException("PAT must have one or more orgnanizations.");
            }

            if (validTo.CompareTo(DateTime.Now) < 0)
            {
                throw new ArgumentException("PAT expiration date must be after the current date.");
            }
        }
 public void MinimalScopeStringTests(AzureDevOpsPATScopes scopes, string expectedString)
 {
     scopes.GetScopeString().Should().Be(expectedString);
 }