protected async override Task AcquireTokenCoreAsync()

in wwauth/Google.Solutions.WWAuth/Adapters/Adfs/AdfsSamlPostAdapter.cs [65:226]


        protected async override Task<ISubjectToken> AcquireTokenCoreAsync(
            CancellationToken cancellationToken)
        {
            this.Logger.Info("Using SAML endpoint {0}",
                this.Request.Destination);

            if (this.Request.SigningCertificate != null)
            {
                //
                // Try to access the private key to prevent the XML
                // signing process from throwing a non-descript
                // error later.
                //

                if (!this.Request.SigningCertificate.HasPrivateKey)
                {
                    throw new TokenAcquisitionException(
                        $"The certificate '{this.Request.SigningCertificate.Subject}' " +
                        $"(Thumbprint: {this.Request.SigningCertificate.Thumbprint}) " +
                        $"does not have a private key and cannot be used for signing");
                }

                try
                {
                    this.Request.SigningCertificate.GetRSAPrivateKey();
                }
                catch (CryptographicException e)
                {
                    throw new TokenAcquisitionException(
                        $"The private key for the " +
                        $"certificate '{this.Request.SigningCertificate.Subject}' " +
                        $"(Thumbprint: {this.Request.SigningCertificate.Thumbprint}) " +
                        $"is not RSA key, or it cannot be accessed by the current user",
                        e);
                }
            }

            using (var handler = new HttpClientHandler())
            {
                handler.Credentials = this.Credential;

                //
                // Initiate a SAML-POST request like a browser would.
                //
                using (var client = new HttpClient(handler))
                {
                    this.Logger.Info(
                        "Sending SAML AuthnRequest for relying party ID '{0}' " +
                            "and ACS '{1}' using IWA",
                        this.Request.RelyingPartyId,
                        this.Request.AssertionConsumerServiceUrl);

                    //
                    // AD FS occasionally fails requests for no good reason, so use
                    // a backoff/retry loop.
                    //
                    var backoff = new ExponentialBackOff();
                    for (int retries = 0; ; retries++)
                    {
                        var request = new HttpRequestMessage(
                            HttpMethod.Post,
                            $"{this.Request.Destination}?" +
                            $"SAMLRequest={WebUtility.UrlEncode(this.Request.ToString())}");

                        //
                        // Set a custom user-agent so that AD FS lets us use IWA.
                        //
                        request.Headers.Add("User-Agent", AdfsAdapterBase.IwaUserAgent);

                        using (var response = await client.SendAsync(
                                request,
                                HttpCompletionOption.ResponseHeadersRead,
                                cancellationToken)
                            .ConfigureAwait(false))
                        {
                            var responseType = response.Content?.Headers?.ContentType?.MediaType;

                            //
                            // If the sign-in succeeded, the resulting HTML page must
                            // have a form that includes a field named 'SAMLResponse'. Instead
                            // of POST-ing the form to the ACS, extract the field's value.
                            //
                            if (responseType == "text/html" &&
                                response.StatusCode == HttpStatusCode.OK)
                            {
                                var responseString = await response
                                    .Content
                                    .ReadAsStringAsync()
                                    .ConfigureAwait(false);

                                //
                                // Load HTML document and look for a form.
                                //
                                var html = new HtmlResponse(responseString);
                                if (html.Error != null)
                                {
                                    this.Logger.Error("Response: {0}", responseString);

                                    throw new TokenAcquisitionException(
                                        $"Authentication failed: {html.Error}. See logs for " +
                                        "full response message");
                                }
                                else if (html.IsSamlLoginForm)
                                {
                                    throw new TokenAcquisitionException(
                                        "The server returned a login form instead of performing a silent " +
                                        "login using Kerberos. Verify that:\n" +
                                        "- The authentication method 'Windows Authentication' is enabled\n" +
                                        "- You are connecting directly to AD FS, not via WAP\n" +
                                        $"- WiaSupportedUserAgents includes the " +
                                        $"agent '{AdfsAdapterBase.IwaUserAgent}'");
                                }
                                else if (html.IsSamlPostbackForm && string.IsNullOrEmpty(html.SamlResponse))
                                {
                                    this.Logger.Error("Response: {0}", responseString);

                                    throw new TokenAcquisitionException(
                                        "Failed to extract SAML assertion from postback form." +
                                        "See log for full response.");
                                }
                                else if (html.IsSamlPostbackForm && !string.IsNullOrEmpty(html.SamlResponse))
                                {
                                    this.Logger.Info("Acquiring SAML response succeeded");
                                    return AuthenticationResponse.Parse(html.SamlResponse);
                                }
                                else
                                {
                                    this.Logger.Error("Response: {0}", responseString);

                                    throw new TokenAcquisitionException(
                                        "The server responded with an unrecognized HTML response. " +
                                        "See log for full response.");
                                }
                            }
                            else if ((response.StatusCode == HttpStatusCode.BadRequest ||
                                        response.StatusCode == (HttpStatusCode)429) &&
                                    retries < backoff.MaxNumOfRetries)
                            {
                                //
                                // Retry.
                                //
                                this.Logger.Warning("Received Bad Request response, retrying");

                                await Task
                                    .Delay(backoff.DeltaBackOff)
                                    .ConfigureAwait(false);
                            }
                            else
                            {
                                //
                                // Unspecific error.
                                //
                                response.EnsureSuccessStatusCode();

                                throw new TokenAcquisitionException(
                                    $"Authentication failed: {response.StatusCode}");
                            }
                        }
                    }
                }
            }
        }