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));
            }
        }
Ejemplo n.º 2
0
        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));
        }
Ejemplo n.º 3
0
        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);
        }