static bool IsAzureDevOpsHttpRemote(string url, out Uri uri)
 {
     return(Uri.TryCreate(url, UriKind.Absolute, out uri) &&
            (StringComparer.OrdinalIgnoreCase.Equals(Uri.UriSchemeHttp, uri.Scheme) ||
             StringComparer.OrdinalIgnoreCase.Equals(Uri.UriSchemeHttps, uri.Scheme)) &&
            UriHelpers.IsAzureDevOpsHost(uri.Host));
 }
 public override bool IsSupported(InputArguments input)
 {
     // We do not support unencrypted HTTP communications to Azure Repos,
     // but we report `true` here for HTTP so that we can show a helpful
     // error message for the user in `CreateCredentialAsync`.
     return((StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") ||
             StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "https")) &&
            UriHelpers.IsAzureDevOpsHost(input.Host));
 }
        public bool IsSupported(InputArguments input)
        {
            if (input is null)
            {
                return(false);
            }

            // We do not support unencrypted HTTP communications to Azure Repos,
            // but we report `true` here for HTTP so that we can show a helpful
            // error message for the user in `CreateCredentialAsync`.
            return(input.TryGetHostAndPort(out string hostName, out _) &&
                   (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") ||
                    StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "https")) &&
                   UriHelpers.IsAzureDevOpsHost(hostName));
        }
        public async Task <string> CreatePersonalAccessTokenAsync(Uri organizationUri, JsonWebToken accessToken, IEnumerable <string> scopes)
        {
            const string sessionTokenUrl = "_apis/token/sessiontokens?api-version=1.0&tokentype=compact";

            EnsureArgument.AbsoluteUri(organizationUri, nameof(organizationUri));
            if (!UriHelpers.IsAzureDevOpsHost(organizationUri.Host))
            {
                throw new ArgumentException($"Provided URI '{organizationUri}' is not a valid Azure DevOps hostname", nameof(organizationUri));
            }
            EnsureArgument.NotNull(accessToken, nameof(accessToken));

            _context.Trace.WriteLine("Getting Azure DevOps Identity Service endpoint...");
            Uri identityServiceUri = await GetIdentityServiceUriAsync(organizationUri, accessToken);

            _context.Trace.WriteLine($"Identity Service endpoint is '{identityServiceUri}'.");

            Uri requestUri = new Uri(identityServiceUri, sessionTokenUrl);

            _context.Trace.WriteLine($"HTTP: POST {requestUri}");
            using (StringContent content = CreateAccessTokenRequestJson(organizationUri, scopes))
                using (HttpRequestMessage request = CreateRequestMessage(HttpMethod.Post, requestUri, content, accessToken))
                    using (HttpResponseMessage response = await HttpClient.SendAsync(request))
                    {
                        _context.Trace.WriteLine($"HTTP: Response {(int)response.StatusCode} [{response.StatusCode}]");

                        string responseText = await response.Content.ReadAsStringAsync();

                        if (!string.IsNullOrWhiteSpace(responseText))
                        {
                            if (response.IsSuccessStatusCode)
                            {
                                if (TryGetFirstJsonStringField(responseText, "token", out string token))
                                {
                                    return(token);
                                }
                            }
                            else
                            {
                                if (TryGetFirstJsonStringField(responseText, "message", out string errorMessage))
                                {
                                    throw new Exception($"Failed to create PAT: {errorMessage}");
                                }
                            }
                        }
                    }

            throw new Exception("Failed to create PAT");
        }
        private async Task <IMicrosoftAuthenticationResult> GetAzureAccessTokenAsync(Uri remoteUri, string userName)
        {
            // We should not allow unencrypted communication and should inform the user
            if (StringComparer.OrdinalIgnoreCase.Equals(remoteUri.Scheme, "http"))
            {
                throw new Exception("Unencrypted HTTP is not supported for Azure Repos. Ensure the repository remote URL is using HTTPS.");
            }

            Uri orgUri = UriHelpers.CreateOrganizationUri(remoteUri, out string orgName);

            _context.Trace.WriteLine($"Determining Microsoft Authentication authority for Azure DevOps organization '{orgName}'...");
            string authAuthority = _authorityCache.GetAuthority(orgName);

            if (authAuthority is null)
            {
                // If there is no cached value we must query for it and cache it for future use
                _context.Trace.WriteLine($"No cached authority value - querying {orgUri} for authority...");
                authAuthority = await _azDevOps.GetAuthorityAsync(orgUri);

                _authorityCache.UpdateAuthority(orgName, authAuthority);
            }
            _context.Trace.WriteLine($"Authority is '{authAuthority}'.");

            //
            // If the remote URI is a classic "*.visualstudio.com" host name and we have a user specified from the
            // remote then take that as the current AAD/MSA user in the first instance.
            //
            // For "dev.azure.com" host names we only use the user info part of the remote when this doesn't
            // match the Azure DevOps organization name. Our friends in Azure DevOps decided "borrow" the username
            // part of the remote URL to include the organization name (not an actual username).
            //
            // If we have no specified user from the remote (or this is [email protected]/org/..) then query the
            // user manager for a bound user for this organization, if one exists...
            //
            var icmp = StringComparer.OrdinalIgnoreCase;

            if (!string.IsNullOrWhiteSpace(userName) &&
                (UriHelpers.IsVisualStudioComHost(remoteUri.Host) ||
                 (UriHelpers.IsAzureDevOpsHost(remoteUri.Host) && !icmp.Equals(orgName, userName))))
            {
                _context.Trace.WriteLine("Using username as specified in remote.");
            }
            else
            {
                _context.Trace.WriteLine($"Looking up user for organization '{orgName}'...");
                userName = _bindingManager.GetUser(orgName);
            }

            _context.Trace.WriteLine(string.IsNullOrWhiteSpace(userName) ? "No user found." : $"User is '{userName}'.");

            // Get an AAD access token for the Azure DevOps SPS
            _context.Trace.WriteLine("Getting Azure AD access token...");
            IMicrosoftAuthenticationResult result = await _msAuth.GetTokenAsync(
                authAuthority,
                GetClientId(),
                GetRedirectUri(),
                AzureDevOpsConstants.AzureDevOpsDefaultScopes,
                userName);

            _context.Trace.WriteLineSecrets(
                $"Acquired Azure access token. Account='{result.AccountUpn}' Token='{{0}}'", new object[] { result.AccessToken });

            return(result);
        }