private async Task RefreshSubscription(IGraphServiceClient client, string id) { try { var subscription = new Subscription { ExpirationDateTime = DateTime.UtcNow + _options.WebhookExpirationTimeSpan, }; var result = await client.Subscriptions[id].Request().UpdateAsync(subscription); _log.LogInformation($"Successfully renewed MS Graph subscription {id}. \n Active until {subscription.ExpirationDateTime}"); } catch (Exception ex) { // If the subscription is expired, it can no longer be renewed, so delete the file var subscriptionEntry = await _subscriptionStore.GetSubscriptionEntryAsync(id); if (subscriptionEntry != null) { if (subscriptionEntry.Subscription.ExpirationDateTime < DateTime.UtcNow) { await _subscriptionStore.DeleteAsync(id); } else { _log.LogError(ex, "A non-expired subscription failed to renew"); } } else { _log.LogWarning("The subscription with id " + id + " was not present in the local subscription store."); } } }
private async Task HandleNotificationsAsync(NotificationPayload notifications, CancellationToken cancellationToken) { // A single webhook might get notifications from different users. List <WebhookTriggerData> resources = new List <WebhookTriggerData>(); foreach (Notification notification in notifications.Value) { var subId = notification.SubscriptionId; var entry = await _subscriptionStore.GetSubscriptionEntryAsync(subId); if (entry == null) { _log.LogError($"No subscription exists in our store for subscription id: {subId}"); // mapping of subscription ID to principal ID does not exist in file system continue; } if (entry.Subscription.ClientState != notification.ClientState) { _log.LogTrace($"The subscription store's client state: {entry.Subscription.ClientState} did not match the notifications's client state: {notification.ClientState}"); // Stored client state does not match client state we just received continue; } // call onto Graph to fetch the resource var userId = entry.UserId; var graphClient = await _clientManager.GetMSGraphClientFromUserIdAsync(userId, cancellationToken); _log.LogTrace($"A graph client was obtained for subscription id: {subId}"); // Prepend with / if necessary if (notification.Resource[0] != '/') { notification.Resource = '/' + notification.Resource; } var url = graphClient.BaseUrl + notification.Resource; HttpRequestMessage request = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(url), }; _log.LogTrace($"Making a GET request to {url} on behalf of subId: {subId}"); await graphClient.AuthenticationProvider.AuthenticateRequestAsync(request); // Add authentication header var response = await graphClient.HttpProvider.SendAsync(request); string responseContent = await response.Content.ReadAsStringAsync(); _log.LogTrace($"Recieved {responseContent} from request to {url}"); var actualPayload = JObject.Parse(responseContent); // Superimpose some common properties onto the JObject for easy access. actualPayload["ClientState"] = entry.Subscription.ClientState; // Drive items only payload without resource data string odataType = null; if (notification.ResourceData != null) { odataType = notification.ResourceData.ODataType; } else if (notification.Resource.StartsWith("/me/drive")) { odataType = "#Microsoft.Graph.DriveItem"; } actualPayload["@odata.type"] = odataType; var data = new WebhookTriggerData { client = graphClient, Payload = actualPayload, odataType = odataType, }; resources.Add(data); } _log.LogTrace($"Triggering {resources.Count} GraphWebhookTriggers"); Task[] webhookReceipts = resources.Select(item => _bindingProvider.PushDataAsync(item)).ToArray(); Task.WaitAll(webhookReceipts); _log.LogTrace($"Finished responding to notifications."); }