private static Exception ExtractErrorsFromTheResponse(HttpResponse response, ref bool shouldLogAsError)
        {
            Exception exceptionToThrow = null;

            // In cases where the end-point is not found (404) response.body will be empty.
            if (string.IsNullOrWhiteSpace(response.Body))
            {
                return(null);
            }

            var msalTokenResponse = JsonHelper.DeserializeFromJson <MsalTokenResponse>(response.Body);

            if (msalTokenResponse?.Error == null)
            {
                return(null);
            }

            exceptionToThrow = MsalServiceExceptionFactory.FromHttpResponse(
                msalTokenResponse.Error,
                msalTokenResponse.ErrorDescription,
                response);

            // For device code flow, AuthorizationPending can occur a lot while waiting
            // for the user to auth via browser and this causes a lot of error noise in the logs.
            // So suppress this particular case to an Info so we still see the data but don't
            // log it as an error since it's expected behavior while waiting for the user.
            if (string.Compare(msalTokenResponse.Error, OAuth2Error.AuthorizationPending,
                               StringComparison.OrdinalIgnoreCase) == 0)
            {
                shouldLogAsError = false;
            }

            return(exceptionToThrow);
        }
        private static void ValidateClassification(
            string suberror,
            UiRequiredExceptionClassification expectedClassification,
            bool expectUiRequiredException = true)
        {
            var newJsonError = JsonError.Replace("some_suberror", suberror);

            // Arrange
            HttpResponse httpResponse = new HttpResponse()
            {
                Body       = newJsonError,
                StatusCode = HttpStatusCode.BadRequest, // 400
            };

            // Act
            var msalException = MsalServiceExceptionFactory.FromHttpResponse(ExCode, ExMessage, httpResponse);

            Assert.AreEqual(ExCode, msalException.ErrorCode);
            Assert.AreEqual(ExMessage, msalException.Message);
            Assert.AreEqual("some_claims", msalException.Claims);
            Assert.AreEqual("6347d33d-941a-4c35-9912-a9cf54fb1b3e", msalException.CorrelationId);
            Assert.AreEqual(suberror ?? "", msalException.SubError);


            if (expectUiRequiredException)
            {
                Assert.AreEqual(expectedClassification, (msalException as MsalUiRequiredException).Classification);
            }

            ValidateExceptionProductInformation(msalException);
        }
        public void MsalServiceException_FromHttpResponse()
        {
            // Arrange
            string responseBody   = JsonError;
            var    statusCode     = HttpStatusCode.BadRequest;
            var    retryAfterSpan = new TimeSpan(3600);

            var httpResponse = new HttpResponseMessage(statusCode)
            {
                Content = new StringContent(responseBody)
            };

            httpResponse.Headers.RetryAfter = new RetryConditionHeaderValue(retryAfterSpan);
            HttpResponse msalhttpResponse = HttpManager.CreateResponseAsync(httpResponse).Result;

            // Act
            var msalException = MsalServiceExceptionFactory.FromHttpResponse(ExCode, ExMessage, msalhttpResponse);

            // Assert
            var msalServiceException = msalException as MsalServiceException;

            Assert.AreEqual(ExCode, msalServiceException.ErrorCode);
            Assert.AreEqual(responseBody, msalServiceException.ResponseBody);
            Assert.AreEqual(ExMessage, msalServiceException.Message);
            Assert.AreEqual((int)statusCode, msalServiceException.StatusCode);
            Assert.AreEqual("some_suberror", msalServiceException.SubError);

            Assert.AreEqual(retryAfterSpan, msalServiceException.Headers.RetryAfter.Delta);
            ValidateExceptionProductInformation(msalException);
        }
        public void ServiceException_ToString()
        {
            // Arrange
            const string jsonError = @"{ ""error"":""invalid_tenant"", ""suberror"":""MySuberror"",
            ""claims"":""some_claims"",
            ""error_description"":""AADSTS90002: Tenant 'x' not found. "", ""error_codes"":[90002],""timestamp"":""2019-01-28 14:16:04Z"",
            ""trace_id"":""43f14373-8d7d-466e-a5f1-6e3889291e00"",
            ""correlation_id"":""6347d33d-941a-4c35-9912-a9cf54fb1b3e""}";

            string innerExMsg     = "innerExMsg";
            var    innerException = new NotImplementedException(innerExMsg);

            HttpResponse httpResponse = new HttpResponse()
            {
                Body       = jsonError,
                StatusCode = HttpStatusCode.BadRequest, // 400
            };

            // Act
            var ex = MsalServiceExceptionFactory.FromHttpResponse("errCode",
                                                                  "errMessage", httpResponse, innerException);

            // Assert
            Assert.IsTrue(ex.ToString().Contains("errCode"));
            Assert.IsTrue(ex.ToString().Contains("errMessage"));
            Assert.IsTrue(ex.ToString().Contains("innerExMsg"));
            Assert.IsTrue(ex.ToString().Contains("invalid_tenant"));
            Assert.IsTrue(ex.ToString().Contains("MySuberror"));
            Assert.IsTrue(ex.ToString().Contains("some_claims"));
            Assert.IsTrue(ex.ToString().Contains("AADSTS90002"));
            Assert.IsFalse(ex is MsalUiRequiredException);

            ValidateExceptionProductInformation(ex);
        }
        /// <inheritdoc/>
        public async Task <MexDocument> GetMexDocumentAsync(string federationMetadataUrl, RequestContext requestContext)
        {
            IDictionary <string, string> msalIdParams = MsalIdHelper.GetMsalIdParameters(requestContext.Logger);

            var          uri          = new UriBuilder(federationMetadataUrl);
            HttpResponse httpResponse = await _httpManager.SendGetAsync(
                uri.Uri,
                msalIdParams,
                requestContext.Logger,
                cancellationToken : requestContext.UserCancellationToken).ConfigureAwait(false);

            if (httpResponse.StatusCode != System.Net.HttpStatusCode.OK)
            {
                string message = string.Format(CultureInfo.CurrentCulture,
                                               MsalErrorMessage.HttpRequestUnsuccessful + "See https://aka.ms/msal-net-ropc for more information. ",
                                               (int)httpResponse.StatusCode, httpResponse.StatusCode);

                throw MsalServiceExceptionFactory.FromHttpResponse(
                          MsalError.AccessingWsMetadataExchangeFailed,
                          message,
                          httpResponse);
            }

            var mexDoc = new MexDocument(httpResponse.Body);

            requestContext.Logger.InfoPii(
                $"MEX document fetched and parsed from '{federationMetadataUrl}'",
                "Fetched and parsed MEX");

            return(mexDoc);
        }
        public async Task ValidateAuthorityAsync(
            AuthorityInfo authorityInfo,
            RequestContext requestContext)
        {
            if (authorityInfo.ValidateAuthority)
            {
                string resource     = string.Format(CultureInfo.InvariantCulture, "https://{0}", authorityInfo.Host);
                string webFingerUrl = Constants.FormatAdfsWebFingerUrl(authorityInfo.Host, resource);


                Http.HttpResponse httpResponse = await _serviceBundle.HttpManager.SendGetAsync(
                    new Uri(webFingerUrl),
                    null,
                    requestContext.Logger,
                    cancellationToken : requestContext.UserCancellationToken).ConfigureAwait(false);

                if (httpResponse.StatusCode != HttpStatusCode.OK)
                {
                    throw MsalServiceExceptionFactory.FromHttpResponse(
                              MsalError.InvalidAuthority,
                              MsalErrorMessage.AuthorityValidationFailed,
                              httpResponse);
                }

                AdfsWebFingerResponse wfr = OAuth2Client.CreateResponse <AdfsWebFingerResponse>(httpResponse, requestContext);
                if (wfr.Links.FirstOrDefault(
                        a => a.Rel.Equals(Constants.DefaultRealm, StringComparison.OrdinalIgnoreCase) &&
                        a.Href.Equals(resource)) == null)
                {
                    throw new MsalClientException(
                              MsalError.InvalidAuthority,
                              MsalErrorMessage.InvalidAuthorityOpenId);
                }
            }
        }
示例#7
0
        /// <inheritdoc/>
        public async Task <MexDocument> GetMexDocumentAsync(string federationMetadataUrl, RequestContext requestContext)
        {
            var          uri          = new UriBuilder(federationMetadataUrl);
            HttpResponse httpResponse = await _httpManager.SendGetAsync(uri.Uri, null, requestContext.Logger).ConfigureAwait(false);

            if (httpResponse.StatusCode != System.Net.HttpStatusCode.OK)
            {
                string message = string.Format(CultureInfo.CurrentCulture,
                                               MsalErrorMessage.HttpRequestUnsuccessful,
                                               (int)httpResponse.StatusCode, httpResponse.StatusCode);

                throw MsalServiceExceptionFactory.FromHttpResponse(
                          MsalError.AccessingWsMetadataExchangeFailed,
                          message,
                          httpResponse);
            }

            var mexDoc = new MexDocument(httpResponse.Body);

            requestContext.Logger.InfoPii(
                $"MEX document fetched and parsed from '{federationMetadataUrl}'",
                "Fetched and parsed MEX");

            return(mexDoc);
        }
        /// <inheritdoc/>
        public async Task <WsTrustResponse> GetWsTrustResponseAsync(
            WsTrustEndpoint wsTrustEndpoint,
            string wsTrustRequest,
            RequestContext requestContext)
        {
            var headers = new Dictionary <string, string>
            {
                { "SOAPAction", (wsTrustEndpoint.Version == WsTrustVersion.WsTrust2005) ? XmlNamespace.Issue2005.ToString() : XmlNamespace.Issue.ToString() }
            };

            var body = new StringContent(
                wsTrustRequest,
                Encoding.UTF8, "application/soap+xml");

            HttpResponse resp = await _httpManager.SendPostForceResponseAsync(wsTrustEndpoint.Uri,
                                                                              headers,
                                                                              body,
                                                                              requestContext.Logger,
                                                                              cancellationToken : requestContext.UserCancellationToken).ConfigureAwait(false);

            if (resp.StatusCode != System.Net.HttpStatusCode.OK)
            {
                string errorMessage = null;
                try
                {
                    errorMessage = WsTrustResponse.ReadErrorResponse(XDocument.Parse(resp.Body, LoadOptions.None), requestContext);
                }
                catch (System.Xml.XmlException)
                {
                    errorMessage = resp.Body;
                }

                string message = string.Format(
                    CultureInfo.CurrentCulture,
                    MsalErrorMessage.FederatedServiceReturnedErrorTemplate,
                    wsTrustEndpoint.Uri,
                    errorMessage);

                throw MsalServiceExceptionFactory.FromHttpResponse(
                          MsalError.FederatedServiceReturnedError,
                          message,
                          resp);
            }

            try
            {
                return(WsTrustResponse.CreateFromResponse(resp.Body, wsTrustEndpoint.Version));
            }
            catch (System.Xml.XmlException ex)
            {
                string message = string.Format(
                    CultureInfo.CurrentCulture,
                    MsalErrorMessage.ParsingWsTrustResponseFailedErrorTemplate,
                    wsTrustEndpoint.Uri,
                    resp.Body);

                throw new MsalClientException(
                          MsalError.ParsingWsTrustResponseFailed, message, ex);
            }
        }
        public async Task <UserRealmDiscoveryResponse> GetUserRealmAsync(
            string userRealmUriPrefix,
            string userName,
            RequestContext requestContext)
        {
            requestContext.Logger.Info("Sending request to userrealm endpoint. ");

            IDictionary <string, string> msalIdParams = MsalIdHelper.GetMsalIdParameters(requestContext.Logger);

            var uri = new UriBuilder(userRealmUriPrefix + userName + "?api-version=1.0").Uri;

            var httpResponse = await _httpManager.SendGetAsync(
                uri,
                msalIdParams,
                requestContext.Logger,
                cancellationToken : requestContext.UserCancellationToken).ConfigureAwait(false);

            if (httpResponse.StatusCode == System.Net.HttpStatusCode.OK)
            {
                return(JsonHelper.DeserializeFromJson <UserRealmDiscoveryResponse>(httpResponse.Body));
            }

            string message = string.Format(CultureInfo.CurrentCulture,
                                           MsalErrorMessage.HttpRequestUnsuccessful,
                                           (int)httpResponse.StatusCode, httpResponse.StatusCode);

            throw MsalServiceExceptionFactory.FromHttpResponse(
                      MsalError.UserRealmDiscoveryFailed,
                      message,
                      httpResponse);
        }
        private static void ThrowServerException(HttpResponse response, RequestContext requestContext)
        {
            bool shouldLogAsError = true;

            var httpErrorCodeMessage = string.Format(CultureInfo.InvariantCulture, "HttpStatusCode: {0}: {1}", (int)response.StatusCode, response.StatusCode.ToString());

            requestContext.Logger.Info(httpErrorCodeMessage);

            Exception exceptionToThrow;

            try
            {
                exceptionToThrow = ExtractErrorsFromTheResponse(response, ref shouldLogAsError);
            }
            catch (JsonException) // in the rare case we get an error response we cannot deserialize
            {
                exceptionToThrow = MsalServiceExceptionFactory.FromHttpResponse(
                    MsalError.NonParsableOAuthError,
                    MsalErrorMessage.NonParsableOAuthError,
                    response);
            }
            catch (Exception ex)
            {
                exceptionToThrow = MsalServiceExceptionFactory.FromHttpResponse(
                    MsalError.UnknownError,
                    response.Body,
                    response,
                    ex);
            }

            if (exceptionToThrow == null)
            {
                exceptionToThrow = MsalServiceExceptionFactory.FromHttpResponse(
                    response.StatusCode == HttpStatusCode.NotFound
                        ? MsalError.HttpStatusNotFound
                        : MsalError.HttpStatusCodeNotOk,
                    httpErrorCodeMessage,
                    response);
            }

            if (shouldLogAsError)
            {
                requestContext.Logger.ErrorPii(exceptionToThrow);
            }
            else
            {
                requestContext.Logger.InfoPii(exceptionToThrow);
            }

            throw exceptionToThrow;
        }
        public void MsalServiceException_HttpResponse_OAuthResponse()
        {
            // Arrange
            int    statusCode     = 400;
            string innerExMsg     = "innerExMsg";
            var    innerException = new NotImplementedException(innerExMsg);

            HttpResponse httpResponse = new HttpResponse()
            {
                Body       = JsonError,
                StatusCode = HttpStatusCode.BadRequest, // 400
            };

            // Act
            var msalException = MsalServiceExceptionFactory.FromHttpResponse(ExCode, ExMessage, httpResponse, innerException);

            // Assert
            var msalServiceException = msalException as MsalServiceException;

            Assert.AreEqual(innerException, msalServiceException.InnerException);
            Assert.AreEqual(ExCode, msalServiceException.ErrorCode);
            Assert.AreEqual(JsonError, msalServiceException.ResponseBody);
            Assert.AreEqual(ExMessage, msalServiceException.Message);
            Assert.AreEqual(statusCode, msalServiceException.StatusCode);

            Assert.AreEqual("some_claims", msalServiceException.Claims);
            Assert.AreEqual("6347d33d-941a-4c35-9912-a9cf54fb1b3e", msalServiceException.CorrelationId);
            Assert.AreEqual("some_suberror", msalServiceException.SubError);
            ValidateExceptionProductInformation(msalException);

            // Act
            string piiMessage = MsalLogger.GetPiiScrubbedExceptionDetails(msalException);

            // Assert
            Assert.IsFalse(string.IsNullOrEmpty(piiMessage));
            Assert.IsTrue(
                piiMessage.Contains(typeof(MsalUiRequiredException).Name),
                "The pii message should contain the exception type");
            Assert.IsTrue(
                piiMessage.Contains(typeof(NotImplementedException).Name),
                "The pii message should have the inner exception type");
            Assert.IsTrue(piiMessage.Contains(ExCode));
            Assert.IsTrue(piiMessage.Contains("6347d33d-941a-4c35-9912-a9cf54fb1b3e")); // Correlation Id

            Assert.IsFalse(piiMessage.Contains(ExMessage));
            Assert.IsFalse(piiMessage.Contains(innerExMsg));
        }
        public void MsalServiceException_Throws_MsalUIRequiredException_When_Throttled()
        {
            // Arrange
            HttpResponse httpResponse = new HttpResponse()
            {
                Body       = JsonError.Replace("AADSTS90002", Constants.AadThrottledErrorCode),
                StatusCode = HttpStatusCode.BadRequest, // 400
            };

            // Act
            var msalException = MsalServiceExceptionFactory.FromHttpResponse(ExCode, ExMessage, httpResponse);

            // Assert
            Assert.AreEqual(typeof(MsalUiRequiredException), msalException.GetType());
            Assert.AreEqual(MsalErrorMessage.AadThrottledError, msalException.Message);
            ValidateExceptionProductInformation(msalException);
        }
示例#13
0
        private static MsalServiceException ExtractErrorsFromTheResponse(HttpResponse response, ref bool shouldLogAsError)
        {
            // In cases where the end-point is not found (404) response.body will be empty.
            if (string.IsNullOrWhiteSpace(response.Body))
            {
                return(null);
            }

            MsalTokenResponse msalTokenResponse = null;

            try
            {
                msalTokenResponse = JsonHelper.DeserializeFromJson <MsalTokenResponse>(response.Body);
            }
            catch (JsonException)
            {
                //Throttled responses for client credential flows do not have a parsable response.
                if ((int)response.StatusCode == 429)
                {
                    return(MsalServiceExceptionFactory.FromThrottledAuthenticationResponse(response));
                }

                throw;
            }

            if (msalTokenResponse?.Error == null)
            {
                return(null);
            }

            // For device code flow, AuthorizationPending can occur a lot while waiting
            // for the user to auth via browser and this causes a lot of error noise in the logs.
            // So suppress this particular case to an Info so we still see the data but don't
            // log it as an error since it's expected behavior while waiting for the user.
            if (string.Compare(msalTokenResponse.Error, OAuth2Error.AuthorizationPending,
                               StringComparison.OrdinalIgnoreCase) == 0)
            {
                shouldLogAsError = false;
            }

            return(MsalServiceExceptionFactory.FromHttpResponse(
                       msalTokenResponse.Error,
                       msalTokenResponse.ErrorDescription,
                       response));
        }
        public void InvalidClientException_IsRepackaged()
        {
            // Arrange
            HttpResponse httpResponse = new HttpResponse()
            {
                Body       = JsonError.Replace("invalid_grant", "invalid_client"),
                StatusCode = HttpStatusCode.BadRequest, // 400
            };

            // Act
            var msalException = MsalServiceExceptionFactory.FromHttpResponse(ExCode, ExMessage, httpResponse);

            // Assert
            var msalServiceException = msalException as MsalServiceException;

            Assert.AreEqual(MsalError.InvalidClient, msalServiceException.ErrorCode);
            Assert.IsTrue(msalServiceException.Message.Contains(MsalErrorMessage.InvalidClient));
            ValidateExceptionProductInformation(msalException);
        }
示例#15
0
        public void MsalUiRequiredException_Oauth2Response()
        {
            // Arrange
            HttpResponse httpResponse = new HttpResponse()
            {
                Body       = JsonError,
                StatusCode = HttpStatusCode.BadRequest, // 400
            };

            // Act
            var msalException = MsalServiceExceptionFactory.FromHttpResponse(ExCode, ExMessage, httpResponse);


            // Assert
            var msalServiceException = msalException as MsalServiceException;

            Assert.AreEqual(ExCode, msalServiceException.ErrorCode);
            Assert.AreEqual(ExMessage, msalServiceException.Message);
            Assert.AreEqual("some_claims", msalServiceException.Claims);
            Assert.AreEqual("6347d33d-941a-4c35-9912-a9cf54fb1b3e", msalServiceException.CorrelationId);
            Assert.AreEqual("some_suberror", msalServiceException.SubError);
        }
        private async Task <HttpResponse> ExecuteWithRetryAsync(
            Uri endpoint,
            IDictionary <string, string> headers,
            HttpContent body,
            HttpMethod method,
            ICoreLogger logger,
            bool doNotThrow = false,
            bool retry      = true,
            CancellationToken cancellationToken = default)
        {
            Exception    timeoutException = null;
            bool         isRetryable      = false;
            bool         is5xxError       = false;
            HttpResponse response         = null;

            try
            {
                HttpContent clonedBody = body;
                if (body != null)
                {
                    // Since HttpContent would be disposed by underlying client.SendAsync(),
                    // we duplicate it so that we will have a copy in case we would need to retry
                    clonedBody = await CloneHttpContentAsync(body).ConfigureAwait(false);
                }

                response = await ExecuteAsync(endpoint, headers, clonedBody, method, cancellationToken).ConfigureAwait(false);

                if (response.StatusCode == HttpStatusCode.OK)
                {
                    return(response);
                }

                logger.Info(string.Format(CultureInfo.InvariantCulture,
                                          MsalErrorMessage.HttpRequestUnsuccessful,
                                          (int)response.StatusCode, response.StatusCode));


                is5xxError  = (int)response.StatusCode >= 500 && (int)response.StatusCode < 600;
                isRetryable = is5xxError && !HasRetryAfterHeader(response);
            }
            catch (TaskCanceledException exception)
            {
                logger.Error("The HTTP request failed or it was canceled. " + exception.Message);
                isRetryable = true;

                if (cancellationToken.IsCancellationRequested)
                {
                    isRetryable = false;
                }

                timeoutException = exception;
            }

            if (isRetryable && retry)
            {
                logger.Info("Retrying one more time..");
                await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);

                return(await ExecuteWithRetryAsync(
                           endpoint,
                           headers,
                           body,
                           method,
                           logger,
                           doNotThrow,
                           retry : false).ConfigureAwait(false));
            }

            logger.Warning("Request retry failed.");
            if (timeoutException != null)
            {
                throw new MsalServiceException(
                          MsalError.RequestTimeout,
                          "Request to the endpoint timed out.",
                          timeoutException);
            }

            if (doNotThrow)
            {
                return(response);
            }

            if (is5xxError)
            {
                throw MsalServiceExceptionFactory.FromHttpResponse(
                          MsalError.ServiceNotAvailable,
                          "Service is unavailable to process the request",
                          response);
            }

            return(response);
        }