Example #1
0
        public async Task Run(
#pragma warning disable CA1801 // Remove unused parameter
            [TimerTrigger("0 */1 * * * *")] TimerInfo timerInfo)
#pragma warning restore CA1801 // Remove unused parameter
        {
            if (_appSettings is null ||
                _appSettings.Value is null ||
                _appSettings.Value.EmailToSmsSettings is null ||
                string.IsNullOrEmpty(_appSettings.Value.EmailToSmsSettings.ResellerApiKey) ||
                string.IsNullOrEmpty(_appSettings.Value.EmailToSmsSettings.ResellerApiSecret) ||
                string.IsNullOrEmpty(_appSettings.Value.EmailToSmsSettings.MailboxAddress))
            {
                _logger.LogWarning("Missing Email to SMS configuration. Aborting!");
                return;
            }

            // TODO Move GraphServiceClient and auth to DI
            IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
                                                                           .Create(_appSettings.Value.EmailToSmsSettings.MicrosoftGraphApiClientId)
                                                                           .WithTenantId(_appSettings.Value.EmailToSmsSettings.MicrosoftGraphApiTenantID)
                                                                           .WithClientSecret(_appSettings.Value.EmailToSmsSettings.MicrosoftGraphApiSecret)
                                                                           .Build();

            var graphServiceClient = new GraphServiceClient(new ClientCredentialProvider(confidentialClientApplication));

            // TODO, make this better or move to config.
            // Hard coding OWNit conveyancing for now.
            var clientIds = new Dictionary <string, int>();

            clientIds.Add("b6rtmjuc", 87627);

            var messageExceptions = new List <Exception>();

            ISMSService smsService = new BurstSMSService(
                _appSettings.Value.EmailToSmsSettings.ResellerApiKey,
                _appSettings.Value.EmailToSmsSettings.ResellerApiSecret);

            var mailboxEmailAddress = _appSettings.Value.EmailToSmsSettings.MailboxAddress;

            var currentMessageRequest = graphServiceClient.Users[mailboxEmailAddress]
                                        .MailFolders.Inbox
                                        .Messages
                                        .Request()
                                        .Select(m => (new
            {
                m.Id,
                m.From,
                m.Subject,
                m.Body,
                m.InternetMessageHeaders,
                m.ParentFolderId,
                m.Categories
            }));

            // Get IDs of standard folders to be able to move messages once they're processed
            var inboxFolder           = await graphServiceClient.Users[mailboxEmailAddress].MailFolders.Inbox.Request().GetAsync();
            var invalidMessagesFolder = await graphServiceClient.Users[mailboxEmailAddress].EnsureFolder("Inbox", _invalidMessagesFolderName);
            var clientsFolder         = await graphServiceClient.Users[mailboxEmailAddress].EnsureFolder("Inbox", _clientsFolderName);
            var clientFolderIds       = new Dictionary <string, string>();

            // Loop through pages
            do
            {
                var currentMessages = await currentMessageRequest.GetAsync();

                // Loop through messages in current page
                foreach (var message in currentMessages)
                {
                    var parsedBodyText = string.Empty;

                    try
                    {
                        var messageBodyHtmlDoc = new HtmlAgilityPack.HtmlDocument();
                        messageBodyHtmlDoc.LoadHtml(message.Body.Content);

                        // - Using InnerText strips HTML.
                        // - Then we replace Windows CRLF's in case there are any. We must strip these first because if we
                        //   stripped LF first then we'd have a bunch of CR's floating around all alone.
                        // - Then strip Unix LF's, in case there are any.
                        parsedBodyText = HttpUtility.HtmlDecode(messageBodyHtmlDoc.DocumentNode.InnerText)
                                         .Replace("\r\n", "", StringComparison.Ordinal)
                                         .Replace("\n", "", StringComparison.Ordinal);

                        var smsEmailJson = JsonConvert.DeserializeObject <SmsEmailJson>(parsedBodyText);

                        // Check for required values
                        if (!smsEmailJson.IsValid(out List <string> missingFields))
                        {
                            // Create a support ticket with the details.
                            var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                            {
                                FromEmail       = GetBestEmail(smsEmailJson),
                                TicketPriority  = TicketPriority.Low,
                                Subject         = GenerateTicketSubject(smsEmailJson),
                                DescriptionHtml = GenerateMessageMissingFields(smsEmailJson, missingFields)
                            });

                            // Store the category for a bad response from the gateway
                            await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, new[] { $"Ticket: {ticketResponse.Id}", EmailCategories.MissingRequiredData });

                            await graphServiceClient.Users[mailboxEmailAddress].Messages[message.Id].Move(invalidMessagesFolder.Id).Request().PostAsync();
                        }
                        else
                        {
                            // Data appears valid, ensure folder exists for client, checking cached IDs first
                            if (smsEmailJson.SmsGo.Value == true)
                            {
                                // Send SMS. Will throw if there's a problem.
                                var clientId = clientIds[smsEmailJson.SmsKey];

                                var cleanMobileNumber = SMSHelper.CleanMobileNumber(smsEmailJson.MobileNumber);

                                if (_appSettings.Value.EmailToSmsSettings.SkipSendingSms)
                                {
                                    _logger.LogWarning(
                                        "Skipping SMS Send: Client ID: {clientId}, Mobile Number: {mobileNumber}, Message: '{message}', Send At: {sendAt}, Replies to Email: {repliesToEmail}",
                                        clientId,
                                        cleanMobileNumber,
                                        smsEmailJson.Message,
                                        smsEmailJson.SendAt,
                                        smsEmailJson.RepliesToEmail);

                                    await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, EmailCategories.SmsSkipped);
                                }
                                else
                                {
                                    var sendAt = SMSHelper.LocalisedSendAt(smsEmailJson.SendAt, smsEmailJson.SendAtTimeZoneID, _logger);

                                    if (sendAt != smsEmailJson.SendAt)
                                    {
                                        _logger.LogInformation(
                                            "SendAt was translated from '{SendAt}' to '{ParsedSendAt}' based on supplied Time Zone ID '{TimeZoneID}'.",
                                            smsEmailJson.SendAt,
                                            sendAt,
                                            smsEmailJson.SendAtTimeZoneID);
                                    }

                                    var sendSmsResponse = await smsService.SendSms(clientId, cleanMobileNumber, smsEmailJson.Message, sendAt, smsEmailJson.RepliesToEmail);

                                    if (sendSmsResponse.Error is null)
                                    {
                                        // Create a support ticket with the details.
                                        var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                                        {
                                            FromEmail       = GetBestEmail(smsEmailJson),
                                            TicketPriority  = TicketPriority.Low,
                                            Subject         = GenerateTicketSubject(smsEmailJson),
                                            DescriptionHtml = GenerateMessageUnknownGatewayResponse(smsEmailJson)
                                        });

                                        await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, $"Ticket: {ticketResponse.Id}", EmailCategories.UnknownError);
                                    }
                                    else if (sendSmsResponse.Error.Code == "SUCCESS")
                                    {
                                        _logger.LogInformation(
                                            "SMS Sent: Cost: {cost}, Sms Message ID: {messageId}, Code: {code}, Descripton: {description}, Send At: {sendAt}",
                                            sendSmsResponse.Cost,
                                            sendSmsResponse.MessageId,
                                            sendSmsResponse.Error?.Code,
                                            sendSmsResponse.Error?.Description,
                                            sendSmsResponse.SendAt);

                                        await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, EmailCategories.SmsSent);
                                    }
                                    else
                                    {
                                        // Create a support ticket with the details.
                                        var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                                        {
                                            FromEmail       = GetBestEmail(smsEmailJson),
                                            TicketPriority  = TicketPriority.Low,
                                            Subject         = GenerateTicketSubject(smsEmailJson),
                                            DescriptionHtml = GenerateMessageGatewayError(message, smsEmailJson, sendSmsResponse)
                                        });

                                        // Store the category for a bad response from the gateway
                                        await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, $"Ticket: {ticketResponse.Id}", EmailCategories.GatewayBadRequest);

                                        _logger.LogError(
                                            "SMS Gateway returned error response. Code: '{code}', Description: '{description}'.",
                                            sendSmsResponse.Error.Code,
                                            sendSmsResponse.Error.Description);
                                    }
                                }
                            }
                            else
                            {
                                await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, EmailCategories.SmsGoFalse);
                            }

                            // Finally, move to client folder
                            string currentClientFolderId;
                            if (clientFolderIds.ContainsKey(smsEmailJson.OrgKey))
                            {
                                currentClientFolderId = clientFolderIds[smsEmailJson.OrgKey];
                            }
                            else
                            {
                                var currentClientFolder = await graphServiceClient.Users[mailboxEmailAddress].EnsureFolder(clientsFolder, smsEmailJson.OrgKey);
                                currentClientFolderId = currentClientFolder.Id;
                            }

                            await graphServiceClient.Users[mailboxEmailAddress].Messages[message.Id].Move(currentClientFolderId).Request().PostAsync();
                        }
                    }
                    catch (Exception ex)
                    {
                        messageExceptions.Add(ex);

                        // Just in case something goes wrong, we don't want a failure during cleanup to prevent processing future messages
                        try
                        {
                            if (ex is JsonReaderException jex)
                            {
                                // Create a support ticket with the details.
                                var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                                {
                                    FromEmail       = _fallbackEmailFrom,
                                    TicketPriority  = TicketPriority.Low,
                                    Subject         = $"SMS Failure - couldn't parse JSON",
                                    DescriptionHtml = GenerateMessageJsonParseError(parsedBodyText, jex)
                                });

                                await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, $"Ticket: {ticketResponse.Id}", EmailCategories.InvalidJson);
                            }
                            else
                            {
                                var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                                {
                                    FromEmail       = _fallbackEmailFrom,
                                    TicketPriority  = TicketPriority.Low,
                                    Subject         = $"SMS Failure - Unknown error",
                                    DescriptionHtml = GenerateMessageUnknownException(message, ex)
                                });

                                await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, EmailCategories.UnknownError);
                            }

                            // Move to generic invalid message folder
                            await graphServiceClient.Users[mailboxEmailAddress].Messages[message.Id].Move(invalidMessagesFolder.Id).Request().PostAsync();
                        }
                        catch (Exception cleanupEx)
                        {
                            _logger.LogError(cleanupEx, "Exception encountered during message cleanup!");
                            messageExceptions.Add(cleanupEx);
                        }
                    }
                }

                currentMessageRequest = currentMessages.NextPageRequest;
            } while (currentMessageRequest != null);

            if (messageExceptions.Count > 0)
            {
                throw new AggregateException("Processing of one or more messages has failed", messageExceptions);
            }
        }
        public static async Task Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            var storage               = CloudStorageAccount.Parse(StorageAccount);
            var cloudTableClient      = storage.CreateCloudTableClient();
            var groupsStatisticsTable = cloudTableClient.GetTableReference("groupsstatistics");
            var groupsOwnersTable     = cloudTableClient.GetTableReference("groupowners");
            var groupsGuestsTable     = cloudTableClient.GetTableReference("groupguests");
            await groupsStatisticsTable.CreateIfNotExistsAsync().ConfigureAwait(false);

            await groupsOwnersTable.CreateIfNotExistsAsync().ConfigureAwait(false);

            await groupsGuestsTable.CreateIfNotExistsAsync().ConfigureAwait(false);

            // Get a Bearer Token with the App
            var    httpClient = new HttpClient();
            string token;

            try
            {
                var app = ConfidentialClientApplicationBuilder.Create(AppId)
                          .WithAuthority($"https://login.windows.net/{TenantId}/oauth2/token")
                          .WithRedirectUri("https://governance365function")
                          .WithClientSecret(AppSecret)
                          .Build();

                var authenticationProvider = new MsalAuthenticationProvider(app, Scopes);
                token = authenticationProvider.GetTokenAsync().Result;
                httpClient.DefaultRequestHeaders.Authorization =
                    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
            }
            catch (Exception)
            {
                throw;
            }

            httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

            JObject groupsPageJson = null;

            do
            {
                if (string.IsNullOrEmpty(groupsPageJson?["@odata.nextLink"]?.ToString()))
                {
                    // first group page
                    groupsPageJson = JObject.Parse(await(await httpClient.GetAsync(groupsUrl + groupsSelection).ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false));
                }
                else
                {
                    // next group page
                    groupsPageJson = JObject.Parse(await(await httpClient.GetAsync(groupsPageJson?["@odata.nextLink"]?.ToString()).ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false));
                }
                if (!(groupsPageJson["value"] is JArray groupsPage))
                {
                    continue;
                }

                // Batch operation to store pages of groups in Azure table storage
                var groupPageBatchOperation = new TableBatchOperation();
                foreach (var group in groupsPage)
                {
                    var memberCount = 0;
                    var ownerCount  = 0;
                    var guestCount  = 0;

                    #region PROCESS GROUP TYPE AND DYNAMIC MEMBERSHIP

                    var groupType         = string.Empty;
                    var dynamicMemberShip = false;
                    var yammerProvisionedOffice365Group = false;

                    foreach (var type in (JArray)group["groupTypes"])
                    {
                        if (type.ToString().Equals("Unified"))
                        {
                            if (group["resourceProvisioningOptions"].ToString().Contains("Team"))
                            {
                                groupType = "Team";
                            }
                            else
                            {
                                groupType = "Office365 Group";
                            }

                            if (group["creationOptions"].ToString().Contains("YammerProvisioning"))
                            {
                                yammerProvisionedOffice365Group = true;
                            }
                        }

                        if (type.ToString().Equals("DynamicMembership"))
                        {
                            dynamicMemberShip = true;
                        }
                    }

                    if (groupType.Equals(string.Empty))
                    {
                        if (!group["mailEnabled"].ToString().ToLower().Equals("true"))
                        {
                            groupType = "Security Group";
                        }
                        else
                        {
                            if (!group["securityEnabled"].ToString().ToLower().Equals("true"))
                            {
                                groupType = "Distribution Group";
                            }
                            else
                            {
                                groupType = "Mail-enabled Security Group";
                            }
                        }
                    }
                    #endregion

                    const string groupMemberSelection = "?$select=id,userPrincipalName,displayName,userType,accountEnabled";
                    var          groupMembersUrl      = $"https://graph.microsoft.com/v1.0/groups/{group["id"]?.ToString()}/members{groupMemberSelection}";
                    var          groupOwnersUrl       = $"https://graph.microsoft.com/v1.0/groups/{group["id"]?.ToString()}/owners{groupMemberSelection}";

                    #region PROCESS GROUP MEMBERS AND GUESTS

                    var processMemberPageResult = await ProcessGroupMemberPageAsync(groupMembersUrl, group, httpClient, groupsGuestsTable, groupsOwnersTable, false).ConfigureAwait(false);

                    memberCount += processMemberPageResult.MemberCount;
                    guestCount  += processMemberPageResult.GuestCount;

                    while (processMemberPageResult.NextLink != null)
                    {
                        processMemberPageResult = await ProcessGroupMemberPageAsync(processMemberPageResult.NextLink, group, httpClient, groupsGuestsTable, groupsOwnersTable, false).ConfigureAwait(false);

                        memberCount += processMemberPageResult.MemberCount;
                        guestCount  += processMemberPageResult.GuestCount;
                    }
                    #endregion

                    #region PROCESS GROUP OWNERS

                    var processOwnerPageResult = await ProcessGroupMemberPageAsync(groupOwnersUrl, group, httpClient, groupsGuestsTable, groupsOwnersTable, true).ConfigureAwait(false);

                    ownerCount += processOwnerPageResult.OwnerCount;
                    while (processOwnerPageResult.NextLink != null)
                    {
                        processOwnerPageResult = await ProcessGroupMemberPageAsync(processOwnerPageResult.NextLink, group, httpClient, groupsGuestsTable, groupsOwnersTable, true).ConfigureAwait(false);

                        ownerCount += processOwnerPageResult.OwnerCount;
                    }
                    #endregion

                    var groupStatisticsEntity = new GroupStatisticsItemTableEntity()
                    {
                        PartitionKey    = "group",
                        RowKey          = group["id"]?.ToString(),
                        Classification  = group["classification"]?.ToString(),
                        CreatedDateTime = group["createdDateTime"] != null &&
                                          !group["createdDateTime"].ToString().Equals(string.Empty)
                                ? DateTimeOffset.Parse(group["createdDateTime"].ToString())
                                : (DateTimeOffset?)null,
                        DeletedDateTime = group["deletedDateTime"] != null &&
                                          !group["deletedDateTime"].ToString().Equals(string.Empty)
                                ? DateTimeOffset.Parse(group["deletedDateTime"].ToString())
                                : (DateTimeOffset?)null,
                        Description       = group["description"]?.ToString(),
                        DisplayName       = group["displayName"]?.ToString(),
                        GroupType         = groupType,
                        DynamicMembership = dynamicMemberShip,
                        Id                    = group["id"]?.ToString(),
                        Mail                  = group["mail"]?.ToString(),
                        MailEnabled           = group["mailEnabled"]?.ToString(),
                        MailNickname          = group["mailNickname"]?.ToString(),
                        OnPremisesSyncEnabled = group["onPremisesSyncEnabled"]?.ToString(),
                        RenewedDateTime       = group["renewedDateTime"] != null &
                                                !group["renewedDateTime"].ToString().Equals(string.Empty)
                                ? DateTimeOffset.Parse(group["renewedDateTime"].ToString())
                                : (DateTimeOffset?)null,
                        SecurityEnabled = group["securityEnabled"]?.ToString(),
                        Visibility      = group["visibility"]?.ToString(),
                        Guests          = guestCount,
                        Owners          = ownerCount,
                        Members         = memberCount
                    };
                    groupPageBatchOperation.Add(TableOperation.InsertOrReplace(groupStatisticsEntity));
                }
                await groupsStatisticsTable.ExecuteBatchAsync(groupPageBatchOperation).ConfigureAwait(false);
            } while (!string.IsNullOrEmpty(groupsPageJson["@odata.nextLink"]?.ToString()));
            httpClient.Dispose();
        }
Example #3
0
        public async Task ServicePrincipal_OBO_LongRunningProcess_PPE_Async()
        {
            //An explanation of the OBO for service principal scenario can be found here https://aadwiki.windows-int.net/index.php?title=App_OBO_aka._Service_Principal_OBO

            var settings = ConfidentialAppSettings.GetSettings(Cloud.Public);
            var cert     = settings.GetCertificate();

            IReadOnlyList <string> middleTierApiScopes = new List <string>()
            {
                OBOServicePpeClientID + "/.default"
            };
            IReadOnlyList <string> downstreamApiScopes = new List <string>()
            {
                OBOServiceDownStreamApiPpeClientID + "/.default"
            };


            var clientConfidentialApp = ConfidentialClientApplicationBuilder
                                        .Create(OBOClientPpeClientID)
                                        .WithAuthority(PPEAuthenticationAuthority)
                                        .WithCertificate(cert)
                                        .WithTestLogging()
                                        .Build();



            var middletierServiceApp = ConfidentialClientApplicationBuilder
                                       .Create(OBOServicePpeClientID)
                                       .WithAuthority(PPEAuthenticationAuthority)
                                       .WithCertificate(cert)
                                       .Build();
            var userCacheRecorder = middletierServiceApp.UserTokenCache.RecordAccess();

            Trace.WriteLine("1. Upstream client gets an app token");
            var authenticationResult = await clientConfidentialApp
                                       .AcquireTokenForClient(middleTierApiScopes)
                                       .ExecuteAsync()
                                       .ConfigureAwait(false);

            string clientToken = authenticationResult.AccessToken;

            Trace.WriteLine("2. MidTier kicks off the long running process by getting an OBO token");
            string cacheKey = null;

            authenticationResult = await(middletierServiceApp as ILongRunningWebApi).
                                   InitiateLongRunningProcessInWebApi(downstreamApiScopes, clientToken, ref cacheKey)
                                   .ExecuteAsync().ConfigureAwait(false);

            Assert.IsNotNull(authenticationResult.AccessToken);
            Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache);
            Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens);
            Assert.AreEqual(cacheKey, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
            Assert.AreEqual(TokenSource.IdentityProvider, authenticationResult.AuthenticationResultMetadata.TokenSource);
            Assert.IsNull(
                userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheExpiry,
                "The cache expiry is not set because there is an RT in the cache");

            Trace.WriteLine("3. Later, mid-tier needs the token again, and one is in the cache");
            authenticationResult = await(middletierServiceApp as ILongRunningWebApi)
                                   .AcquireTokenInLongRunningProcess(downstreamApiScopes, cacheKey)
                                   .ExecuteAsync()
                                   .ConfigureAwait(false);

            Assert.AreEqual(TokenSource.Cache, authenticationResult.AuthenticationResultMetadata.TokenSource);

            Trace.WriteLine("4. After the original token expires, the mid-tier needs a token again. RT will be used.");
            TokenCacheHelper.ExpireAllAccessTokens(middletierServiceApp.UserTokenCache as ITokenCacheInternal);

            authenticationResult = await(middletierServiceApp as ILongRunningWebApi)
                                   .AcquireTokenInLongRunningProcess(downstreamApiScopes, cacheKey)
                                   .ExecuteAsync()
                                   .ConfigureAwait(false);

            Assert.IsNotNull(authenticationResult.AccessToken);
            Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache);
            Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens);
            Assert.AreEqual(cacheKey, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
            Assert.AreEqual(TokenSource.IdentityProvider, authenticationResult.AuthenticationResultMetadata.TokenSource);
            Assert.IsNull(
                userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheExpiry,
                "The cache expiry is not set because there is an RT in the cache");
        }
Example #4
0
 private IConfidentialClientApplication BuildConfidentialClientApplication()
 => ConfidentialClientApplicationBuilder
 .Create(AzureAdB2CConfiguration.ClientId)
 .WithTenantId(AzureAdB2CConfiguration.TenantId)
 .WithClientSecret(AzureAdB2CConfiguration.ClientSecret)
 .Build();
        private static async Task RunAsync()
        {
            AuthConfig authConfig = AuthConfig.ReadJsonFromfile("appsettings.json");

            IConfidentialClientApplication application;

            application = ConfidentialClientApplicationBuilder.Create(authConfig.ClientId)
                          .WithClientSecret(authConfig.ClientSecret)
                          .WithAuthority(new Uri(authConfig.Authority))
                          .Build();

            string[]             Resourceids = new string[] { authConfig.ResourceId };
            AuthenticationResult result      = null;

            try
            {
                result = await application.AcquireTokenForClient(Resourceids).ExecuteAsync();

                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("Token Acquired \n");
                Console.WriteLine(result.AccessToken);
                Console.ResetColor();
            }
            catch (MsalClientException ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(ex.Message);
                Console.ResetColor();
            }

            if (!string.IsNullOrEmpty(result.AccessToken))
            {
                var httpclient            = new HttpClient();
                var defaultrequestHeaders = httpclient.DefaultRequestHeaders;

                if (defaultrequestHeaders == null || !defaultrequestHeaders.Accept.Any
                        (m => m.MediaType == "application/Json"))
                {
                    httpclient.DefaultRequestHeaders.Accept.Add(new
                                                                MediaTypeWithQualityHeaderValue("application/Json"));
                }

                defaultrequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);

                HttpResponseMessage responseMessage = await httpclient.GetAsync(authConfig.BaseAddress);

                if (responseMessage.IsSuccessStatusCode)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    string json = await responseMessage.Content.ReadAsStringAsync();

                    Console.WriteLine(json);
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"failed to call  API: {responseMessage.StatusCode}");
                    string content = await responseMessage.Content.ReadAsStringAsync();

                    Console.WriteLine($"Content:{content}");
                }

                Console.ResetColor();
            }
        }
Example #6
0
        private static async Task RunAsync()
        {
            // Read application settings from appsettings.json (tenant ID, app ID, client secret, etc.)
            AppSettings config = AppSettingsFile.ReadFromJsonFile();

            // Initialize the client credential auth provider
            IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
                                                                           .Create(config.AppId)
                                                                           .WithTenantId(config.TenantId)
                                                                           .WithClientSecret(config.ClientSecret)
                                                                           .Build();
            ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);

            // Set up the Microsoft Graph service client with client credentials
            GraphServiceClient graphClient = new GraphServiceClient(authProvider);

            Program.PrintCommands();

            try
            {
                while (true)
                {
                    Console.Write("Enter command, then press ENTER: ");
                    string decision = Console.ReadLine();
                    switch (decision.ToLower())
                    {
                    case "1":
                        await UserService.ListUsers(graphClient);;
                        break;

                    case "2":
                        await UserService.GetUserById(graphClient);;
                        break;

                    case "3":
                        await UserService.GetUserBySignInName(config, graphClient);;
                        break;

                    case "4":
                        await UserService.DeleteUserById(graphClient);

                        break;

                    case "5":
                        await UserService.SetPasswordByUserId(graphClient);

                        break;

                    case "6":
                        await UserService.BulkCreate(config, graphClient);

                        break;

                    case "7":
                        await UserService.ListApplications(graphClient);

                        break;

                    case "help":
                        Program.PrintCommands();
                        break;

                    case "exit":
                        return;

                    default:
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine("Invalid command. Enter 'help' to show a list of commands.");
                        Console.ResetColor();
                        break;
                    }

                    Console.ResetColor();
                }
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;

                var innerException = ex.InnerException;
                if (innerException != null)
                {
                    while (innerException != null)
                    {
                        Console.WriteLine(innerException.Message);
                        innerException = innerException.InnerException;
                    }
                }
                else
                {
                    Console.WriteLine(ex.Message);
                }
            }
            finally
            {
                Console.ResetColor();
            }

            Console.ReadLine();
        }
        private async Task <UserProfile> CallGraphAPIOnBehalfOfUser()
        {
            string[]    scopes  = { "user.read" };
            UserProfile profile = null;

            // We will use MSAL.NET to get a token to call the API On Behalf Of the current user
            try
            {
                string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);

                // Creating a ConfidentialClientApplication using the Build pattern (https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-Applications)
                var app = ConfidentialClientApplicationBuilder.Create(clientId)
                          .WithAuthority(authority)
                          .WithClientSecret(appKey)
                          .WithRedirectUri(redirectUri)
                          .Build();

                // Hooking MSALPerUserSqlTokenCacheProvider class on ConfidentialClientApplication's UserTokenCache.
                MSALPerUserSqlTokenCacheProvider sqlCache = new MSALPerUserSqlTokenCacheProvider(app.UserTokenCache, dbContext, ClaimsPrincipal.Current);

                //Grab the Bearer token from the HTTP Header using the identity bootstrap context. This requires SaveSigninToken to be true at Startup.Auth.cs
                var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;

                // Creating a UserAssertion based on the Bearer token sent by TodoListClient request.
                //urn:ietf:params:oauth:grant-type:jwt-bearer is the grant_type required when using On Behalf Of flow: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow
                UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer");

                // Acquiring an AuthenticationResult for the scope user.read, impersonating the user represented by userAssertion, using the OBO flow
                AuthenticationResult result = await app.AcquireTokenOnBehalfOf(scopes, userAssertion)
                                              .ExecuteAsync();

                string accessToken = result.AccessToken;
                if (accessToken == null)
                {
                    throw new Exception("Access Token could not be acquired.");
                }

                // Call the Graph API and retrieve the user's profile.
                string             requestUrl = String.Format(CultureInfo.InvariantCulture, graphUserUrl, HttpUtility.UrlEncode(tenant));
                HttpClient         client     = new HttpClient();
                HttpRequestMessage request    = new HttpRequestMessage(HttpMethod.Get, requestUrl);
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
                HttpResponseMessage response = await client.SendAsync(request);

                // Return the user's profile.
                if (response.IsSuccessStatusCode)
                {
                    string responseString = await response.Content.ReadAsStringAsync();

                    profile = JsonConvert.DeserializeObject <UserProfile>(responseString);
                    return(profile);
                }

                // An unexpected error occurred calling the Graph API.
                throw new Exception("An unexpected error occurred calling the Graph API.");
            }
            catch (MsalUiRequiredException msalServiceException)
            {
                /*
                 * If you used the scope `.default` on the client application, the user would have been prompted to consent for Graph API back there
                 * and no incremental consents are required (this exception is not expected). However, if you are using the scope `user_impersonation`,
                 * this exception will be thrown at the first time the API tries to access Graph on behalf of the user for an incremental consent.
                 * You must then, add the logic to delegate the consent screen to your client application here.
                 * This sample doesn't use the incremental consent strategy.
                 */
                throw msalServiceException;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
Example #8
0
        static void Main(string[] args)
        {
            string requestUrl   = ConfigurationManager.AppSettings["RequestURL"];
            string ClientID     = ConfigurationManager.AppSettings["ClientID"];
            string TenantID     = ConfigurationManager.AppSettings["TenantID"];
            string Authority    = $"https://login.microsoftonline.com/{TenantID}/v2.0";
            string ClientSecret = ConfigurationManager.AppSettings["ClientSecret"];

            string[] scopes = ConfigurationManager.AppSettings["Scopes"].ToString().Split(',');

            //Setting app with MSAL
            IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(ClientID)
                                                 .WithClientSecret(ClientSecret)
                                                 .WithAuthority(new Uri(Authority))
                                                 .Build();

            //requesting token
            AuthenticationResult result = null;

            try
            {
                Task <AuthenticationResult> authTask = app.AcquireTokenForClient(scopes)
                                                       .ExecuteAsync();
                //authTask.Start();
                authTask.Wait();
                result = authTask.Result;
            }
            catch (MsalUiRequiredException ex)
            {
                // The application doesn't have sufficient permissions.
                // - Did you declare enough app permissions during app creation?
                // - Did the tenant admin grant permissions to the application?
            }
            catch (MsalServiceException ex) when(ex.Message.Contains("AADSTS70011"))
            {
                // Invalid scope. The scope has to be in the form "https://resourceurl/.default"
                // Mitigation: Change the scope to be as expected.
            }


            // Call Web API
            var httpClient = new HttpClient();

            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            string json = "{'name':'Mr. Testbuddy'}";
            var    data = new StringContent(json, Encoding.UTF8, "application/json");

            Task <HttpResponseMessage> postTask = httpClient.PostAsync(requestUrl, data);

            postTask.Wait();

            var webApiResponse = postTask.Result;

            Task <string> messageTask = webApiResponse.Content.ReadAsStringAsync();

            messageTask.Wait();

            Console.WriteLine(messageTask.Result);
            Console.ReadLine();
        }
Example #9
0
        public static async Task GetSpoDocumentItems(GraphServiceClient graphClient, List <DriveItem> docLibItems, string driveId, CloudBlobContainer container, bool getAcls)
        {
            foreach (var item in docLibItems)
            {
                if (item.Folder != null)
                {
                    string ParentFolderPathString = null;
                    string fullFolderNamePath     = null;
                    var    folderName             = item.Name;

                    if (item.ParentReference.Path != null)
                    {
                        var ParentFolderPathSplit = item.ParentReference.Path.Split(":");
                        if (ParentFolderPathSplit.Length >= 1)
                        {
                            ParentFolderPathString = ParentFolderPathSplit[1];
                            if (ParentFolderPathString.Length >= 1)
                            {
                                fullFolderNamePath = String.Format("{0}/{1}", ParentFolderPathString, folderName);
                            }
                            else
                            {
                                fullFolderNamePath = folderName;
                            }
                        }
                    }
                    else
                    {
                        fullFolderNamePath = folderName;
                    }


                    var folderItems = await GetFolderContents(graphClient, fullFolderNamePath, driveId);

                    if (folderItems.Count > 0)
                    {
                        await GetSpoDocumentItems(graphClient, folderItems, driveId, container, _getAcls);
                    }
                }
                // Let's download the first file we get in the response.
                if (item.File != null)
                {
                    // We'll use the file metadata to determine size and the name of the downloaded file
                    // and to get the download URL.
                    if (item.Deleted != null)
                    {
                        if (item.Deleted.State == "deleted")
                        {
                            Console.WriteLine("Deleted Item detected");

                            var spoItemUrl = await azTableStorage.GetSpoItemEntitiesInPartion(item.Id);

                            //Clean up the Storage account path for the deleted item so we dont index it again
                            await AzureBLOBStorage.DeleteFileFromAzureBLOB(spoItemUrl, container);

                            //Clean up the json metadata file for the above file:
                            string spoItemUrlJson = ($"{spoItemUrl}.json");
                            await AzureBLOBStorage.DeleteFileFromAzureBLOB(spoItemUrlJson, container);

                            break;
                        }
                    }

                    var driveItemInfo = await graphClient.Drives[driveId].Items[item.Id].Request().GetAsync();

                    var SPWebUrl = driveItemInfo.WebUrl;
                    var createdAuthorDisplayName = driveItemInfo.CreatedBy.User.DisplayName;
                    var baseFileName             = SPWebUrl;
                    var jsonMetadataFileName     = String.Format("{0}.json", baseFileName);

                    //Below is for ACL Security trimming extraction which is still work in progress.
                    if (getAcls)
                    {
                        var driveItemPermissions = await graphClient.Drives[driveId].Items[item.Id].Permissions.Request().GetAsync();

                        foreach (var driveItemPermission in driveItemPermissions)
                        {
                            var grantedDispayName = driveItemPermission.GrantedTo.User.DisplayName;
                            var grantedObjectId   = driveItemPermission.GrantedTo.User.Id;

                            //If no ID is present then its a sharepoint group
                            if (grantedObjectId == null)
                            {
                                //var groupLookup = await graphClient.Groups.Request()//.Filter("displayName+eq+true")
                                //.Select(e => new {
                                //    e.Id,
                                //    e.DisplayName
                                //})
                                //.GetAsync();
                                //var groupLookup = await graphClient.Groups.Request().GetAsync();
                                //var groupLookup = await graphClient.Groups.Request().Filter(string.Format("displayName+eq+{0}", grantedDispayName)).GetAsync();

                                var scopes = new[] { _spoHostName + "/.default" };
                                //var scopes = new[] { _spoHostName + "/Sites.FullControl.All" };

                                //var scopes = new[] { "https://graph.microsoft.com/contacts.read" };

                                var v1Authority = _authority.Replace("/v2.0", "");

                                var clientApplication = ConfidentialClientApplicationBuilder.Create(_clientId)
                                                        .WithAuthority(_authority)
                                                        .WithClientSecret(_clientSecret)
                                                        .WithClientId(_clientId)
                                                        .WithTenantId(_tenantId)
                                                        .Build();

                                var result = await clientApplication.AcquireTokenForClient(scopes).ExecuteAsync();

                                HttpClient client = new HttpClient();
                                client.DefaultRequestHeaders.Add("Authorization", "Bearer " + result.AccessToken);
                                client.DefaultRequestHeaders.Add("Accept", "application/json");

                                ////setup the client get
                                HttpResponseMessage result2 = await client.GetAsync(String.Format("{0}/_api/Web/SiteGroups/GetByName('{1}')/users", SPWebUrl, grantedDispayName));

                                //var requestUrl = "https://m365x069897.sharepoint.com/_api/web?$select=Title";
                                var requestUrl = "https://m365x069897.sharepoint.com/_api/web/SiteGroups/";

                                HttpResponseMessage result3 = await client.GetAsync(requestUrl);


                                string filter = string.Format("startswith(displayName, {0}", grantedDispayName);
                                //string filter = string.Format("displayName startswith '{0}'", grantedDispayName);
                                var groupLookup = await graphClient.Groups
                                                  .Request()
                                                  .Filter($"startswith(displayName, '{grantedDispayName}')")
                                                  //.Filter(filter)
                                                  .Select("id, displayName").GetAsync();

                                var ac = groupLookup;
                            }
                        }
                    }
                    var fields = await graphClient.Drives[driveId].Items[item.Id].ListItem.Fields.Request().GetAsync();

                    //generate metadata content and upload to blob
                    var metadataFields = fields.AdditionalData;

                    foreach (var metadataFieldToIgnore in metadataFieldsToIgnore)
                    {
                        //Console.WriteLine("Removing key [{0}] from metadata fields to extract", metadataFieldToIgnore);
                        try
                        {
                            metadataFields.Remove(metadataFieldToIgnore);
                        }
                        catch
                        {
                            //swallow exceptions - where fields we want to remove may not exist / theres a better way to do this altogether.
                        }
                    }
                    metadataFields.Add("SPWebUrl", SPWebUrl);
                    metadataFields.Add("createdAuthorDisplayName", createdAuthorDisplayName);


                    // Get the download URL. This URL is preauthenticated and has a short TTL.
                    object downloadUrl;
                    driveItemInfo.AdditionalData.TryGetValue("@microsoft.graph.downloadUrl", out downloadUrl);
                    long size = (long)driveItemInfo.Size;

                    Console.WriteLine("located file {0}, full url [{1}]", baseFileName, downloadUrl.ToString());
                    //await DownloadFileLocal(graphClient, downloadUrl, fileName);
                    if (metadataJSONStore.Equals("True"))
                    {
                        //Metadata JSON logic
                        using (var metadataJson = GenerateJsonMetadataFile(metadataFields))
                        {
                            var uploadUri = await AzureBLOBStorage.UploadFileToAzureBLOB(metadataJson, jsonMetadataFileName, container);

                            //External JSON file approach
                            await AzureBLOBStorage.DownloadFileToAzureBLOB(graphClient, downloadUrl, baseFileName, container, uploadUri);
                        }
                    }
                    else
                    {
                        //BLOB metadata approach
                        await AzureBLOBStorage.DownloadFileToAzureBLOB(graphClient, downloadUrl, baseFileName, container, metadataFields);
                    }

                    //Persist the itemId and url to Storage Table
                    SpoItem spoItemEntity = new SpoItem(item.Id, SPWebUrl);
                    azTableStorage.InsertSpoItemEntity(spoItemEntity);
                }
            }
        }
Example #10
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure <CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential
                // cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                // requires using Microsoft.AspNetCore.Http;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
            .AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));

            services.AddAuthorization(options =>
            {
                options.AddPolicy("OrgAManager", policy =>
                                  policy.RequireAssertion(context =>
                                                          context.User.HasClaim(c => c.Type == "extension_zaprole" && c.Value == "org_a_manager")));

                options.AddPolicy("OrgBEmployee", policy =>
                                  policy.RequireAssertion(context =>
                                                          context.User.HasClaim(c => c.Type == "extension_zaprole" && c.Value == "org_b_employee")));

                options.AddPolicy("ShiftViewer", policy =>
                                  policy.RequireAssertion(context =>
                                                          context.User.HasClaim(c => c.Type == "extension_zaprole" && c.Value == "org_a_manager" || c.Type == "extension_zaprole" && c.Value == "org_b_employee")));
            });

            services.AddLocalization(options => options.ResourcesPath = "Resources");
            services.AddControllers().AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
            .AddDataAnnotationsLocalization();
            services.AddRazorPages();

            services.AddSingleton <IValidationAttributeAdapterProvider, DateLessThanAttributeAdapterProvider>();

            services.AddTransient <Database>(x => this.GetCosmosDatabase().Result);

            services.AddSingleton <IRepository <Library.Models.Organization> >(x => new OrganizationRepository(x.GetService <Database>(), this.Configuration["OrganizationName"]));
            services.AddSingleton <IRepository <Library.Models.Shift>, ShiftRepository>();
            services.AddSingleton <IRepository <PartnerOrganization>, PartnerRepository>();
            services.AddSingleton <IRepository <Employee>, EmployeeRepository>();

            services.AddTransient <IConfidentialClientApplication>(x => ConfidentialClientApplicationBuilder
                                                                   .Create(this.Configuration["ClientId"])
                                                                   .WithTenantId(this.Configuration["TenantId"])
                                                                   .WithClientSecret(this.Configuration["ClientSecret"])
                                                                   .Build());
            services.AddTransient <IAuthenticationProvider, ClientCredentialProvider>();
            services.AddSingleton <IGraphServiceClient, GraphServiceClient>();

            services.Configure <RequestLocalizationOptions>(options =>
            {
                var supportedCultures = new List <CultureInfo>
                {
                    new CultureInfo("en-US"),
                    new CultureInfo("fr")
                };

                options.DefaultRequestCulture = new RequestCulture("en-US");
                options.SupportedCultures     = supportedCultures;
                options.SupportedUICultures   = supportedCultures;
            });
        }
            public OnBehalfOfCredential(string clientId, string clientSecret, string userAccessToken)
            {
                _confidentialClient = ConfidentialClientApplicationBuilder.Create(clientId).WithClientSecret(clientSecret).Build();

                _userAssertion = new UserAssertion(userAccessToken);
            }
Example #12
0
        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");

            if (ModelState.IsValid)
            {
                // This doesn't count login failures towards account lockout
                // To enable password failures to trigger account lockout, set lockoutOnFailure: true
                var result = await _signInManager.PasswordSignInAsync(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: false);

                // Logged in user
                if (result.Succeeded)
                {
                    var user = await _userManager.FindByNameAsync(Input.UserName);

                    // Set user id claim
                    if (!this.User.Claims.Any(x => x.Value == "Id"))
                    {
                        var userIdClaim = new Claim("Id", user.Id);
                        await _userManager.AddClaimAsync(user, userIdClaim);
                    }

                    // Getting bearer token from Azure AD once we're successfully logged in
                    var tokenOptions = new ApiOptions();
                    _configuration.GetSection(ApiOptions.Token).Bind(tokenOptions);

                    IConfidentialClientApplication app;

                    app = ConfidentialClientApplicationBuilder.Create(tokenOptions.ClientId)
                        .WithClientSecret(tokenOptions.ClientSecret)
                        .WithAuthority(new Uri(tokenOptions.Authority))
                        .Build();

                    string[] resourceIds = new string[] { tokenOptions.ResourceId };

                    AuthenticationResult authResult = null;

                    try
                    {
                        authResult = await app.AcquireTokenForClient(resourceIds).ExecuteAsync();
                        Response.Cookies.Append("OnebrbApiToken", authResult.AccessToken);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception($"Failed to fetch bearer token. {ex.Message}");
                    }

                    _logger.LogInformation("User logged in.");
                    return LocalRedirect(returnUrl);
                }
                if (result.RequiresTwoFactor)
                {
                    return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
                }
                if (result.IsLockedOut)
                {
                    _logger.LogWarning("User account locked out.");
                    return RedirectToPage("./Lockout");
                }
                else
                {
                    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                    return Page();
                }
            }

            // If we got this far, something failed, redisplay form
            return Page();
        }
Example #13
0
        public static async Task Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            var storage             = CloudStorageAccount.Parse(StorageAccount);
            var cloudTableClient    = storage.CreateCloudTableClient();
            var usersTable          = cloudTableClient.GetTableReference("users");
            var userStatisticsTable = cloudTableClient.GetTableReference("userstatistics");
            await usersTable.CreateIfNotExistsAsync().ConfigureAwait(false);

            await userStatisticsTable.CreateIfNotExistsAsync().ConfigureAwait(false);

            // Get a Bearer Token with the App
            var    httpClient = new HttpClient();
            string token;

            try
            {
                var app = ConfidentialClientApplicationBuilder.Create(AppId)
                          .WithAuthority($"https://login.windows.net/{TenantId}/oauth2/token")
                          .WithRedirectUri("https://governance365function")
                          .WithClientSecret(AppSecret)
                          .Build();

                var authenticationProvider = new MsalAuthenticationProvider(app, Scopes);
                token = authenticationProvider.GetTokenAsync().Result;
                httpClient.DefaultRequestHeaders.Authorization =
                    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
            }
            catch (Exception)
            {
                throw;
            }

            httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

            //initialize object for storing all user statistics
            UserQueryObject usersPage      = null;
            var             userStatistics = new UserStatisticsTableEntity()
            {
                PartitionKey     = "userstatistics",
                RowKey           = TenantId,
                DeactivatedUsers = 0,
                DeletedUsers     = 0,
                GuestUsers       = 0,
                InternalUsers    = 0,
                Users            = 0
            };

            //get all users (until nextlinnk is empty) and members/guests + sum up statistics
            do
            {
                if (string.IsNullOrEmpty(usersPage?.OdataNextLink?.ToString()))
                {
                    //first request
                    usersPage = JsonConvert.DeserializeObject <Governance365.Models.UserQueryObject>(await(await httpClient.GetAsync(usersUrl + UserSelectedProperties).ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false));
                }
                else
                {
                    //next page request
                    usersPage = JsonConvert.DeserializeObject <Governance365.Models.UserQueryObject>(await(await httpClient.GetAsync(usersPage?.OdataNextLink?.ToString()).ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false));
                }

                //batch request to store pages of users in Azure storage
                var userPageBatchOperation = new TableBatchOperation();
                foreach (var user in usersPage.Value)
                {
                    if (user.UserType != null)
                    {
                        userStatistics.Users++;
                        if (user.UserType.Equals("Member"))
                        {
                            userStatistics.InternalUsers++;
                        }
                        else if (user.UserType.Equals("Guest"))
                        {
                            userStatistics.GuestUsers++;
                        }
                        if (string.IsNullOrEmpty(user.AccountEnabled) && !bool.Parse(user.AccountEnabled))
                        {
                            userStatistics.DeactivatedUsers++;
                        }
                    }
                    user.PartitionKey = "user";
                    user.RowKey       = user.Id.ToString();
                    //add user entity to batch operation
                    userPageBatchOperation.Add(TableOperation.InsertOrReplace(user));
                }
                //write user page to Azure tabel storage
                await usersTable.ExecuteBatchAsync(userPageBatchOperation).ConfigureAwait(false);
            } while (!string.IsNullOrEmpty(usersPage.OdataNextLink?.ToString()));

            //write user statistics to table "userstatistics" -> single value with overwrite
            var insertUserStatisticsOperation = TableOperation.InsertOrReplace(userStatistics);
            await userStatisticsTable.ExecuteAsync(insertUserStatisticsOperation).ConfigureAwait(false);

            httpClient.Dispose();
        }
Example #14
0
        public static async Task <IActionResult> RunAsync(
            [HttpTrigger(
                 AuthorizationLevel.Anonymous,
                 "get",
                 Route = "moderation/permission")
            ] HttpRequestMessage req,
            ClaimsPrincipal identity,
            TraceWriter log)
        {
            string authorizationStatus = req.Headers.GetValues("AuthorizationStatus").FirstOrDefault();

            if (Convert.ToInt32(authorizationStatus).Equals((int)HttpStatusCode.Accepted))
            {
                string redirectUri = "https://th-admin-dev-weu-func.azurewebsites.net";
                IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(Environment.GetEnvironmentVariable("TheatreersAdminClientId"))
                                                     .WithTenantId("74b32fe7-9082-4068-9cbe-876773845c52")
                                                     .WithClientSecret(Environment.GetEnvironmentVariable("TheatreersAdminClientSecret"))
                                                     .WithRedirectUri(redirectUri)
                                                     .Build();

                // With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
                // application permissions need to be set statically (in the portal or by PowerShell), and then granted by
                // a tenant administratorResourceId = "someAppIDURI";
                string   ResourceId = "https://graph.windows.net";
                string[] scopes     = new[] { ResourceId + "/.default" };

                AuthenticationResult token = null;
                try
                {
                    token = await app.AcquireTokenForClient(scopes)
                            .ExecuteAsync();

                    log.Info($"Token: {token}");
                }
                catch (MsalServiceException ex)
                {
                    // Case when ex.Message contains:
                    // AADSTS70011 Invalid scope. The scope has to be of the form "https://resourceUrl/.default"
                    // Mitigation: change the scope to be as expected
                }

                log.Info($"Token: {token}");
                log.Info("Getting user permissions in B2C Directory");

                string requestBody = await req.Content.ReadAsStringAsync();

                dynamic data    = JsonConvert.DeserializeObject(requestBody);
                var     request = new HttpRequestMessage();
                log.Info($"Token: {token}");

                request.RequestUri            = new System.Uri("https://graph.windows.net/theatreers.onmicrosoft.com/users?api-version=1.6");
                request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.AccessToken);
                var client = await httpClient.SendAsync(request);

                var body = await client.Content.ReadAsStringAsync();

                return(new OkObjectResult(body));
            }
            else
            {
                return(new UnauthorizedResult());
            }
        }
Example #15
0
        static async Task Main(string[] args)
        {
            // Read application settings from appsettings.json (tenant ID, app ID, client secret, etc.)
            AppSettings config = AppSettingsFile.ReadFromJsonFile();

            // Initialize the client credential auth provider
            IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
                                                                           .Create(config.AppId)
                                                                           .WithTenantId(config.TenantId)
                                                                           .WithClientSecret(config.ClientSecret)
                                                                           .Build();
            ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);

            // Set up the Microsoft Graph service client with client credentials
            GraphServiceClient graphClient = new GraphServiceClient(authProvider);

            // Create object to store create user responses
            List <SpreadsheetModelUserCreated> responses = new List <SpreadsheetModelUserCreated>();

            PrintCommands();

            try
            {
                while (true)
                {
                    Console.Write("Enter command, then press ENTER: ");
                    string decision = Console.ReadLine();
                    switch (decision.ToLower())
                    {
                    case "1":
                        await UserService.ListUsers(graphClient);

                        break;

                    case "2":
                        await UserService.GetUserById(graphClient);

                        break;

                    case "3":
                        await UserService.GetUserBySignInName(config, graphClient);

                        break;

                    case "4":
                        await UserService.DeleteUserById(graphClient);

                        break;

                    case "5":
                        await UserService.SetPasswordByUserId(graphClient);

                        break;

                    case "6":
                        await UserService.BulkCreate(config, graphClient);

                        break;

                    case "7":
                        await UserService.CreateUserWithCustomAttribute(graphClient, config.B2cExtensionAppClientId, config.TenantId);

                        break;

                    case "8":
                        await UserService.ListUsersWithCustomAttribute(graphClient, config.B2cExtensionAppClientId);

                        break;

                    case "9":
                        await UserService.BulkCreateFromCsv(config, graphClient, responses);

                        SpreadsheetService.SaveResponseDataAsCSV(responses);
                        break;

                    case "help":
                        Program.PrintCommands();
                        break;

                    case "exit":
                        return;

                    default:
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine("Invalid command. Enter 'help' to show a list of commands.");
                        Console.ResetColor();
                        break;
                    }

                    Console.ResetColor();
                }
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"An error occurred: {ex}");
                Console.ResetColor();
            }
            Console.ReadLine();
        }
        static async Task Main(string[] args)
        {
            string clientId = "6af093f3-b445-4b7a-beae-046864468ad6";
            string tenant   = "msidentitysamplestesting.onmicrosoft.com";

            string[] scopes = new[] { "api://8206b76f-586e-4098-b1e5-598c1aa3e2a1/.default" };

            // Simulates the configuration, could be a IConfiguration or anything
            Dictionary <string, string> Configuration = new Dictionary <string, string>();

            // Certificate Loading
            string keyVaultContainer = "https://WebAppsApisTests.vault.azure.net";
            string keyVaultReference = "Self-Signed-5-5-22";
            CertificateDescription certDescription   = CertificateDescription.FromKeyVault(keyVaultContainer, keyVaultReference);
            ICertificateLoader     certificateLoader = new DefaultCertificateLoader();

            certificateLoader.LoadIfNeeded(certDescription);

            // Create the confidential client application
            IConfidentialClientApplication app;

            app = ConfidentialClientApplicationBuilder.Create(clientId)
                  // Alternatively to the certificate you can use .WithClientSecret(clientSecret)
                  .WithCertificate(certDescription.Certificate)
                  .WithTenantId(tenant)
                  .Build();

            //  In memory token caches (App and User caches)
            // app.AddInMemoryTokenCache();

            // Or

            // Distributed token caches (App and User caches)
            // Add one of the below: SQL, Redis, CosmosDb
            app.AddDistributedTokenCache(services =>
            {
                services.AddDistributedMemoryCache();
                services.AddLogging(configure => configure.AddConsole())
                .Configure <LoggerFilterOptions>(options => options.MinLevel = Microsoft.Extensions.Logging.LogLevel.Debug);

                /* Remove comments to use SQL cache implementation
                 * services.AddDistributedSqlServerCache(options =>
                 * {
                 *  // SQL Server token cache
                 *  // Requires to reference Microsoft.Extensions.Caching.SqlServer
                 *  options.ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TestCache;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
                 *  options.SchemaName = "dbo";
                 *  options.TableName = "TestCache";
                 *
                 *  // You don't want the SQL token cache to be purged before the access token has expired. Usually
                 *  // access tokens expire after 1 hour (but this can be changed by token lifetime policies), whereas
                 *  // the default sliding expiration for the distributed SQL database is 20 mins.
                 *  // Use a value which is above 60 mins (or the lifetime of a token in case of longer lived tokens)
                 *  options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
                 * });
                 */

                /* Remove comments to use Redis cache implementation
                 * // Add Redis
                 * services.AddStackExchangeRedisCache(options =>
                 * {
                 *  options.Configuration = "localhost";
                 *  options.InstanceName = "Redis";
                 * });
                 */

                /* Remove comments to use CosmosDB cache implementation
                 * // Add CosmosDB
                 * services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
                 * {
                 *  cacheOptions.ContainerName = Configuration["CosmosCacheContainer"];
                 *  cacheOptions.DatabaseName = Configuration["CosmosCacheDatabase"];
                 *  cacheOptions.ClientBuilder = new CosmosClientBuilder(Configuration["CosmosConnectionString"]);
                 *  cacheOptions.CreateIfNotExists = true;
                 * });
                 */
            });

            // Acquire a token (twice)
            var result = await app.AcquireTokenForClient(scopes)
                         .ExecuteAsync();

            Console.WriteLine(result.AuthenticationResultMetadata.TokenSource);

            result = await app.AcquireTokenForClient(scopes)
                     .ExecuteAsync();

            Console.WriteLine(result.AuthenticationResultMetadata.TokenSource);
        }
Example #17
0
        /// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/AcquireTokenAsync/*'/>
        public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async() =>
        {
            AuthenticationResult result;
            string scope    = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix;
            string[] scopes = new string[] { scope };

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal)
            {
                IConfidentialClientApplication ccApp = ConfidentialClientApplicationBuilder.Create(parameters.UserId)
                                                       .WithAuthority(parameters.Authority)
                                                       .WithClientSecret(parameters.Password)
                                                       .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                                                       .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
                                                       .Build();

                result = ccApp.AcquireTokenForClient(scopes).ExecuteAsync().Result;
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", result.ExpiresOn);
                return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
            }

            /*
             * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows
             * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend
             * that you use https://login.microsoftonline.com/common/oauth2/nativeclient.
             *
             * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris
             */
            string redirectUri = s_nativeClientRedirectUri;

#if NETCOREAPP
            if (parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)
            {
                redirectUri = "http://localhost";
            }
#endif
            PublicClientAppKey pcaKey = new PublicClientAppKey(parameters.Authority, redirectUri, _applicationClientId
#if NETFRAMEWORK
                                                               , _iWin32WindowFunc
#endif
#if NETSTANDARD
                                                               , _parentActivityOrWindowFunc
#endif
                                                               );

            IPublicClientApplication app = GetPublicClientAppInstance(pcaKey);

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
            {
                if (!string.IsNullOrEmpty(parameters.UserId))
                {
                    result = app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .WithUsername(parameters.UserId)
                             .ExecuteAsync().Result;
                }
                else
                {
                    result = app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .ExecuteAsync().Result;
                }
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result.ExpiresOn);
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword)
            {
                SecureString password = new SecureString();
                foreach (char c in parameters.Password)
                {
                    password.AppendChar(c);
                }
                password.MakeReadOnly();
                result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password)
                         .WithCorrelationId(parameters.ConnectionId)
                         .ExecuteAsync().Result;
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result.ExpiresOn);
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive ||
                     parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)
            {
                // Fetch available accounts from 'app' instance
                System.Collections.Generic.IEnumerable <IAccount> accounts = await app.GetAccountsAsync();
                IAccount account;
                if (!string.IsNullOrEmpty(parameters.UserId))
                {
                    account = accounts.FirstOrDefault(a => parameters.UserId.Equals(a.Username, System.StringComparison.InvariantCultureIgnoreCase));
                }
                else
                {
                    account = accounts.FirstOrDefault();
                }

                if (null != account)
                {
                    try
                    {
                        // If 'account' is available in 'app', we use the same to acquire token silently.
                        // Read More on API docs: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.clientapplicationbase.acquiretokensilent
                        result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync();
                        SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
                    }
                    catch (MsalUiRequiredException)
                    {
                        // An 'MsalUiRequiredException' is thrown in the case where an interaction is required with the end user of the application,
                        // for instance, if no refresh token was in the cache, or the user needs to consent, or re-sign-in (for instance if the password expired),
                        // or the user needs to perform two factor authentication.
                        result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod);
                        SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
                    }
                }
                else
                {
                    // If no existing 'account' is found, we request user to sign in interactively.
                    result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod);
                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
                }
            }
            else
            {
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod);
                throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod);
            }

            return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
        });
Example #18
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
            .AddAzureAD(options => Configuration.Bind("AzureAd", options));


            // <added>
            List <string> scopes = new List <string>();

            scopes.Add("offline_access");
            scopes.Add("user.read");

            var appSettings = new AzureADOptions();

            Configuration.Bind("AzureAd", appSettings);

            var application = ConfidentialClientApplicationBuilder.Create(appSettings.ClientId)
                              .WithAuthority(appSettings.Instance + appSettings.TenantId + "/v2.0")
                              .WithRedirectUri("https://localhost:5001" + appSettings.CallbackPath)
                              .WithClientSecret(appSettings.ClientSecret)
                              .Build();

            // TODO: add MS Graph Code here
            var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(async(request) => {
                var graphUserAccount          = new Helpers.GraphUserAccount(request.Properties["User"] as System.Security.Claims.ClaimsPrincipal);
                var accessToken               = await application.AcquireTokenSilent(scopes, graphUserAccount).ExecuteAsync();
                request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", accessToken.AccessToken);
            }));

            services.AddSingleton <GraphServiceClient>(graphServiceClient);

            services.Configure <OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, async options =>
            {
                // configure authority to use v2 endpoint
                options.Authority = options.Authority + "/v2.0/";

                // asking Azure AD for id_token (to establish identity) and authorization code (to get access/refresh tokens for calling services)
                options.ResponseType = OpenIdConnectResponseType.CodeIdToken;

                // add the permission scopes you want the application to use
                options.Scope.Add("offline_access");
                options.Scope.Add("user.read");

                // validate the token issuer
                options.TokenValidationParameters.NameClaimType = "preferred_username";

                // wire up event to do second part of code authorization flow (exchanging authorization code for token)
                var handler = options.Events.OnAuthorizationCodeReceived;
                options.Events.OnAuthorizationCodeReceived = async context =>
                {
                    // handle the auth code returned post signin
                    context.HandleCodeRedemption();
                    if (!context.HttpContext.User.Claims.Any())
                    {
                        (context.HttpContext.User.Identity as ClaimsIdentity).AddClaims(context.Principal.Claims);
                    }

                    // get token
                    var token = await application.AcquireTokenByAuthorizationCode(options.Scope, context.ProtocolMessage.Code).ExecuteAsync();

                    context.HandleCodeRedemption(null, token.IdToken);
                    await handler(context).ConfigureAwait(false);
                };
            });
            // </added>



            services.AddControllersWithViews(options =>
            {
                var policy = new AuthorizationPolicyBuilder()
                             .RequireAuthenticatedUser()
                             .Build();
                options.Filters.Add(new AuthorizeFilter(policy));
            });
            services.AddRazorPages();
        }
Example #19
0
        private async Task RunOnBehalfOfTestAsync(LabResponse labResponse)
        {
            LabUser user = labResponse.User;
            string  oboHost;
            string  secret;
            string  authority;
            string  publicClientID;
            string  confidentialClientID;

            string[] oboScope;

            switch (labResponse.User.AzureEnvironment)
            {
            case AzureEnvironment.azureusgovernment:
                oboHost              = ArlingtonCloudHost;
                secret               = _keyVault.GetSecret(TestConstants.MsalArlingtonOBOKeyVaultUri).Value;
                authority            = labResponse.Lab.Authority + "organizations";
                publicClientID       = ArlingtonPublicClientIDOBO;
                confidentialClientID = ArlingtonConfidentialClientIDOBO;
                oboScope             = s_arlingtonOBOServiceScope;
                break;

            default:
                oboHost              = PublicCloudHost;
                secret               = _keyVault.GetSecret(TestConstants.MsalOBOKeyVaultUri).Value;
                authority            = TestConstants.AuthorityOrganizationsTenant;
                publicClientID       = PublicCloudPublicClientIDOBO;
                confidentialClientID = PublicCloudConfidentialClientIDOBO;
                oboScope             = s_publicCloudOBOServiceScope;
                break;
            }

            //TODO: acquire scenario specific client ids from the lab response

            SecureString securePassword = new NetworkCredential("", user.GetOrFetchPassword()).SecurePassword;

            var msalPublicClient = PublicClientApplicationBuilder.Create(publicClientID)
                                   .WithAuthority(authority)
                                   .WithRedirectUri(TestConstants.RedirectUri)
                                   .WithTestLogging()
                                   .Build();

            var builder = msalPublicClient.AcquireTokenByUsernamePassword(oboScope, user.Upn, securePassword);

            builder.WithAuthority(authority);

            var authResult = await builder.ExecuteAsync().ConfigureAwait(false);

            var confidentialApp = ConfidentialClientApplicationBuilder
                                  .Create(confidentialClientID)
                                  .WithAuthority(new Uri(oboHost + authResult.TenantId), true)
                                  .WithClientSecret(secret)
                                  .WithTestLogging()
                                  .Build();

            var userCacheRecorder = confidentialApp.UserTokenCache.RecordAccess();

            UserAssertion userAssertion = new UserAssertion(authResult.AccessToken);

            string atHash = userAssertion.AssertionHash;

            authResult = await confidentialApp.AcquireTokenOnBehalfOf(s_scopes, userAssertion)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

            MsalAssert.AssertAuthResult(authResult, user);
            Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);

            await confidentialApp.GetAccountsAsync().ConfigureAwait(false);

            Assert.IsNull(userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
        }
Example #20
0
        static async System.Threading.Tasks.Task Main(string[] args)
        {
            Console.WriteLine(".NET Core Graph Tutorial\n");

            var appConfig = LoadAppSettings();

            if (appConfig == null)
            {
                Console.WriteLine("Missing or invalid appsettings.json...exiting");
                return;
            }

            var appId        = appConfig["appId"];
            var tenantId     = appConfig["tenantId"];
            var clientSecret = appConfig["clientSecret"];

            // Initialize the auth provider with values from appsettings.json
            IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
                                                                           .Create(appId)
                                                                           .WithTenantId(tenantId)
                                                                           .WithClientSecret(clientSecret)
                                                                           .Build();


            //Install-Package Microsoft.Graph.Auth -PreRelease
            ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);

            // Initialize Graph client
            GraphHelper.Initialize(authProvider);

            int choice = -1;

            while (choice != 0)
            {
                Console.WriteLine("Please choose one of the following options:");
                Console.WriteLine("0. Exit");
                Console.WriteLine("1. Get meeting's members");
                Console.WriteLine("2. Get call's attendance");

                try
                {
                    choice = int.Parse(Console.ReadLine());
                }
                catch (System.FormatException)
                {
                    // Set to invalid value
                    choice = -1;
                }

                switch (choice)
                {
                case 0:
                    // Exit the program
                    Console.WriteLine("Goodbye...");
                    break;

                case 1:
                    Console.WriteLine("Please input the meetingId");
                    string meetingId = Console.ReadLine();
                    var    members   = await GraphHelper.GetTeamMembers(meetingId);

                    foreach (User member in members.ToList())
                    {
                        Console.WriteLine("Found member: " + member.DisplayName);
                    }
                    //var meeting = await GraphHelper.GetMeeting("https://teams.microsoft.com/l/meetup-join/19%3ameeting_NTg3ZGQ5YjYtNjI1Ny00ZTQ4LTg0ZWMtMmI4ZjZkYjJkNGRj%40thread.v2/0?context=%7b%22Tid%22%3a%221aed5afa-c363-478e-ae14-b73b6949addb%22%2c%22Oid%22%3a%228b406a47-ed00-45cb-ad12-cd01d6143bbb%22%7d");
                    break;

                case 2:
                    Console.WriteLine("Please input the callId");
                    string callId = Console.ReadLine();
                    //var callRecord = await GraphHelper.GetCallRecord(callId != "" ? callId : "f4ea5721-a7b5-44ee-8ceb-9dfa7a6dd41e");
                    var callRecord = await GraphHelper.GetCallRecordSessions(callId != ""?callId : "f4ea5721-a7b5-44ee-8ceb-9dfa7a6dd41e");

                    var joinWebUrl = callRecord.JoinWebUrl;
                    if (joinWebUrl == null)
                    {
                        break;
                    }
                    foreach (Session session in callRecord.Sessions)
                    {
                        ParticipantEndpoint caller = (ParticipantEndpoint)session.Caller;
                        var user = await GraphHelper.GetUserAsync(caller.Identity.User.Id);

                        //TODO - Find the mapping between this userId and the university's student ID.
                        StudentEvent studentEvent = new StudentEvent
                        {
                            //TODO - Find course ID based on joinWebUrl.
                            CourseID     = "COMP0088", // Course ID Upper case.
                            Timestamp    = ((DateTimeOffset)session.StartDateTime).UtcDateTime,
                            EventType    = EventType.Attendance,
                            ActivityType = "Meeting",
                            ActivityName = "Weekly Lecture",
                            Student      = new Student
                            {
                                Email     = user.Mail,
                                FirstName = user.GivenName,
                                LastName  = user.Surname,
                                ID        = user.Id
                            }
                        };
                        Console.WriteLine(studentEvent.ToString());
                        //_eventAggregator.ProcessEvent(studentEvent);
                    }
                    break;

                default:
                    Console.WriteLine("Invalid choice! Please try again.");
                    break;
                }
            }
        }
Example #21
0
        public async Task <IActionResult> Submit([FromBody] FieldData body)
        {
            AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json");

            // You can run this sample using ClientSecret or Certificate. The code will differ only when instantiating the IConfidentialClientApplication
            bool isUsingClientSecret = AppUsesClientSecret(config);

            // Even if this is a console application here, a daemon application is a confidential client application
            IConfidentialClientApplication app;

            if (isUsingClientSecret)
            {
                app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                      .WithClientSecret(config.ClientSecret)
                      .WithAuthority(new Uri(config.Authority))
                      .Build();
            }

            else
            {
                X509Certificate2 certificate = ReadCertificate(config.CertificateName);
                app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                      .WithCertificate(certificate)
                      .WithAuthority(new Uri(config.Authority))
                      .Build();
            }

            // With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
            // application permissions need to be set statically (in the portal or by PowerShell), and then granted by
            // a tenant administrator.
            string[] scopes = new string[] { $"{config.ApiUrl}.default" };

            AuthenticationResult result = null;

            try
            {
                result = await app.AcquireTokenForClient(scopes)
                         .ExecuteAsync();
            }
            catch (MsalServiceException ex) when(ex.Message.Contains("AADSTS70011"))
            {
                // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
                // Mitigation: change the scope to be as expected
                return(BadRequest("Scope not supported"));
            }

            if (result != null)
            {
                var SOBListLocation    = $"{config.CpscSharepoint},c0cefe40-beeb-41a9-b4f5-9960bcfa010b,fbb78c64-1220-42fe-a319-94c493a9a105/lists/6f53c37b-d6ba-46c3-91a9-2e942a984af9/items";
                var webapiUrl          = $"{config.ApiUrl}v1.0/sites/{SOBListLocation}";
                var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, webapiUrl);
                httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
                var httpClient = new HttpClient();

                var apiCaller = new ProtectedApiCallHelper(httpClient);
                var res       = await apiCaller.CallWebApiAndProcessResultASync(httpRequestMessage, result.AccessToken);

                return(Ok(res));
                // await apiCaller.AddToSiteList("siteid", "listId", "payload", Display);//
            }
            else
            {
                return(Ok("result is null"));
            }
        }
Example #22
0
        private static async Task RunAsync()
        {
            AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json");

            // You can run this sample using ClientSecret or Certificate. The code will differ only when instantiating the IConfidentialClientApplication
            bool isUsingClientSecret = AppUsesClientSecret(config);

            // Even if this is a console application here, a daemon application is a confidential client application
            IConfidentialClientApplication app;

            if (isUsingClientSecret)
            {
                app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                      .WithClientSecret(config.ClientSecret)
                      .WithAuthority(new Uri(config.Authority))
                      .Build();
            }

            else
            {
                X509Certificate2 certificate = ReadCertificate(config.CertificateName);
                app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                      .WithCertificate(certificate)
                      .WithAuthority(new Uri(config.Authority))
                      .Build();
            }

            // With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
            // application permissions need to be set statically (in the portal or by PowerShell), and then granted by
            // a tenant administrator.
            string[] scopes = new string[] { $"{config.ApiUrl}.default" };

            AuthenticationResult result = null;

            try
            {
                result = await app.AcquireTokenForClient(scopes)
                         .ExecuteAsync();

                //Console.ForegroundColor = ConsoleColor.Green;
                //Console.WriteLine("Token acquired");
                //Console.ResetColor();
            }
            catch (MsalServiceException ex) when(ex.Message.Contains("AADSTS70011"))
            {
                // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
                // Mitigation: change the scope to be as expected
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Scope provided is not supported");
                Console.ResetColor();
            }

            if (result != null)
            {
                var httpClient = new HttpClient();
                var apiCaller  = new ProtectedApiCallHelper(httpClient);
                //await apiCaller.CallWebApiAndProcessResultASync($"{config.ApiUrl}v1.0/users", result.AccessToken, Display);
                //await apiCaller.CallWebApiAndProcessResultASync($"{config.ApiUrl}v1.0/users/[email protected]/events", result.AccessToken, Display);
                //await apiCaller.CallWebApiAndProcessResultASync($"{config.ApiUrl}v1.0/users/[email protected]/calendarView?startDateTime=2020-09-01T16:00:00.0000000&endDateTime=2020-12-07T16:00:00.0000000", result.AccessToken, Display);
                //await apiCaller.CallWebApiAndProcessResultASync($"{config.ApiUrl}v1.0/users/[email protected]/calendarView?startDateTime=2020-09-01T16:00:00.0000000&endDateTime=2020-12-07T16:00:00.0000000", result.AccessToken, Display);
                //await apiCaller.CallWebApiAndProcessResultASync($"{config.ApiUrl}v1.0/users//[email protected]/calendargroup/calendars/calendarView?startDateTime=2020-09-01T16:00:00.0000000&endDateTime=2020-12-07T16:00:00.0000000", result.AccessToken, Display);
                //await apiCaller.CallWebApiAndProcessResultASync($"{config.ApiUrl}v1.0/me/calendar", result.AccessToken, Display);

                await apiCaller.CallWebApiAndProcessResultASync($"{config.ApiUrl}v1.0/users/[email protected]/calendar/getSchedule", result.AccessToken, Display);
            }
        }
Example #23
0
        public static async Task <string> GetLabAccessTokenAsync(string authority, string[] scopes, LabAccessAuthenticationType authType, string clientId, string certThumbprint, string clientSecret)
        {
            AuthenticationResult           authResult;
            IConfidentialClientApplication confidentialApp;
            X509Certificate2 cert;

            switch (authType)
            {
            case LabAccessAuthenticationType.ClientCertificate:
                var clientIdForCertAuth  = String.IsNullOrEmpty(clientId) ? LabAccessConfidentialClientId : clientId;
                var certThumbprintForLab = String.IsNullOrEmpty(clientId) ? LabAccessThumbPrint : certThumbprint;

                cert = CertificateHelper.FindCertificateByThumbprint(certThumbprintForLab);
                if (cert == null)
                {
                    throw new InvalidOperationException(
                              "Test setup error - cannot find a certificate in the My store for KeyVault. This is available for Microsoft employees only.");
                }

                confidentialApp = ConfidentialClientApplicationBuilder
                                  .Create(clientIdForCertAuth)
                                  .WithAuthority(new Uri(authority), true)
                                  .WithCertificate(cert)
                                  .Build();

                s_staticCache.Bind(confidentialApp.AppTokenCache);

                authResult = await confidentialApp
                             .AcquireTokenForClient(scopes)
                             .ExecuteAsync(CancellationToken.None)
                             .ConfigureAwait(false);

                break;

            case LabAccessAuthenticationType.ClientSecret:
                var clientIdForSecretAuth = String.IsNullOrEmpty(clientId) ? LabAccessConfidentialClientId : clientId;
                var clientSecretForLab    = String.IsNullOrEmpty(clientId) ? s_secret : clientSecret;

                confidentialApp = ConfidentialClientApplicationBuilder
                                  .Create(clientIdForSecretAuth)
                                  .WithAuthority(new Uri(authority), true)
                                  .WithClientSecret(clientSecretForLab)
                                  .Build();
                s_staticCache.Bind(confidentialApp.AppTokenCache);

                authResult = await confidentialApp
                             .AcquireTokenForClient(scopes)
                             .ExecuteAsync(CancellationToken.None)
                             .ConfigureAwait(false);

                break;

            case LabAccessAuthenticationType.UserCredential:
                var clientIdForPublicClientAuth = String.IsNullOrEmpty(clientId) ? LabAccessPublicClientId : clientId;
                var publicApp = PublicClientApplicationBuilder
                                .Create(clientIdForPublicClientAuth)
                                .WithAuthority(new Uri(authority), true)
                                .Build();
                s_staticCache.Bind(publicApp.UserTokenCache);

                authResult = await publicApp
                             .AcquireTokenByIntegratedWindowsAuth(scopes)
                             .ExecuteAsync(CancellationToken.None)
                             .ConfigureAwait(false);

                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            return(authResult?.AccessToken);
        }
Example #24
0
        public async Task <int> SignAsync
        (
            CommandOption configFile,
            CommandOption inputFile,
            CommandOption outputFile,
            CommandOption fileList,
            CommandOption clientSecret,
            CommandOption username,
            CommandOption name,
            CommandOption description,
            CommandOption descriptionUrl
        )
        {
            try
            {
                // verify required parameters
                if (!configFile.HasValue())
                {
                    signCommandLineApplication.Error.WriteLine("--config parameter is required");
                    return(EXIT_CODES.INVALID_OPTIONS);
                }

                if (!inputFile.HasValue())
                {
                    signCommandLineApplication.Error.WriteLine("--input parameter is required");
                    return(EXIT_CODES.INVALID_OPTIONS);
                }

                if (!name.HasValue())
                {
                    signCommandLineApplication.Error.WriteLine("--name parameter is required");
                    return(EXIT_CODES.INVALID_OPTIONS);
                }

                if (!outputFile.HasValue())
                {
                    // use input as the output value
                    outputFile.Values.Add(inputFile.Value());
                }

                var builder = new ConfigurationBuilder()
                              .AddJsonFile(ExpandFilePath(configFile.Value()))
                              .AddEnvironmentVariables();

                var configuration = builder.Build();

                // Setup Refit
                var settings = new RefitSettings
                {
                    AuthorizationHeaderValueGetter = async() =>
                    {
                        var authority = $"{configuration["SignClient:AzureAd:AADInstance"]}{configuration["SignClient:AzureAd:TenantId"]}";

                        var clientId   = configuration["SignClient:AzureAd:ClientId"];
                        var resourceId = configuration["SignClient:Service:ResourceId"];

                        // See if we have a Username option
                        if (username.HasValue())
                        {
                            // ROPC flow
                            var pca = PublicClientApplicationBuilder.Create(clientId)
                                      .WithAuthority(authority)
                                      .Build();

                            var secret = new NetworkCredential("", clientSecret.Value()).SecurePassword;

                            var tokenResult = await pca.AcquireTokenByUsernamePassword(new[] { $"{resourceId}/user_impersonation" }, username.Value(), secret).ExecuteAsync();

                            return(tokenResult.AccessToken);
                        }
                        else
                        {
                            var context = ConfidentialClientApplicationBuilder.Create(clientId)
                                          .WithAuthority(authority)
                                          .WithClientSecret(clientSecret.Value())
                                          .Build();
                            // Client credential flow
                            var res = await context.AcquireTokenForClient(new[] { $"{resourceId}/.default" }).ExecuteAsync();

                            return(res.AccessToken);
                        }
                    }
                };


                var client = RestService.For <ISignService>(configuration["SignClient:Service:Url"], settings);
                client.Client.Timeout = Timeout.InfiniteTimeSpan; // TODO: Make configurable on command line

                // Prepare input/output file
                var input  = new FileInfo(ExpandFilePath(inputFile.Value()));
                var output = new FileInfo(ExpandFilePath(outputFile.Value()));
                Directory.CreateDirectory(output.DirectoryName);

                // Do action

                HttpResponseMessage response;

                response = await client.SignFile(input,
                                                 fileList.HasValue()?new FileInfo(ExpandFilePath(fileList.Value())) : null,
                                                 HashMode.Sha256,
                                                 name.Value(),
                                                 description.Value(),
                                                 descriptionUrl.Value());

                // Check response

                if (!response.IsSuccessStatusCode)
                {
                    Console.Error.WriteLine($"Server returned non Ok response: {(int)response.StatusCode} {response.ReasonPhrase}");
                    return(-1);
                }

                var str = await response.Content.ReadAsStreamAsync();

                using (var fs = output.OpenWrite())
                {
                    await str.CopyToAsync(fs).ConfigureAwait(false);
                }
            }
            catch (AuthenticationException e)
            {
                signCommandLineApplication.Error.WriteLine(e.Message);
                return(EXIT_CODES.FAILED);
            }
            catch (Exception e)
            {
                signCommandLineApplication.Error.WriteLine("Exception: " + e);
                return(EXIT_CODES.FAILED);
            }

            return(EXIT_CODES.SUCCESS);
        }
        public async Task <IActionResult> Api()
        {
            string responseString;

            try
            {
                // Retrieve the token with the specified scopes
                var    scope          = AzureAdB2COptions.ApiScopes.Split(' ');
                string signedInUserID = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;

                IConfidentialClientApplication cca =
                    ConfidentialClientApplicationBuilder.Create(AzureAdB2COptions.ClientId)
                    .WithLogging(MyLoggingMethod, LogLevel.Verbose, enablePiiLogging: false, enableDefaultPlatformLogging: true)
                    .WithRedirectUri(AzureAdB2COptions.RedirectUri)
                    .WithClientSecret(AzureAdB2COptions.ClientSecret)
                    .WithB2CAuthority(AzureAdB2COptions.Authority)
                    .Build();
                new MSALStaticCache(signedInUserID, this.HttpContext).EnablePersistence(cca.UserTokenCache);

                var accounts = await cca.GetAccountsAsync();

                AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault())
                                              .ExecuteAsync();

                if (result.AccessToken == null)
                {
                    ViewData["Title"]   = "JWT Token Problem";
                    ViewData["Payload"] = "The current user session does not have a valid access_token. Most likely the scopes do not match the App registration.";
                    return(View());
                }
                HttpClient         client  = new HttpClient();
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AzureAdB2COptions.ApiUrl);

                // Add token to the Authorization header and make the request
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
                HttpResponseMessage response = await client.SendAsync(request);

                // Handle the response
                switch (response.StatusCode)
                {
                case HttpStatusCode.OK:
                    responseString = await response.Content.ReadAsStringAsync();

                    break;

                case HttpStatusCode.Unauthorized:
                    responseString = $"Please sign in again. {response.ReasonPhrase}";
                    break;

                default:
                    responseString = $"Error calling API. StatusCode=${response.StatusCode}";
                    break;
                }
                client.Dispose();
            }
            catch (MsalUiRequiredException ex)
            {
                responseString = $"Session has expired. Please sign in again. {ex.Message}";
            }
            catch (Exception ex)
            {
                responseString = $"Error calling API: {ex.Message}";
            }

            ViewData["Payload"] = $"{responseString}";
            return(View());
        }
Example #26
0
        public void TestConstructor_WithDebugLoggingCallback()
        {
            var cca = ConfidentialClientApplicationBuilder.Create(MsalTestConstants.ClientId).WithDebugLoggingCallback().Build();

            Assert.IsNotNull(cca.AppConfig.LoggingCallback);
        }
Example #27
0
 public IConfidentialClientApplication CreateMsalConfidentialClient(string tenantId, string clientId, string clientSecret)
 {
     return(ConfidentialClientApplicationBuilder.Create(clientId).WithHttpClientFactory(new HttpPipelineClientFactory(HttpPipeline)).WithTenantId(tenantId).WithClientSecret(clientSecret).Build());
 }
Example #28
0
        private static async Task RunAsync()
        {
            AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json");

            // You can run this sample using ClientSecret or Certificate. The code will differ only when instantiating the IConfidentialClientApplication
            bool isUsingClientSecret = AppUsesClientSecret(config);

            // Even if this is a console application here, a daemon application is a confidential client application
            IConfidentialClientApplication app;

            if (isUsingClientSecret)
            {
                app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                      .WithClientSecret(config.ClientSecret)
                      .WithAuthority(new Uri(config.Authority))
                      .Build();
            }

            else
            {
                X509Certificate2 certificate = ReadCertificate(config.CertificateName);
                app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                      .WithCertificate(certificate)
                      .WithAuthority(new Uri(config.Authority))
                      .Build();
            }

            // With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
            // application permissions need to be set statically (in the portal or by PowerShell), and then granted by
            // a tenant administrator
            string[] scopes = new string[] { config.TargetApiScope };

            AuthenticationResult result = null;

            try
            {
                result = await app.AcquireTokenForClient(scopes)
                         .ExecuteAsync();

                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("Token acquired \n");
                Console.ResetColor();
            }
            catch (MsalServiceException ex) when(ex.Message.Contains("AADSTS70011"))
            {
                // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
                // Mitigation: change the scope to be as expected
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Scope provided is not supported");
                Console.ResetColor();
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(ex);
                Console.ResetColor();
            }

            if (result != null)
            {
                var httpClient = new HttpClient();
                var apiCaller  = new ProtectedApiCallHelper(httpClient);
                await apiCaller.CallWebApiAndProcessResultASync($"{config.TargetApiBaseAddress}/WeatherForecast/GetDataByRole", result.AccessToken, Display);
            }
        }
Example #29
0
        //Since this test performs a large number of operations it should not be rerun on other clouds.
        private async Task RunOnBehalfOfTestWithTokenCacheAsync(LabResponse labResponse)
        {
            LabUser user = labResponse.User;
            string  oboHost;
            string  secret;
            string  authority;
            string  publicClientID;
            string  confidentialClientID;

            string[] oboScope;

            oboHost              = PublicCloudHost;
            secret               = _keyVault.GetSecret(TestConstants.MsalOBOKeyVaultUri).Value;
            authority            = TestConstants.AuthorityOrganizationsTenant;
            publicClientID       = PublicCloudPublicClientIDOBO;
            confidentialClientID = PublicCloudConfidentialClientIDOBO;
            oboScope             = s_publicCloudOBOServiceScope;

            //TODO: acquire scenario specific client ids from the lab response

            SecureString securePassword = new NetworkCredential("", user.GetOrFetchPassword()).SecurePassword;
            var          factory        = new HttpSnifferClientFactory();

            var msalPublicClient = PublicClientApplicationBuilder.Create(publicClientID)
                                   .WithAuthority(authority)
                                   .WithRedirectUri(TestConstants.RedirectUri)
                                   .WithTestLogging()
                                   .WithHttpClientFactory(factory)
                                   .Build();

            var authResult = await msalPublicClient.AcquireTokenByUsernamePassword(oboScope, user.Upn, securePassword)
                             .ExecuteAsync()
                             .ConfigureAwait(false);

            var confidentialApp = ConfidentialClientApplicationBuilder
                                  .Create(confidentialClientID)
                                  .WithAuthority(new Uri(oboHost + authResult.TenantId), true)
                                  .WithClientSecret(secret)
                                  .WithTestLogging()
                                  .BuildConcrete();

            var userCacheRecorder = confidentialApp.UserTokenCache.RecordAccess();

            UserAssertion userAssertion = new UserAssertion(authResult.AccessToken);

            string atHash = userAssertion.AssertionHash;

            authResult = await confidentialApp.AcquireTokenOnBehalfOf(s_scopes, userAssertion)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

            MsalAssert.AssertAuthResult(authResult, user);
            Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
            Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource);

            //Run OBO again. Should get token from cache
            authResult = await confidentialApp.AcquireTokenOnBehalfOf(s_scopes, userAssertion)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

            Assert.IsNotNull(authResult);
            Assert.IsNotNull(authResult.AccessToken);
            Assert.IsNotNull(authResult.IdToken);
            Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache);
            Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens);
            Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
            Assert.AreEqual(TokenSource.Cache, authResult.AuthenticationResultMetadata.TokenSource);

            //Expire access tokens
            TokenCacheHelper.ExpireAllAccessTokens(confidentialApp.UserTokenCacheInternal);

            //Run OBO again. Should do OBO flow since the AT is expired and RTs aren't cached for normal OBO flow
            authResult = await confidentialApp.AcquireTokenOnBehalfOf(s_scopes, userAssertion)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

            Assert.IsNotNull(authResult);
            Assert.IsNotNull(authResult.AccessToken);
            Assert.IsNotNull(authResult.IdToken);
            Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache);
            Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens);
            Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
            Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource);
            AssertLastHttpContent("on_behalf_of");

            //creating second app with no refresh tokens
            var atItems          = confidentialApp.UserTokenCacheInternal.Accessor.GetAllAccessTokens();
            var confidentialApp2 = ConfidentialClientApplicationBuilder
                                   .Create(confidentialClientID)
                                   .WithAuthority(new Uri(oboHost + authResult.TenantId), true)
                                   .WithClientSecret(secret)
                                   .WithTestLogging()
                                   .WithHttpClientFactory(factory)
                                   .BuildConcrete();

            TokenCacheHelper.ExpireAccessToken(confidentialApp2.UserTokenCacheInternal, atItems.FirstOrDefault());

            //Should perform OBO flow since the access token is expired and the refresh token does not exist
            authResult = await confidentialApp2.AcquireTokenOnBehalfOf(s_scopes, userAssertion)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

            Assert.IsNotNull(authResult);
            Assert.IsNotNull(authResult.AccessToken);
            Assert.IsNotNull(authResult.IdToken);
            Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache);
            Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens);
            Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
            Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource);
            AssertLastHttpContent("on_behalf_of");

            TokenCacheHelper.ExpireAllAccessTokens(confidentialApp2.UserTokenCacheInternal);
            TokenCacheHelper.UpdateUserAssertions(confidentialApp2);

            //Should perform OBO flow since the access token and the refresh token contains the wrong user assertion hash
            authResult = await confidentialApp2.AcquireTokenOnBehalfOf(s_scopes, userAssertion)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

            Assert.IsNotNull(authResult);
            Assert.IsNotNull(authResult.AccessToken);
            Assert.IsNotNull(authResult.IdToken);
            Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache);
            Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens);
            Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
            Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource);
            AssertLastHttpContent("on_behalf_of");
        }
        private async Task AcquireAndAcquireSilent_MultipleKeys_Async(LabResponse labResponse)
        {
            var popConfig1 = new PoPAuthenticationConfiguration(new Uri("https://www.contoso.com/path1/path2?queryParam1=a&queryParam2=b"));

            popConfig1.HttpMethod = HttpMethod.Get;
            var popConfig2 = new PoPAuthenticationConfiguration(new Uri("https://www.bing.com/path3/path4?queryParam5=c&queryParam6=d"));

            popConfig2.HttpMethod = HttpMethod.Post;

            var          user           = labResponse.User;
            SecureString securePassword = new NetworkCredential("", user.GetOrFetchPassword()).SecurePassword;

            var pca = ConfidentialClientApplicationBuilder.Create(PublicCloudConfidentialClientID)
                      .WithExperimentalFeatures()
                      .WithTestLogging()
                      .WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
                      .WithClientSecret(s_publicCloudCcaSecret).Build();

            ConfigureInMemoryCache(pca);

            var result = await pca
                         .AcquireTokenForClient(s_keyvaultScope)
                         .WithExtraQueryParameters(GetTestSliceParams())
                         .WithProofOfPossession(popConfig1)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

            Assert.AreEqual("pop", result.TokenType);
            await VerifyPoPTokenAsync(
                PublicCloudConfidentialClientID,
                popConfig1, result).ConfigureAwait(false);

            // recreate the pca to ensure that the silent call is served from the cache, i.e. the key remains stable
            pca = ConfidentialClientApplicationBuilder
                  .Create(PublicCloudConfidentialClientID)
                  .WithExperimentalFeatures()
                  .WithClientSecret(s_publicCloudCcaSecret)
                  .WithHttpClientFactory(new NoAccessHttpClientFactory()) // token should be served from the cache, no network access necessary
                  .Build();
            ConfigureInMemoryCache(pca);

            var accounts = await pca.GetAccountsAsync().ConfigureAwait(false);

            result = await pca
                     .AcquireTokenSilent(s_keyvaultScope, accounts.Single())
                     .WithProofOfPossession(popConfig1)
                     .ExecuteAsync()
                     .ConfigureAwait(false);

            Assert.AreEqual("pop", result.TokenType);
            await VerifyPoPTokenAsync(
                PublicCloudConfidentialClientID,
                popConfig1, result).ConfigureAwait(false);

            // Call some other Uri - the same pop assertion can be reused, i.e. no need to call Evo
            result = await pca
                     .AcquireTokenSilent(s_keyvaultScope, accounts.Single())
                     .WithProofOfPossession(popConfig2)
                     .ExecuteAsync()
                     .ConfigureAwait(false);

            Assert.AreEqual("pop", result.TokenType);
            await VerifyPoPTokenAsync(
                PublicCloudConfidentialClientID,
                popConfig2, result).ConfigureAwait(false);
        }