public ActionResult Listen() { // Validate the new subscription by sending the token back to Microsoft Graph. // This response is required for each subscription. if (Request.QueryString["validationToken"] != null) { var token = Request.QueryString["validationToken"]; return(Content(token, "plain/text")); } // Parse the received notifications. else { try { using (var inputStream = new System.IO.StreamReader(Request.InputStream)) { 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 time span. JArray value = JArray.Parse(jsonObject["value"].ToString()); foreach (var notification in value) { Notification current = JsonConvert.DeserializeObject <Notification>(notification.ToString()); // Check client state to verify the message is from Microsoft Graph. SubscriptionStore subscription = SubscriptionStore.GetSubscriptionInfo(current.SubscriptionId); // This sample only works with subscriptions that are still cached. if (subscription != null) { if (current.ClientState == subscription.ClientState) { //Store the notifications in application state. A production //application would likely queue for additional processing. var notificationArray = (ConcurrentBag <Notification>)HttpContext.Application["notifications"]; if (notificationArray == null) { notificationArray = new ConcurrentBag <Notification>(); } notificationArray.Add(current); HttpContext.Application["notifications"] = notificationArray; } } } } } } catch (Exception) { // TODO: Handle the exception. // Still return a 202 so the service doesn't resend the notification. } return(new HttpStatusCodeResult(202)); } }
public async Task <ActionResult> CreateSubscription() { string subscriptionsEndpoint = "https://graph.microsoft.com/v1.0/subscriptions/"; // This sample subscribes to get notifications when the user receives an email. HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, subscriptionsEndpoint); Subscription subscription = new Subscription { Resource = "me/mailFolders('Inbox')/messages", ChangeType = "created", NotificationUrl = ConfigurationManager.AppSettings["ida:NotificationUrl"], ClientState = Guid.NewGuid().ToString(), //ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 4230, 0) // current maximum timespan for messages ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 15, 0) // shorter duration useful for testing }; string contentString = JsonConvert.SerializeObject(subscription, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); request.Content = new StringContent(contentString, System.Text.Encoding.UTF8, "application/json"); // try to get token silently string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value; TokenCache userTokenCache = new MSALSessionCache(signedInUserID, this.HttpContext).GetMsalCacheInstance(); ConfidentialClientApplication cca = new ConfidentialClientApplication(clientId, redirectUri, new ClientCredential(appKey), userTokenCache, null); if (cca.Users.Count() > 0) { string[] scopes = { "Mail.Read" }; try { AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes, cca.Users.First()); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken); HttpResponseMessage response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { ViewBag.AuthorizationRequest = null; string stringResult = await response.Content.ReadAsStringAsync(); SubscriptionViewModel viewModel = new SubscriptionViewModel { Subscription = JsonConvert.DeserializeObject <Subscription>(stringResult) }; // This sample temporarily stores the current subscription ID, client state, user object ID, and tenant ID. // This info is required so the NotificationController, which is not authenticated, can retrieve an access token from the cache and validate the subscription. // Production apps typically use some method of persistent storage. SubscriptionStore.SaveSubscriptionInfo(viewModel.Subscription.Id, viewModel.Subscription.ClientState, ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value, ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid")?.Value); // This sample just saves the current subscription ID to the session so we can delete it later. Session["SubscriptionId"] = viewModel.Subscription.Id; return(View("Subscription", viewModel)); } } catch (MsalUiRequiredException) { try {// when failing, manufacture the URL and assign it string authReqUrl = await WebApp.Utils.OAuth2RequestManager.GenerateAuthorizationRequestUrl(scopes, cca, this.HttpContext, Url); ViewBag.AuthorizationRequest = authReqUrl; } catch (Exception ee) { } } } else { } return(View("Subscription", null)); }
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(WebUtility.HtmlEncode(validationToken))); } }
public async Task <ActionResult> CreateSubscription() { string subscriptionsEndpoint = "https://graph.microsoft.com/v1.0/subscriptions/"; string accessToken; try { // Get an access token. accessToken = await AuthHelper.GetAccessTokenAsync(); } catch (Exception e) { ViewBag.Message = BuildErrorMessage(e); return(View("Error", e)); } // Build the request. HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // This sample subscribes to get notifications when the user receives an email. HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, subscriptionsEndpoint); Subscription subscription = new Subscription { Resource = "me/mailFolders('Inbox')/messages", ChangeType = "created", NotificationUrl = ConfigurationManager.AppSettings["ida:NotificationUrl"], ClientState = Guid.NewGuid().ToString(), //ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 4230, 0) // current maximum timespan for messages ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 15, 0) // shorter duration useful for testing }; string contentString = JsonConvert.SerializeObject(subscription, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); request.Content = new StringContent(contentString, System.Text.Encoding.UTF8, "application/json"); // Send the `POST subscriptions` request and parse the response. HttpResponseMessage response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { string stringResult = await response.Content.ReadAsStringAsync(); SubscriptionViewModel viewModel = new SubscriptionViewModel { Subscription = JsonConvert.DeserializeObject <Subscription>(stringResult) }; // This sample temporarily stores the current subscription ID, client state, user object ID, and tenant ID. // This info is required so the NotificationController, which is not authenticated, can retrieve an access token from the cache and validate the subscription. // Production apps typically use some method of persistent storage. SubscriptionStore.SaveSubscriptionInfo(viewModel.Subscription.Id, viewModel.Subscription.ClientState, ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value, ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid")?.Value); // This sample just saves the current subscription ID to the session so we can delete it later. Session["SubscriptionId"] = viewModel.Subscription.Id; return(View("Subscription", viewModel)); } else { return(RedirectToAction("Index", "Error", new { message = response.StatusCode, debug = await response.Content.ReadAsStringAsync() })); } }
// Get information about the changed messages and send to browser via SignalR. // A production application would typically queue a background job for reliability. private async Task GetChangedMessagesAsync(IEnumerable <ChangeNotification> notifications) { List <NotificationViewModel> notificationsToDisplay = new List <NotificationViewModel>(); foreach (var notification in notifications) { SubscriptionStore subscription = subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId.Value); // Set the claims for ObjectIdentifier and TenantId, and // use the above claims for the current HttpContext if (!string.IsNullOrEmpty(subscription.UserId)) { HttpContext.User = ClaimsPrincipalFactory.FromTenantIdAndObjectId(subscription.TenantId, subscription.UserId); } // Initialize the GraphServiceClient. var graphClient = await GraphServiceClientFactory.GetAuthenticatedGraphClient(appSettings.Value.GraphApiUrl, async() => { if (string.IsNullOrEmpty(subscription.UserId)) { return(await tokenAcquisition.GetAccessTokenForAppAsync(new string[] { $"{appSettings.Value.GraphApiUrl}/.default" })); } else { return(await tokenAcquisition.GetAccessTokenForUserAsync(new[] { subscriptionOptions.Value.Scope })); } }); if (notification.ResourceData.ODataType == "#Microsoft.Graph.Message") { var request = new MessageRequest(graphClient.BaseUrl + "/" + notification.Resource, graphClient, null); try { var responseValue = await request.GetAsync(); notificationsToDisplay.Add(new NotificationViewModel(new { From = responseValue?.From?.EmailAddress?.Address, responseValue?.Subject, SentDateTime = responseValue?.SentDateTime.HasValue ?? false ? responseValue?.SentDateTime.Value.ToString() : string.Empty, To = responseValue?.ToRecipients?.Select(x => x.EmailAddress.Address)?.ToList(), })); } catch (ServiceException se) { string errorMessage = se.Error.Message; string requestId = se.Error.InnerError.AdditionalData["request-id"].ToString(); string requestDate = se.Error.InnerError.AdditionalData["date"].ToString(); logger.LogError($"RetrievingMessages: { errorMessage } Request ID: { requestId } Date: { requestDate }"); } } else { notificationsToDisplay.Add(new NotificationViewModel(notification.Resource)); } } if (notificationsToDisplay.Count > 0) { await notificationService.SendNotificationToClient(notificationHub, notificationsToDisplay); } }
/// <inheritdoc cref="Initialize"/> public override async Task <TransportInfrastructure> Initialize(HostSettings hostSettings, ReceiveSettings[] receiversSettings, string[] sendingAddresses, CancellationToken cancellationToken = default) { if (queueServiceClientProvider == null) { //legacy shim API guard: if queueServiceClientProvider is null it means that ConnectionString() has not been invoked throw new Exception("Cannot initialize the transport without a valid connection " + "string or a configured QueueServiceClient. If using the obsoleted API to " + "configure the transport, make sure to call transportConfig.ConnectionString() " + "to configure the client connection string."); } Guard.AgainstNull(nameof(hostSettings), hostSettings); Guard.AgainstNull(nameof(receiversSettings), receiversSettings); Guard.AgainstNull(nameof(sendingAddresses), sendingAddresses); ValidateReceiversSettings(receiversSettings); if (hostSettings.SetupInfrastructure) { var queuesToCreate = receiversSettings.Select(settings => settings.ReceiveAddress).Union(sendingAddresses).ToList(); if (SupportsDelayedDelivery && !string.IsNullOrWhiteSpace(DelayedDelivery.DelayedDeliveryPoisonQueue)) { queuesToCreate.Add(DelayedDelivery.DelayedDeliveryPoisonQueue); } var queueCreator = new AzureMessageQueueCreator(queueServiceClientProvider, GetQueueAddressGenerator()); await queueCreator.CreateQueueIfNecessary(queuesToCreate, cancellationToken) .ConfigureAwait(false); } var localAccountInfo = new AccountInfo("", queueServiceClientProvider.Client, cloudTableClientProvider.Client); var azureStorageAddressing = new AzureStorageAddressingSettings(GetQueueAddressGenerator(), AccountRouting.DefaultAccountAlias, Subscriptions.SubscriptionTableName, AccountRouting.Mappings, localAccountInfo); object delayedDeliveryPersistenceDiagnosticSection = new { }; CloudTable delayedMessagesStorageTable = null; var nativeDelayedDeliveryPersistence = NativeDelayDeliveryPersistence.Disabled(); if (SupportsDelayedDelivery) { delayedMessagesStorageTable = await EnsureNativeDelayedDeliveryTable( hostSettings.Name, DelayedDelivery.DelayedDeliveryTableName, cloudTableClientProvider.Client, hostSettings.SetupInfrastructure, cancellationToken).ConfigureAwait(false); nativeDelayedDeliveryPersistence = new NativeDelayDeliveryPersistence(delayedMessagesStorageTable); delayedDeliveryPersistenceDiagnosticSection = new { DelayedDeliveryTableName = delayedMessagesStorageTable.Name, UserDefinedDelayedDeliveryTableName = !string.IsNullOrWhiteSpace(DelayedDelivery.DelayedDeliveryTableName) }; } var serializerSettingsHolder = hostSettings.CoreSettings; if (serializerSettingsHolder == null) { //in raw transport mode to set up the required serializer a settings holder //is needed to store MessageMetadataRegistry and Conventions instances. //https://github.com/Particular/NServiceBus.AzureStorageQueues/issues/524 var tempSettingsHolder = new SettingsHolder(); const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance; var conventions = tempSettingsHolder.GetOrCreate <Conventions>(); var registry = (MessageMetadataRegistry)Activator.CreateInstance(typeof(MessageMetadataRegistry), flags, null, new object[] { new Func <Type, bool>(t => conventions.IsMessageType(t)) }, CultureInfo.InvariantCulture); tempSettingsHolder.Set(registry); serializerSettingsHolder = tempSettingsHolder; } var serializer = BuildSerializer(MessageWrapperSerializationDefinition, serializerSettingsHolder); object subscriptionsPersistenceDiagnosticSection = new { }; ISubscriptionStore subscriptionStore = new NoOpSubscriptionStore(); if (SupportsPublishSubscribe) { var subscriptionTable = await EnsureSubscriptionTableExists(cloudTableClientProvider.Client, Subscriptions.SubscriptionTableName, hostSettings.SetupInfrastructure, cancellationToken) .ConfigureAwait(false); object subscriptionPersistenceCachingSection = new { IsEnabled = false }; subscriptionStore = new SubscriptionStore(azureStorageAddressing); if (Subscriptions.DisableCaching == false) { subscriptionPersistenceCachingSection = new { IsEnabled = true, Subscriptions.CacheInvalidationPeriod }; subscriptionStore = new CachedSubscriptionStore(subscriptionStore, Subscriptions.CacheInvalidationPeriod); } subscriptionsPersistenceDiagnosticSection = new { SubscriptionTableName = subscriptionTable.Name, UserDefinedSubscriptionTableName = !string.IsNullOrWhiteSpace(Subscriptions.SubscriptionTableName), Caching = subscriptionPersistenceCachingSection }; } var dispatcher = new Dispatcher(GetQueueAddressGenerator(), azureStorageAddressing, serializer, nativeDelayedDeliveryPersistence, subscriptionStore); object delayedDeliveryProcessorDiagnosticSection = new { }; var nativeDelayedDeliveryProcessor = NativeDelayedDeliveryProcessor.Disabled(); if (SupportsDelayedDelivery) { var nativeDelayedDeliveryErrorQueue = DelayedDelivery.DelayedDeliveryPoisonQueue ?? hostSettings.CoreSettings?.GetOrDefault <string>(ErrorQueueSettings.SettingsKey) ?? receiversSettings.Select(settings => settings.ErrorQueue).FirstOrDefault(); nativeDelayedDeliveryProcessor = new NativeDelayedDeliveryProcessor( dispatcher, delayedMessagesStorageTable, blobServiceClientProvider.Client, nativeDelayedDeliveryErrorQueue, TransportTransactionMode, new BackoffStrategy(PeekInterval, MaximumWaitTimeWhenIdle)); nativeDelayedDeliveryProcessor.Start(cancellationToken); delayedDeliveryProcessorDiagnosticSection = new { DelayedDeliveryPoisonQueue = nativeDelayedDeliveryErrorQueue, UserDefinedDelayedDeliveryPoisonQueue = !string.IsNullOrWhiteSpace(DelayedDelivery.DelayedDeliveryPoisonQueue) }; } var messageReceivers = receiversSettings.Select(settings => BuildReceiver(hostSettings, settings, serializer, hostSettings.CriticalErrorAction, subscriptionStore)) .ToDictionary(receiver => receiver.Id, receiver => receiver.Receiver); var infrastructure = new AzureStorageQueueInfrastructure( dispatcher, new ReadOnlyDictionary <string, IMessageReceiver>(messageReceivers), nativeDelayedDeliveryProcessor); hostSettings.StartupDiagnostic.Add("NServiceBus.Transport.AzureStorageQueues", new { ConnectionMechanism = new { Queue = queueServiceClientProvider is QueueServiceClientByConnectionString ? "ConnectionString" : "QueueServiceClient", Table = cloudTableClientProvider is CloudTableClientByConnectionString ? "ConnectionString" : "CloudTableClient", Blob = blobServiceClientProvider is BlobServiceClientProvidedByConnectionString ? "ConnectionString" : "BlobServiceClient", }, MessageWrapperSerializer = MessageWrapperSerializationDefinition == null ? "Default" : "Custom", MessageEnvelopeUnwrapper = MessageUnwrapper == null ? "Default" : "Custom", NativeDelayedDelivery = new { IsEnabled = SupportsDelayedDelivery, Processor = delayedDeliveryProcessorDiagnosticSection, Persistence = delayedDeliveryPersistenceDiagnosticSection }, Subscriptions = new { IsEnabled = SupportsPublishSubscribe, Persistence = subscriptionsPersistenceDiagnosticSection }, TransactionMode = Enum.GetName(typeof(TransportTransactionMode), TransportTransactionMode), ReceiverBatchSize = ReceiverBatchSize.HasValue ? ReceiverBatchSize.Value.ToString(CultureInfo.InvariantCulture) : "Default", DegreeOfReceiveParallelism = DegreeOfReceiveParallelism.HasValue ? DegreeOfReceiveParallelism.Value.ToString(CultureInfo.InvariantCulture) : "Default", MaximumWaitTimeWhenIdle, PeekInterval, MessageInvisibleTime }); return(infrastructure); }