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}");
}
}
}
}
}
}