Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EWS Notification Hub for multiple users

I am trying to create a service to push exchange notifications to asp.net applications, eventually using SignalR.

My plan is to create a notification hub that subscribes each user as they log in to the asp application and listen for notifications for them. As a notification is received the second part of the project is to then use signalR to only send the correct notifications to each user. Once they log out or time out the notification hub will unsubscribe them.

So far I have done a little basic testing and can receive notifications in a little console app for myself with my credentials hard coded. What I am struggling with is how to do this for multiple people simultaneously. For example would I have to create separate threads of this for each user or is there a better way?

I guess regardless I am going to have to use impersonation rather than holding each users credentials right? I'm also going to have to work out a way to auto refresh the timeout for each user if they have an active session.

Below is a little code I found and have been playing with, I would be grateful for any ideas or example anyone could share of how I could best achieve this.

Many thanks

Andy

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Exchange.WebServices.Data;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {

                ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
                service.Url = new Uri("https://server/EWS/Exchange.asmx");
                service.Credentials = new NetworkCredential("user", "pass", "domain");
                SetStreamingNotifications(service); 
                while (true)
                { }


        }

        static void SetStreamingNotifications(ExchangeService service)
        {
            // Subscribe to streaming notifications on the Inbox folder, and listen
            // for "NewMail", "Created", and "Deleted" events.
            StreamingSubscription streamingsubscription = service.SubscribeToStreamingNotifications(
                new FolderId[] { WellKnownFolderName.Calendar, WellKnownFolderName.Inbox },
                EventType.Created,
                EventType.Modified);

            StreamingSubscriptionConnection connection = new StreamingSubscriptionConnection(service, 9);

            connection.AddSubscription(streamingsubscription);
            // Delegate event handlers.
            connection.OnNotificationEvent +=
                new StreamingSubscriptionConnection.NotificationEventDelegate(OnEvent);
            connection.OnSubscriptionError +=
                new StreamingSubscriptionConnection.SubscriptionErrorDelegate(OnError);
            connection.OnDisconnect +=
                new StreamingSubscriptionConnection.SubscriptionErrorDelegate(OnDisconnect);
            connection.Open();

            Console.WriteLine("--------- StreamSubscription event -------");
        }

        static private void OnDisconnect(object sender, SubscriptionErrorEventArgs args)
        {
            // Cast the sender as a StreamingSubscriptionConnection object.          
            StreamingSubscriptionConnection connection = (StreamingSubscriptionConnection)sender;
            // Ask the user if they want to reconnect or close the subscription.
            ConsoleKeyInfo cki;
            Console.WriteLine("The connection to the subscription is disconnected.");
            Console.WriteLine("Do you want to reconnect to the subscription? Y/N");
            while (true)
            {
                cki = Console.ReadKey(true);
                {
                    if (cki.Key == ConsoleKey.Y)
                    {
                        connection.Open();
                        Console.WriteLine("Connection open.");
                        break;
                    }
                    else if (cki.Key == ConsoleKey.N)
                    {
                        // The ReadKey in the Main() consumes the E.
                        Console.WriteLine("\n\nPress E to exit");
                        break;
                    }
                }
            }
        }

        static void OnEvent(object sender, NotificationEventArgs args)
        {
            StreamingSubscription subscription = args.Subscription;

            // Loop through all item-related events.
            foreach (NotificationEvent notification in args.Events)
            {

                switch (notification.EventType)
                {
                    case EventType.NewMail:
                        Console.WriteLine("\n-------------Mail created:-------------");
                        break;
                    case EventType.Created:
                        Console.WriteLine("\n-------------Item or folder created:-------------");
                        break;
                    case EventType.Deleted:
                        Console.WriteLine("\n-------------Item or folder deleted:-------------");
                        break;
                }
                // Display the notification identifier.
                if (notification is ItemEvent)
                {
                    // The NotificationEvent for an e-mail message is an ItemEvent.
                    ItemEvent itemEvent = (ItemEvent)notification;
                    Console.WriteLine("\nItemId: " + itemEvent.ItemId.UniqueId);

                }
                else
                {
                    // The NotificationEvent for a folder is an FolderEvent.
                    //FolderEvent folderEvent = (FolderEvent)notification;
                    //Console.WriteLine("\nFolderId: " + folderEvent.FolderId.UniqueId);
                }
            }
        }
        static void OnError(object sender, SubscriptionErrorEventArgs args)
        {
            // Handle error conditions.
            Exception e = args.Exception;
            Console.WriteLine("\n-------------Error ---" + e.Message + "-------------");
        }
    }
}
like image 634
mcinnes01 Avatar asked Oct 21 '22 11:10

mcinnes01


1 Answers

The way I solved this problem is by:

  1. Having an account that has the right to impersonate all users.
  2. I create a service for that account with giving a username and password.
  3. I impersonate a user and add the subscription of the user to the connection
  4. I create another service which is a close for the main service with the same username and password, which will impersonate another user and then add the subscription to the same connection before

Here are two parts of my code . Forget about the LogDevice it is just something internally. The first part is the detailed impersonation and adding the service to the list of services the list of services in my case is a dictionary with the userSMTP is the key , the UserSMTP key here is the impersonated account.

    /// <summary>
    /// Impersonate one user at a time and without using the autodiscovery method to find the proper url for the userSmtp, 
    /// and copy the provided url to the usersmtp.
    /// </summary>
    /// <param name="url"> </param>
    /// <param name="userSmtp">user smtp</param>
    /// <param name="enableTrace">to enable logging from the XML tracing </param>
    /// <param name="exchangeVersion">Exchange server version used</param>
    private Uri ImpersonateUser(Uri url, string userSmtp, bool enableTrace, ExchangeVersion exchangeVersion)
    {
        Uri result = url;
        var log = "ImpersonateUser \n";
        try
        {
            log += "0/8 Checking services redundancy\n";
            if (Services.ContainsKey(userSmtp))
            {
                Services.Remove(userSmtp);
            }
            log += "1/8 Create a new service for " + userSmtp + "\n";
            var service = new ExchangeService(exchangeVersion);

            log += "2/8 Get credentials for the service\n";
            var serviceCred = ((System.Net.NetworkCredential)(((WebCredentials)(Services.First().Value.Credentials)).Credentials));

            log += "3/8 Assign credentials to the new service\n";
            service.Credentials = new WebCredentials(serviceCred.UserName, serviceCred.Password);

            log += "4/8 TraceEnabled is" + enableTrace.ToString() + "\n";
            service.TraceEnabled = enableTrace;

            log += "5/8 Get the Url for the  service with AutodiscoverUrl \n";
            service.Url = url;

            log += "6/8 Assign a new ImpersonatedUserId to the new service for" + userSmtp + "\n";
            service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, userSmtp);

            try
            {
                log += "7/8 Validating the impersonation\n";
                RuleCollection rulecoll = service.GetInboxRules();
            }
            catch (Exception ex)
            {
                _logDevice.LogSrvMessage(1, "ExchangeLiteService: ImpersonateUser: failed to validate the impersonation for {0}\n Exception: {1}\n", userSmtp, ex.Message);
                int hr = System.Runtime.InteropServices.Marshal.GetHRForException(ex);

                if (hr == -2146233088) // We do not have right to impersonate this user.
                {
                    result = null;
                    return result;
                }
                else
                {
                    _logDevice.LogSrvMessage(1, "ExchangeLiteService: ImpersonateUser(2): trying to resolve {0} with Autodiscover instead...", userSmtp);
                    result = ImpersonateUser(userSmtp, enableTrace, exchangeVersion);
                }

            }

            log += "8/8 Adding the service \n";
            if (!Services.ContainsKey(userSmtp))
            {
                Services.Add(userSmtp, service);
                _logDevice.LogSrvMessage(1, "ExchangeLiteService: ImpersonateUser(2): {0} has been impersonated\n", service.ImpersonatedUserId.Id);
            }
        }
        catch (Exception ex)
        {
            _logDevice.LogSrvMessage(1, "ExchangeLiteService: ImpersonateUser(2): exception {0}\n The exception occured after the following steps: \n{1}", ex.Message, log);
        }
        return result;
    }

And here is the code that calls the previous function (i.e. for all users) put in mind that you should somehow storing the email address for every account you want to impersonate.

/// <summary>
        /// To Impersonate users in order to get the info from them. 
        /// </summary>
        /// <param name="userSmtps">List of users to be impersonated</param>
        /// <param name="enableTrace"> To enable logging from the XML tracing</param>
        /// <param name="exchangeVersion">Exchange server version used </param>
        public void ImpersonateUsers(ICollection<string> userSmtps)
        {
            var log = "ImpersonateUsers\n";
            var firstUserSmtp = "";
            if (userSmtps != null)

                if (userSmtps.Count > 0)
                {

                    //the url for the first smtp
                    try
                    {
                        log += "1/2 Impersonating the first userSmtp\n";
                        _logDevice.LogSrvMessage(1, "ExchangeLiteService: ImpersonateUsers:  Getting the Url from the autodiscovery for the first smtp {0} ", userSmtps.First());
                        bool enableTrace = Services.First().Value.TraceEnabled;

                        ExchangeVersion exchangeVersion = Services.First().Value.RequestedServerVersion;
                        firstUserSmtp = userSmtps.First();
                        var commonSmtpUrl = ImpersonateUser(userSmtps.First(), enableTrace, exchangeVersion);
                        if (commonSmtpUrl == null) userSmtps.Remove(firstUserSmtp);
                        // If the list contains other than the first one 
                        log += "2/2 Impersonating " + (userSmtps.Count - 1) + " userSmtps\n";

                        if (userSmtps.Count >= 1)
                        {
                            foreach (var userSmtp in userSmtps)
                            {
                                try
                                { //skip ther first one because it is already impersonated. 
                                    if (userSmtp == firstUserSmtp)
                                    {
                                        continue;
                                    }
                                    // Impersonate the users
                                    _logDevice.LogSrvMessage(1, "ExchangeLiteService: ImpersonateUsers:  Impersonating {0} ...", userSmtp);

                                        commonSmtpUrl = ImpersonateUser(userSmtp, enableTrace, exchangeVersion);

                                }
                                catch (Exception ex)
                                {
                                    _logDevice.LogSrvMessage(1, "ExchangeLiteService: ImpersonateUsers: Impersonating {1}\n exception {0}\n", ex.Message, userSmtp);
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        _logDevice.LogSrvMessage(1, "ExchangeLiteService: ImpersonateUsers: exception {0}\n The exception occured after the following steps: \n{1}", ex.Message, log);
                    }
                }
        }

I would have put the subscription part and adding to the connection , but it is a bit ugly and hard to get. but the idea is simply that you should have a connection, and then you go to each service you made and then `connection.AddSubscription(streamingSubscription); Where streamingSubscription is extracted from the service.

like image 191
Ahmad ElMadi Avatar answered Oct 24 '22 11:10

Ahmad ElMadi