Example #1
0
        public async Task <Token> GetTokenAsync(OAuthOptions configuration, CancellationToken cancellationToken)
        {
            Token token = _tokenCache.GetToken(configuration);

            if (token != null && !token.IsAccessTokenExpired)
            {
                return(token);
            }

            // TODO: use refresh token if available
            token = await _client.GetTokenAsync(configuration, cancellationToken);

            _tokenCache.SaveToken(configuration, token);

            return(token);
        }
Example #2
0
        /// <summary>
        /// Gets the XML RequestSecurityTokenResponse from the ADFS Secure Token Service (STS).
        /// </summary>
        /// <param name="relyingParty"></param>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="stsUrl"></param>
        /// <param name="cached"></param>
        /// <returns></returns>
        public async Task <string> GetStsSamlTokenAsync(string relyingParty, string username, string password,
                                                        string stsUrl, bool cached = true)
        {
            string securityTokenResponse;

            SamlTokenParameters tokenParameters = new SamlTokenParameters(relyingParty, username, stsUrl);

            if (cached)
            {
                _logger.LogDebug("Checking for cached token");
                securityTokenResponse = _tokenCache.GetToken(tokenParameters);
                if (securityTokenResponse != null)
                {
                    _logger.LogTrace("Returning cached token");
                    return(securityTokenResponse);
                }

                _logger.LogTrace("Cached token not found");
            }

            // Makes a request that conforms with the WS-Trust standard to
            // the Security Token Service to get a SAML security token back

            // generate the WS-Trust security token request SOAP message
            string samlSoapRequest = CreateSamlSoapRequest(relyingParty, username, password, stsUrl);

            using var content = new StringContent(samlSoapRequest, Encoding.UTF8, "application/soap+xml");

            // tried this, it didn't help the slow requests, tokens are cached, so this isn't called on every request
            //using var handler = new SocketsHttpHandler { MaxConnectionsPerServer = 100 };
            //using var client = new HttpClient(handler);

            using var client = new HttpClient();
            var responseMessage = await client.PostAsync(stsUrl, content);

            // A valid response needs to be a SOAP element, Content Type = application/soap+xml
            // Invalid parameters can still return 200 but will be an HTML document

            var responseContent = await responseMessage.Content.ReadAsStringAsync();

            if (TryParseXml(responseContent, out XmlDocument soapResponse))
            {
                // <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue</a:Action>
                // <a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
                string action = SelectSoapAction(soapResponse);

                // The response could also be a SOAP Fault
                if (!responseMessage.IsSuccessStatusCode)
                {
                    if (action == "http://www.w3.org/2005/08/addressing/soap/fault")
                    {
                        /*
                         *    <s:Body>
                         *        <s:Fault>
                         *            <s:Code>
                         *                <s:Value>s:Sender</s:Value>
                         *                <s:Subcode>
                         *                    <s:Value>a:DestinationUnreachable</s:Value>
                         *                </s:Subcode>
                         *            </s:Code>
                         *            <s:Reason>
                         *                <s:Text xml:lang="en-US">The message with To 'https://ststest.gov.bc.ca/adfs/services/trust/2005/UsernameMixed2' cannot be processed at the receiver, due to an AddressFilter mismatch at the EndpointDispatcher.  Check that the sender and receiver's EndpointAddresses agree.</s:Text>
                         *            </s:Reason>
                         *        </s:Fault>
                         *    </s:Body>
                         *
                         */
                        XmlNamespaceManager namespaceManager = GetXmlNamespaceManager(soapResponse);

                        var code    = GetNodeInnerText(soapResponse, namespaceManager, "/s:Envelope/s:Body/s:Fault/s:Code/s:Value");
                        var subcode = GetNodeInnerText(soapResponse, namespaceManager, "/s:Envelope/s:Body/s:Fault/s:Code/s:Subcode");
                        var reason  = GetNodeInnerText(soapResponse, namespaceManager, "/s:Envelope/s:Body/s:Fault/s:Reason/s:Text");

                        _logger.LogWarning("Request for security token resulted in SOAP Fault and was not successful. STSUrl={StsUrl}, RelyingParty={RelyingParty}, Fault={@Fault}",
                                           stsUrl,
                                           relyingParty,
                                           new { Code = code, Subcode = subcode, Reason = reason });

                        throw new SamlAuthenticationException($"Request to {stsUrl} return HTTP Status {responseMessage.StatusCode}", code, subcode, reason);
                    }
                    else
                    {
                        _logger.LogWarning("Request for security token was not successful and was not a SOAP Fault. Returned {HttpStatusCode}, STS Url is {StsUrl} and Relying Party {RelyingParty}",
                                           responseMessage.StatusCode,
                                           stsUrl,
                                           relyingParty);
                    }

                    // TODO: change to better custom exception, read any possible content and log
                    throw new SamlAuthenticationException($"Request to {stsUrl} return HTTP Status {responseMessage.StatusCode}", string.Empty, string.Empty, string.Empty);
                }

                else if (action == "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue")
                {
                    securityTokenResponse = ExtractSoapBody(soapResponse);

                    // we don't need the lifetime if we are not caching
                    if (cached)
                    {
                        var lifetime = ExtractLifetimeTimestamps(soapResponse);
                        _logger.LogInformation("Security token has {@Lifetime} and will be cached until it expires", lifetime);

                        _tokenCache.SaveToken(tokenParameters, securityTokenResponse, lifetime.Expires);
                    }

                    return(securityTokenResponse);
                }
                else
                {
                    // TODO: throw custom exception related to SOAP fault
                    throw new SamlAuthenticationException($"Unexpected SOAP Action : {action}");
                }
            }
            else
            {
                // response could be a HTML error page
                // TODO: throw custom exception related to ADFS error page
                throw new SamlAuthenticationException("Invalid SOAP Response. Content is not valid XML");
            }
        }