public async Task <ActionResult> Listen() { // Validate the new subscription by sending the token back to Microsoft Graph. // This response is required for each subscription. var query = QueryHelpers.ParseQuery(Request.QueryString.ToString()); if (query.ContainsKey("validationToken")) { return(Content(query["validationToken"], "plain/text")); } // Parse the received notifications. else { try { Dictionary <string, Notification> notifications = new Dictionary <string, Notification>(); using (var inputStream = new System.IO.StreamReader(Request.Body)) { JObject jsonObject = JObject.Parse(inputStream.ReadToEnd()); if (jsonObject != null) { // Notifications are sent in a 'value' array. The array might contain multiple notifications for events that are // registered for the same notification endpoint, and that occur within a short timespan. JArray value = JArray.Parse(jsonObject["value"].ToString()); foreach (var notification in value) { Notification current = JsonConvert.DeserializeObject <Notification>(notification.ToString()); SubscriptionStore subscription = subscriptionStore.GetSubscriptionInfo(current.SubscriptionId); // Verify the current client state matches the one that was sent. if (current.ClientState == subscription.ClientState) { // Just keep the latest notification for each resource. No point pulling data more than once. notifications[current.Resource] = current; } } if (notifications.Count > 0) { // Query for the changed messages. await GetChangedMessagesAsync(notifications.Values); } } } } catch (Exception ex) { logger.LogError($"ParsingNotification: { ex.Message }"); // TODO: Handle the exception. // Still return a 202 so the service doesn't resend the notification. } return(new StatusCodeResult(202)); } }
public async Task <IActionResult> Listen([FromQuery] string validationToken = null) { if (string.IsNullOrEmpty(validationToken)) { try { // Parse the received notifications. var plainNotifications = new Dictionary <string, ChangeNotification>(); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); var collection = await JsonSerializer.DeserializeAsync <ChangeNotificationCollection>(Request.Body, options); foreach (var notification in collection.Value.Where(x => x.EncryptedContent == null)) { SubscriptionStore subscription = subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId.Value); // Verify the current client state matches the one that was sent. if (notification.ClientState == subscription.ClientState) { // Just keep the latest notification for each resource. No point pulling data more than once. plainNotifications[notification.Resource] = notification; } } if (plainNotifications.Count > 0) { // Query for the changed messages. await GetChangedMessagesAsync(plainNotifications.Values); } if (collection.ValidationTokens != null && collection.ValidationTokens.Any()) { // we're getting notifications with resource data and we should validate tokens and decrypt data TokenValidator tokenValidator = new TokenValidator(identityOptions.Value.TenantId, new[] { identityOptions.Value.ClientId }); bool areValidationTokensValid = (await Task.WhenAll( collection.ValidationTokens.Select(x => tokenValidator.ValidateToken(x))).ConfigureAwait(false)) .Aggregate((x, y) => x && y); if (areValidationTokensValid) { List <NotificationViewModel> notificationsToDisplay = new List <NotificationViewModel>(); foreach (var notificationItem in collection.Value.Where(x => x.EncryptedContent != null)) { string decryptedpublisherNotification = Decryptor.Decrypt( notificationItem.EncryptedContent.Data, notificationItem.EncryptedContent.DataKey, notificationItem.EncryptedContent.DataSignature, await keyVaultManager.GetDecryptionCertificate().ConfigureAwait(false)); notificationsToDisplay.Add(new NotificationViewModel(decryptedpublisherNotification)); } await notificationService.SendNotificationToClient(notificationHub, notificationsToDisplay); return(Accepted()); } else { return(Unauthorized("Token Validation failed")); } } } catch (Exception ex) { logger.LogError($"ParsingNotification: { ex.Message }"); // Still return a 202 so the service doesn't resend the notification. } return(Accepted()); } else { // Validate the new subscription by sending the token back to Microsoft Graph. // This response is required for each subscription. return(Content(validationToken)); } }