private async Task <string> FetchTransferTokenAsync( WebAccountProvider accountProvider, WebAccount wamAcc, string clientId) { var transferTokenRequest = await _msaPlugin.CreateWebTokenRequestAsync( accountProvider, clientId, TransferTokenScopes) .ConfigureAwait(true); var transferResponse = await _wamProxy.RequestTokenForWindowAsync( _parentHandle, transferTokenRequest, wamAcc).ConfigureAwait(false); if (!transferResponse.ResponseStatus.IsSuccessStatus()) { var errorResp = WamAdapters.CreateMsalResponseFromWamResponse(transferResponse, _msaPlugin, _logger, true); throw new MsalServiceException( errorResp.Error, "Error fetching the MSA-PT transfer token - " + errorResp.ErrorDescription); } var resp = _msaPlugin.ParseSuccessfullWamResponse(transferResponse.ResponseData[0], out var properties); properties.TryGetValue("code", out string code); _logger.Info("WAM MSA-PT: Transfer token obtained? " + !string.IsNullOrEmpty(code)); return(code); }
public async Task FetchTransferToken_Silent_Async() { // Arrange using (MockHttpAndServiceBundle harness = CreateTestHarness()) { var msaProvider = new WebAccountProvider("id", "*****@*****.**", null); Client.Internal.Requests.AuthenticationRequestParameters requestParams = harness.CreateAuthenticationRequestParameters( TestConstants.AuthorityHomeTenant, validateAuthority: true); requestParams.AppConfig.WindowsBrokerOptions = new WindowsBrokerOptions() { MsaPassthrough = true }; var msaRequest = new WebTokenRequest(msaProvider); _msaPlugin.CreateWebTokenRequestAsync(msaProvider, requestParams, false, false, true, MsaPassthroughHandler.TransferTokenScopes) .Returns(Task.FromResult(msaRequest)); var webTokenResponseWrapper = Substitute.For <IWebTokenRequestResultWrapper>(); webTokenResponseWrapper.ResponseStatus.Returns(WebTokenRequestStatus.Success); WebAccount accountFromMsaProvider = new WebAccount(msaProvider, "*****@*****.**", WebAccountState.Connected); var webTokenResponse = new WebTokenResponse("transfer_token", accountFromMsaProvider); webTokenResponseWrapper.ResponseData.Returns(new List <WebTokenResponse>() { webTokenResponse }); _wamProxy.RequestTokenForWindowAsync(IntPtr.Zero, msaRequest, accountFromMsaProvider).Returns(webTokenResponseWrapper); _msaPlugin.ParseSuccessfullWamResponse(Arg.Any <WebTokenResponse>(), out Arg.Any <Dictionary <string, string> >()) .Returns(x => { x[1] = new Dictionary <string, string>(); (x[1] as Dictionary <string, string>).Add("code", "actual_transfer_token"); return(new MsalTokenResponse()); }); // Act var transferToken = await _msaPassthroughHandler.TryFetchTransferTokenSilentAsync( requestParams, accountFromMsaProvider) .ConfigureAwait(false); // Assert Assert.AreEqual("actual_transfer_token", transferToken); } }
public async Task ATS_AccountMatchingInWAM_MatchingHomeAccId_Async() { string homeAccId = $"{TestConstants.Uid}.{TestConstants.Utid}"; // Arrange using (var harness = CreateTestHarness()) { var wamAccountProvider = new WebAccountProvider("id", "*****@*****.**", null); var requestParams = harness.CreateAuthenticationRequestParameters(TestConstants.AuthorityConsumerTidTenant); // MSA var webAccount = new WebAccount(wamAccountProvider, "*****@*****.**", WebAccountState.Connected); IReadOnlyList <WebAccount> webAccounts = new List <WebAccount>() { webAccount }; var webTokenRequest = new WebTokenRequest(wamAccountProvider); var webTokenResponseWrapper = Substitute.For <IWebTokenRequestResultWrapper>(); webTokenResponseWrapper.ResponseStatus.Returns(WebTokenRequestStatus.Success); var webTokenResponse = new WebTokenResponse(); webTokenResponseWrapper.ResponseData.Returns(new List <WebTokenResponse>() { webTokenResponse }); _wamProxy.FindAllWebAccountsAsync(wamAccountProvider, TestConstants.ClientId).Returns(Task.FromResult(webAccounts)); // WAM can give MSAL the home account ID of a Wam account, which MSAL matches to a WAM account _msaPlugin.GetHomeAccountIdOrNull(webAccount).Returns(homeAccId); _msaPlugin.CreateWebTokenRequestAsync( wamAccountProvider, requestParams, isForceLoginPrompt: false, isAccountInWam: true, isInteractive: false) .Returns(Task.FromResult(webTokenRequest)); requestParams.Account = new Account( homeAccId, // matching in on home acc id "*****@*****.**", // matching is not on UPN null); // account does not have wam_id, might be coming directly from WAM var atsParams = new AcquireTokenSilentParameters(); _webAccountProviderFactory.GetAccountProviderAsync(null).ReturnsForAnyArgs(Task.FromResult(wamAccountProvider)); _wamProxy.GetTokenSilentlyAsync(webAccount, webTokenRequest). Returns(Task.FromResult(webTokenResponseWrapper)); _msaPlugin.ParseSuccessfullWamResponse(webTokenResponse, out _).Returns(_msalTokenResponse); // Act var result = await _wamBroker.AcquireTokenSilentAsync(requestParams, atsParams).ConfigureAwait(false); // Assert Assert.AreSame(_msalTokenResponse, result); } }
private string ExtractTransferToken( string clientId, IWebTokenRequestResultWrapper transferResponse, bool isInteractive) { if (!transferResponse.ResponseStatus.IsSuccessStatus()) { try { _ = WamAdapters.CreateMsalResponseFromWamResponse( transferResponse, _msaPlugin, clientId, _logger, isInteractive: isInteractive); } catch (MsalServiceException exception) { _logger.Warning( "WAM MSA-PT: could not get a transfer token, ussually this is because the " + "1st party app is configured for MSA-PT but not configured to login MSA users (signinaudience =2). " + "Error was: " + exception.ErrorCode + " " + exception.Message); } return(null); } _ = _msaPlugin.ParseSuccessfullWamResponse(transferResponse.ResponseData[0], out var properties); properties.TryGetValue("code", out string code); // Important: cannot use this WebAccount with the AAD provider WebAccount msaPtWebAccount = transferResponse.ResponseData[0].WebAccount; _logger.InfoPii($"Obtained a transfer token for {msaPtWebAccount.UserName} ? {code != null}", $"Obtained a transfer token? {code != null}"); return(code); }
internal static MsalTokenResponse CreateMsalResponseFromWamResponse( IWebTokenRequestResultWrapper wamResponse, IWamPlugin wamPlugin, ICoreLogger logger, bool isInteractive) { string internalErrorCode = null; string errorMessage; string errorCode; switch (wamResponse.ResponseStatus) { case WebTokenRequestStatus.Success: logger.Info("WAM response status success"); return(wamPlugin.ParseSuccessfullWamResponse(wamResponse.ResponseData[0], out _)); // Account Switch occurs when a login hint is passed to WAM but the user chooses a different account for login. // MSAL treats this as a success scenario case WebTokenRequestStatus.AccountSwitch: logger.Info("WAM response status account switch. Treating as success"); return(wamPlugin.ParseSuccessfullWamResponse(wamResponse.ResponseData[0], out _)); case WebTokenRequestStatus.UserInteractionRequired: errorCode = wamPlugin.MapTokenRequestError(wamResponse.ResponseStatus, wamResponse.ResponseError?.ErrorCode ?? 0, isInteractive); internalErrorCode = (wamResponse.ResponseError?.ErrorCode ?? 0).ToString(CultureInfo.InvariantCulture); errorMessage = WamErrorPrefix + $"Wam plugin {wamPlugin.GetType()}" + $" error code: {internalErrorCode}" + $" error: " + wamResponse.ResponseError?.ErrorMessage; break; case WebTokenRequestStatus.UserCancel: errorCode = MsalError.AuthenticationCanceledError; errorMessage = MsalErrorMessage.AuthenticationCanceled; break; case WebTokenRequestStatus.ProviderError: errorCode = wamPlugin.MapTokenRequestError(wamResponse.ResponseStatus, wamResponse.ResponseError?.ErrorCode ?? 0, isInteractive); errorMessage = WamErrorPrefix + " " + wamPlugin.GetType() + $" Error Code: {errorCode}." + $" Possible causes: no Internet connection or invalid redirect uri - please see https://aka.ms/msal-net-wam" + $" Details: " + wamResponse.ResponseError?.ErrorMessage; internalErrorCode = (wamResponse.ResponseError?.ErrorCode ?? 0).ToString(CultureInfo.InvariantCulture); break; default: errorCode = MsalError.UnknownBrokerError; internalErrorCode = wamResponse.ResponseError.ErrorCode.ToString(CultureInfo.InvariantCulture); errorMessage = $"Unknown WebTokenRequestStatus {wamResponse.ResponseStatus} (internal error code {internalErrorCode})"; break; } return(new MsalTokenResponse() { Error = errorCode, ErrorCodes = internalErrorCode != null ? new[] { internalErrorCode } : null, ErrorDescription = errorMessage }); }
public async Task ATS_AccountWithWamId_Async() { // Arrange using (MockHttpAndServiceBundle harness = CreateTestHarness()) { _webAccountProviderFactory.ClearReceivedCalls(); var wamAccountProvider = new WebAccountProvider("id", "*****@*****.**", null); var extraQP = new Dictionary <string, string>() { { "extraQp1", "extraVal1" }, { "instance_aware", "true" } }; var requestParams = harness.CreateAuthenticationRequestParameters( TestConstants.AuthorityHomeTenant, extraQueryParameters: extraQP, validateAuthority: true); // AAD requestParams.UserConfiguredAuthority = Authority.CreateAuthority("https://login.microsoftonline.com/organizations"); requestParams.Account = new Account( $"{TestConstants.Uid}.{TestConstants.Utid}", TestConstants.DisplayableId, null, new Dictionary <string, string>() { { TestConstants.ClientId, "wam_id_1" } }); // account has wam_id! var webAccount = new WebAccount(wamAccountProvider, "*****@*****.**", WebAccountState.Connected); var webTokenRequest = new WebTokenRequest(wamAccountProvider); var webTokenResponseWrapper = Substitute.For <IWebTokenRequestResultWrapper>(); webTokenResponseWrapper.ResponseStatus.Returns(WebTokenRequestStatus.Success); var webTokenResponse = new WebTokenResponse(); webTokenResponseWrapper.ResponseData.Returns(new List <WebTokenResponse>() { webTokenResponse }); _webAccountProviderFactory.GetAccountProviderAsync(null).ReturnsForAnyArgs(Task.FromResult(wamAccountProvider)); _wamProxy.FindAccountAsync(Arg.Any <WebAccountProvider>(), "wam_id_1").Returns(Task.FromResult(webAccount)); _aadPlugin.CreateWebTokenRequestAsync( wamAccountProvider, requestParams, isForceLoginPrompt: false, isAccountInWam: true, isInteractive: false) .Returns(Task.FromResult(webTokenRequest)); var atsParams = new AcquireTokenSilentParameters(); _wamProxy.GetTokenSilentlyAsync(webAccount, webTokenRequest). Returns(Task.FromResult(webTokenResponseWrapper)); _aadPlugin.ParseSuccessfullWamResponse(webTokenResponse, out _).Returns(_msalTokenResponse); // Act var result = await _wamBroker.AcquireTokenSilentAsync(requestParams, atsParams).ConfigureAwait(false); // Assert Assert.AreSame(_msalTokenResponse, result); Assert.AreEqual("yes", webTokenRequest.Properties["validateAuthority"]); Assert.AreEqual("extraVal1", webTokenRequest.Properties["extraQp1"]); // Although at the time of writing, MSAL does not support instance aware ... // WAM does support it but the param is different - discovery=home Assert.AreEqual("home", webTokenRequest.Properties["discover"]); Assert.AreEqual("https://login.microsoftonline.com/organizations/", webTokenRequest.Properties["authority"]); } }
internal static MsalTokenResponse CreateMsalResponseFromWamResponse( IWebTokenRequestResultWrapper wamResponse, IWamPlugin wamPlugin, string clientId, ICoreLogger logger, bool isInteractive) { string internalErrorCode = null; string errorMessage; string errorCode; Tuple <string, string, bool> error; switch (wamResponse.ResponseStatus) { case WebTokenRequestStatus.Success: logger.Info("WAM response status success"); return(wamPlugin.ParseSuccessfullWamResponse(wamResponse.ResponseData[0], out _)); // Account Switch occurs when a login hint is passed to WAM but the user chooses a different account for login. // MSAL treats this as a success scenario case WebTokenRequestStatus.AccountSwitch: logger.Info("WAM response status account switch. Treating as success"); return(wamPlugin.ParseSuccessfullWamResponse(wamResponse.ResponseData[0], out _)); case WebTokenRequestStatus.UserInteractionRequired: error = wamPlugin.MapTokenRequestError(wamResponse.ResponseStatus, wamResponse.ResponseError?.ErrorCode ?? 0, isInteractive); errorCode = error?.Item1; internalErrorCode = (wamResponse.ResponseError?.ErrorCode ?? 0).ToString(CultureInfo.InvariantCulture); errorMessage = WamErrorPrefix + $"Wam Plugin: {wamPlugin.GetType()}" + $" Error Code: {internalErrorCode}" + $" Error Message: {wamResponse.ResponseError?.ErrorMessage}" + $" Internal Error Code: {internalErrorCode}"; throw new MsalUiRequiredException(errorCode, errorMessage); case WebTokenRequestStatus.UserCancel: throw new MsalClientException(MsalError.AuthenticationCanceledError, MsalErrorMessage.AuthenticationCanceled); case WebTokenRequestStatus.ProviderError: error = wamPlugin.MapTokenRequestError(wamResponse.ResponseStatus, wamResponse.ResponseError?.ErrorCode ?? 0, isInteractive); errorCode = error?.Item1; internalErrorCode = (wamResponse.ResponseError?.ErrorCode ?? 0).ToString(CultureInfo.InvariantCulture); errorMessage = $"{WamErrorPrefix} {wamPlugin.GetType()} \n" + $" Error Code: {errorCode} \n" + $" Error Message: {error?.Item2} \n" + $" WAM Error Message: {wamResponse.ResponseError?.ErrorMessage} \n" + $" Internal Error Code: {internalErrorCode} \n" + $" Is Retryable: {error?.Item3} \n" + $" Possible causes: \n " + $"- Invalid redirect uri - ensure you have configured the following url in the AAD portal App Registration: {GetExpectedRedirectUri(clientId)} \n" + $"- No Internet connection \n" + $"Please see https://aka.ms/msal-net-wam for details about Windows Broker integration"; MsalServiceException serviceException = new MsalServiceException(errorCode, errorMessage); serviceException.IsRetryable = serviceException.IsRetryable || error.Item3; throw serviceException; default: internalErrorCode = wamResponse.ResponseError.ErrorCode.ToString(CultureInfo.InvariantCulture); errorMessage = $"Unknown WebTokenRequestStatus {wamResponse.ResponseStatus} (internal error code {internalErrorCode})"; throw new MsalServiceException(MsalError.UnknownBrokerError, errorMessage); } }