Beispiel #1
0
        // only works for AAD plugin. MSA plugin does not allow for privacy reasons
        private async Task <MsalTokenResponse> AcquireInteractiveWithAadBrowserAsync(
            AuthenticationRequestParameters authenticationRequestParameters,
            Prompt msalPrompt)
        {
            var provider = await _webAccountProviderFactory.GetAccountProviderAsync(
                authenticationRequestParameters.Authority.TenantId).ConfigureAwait(true);

            WebTokenRequest webTokenRequest = await _aadPlugin.CreateWebTokenRequestAsync(
                provider,
                authenticationRequestParameters,
                isForceLoginPrompt : true,
                isInteractive : true,
                isAccountInWam : false)
                                              .ConfigureAwait(false);

            WamAdapters.AddMsalParamsToRequest(authenticationRequestParameters, webTokenRequest, _logger);
            AddPromptToRequest(msalPrompt, true, webTokenRequest);

            var wamResult = await _wamProxy.RequestTokenForWindowAsync(
                _parentHandle,
                webTokenRequest).ConfigureAwait(false);

            return(WamAdapters.CreateMsalResponseFromWamResponse(
                       wamResult,
                       _aadPlugin,
                       authenticationRequestParameters.AppConfig.ClientId,
                       _logger,
                       isInteractive: true));
        }
        /// <summary>
        /// The algorithm here is much more complex in order to workaround a limitation in the AAD plugin's
        /// handling of guest accounts:
        ///
        /// 1. Read the accounts from WAM.AADPlugin
        /// 2. For each account, we need to find its home_account_id as the one from WAM may not be correct
        /// 3. If we can find a cached account with the same LocalAccountId or UPN, use it
        /// 4. If not, make a simple silent token request and use the client info provided
        /// </summary>
        public async Task <IReadOnlyList <IAccount> > GetAccountsAsync(
            string clientId,
            AuthorityInfo authorityInfo,
            Cache.ICacheSessionManager cacheSessionManager,
            Instance.Discovery.IInstanceDiscoveryManager instanceDiscoveryManager)
        {
            var webAccountProvider = await _webAccountProviderFactory.GetAccountProviderAsync("organizations").ConfigureAwait(false);

            var wamAccounts = await _wamProxy.FindAllWebAccountsAsync(webAccountProvider, clientId).ConfigureAwait(false);

            if (wamAccounts.Count > 0)
            {
                var webAccountEnvs = wamAccounts
                                     .Select(w =>
                {
                    _wamProxy.TryGetAccountProperty(w, "Authority", out string accountAuthority);
                    if (accountAuthority != null)
                    {
                        return((new Uri(accountAuthority)).Host);
                    }
                    else
                    {
                        _logger.WarningPii(
                            $"[WAM AAD Provider] Could not convert the WAM account {w.UserName} (id: {w.Id}) to an MSAL account because the Authority could not be found",
                            $"[WAM AAD Provider] Could not convert the WAM account {w.Id} to an MSAL account because the Authority could not be found");

                        return(null);
                    }
                })
                                     .Where(a => a != null);

                var instanceMetadata = await instanceDiscoveryManager.GetMetadataEntryTryAvoidNetworkAsync(authorityInfo, webAccountEnvs, cacheSessionManager.RequestContext)
                                       .ConfigureAwait(false);

                var accountsFromCache = await cacheSessionManager.GetAccountsAsync().ConfigureAwait(false);

                var msalAccountTasks = wamAccounts
                                       .Select(
                    async webAcc =>
                    await ConvertToMsalAccountOrNullAsync(
                        clientId,
                        webAcc,
                        instanceMetadata,
                        cacheSessionManager,
                        accountsFromCache).ConfigureAwait(false));

                var msalAccounts = (await Task.WhenAll(msalAccountTasks).ConfigureAwait(false)).Where(a => a != null).ToList();

                _logger.Info($"[WAM AAD Provider] GetAccountsAsync converted {msalAccounts.Count} accounts from {wamAccounts.Count} WAM accounts");
                return(msalAccounts);
            }

            _logger.Info("[WAM AAD provider] No accounts found.");
            return(Array.Empty <IAccount>());
        }
Beispiel #3
0
        private async Task <WebAccountProvider> GetProviderAsync(
            string authority,
            bool isMsa)
        {
            WebAccountProvider provider;
            string             tenantOrAuthority = isMsa ? "consumers" : authority;

            provider = await _webAccountProviderFactory.GetAccountProviderAsync(tenantOrAuthority)
                       .ConfigureAwait(false);

            return(provider);
        }
Beispiel #4
0
        /// <summary>
        /// In WAM, AcquireTokenInteractive is always associated to an account. WAM also allows for an "account picker" to be displayed,
        /// which is similar to the EVO browser experience, allowing the user to add an account or use an existing one.
        ///
        /// MSAL does not have a concept of account picker so MSAL.AccquireTokenInteractive will:
        ///
        /// 1. Call WAM.AccountPicker if an IAccount (or possibly login_hint) is not configured
        /// 2. Figure out the WAM.AccountID associated to the MSAL.Account
        /// 3. Call WAM.AcquireTokenInteractive with the WAM.AccountID
        ///
        /// To make matters more complicated, WAM has 2 plugins - AAD and MSA. With AAD plugin,
        /// it is possible to list all WAM accounts and find the one associated to the MSAL account.
        /// However, MSA plugin does NOT allow listing of accounts, and the only way to figure out the
        /// WAM account ID is to use the account picker. This makes AcquireTokenSilent impossible for MSA,
        /// because we would not be able to map an Msal.Account to a WAM.Account. To overcome this,
        /// we save the WAM.AccountID in MSAL's cache.
        /// </summary>
        public async Task <MsalTokenResponse> AcquireTokenInteractiveAsync(
            AuthenticationRequestParameters authenticationRequestParameters,
            AcquireTokenInteractiveParameters acquireTokenInteractiveParameters)
        {
            if (_synchronizationContext == null)
            {
                throw new MsalClientException(
                          MsalError.WamUiThread,
                          "AcquireTokenInteractive with broker must be called from the UI thread when using WAM. " +
                          "Note that console applications are not currently supported in conjuction with WAM." + ErrorMessageSuffix);
            }


            if (authenticationRequestParameters.Account != null ||
                !string.IsNullOrEmpty(authenticationRequestParameters.LoginHint))
            {
                bool isMsa = IsMsaRequest(
                    authenticationRequestParameters.Authority,
                    authenticationRequestParameters?.Account?.HomeAccountId?.TenantId, // TODO: we could furher optimize here by searching for an account based on UPN
                    IsMsaPassthrough(authenticationRequestParameters));

                IWamPlugin         wamPlugin = isMsa ? _msaPlugin : _aadPlugin;
                WebAccountProvider provider;
                if (isMsa)
                {
                    provider = await _webAccountProviderFactory.GetAccountProviderAsync("consumers").ConfigureAwait(false);
                }
                else
                {
                    provider = await _webAccountProviderFactory.GetAccountProviderAsync(authenticationRequestParameters.Authority.AuthorityInfo.CanonicalAuthority)
                               .ConfigureAwait(false);
                }

                var wamAccount = await FindWamAccountForMsalAccountAsync(
                    provider,
                    wamPlugin,
                    authenticationRequestParameters.Account,
                    authenticationRequestParameters.LoginHint,
                    authenticationRequestParameters.ClientId).ConfigureAwait(false);

                if (wamAccount != null)
                {
                    var wamResult = await AcquireInteractiveWithoutPickerAsync(
                        authenticationRequestParameters,
                        acquireTokenInteractiveParameters.Prompt,
                        wamPlugin,
                        provider,
                        wamAccount)
                                    .ConfigureAwait(false);

                    return(CreateMsalTokenResponse(wamResult, wamPlugin, isInteractive: true));
                }
            }

            return(await AcquireInteractiveWithPickerAsync(
                       authenticationRequestParameters)
                   .ConfigureAwait(false));
        }
Beispiel #5
0
        /// <summary>
        /// Generally the MSA plugin will NOT return the accounts back to the app. This is due
        /// to privacy concerns. However, some test apps are allowed to do this, hence the code.
        /// Normal 1st and 3rd party apps must use AcquireTokenInteractive to login first, and then MSAL will
        /// save the account for later use.
        /// </summary>
        public async Task <IEnumerable <IAccount> > GetAccountsAsync(string clientID)
        {
            var webAccounProvider = await _webAccountProviderFactory.GetAccountProviderAsync("consumers").ConfigureAwait(false);

            var webAccounts = await _wamProxy.FindAllWebAccountsAsync(webAccounProvider, clientID).ConfigureAwait(false);

            var msalAccounts = webAccounts
                               .Select(webAcc => ConvertToMsalAccountOrNull(webAcc))
                               .Where(a => a != null)
                               .ToList();

            _logger.Info($"[WAM MSA Plugin] GetAccountsAsync converted {webAccounts.Count()} MSAL accounts");
            return(msalAccounts);
        }
Beispiel #6
0
        public async Task GetAccounts_WamAccounts_NoAuthority_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = CreateTestHarness())
            {
                var wamAccountProvider = new WebAccountProvider("id", "*****@*****.**", null);
                _webAccountProviderFactory.GetAccountProviderAsync("organizations").Returns(wamAccountProvider);
                var wamAccount = new WebAccount(wamAccountProvider, "*****@*****.**", WebAccountState.Connected);
                // no authority ... skip these accounts
                _wamProxy.FindAllWebAccountsAsync(wamAccountProvider, TestConstants.ClientId).Returns(new[] { wamAccount });

                // Act
                var accounts = await _aadPlugin.GetAccountsAsync(
                    TestConstants.ClientId,
                    AuthorityInfo.FromAuthorityUri(TestConstants.AuthorityCommonTenant, true),
                    _cacheSessionManager,
                    _instanceDiscoveryManager).ConfigureAwait(false);

                // Assert
                Assert.AreEqual(0, accounts.Count());
            }
        }
Beispiel #7
0
        /// <summary>
        /// Generally the MSA plugin will NOT return the accounts back to the app. This is due
        /// to privacy concerns. However, some test apps are allowed to do this, hence the code.
        /// Normal 1st and 3rd party apps must use AcquireTokenInteractive to login first, and then MSAL will
        /// save the account for later use.
        /// </summary>
        public async Task <IReadOnlyList <IAccount> > GetAccountsAsync(
            string clientID,
            string authority,
            ICacheSessionManager cacheSessionManager,
            IInstanceDiscoveryManager instanceDiscoveryManager)
        {
            var webAccounProvider = await _webAccountProviderFactory.GetAccountProviderAsync("consumers").ConfigureAwait(false);

            var webAccounts = await _wamProxy.FindAllWebAccountsAsync(webAccounProvider, clientID).ConfigureAwait(false);

            var msalAccounts = webAccounts
                               .Select(webAcc => ConvertToMsalAccountOrNull(webAcc))
                               .Where(a => a != null)
                               .ToList();

            _logger.Info($"[WAM MSA Plugin] GetAccountsAsync converted {webAccounts.Count} MSAL accounts");
            return(msalAccounts);
        }
        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.ParseSuccesfullWamResponse(webTokenResponse).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"]);
            }
        }
        private async Task <MsalTokenResponse> AcquireInteractiveWithPickerAsync(
            AuthenticationRequestParameters authenticationRequestParameters,
            Prompt msalPrompt)
        {
            bool isMsaPassthrough = authenticationRequestParameters.AppConfig.IsMsaPassthrough;
            var  accountPicker    = _accountPickerFactory.Create(
                _parentHandle,
                _logger,
                _synchronizationContext,
                authenticationRequestParameters.Authority,
                isMsaPassthrough);

            IWamPlugin      wamPlugin;
            WebTokenRequest webTokenRequest;

            try
            {
                WebAccountProvider accountProvider = await
                                                     accountPicker.DetermineAccountInteractivelyAsync().ConfigureAwait(false);

                if (accountProvider == null)
                {
                    throw new MsalClientException(MsalError.AuthenticationCanceledError, "WAM Account Picker did not return an account.");
                }

                bool isConsumerTenant = _webAccountProviderFactory.IsConsumerProvider(accountProvider);
                // WAM returns the tenant here, not the full authority
                wamPlugin = (isConsumerTenant && !isMsaPassthrough) ? _msaPlugin : _aadPlugin;

                string transferToken      = null;
                bool   isForceLoginPrompt = false;
                if (isConsumerTenant && isMsaPassthrough)
                {
                    // Get a transfer token to avoid prompting the user twice
                    transferToken = await _msaPassthroughHandler.TryFetchTransferTokenAsync(
                        authenticationRequestParameters,
                        accountProvider).ConfigureAwait(false);

                    // If a TT cannot be obtained, force the interactive experience again
                    isForceLoginPrompt = string.IsNullOrEmpty(transferToken);

                    // For MSA-PT, the MSA provider will issue v1 token, which cannot be used.
                    // Only the AAD provider can issue a v2 token
                    accountProvider = await _webAccountProviderFactory.GetAccountProviderAsync(
                        authenticationRequestParameters.Authority.TenantId)
                                      .ConfigureAwait(false);
                }

                webTokenRequest = await wamPlugin.CreateWebTokenRequestAsync(
                    accountProvider,
                    authenticationRequestParameters,
                    isForceLoginPrompt : isForceLoginPrompt,
                    isInteractive : true,
                    isAccountInWam : false)
                                  .ConfigureAwait(true);

                _msaPassthroughHandler.AddTransferTokenToRequest(webTokenRequest, transferToken);

                WamAdapters.AddMsalParamsToRequest(authenticationRequestParameters, webTokenRequest);
                AddPromptToRequest(msalPrompt, isForceLoginPrompt, webTokenRequest);
            }
            catch (Exception ex) when(!(ex is MsalException))
            {
                _logger.ErrorPii(ex);
                throw new MsalServiceException(
                          MsalError.WamPickerError,
                          "Could not get the account provider - account picker. See inner exception for details", ex);
            }

            IWebTokenRequestResultWrapper wamResult;

            try
            {
                wamResult = await _wamProxy.RequestTokenForWindowAsync(_parentHandle, webTokenRequest).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _logger.ErrorPii(ex);
                throw new MsalServiceException(
                          MsalError.WamPickerError,
                          "Could not get the result - account picker. See inner exception for details", ex);
            }

            return(CreateMsalTokenResponse(wamResult, wamPlugin, isInteractive: true));
        }