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);
            }
        }
Beispiel #3
0
        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);
            }
        }
Beispiel #4
0
        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
            });
        }
Beispiel #6
0
        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"]);
            }
        }
Beispiel #7
0
        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);
            }
        }