public async Task Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer, ILogger log) { log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); if (myTimer.IsPastDue) { log.LogInformation("Skipping past due invocation, waiting for next scheduled run"); return; } var subscriptions = await _msalAccountActivityStore.GetSubscriptionActivities(); foreach (var subscription in subscriptions) { // Get the most recently updated msal account information for this subscription var account = await _msalAccountActivityStore.GetMsalAccountActivityForSubscription(subscription.SubscriptionId); if (account != null) { // Configure the confidential client to get an access token for the needed resource var app = ConfidentialClientApplicationBuilder.Create(_config.GetValue <string>("AzureAd:ClientId")) .WithClientSecret(_config.GetValue <string>("AzureAd:ClientSecret")) .WithAuthority($"{_config.GetValue<string>("AzureAd:Instance")}{_config.GetValue<string>("AzureAd:TenantId")}") .Build(); // Initialize the MSAL cache for the specific account var msalCache = new BackgroundWorkerTokenCacheAdapter(account.AccountCacheKey, _serviceProvider.GetService <IDistributedCache>(), _serviceProvider.GetService <ILogger <MsalDistributedTokenCacheAdapter> >(), _serviceProvider.GetService <IOptions <MsalDistributedTokenCacheAdapterOptions> >()); await msalCache.InitializeAsync(app.UserTokenCache); // Prepare an MsalAccount instance representing the user we want to get a token for var hydratedAccount = new MsalAccount { HomeAccountId = new AccountId( account.AccountIdentifier, account.AccountObjectId, account.AccountTenantId) }; try { // Use the confidential MSAL client to get a token for the user we need to impersonate var result = await app.AcquireTokenSilent(Constants.BasePermissionScopes, hydratedAccount) .ExecuteAsync() .ConfigureAwait(false); // Configure the Graph SDK to use an auth provider that takes the token we've just requested var authenticationProvider = new DelegateAuthenticationProvider( (requestMessage) => { requestMessage.Headers.Authorization = new AuthenticationHeaderValue(CoreConstants.Headers.Bearer, result.AccessToken); return(Task.FromResult(0)); }); var graphClient = new GraphServiceClient(authenticationProvider); // Add/renew the subscriptions await SubscriptionManagement.ManageSubscription(subscription, account.AccountObjectId, account.AccountTenantId, account.UserPrincipalName, _config, _msalAccountActivityStore, graphClient, _msalTokenCacheProvider); } catch (MsalUiRequiredException ex) { /* * If MsalUiRequiredException is thrown for an account, it means that a user interaction is required * thus the background worker wont be able to acquire a token silently for it. * The user of that account will have to access the web app to perform this interaction. * Examples that could cause this: MFA requirement, token expired or revoked, token cache deleted, etc */ await _msalAccountActivityStore.HandleIntegratedTokenAcquisitionFailure(account); log.LogError($"Could not acquire token for account {account.UserPrincipalName}."); log.LogError($"Error: {ex.Message}"); } catch (Exception ex) { throw ex; } } } }
private static async Task RunAsync() { var scopes = new string[] { "User.Read" }; // Return the MsalAccountActivities of the users that you would like to acquire a token silently var someTimeAgo = DateTime.Now.AddSeconds(-30); var accountsToAcquireToken = await _msalAccountActivityStore.GetMsalAccountActivitesSince(someTimeAgo); // Or you could also return the account activity of a particular user //var userActivityAccount = await _msalAccountActivityStore.GetMsalAccountActivityForUser("User-UPN"); if (accountsToAcquireToken == null || accountsToAcquireToken.Count() == 0) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"No accounts returned"); Console.ResetColor(); } else { Console.WriteLine($"Trying to acquire token silently for {accountsToAcquireToken.Count()} activities..."); Console.Write(Environment.NewLine); IConfidentialClientApplication app = await GetConfidentialClientApplication(config); // For each user's record, hydrate an IAccount with the values saved on the table, and call AcquireTokenSilent for this account. foreach (var account in accountsToAcquireToken) { var hydratedAccount = new MsalAccount { HomeAccountId = new AccountId( account.AccountIdentifier, account.AccountObjectId, account.AccountTenantId) }; try { var result = await app.AcquireTokenSilent(scopes, hydratedAccount) .ExecuteAsync() .ConfigureAwait(false); Console.WriteLine($"Token acquired for account: {account.UserPrincipalName}"); Console.WriteLine($"Access token preview: {result.AccessToken.Substring(0, 70)} ..."); Console.WriteLine(" <------------------------> "); Console.Write(Environment.NewLine); } catch (MsalUiRequiredException ex) { /* * If MsalUiRequiredException is thrown for an account, it means that a user interaction is required * thus the background worker wont be able to acquire a token silently for it. * The user of that account will have to access the web app to perform this interaction. * Examples that could cause this: MFA requirement, token expired or revoked, token cache deleted, etc */ await _msalAccountActivityStore.HandleIntegratedTokenAcquisitionFailure(account); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Could not acquire token for account {account.UserPrincipalName}."); Console.WriteLine($"Error: {ex.Message}"); Console.WriteLine(" <------------------------> "); Console.Write(Environment.NewLine); Console.ResetColor(); } catch (Exception ex) { throw ex; } } } Console.ForegroundColor = ConsoleColor.Green; Console.Write(Environment.NewLine); Console.WriteLine($"Task completed."); Console.ResetColor(); }
public async Task Run([QueueTrigger(Constants.OneDriveFileNotificationsQueue)] string myQueueItem, ILogger log) { log.LogInformation($"C# Queue trigger function processed: {myQueueItem}"); var notification = JsonSerializer.Deserialize <ChangeNotification>(myQueueItem); // Get the most recently updated msal account information for this subscription var account = await _msalAccountActivityStore.GetMsalAccountActivityForSubscription(notification.SubscriptionId); if (account != null) { // Configure the confidential client to get an access token for the needed resource var app = ConfidentialClientApplicationBuilder.Create(_config.GetValue <string>("AzureAd:ClientId")) .WithClientSecret(_config.GetValue <string>("AzureAd:ClientSecret")) .WithAuthority($"{_config.GetValue<string>("AzureAd:Instance")}{_config.GetValue<string>("AzureAd:TenantId")}") .Build(); // Initialize the MSAL cache for the specific account var msalCache = new BackgroundWorkerTokenCacheAdapter(account.AccountCacheKey, _serviceProvider.GetService <IDistributedCache>(), _serviceProvider.GetService <ILogger <MsalDistributedTokenCacheAdapter> >(), _serviceProvider.GetService <IOptions <MsalDistributedTokenCacheAdapterOptions> >()); await msalCache.InitializeAsync(app.UserTokenCache); // Prepare an MsalAccount instance representing the user we want to get a token for var hydratedAccount = new MsalAccount { HomeAccountId = new AccountId( account.AccountIdentifier, account.AccountObjectId, account.AccountTenantId) }; try { // Use the confidential MSAL client to get a token for the user we need to impersonate var result = await app.AcquireTokenSilent(Constants.BasePermissionScopes, hydratedAccount) .ExecuteAsync() .ConfigureAwait(false); //log.LogInformation($"Token acquired: {result.AccessToken}"); // Configure the Graph SDK to use an auth provider that takes the token we've just requested var authenticationProvider = new DelegateAuthenticationProvider( (requestMessage) => { requestMessage.Headers.Authorization = new AuthenticationHeaderValue(CoreConstants.Headers.Bearer, result.AccessToken); return(Task.FromResult(0)); }); var graphClient = new GraphServiceClient(authenticationProvider); // Retrieve the last used subscription activity information for this user+subscription var subscriptionActivity = await _msalAccountActivityStore.GetSubscriptionActivityForUserSubscription(account.AccountObjectId, account.AccountTenantId, account.UserPrincipalName, notification.SubscriptionId); // Make graph call on behalf of the user: do the delta query providing the last used change token so only new changes are returned IDriveItemDeltaCollectionPage deltaCollection = await graphClient.Me.Drive.Root.Delta(subscriptionActivity.LastChangeToken).Request().GetAsync(); bool morePagesAvailable = false; do { // If there is a NextPageRequest, there are more pages morePagesAvailable = deltaCollection.NextPageRequest != null; foreach (var driveItem in deltaCollection.CurrentPage) { await ProcessDriveItemChanges(driveItem, log); } if (morePagesAvailable) { // Get the next page of results deltaCollection = await deltaCollection.NextPageRequest.GetAsync(); } }while (morePagesAvailable); // Get the last used change token var deltaLink = deltaCollection.AdditionalData["@odata.deltaLink"]; if (!string.IsNullOrEmpty(deltaLink.ToString())) { var token = GetChangeTokenFromUrl(deltaLink.ToString()); subscriptionActivity.LastChangeToken = token; } // Persist back the last used change token await _msalAccountActivityStore.UpsertSubscriptionActivity(subscriptionActivity); } catch (MsalUiRequiredException ex) { /* * If MsalUiRequiredException is thrown for an account, it means that a user interaction is required * thus the background worker wont be able to acquire a token silently for it. * The user of that account will have to access the web app to perform this interaction. * Examples that could cause this: MFA requirement, token expired or revoked, token cache deleted, etc */ await _msalAccountActivityStore.HandleIntegratedTokenAcquisitionFailure(account); log.LogError($"Could not acquire token for account {account.UserPrincipalName}."); log.LogError($"Error: {ex.Message}"); } catch (Exception ex) { throw ex; } } }