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