Running Linq with Impersonation

Posts   
 
    
Isz
User
Posts: 108
Joined: 26-Jan-2006
# Posted on: 25-May-2011 05:00:40   

Hello, I'm looking for a hook into executing a LINQ query within an Impersonation delegate. Currently I am using the following:

SD.LLBLGen.Pro.LinqSupportClasses.NET35.dll 3.1.11.426

What I would like to achieve is something like the following, but at a lower level:



            IList<ApplicationConfigurationMetaData> applicationConfigurations = null;

            Impersonation.Impersonate(
                  ApplicationConstants.IMPERSONATION_USERNAME
                , ApplicationConstants.IMPERSONATION_PASSWWORD
                , ApplicationConstants.IMPERSONATION_DOMAIN
                , false
                , delegate
                {

                    LinqMetaData linqMetaData = new LinqMetaData(new DataAccessAdapter(false));
                
                    applicationConfigurations = linqMetaData.ApplicationConfiguration
                        .Select(c => new ApplicationConfigurationMetaData
                        {
                            ApplicationConfigurationId = c.ApplicationConfigurationId,
                            Description = c.Description,
                            KeyName = c.KeyName,
                            Value = c.Value,

                        }
                        )
                        .ToList()
                        ;
                }
                );

            Assert.IsTrue(applicationConfigurations.Count > 0, "There should be more than 0 application configuration records.");


The above code example works great, because there is no deferred execution. Calling ToList() appears to invoke the LLBLGenProProviderBase's protected virtual object Execute(Expression expression); but I can't always call ToList() right away.

For example, using a DevExpress web control like dx:LinqServerModeDataSource, execution doesn't occur until after the OnSelecting event falls out of scope, so there is no place to wrap impersonation, and calling ToList() prematurely may impact performance given additional query parameters such as filtering and pagination may be appended auto-magically.

As always, I am open to alternate ideas, but thought to ask if there is some type of hook/delegate mechanism into the LINQ to LLBLGen library that I haven't spotted.

Thanks!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 25-May-2011 14:07:03   

There's no 'hook' on the IQueryable<T> produced by LinqMetaData, so you can't wrap the call somewhere, at least not directly.

You could, but this is a bit complicated perhaps, derive from DataAccessAdapter and override public virtual IDataReader FetchDataReader( IRetrievalQuery queryToExecute, CommandBehavior readerBehavior )

In that override, do the impersonation and call in the delegate the base method, but I don't know how the impersonation method works, it doesn't seem to be able to handle returnvalues well, correct?

Frans Bouma | Lead developer LLBLGen Pro
Isz
User
Posts: 108
Joined: 26-Jan-2006
# Posted on: 26-May-2011 22:56:35   

I see... that should give me enough to go on. Currently the Impersonation doesn't return anything, but I could write it to do so.

I'll take your hints and see how far I get, mark this closed, and reopen if I need more help, but hopefully I can figure something out. simple_smile

Also, if I feel it could be useful, I'll post back some findings.

Thanks!!!

Isz
User
Posts: 108
Joined: 26-Jan-2006
# Posted on: 04-Jun-2011 14:43:41   

I gave your recommendation a go, and it looks to be just what I require. I'm posting code here so it may be of use for developers using SSPI and require impersonation using LINQ to LLBLGen.



        [TestMethod]
        public void TestImpersonationDataAccessAdapter()
        {
            using (AppService a = new AppService(WindowsIdentity.GetAnonymous()))
            {
                string user = a.User.Name;

                IQueryable<ApplicationConfigurationMetaData> configs = a.GetApplicationConfigurations();

                IList<ApplicationConfigurationMetaData> configList = configs.ToList();

                // If impersonation fails, an exception will be raised prior to this point.
                Assert.IsTrue(configList.Count > 0, "No results were returned for this test.");

            }
        }


The AppService object above is a partial class which gives access into a business layer, and isn't relevant to get the idea. Mainly, it has an IDataAccessAdapter to do database work with. Here is my extended ImpersonatingDataAccessAdapter:


    public partial class ImpersonatingDataAccessAdapter : DataAccessAdapter
    {
        public ImpersonatingDataAccessAdapter(bool keepConnectionOpen)
            : base(keepConnectionOpen)
        {
        }

        public override IDataReader FetchDataReader(IRetrievalQuery queryToExecute, CommandBehavior readerBehavior)
        {
            IDataReader reader = null;

            Impersonation.Impersonate(
                  "me"
                , "secret
                , "the-domain.com"
                , false
                , delegate
                {
                    reader = base.FetchDataReader(queryToExecute, readerBehavior);
                }
                );

            return reader;
        }  
    }


Finally, here is the impersonation logic. Background, it was written to support windows applications and so has some caching mechanism commented out, but I'm using it in SharePoint currently, where the you're most likely going to be using an IIS application pool account, so it will appear overkill.



    public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() : base(true) { }

        internal SafeTokenHandle(IntPtr handle)
            : base(true)
        {
            SetHandle(handle);
        }

        internal static SafeTokenHandle InvalidHandle
        {
            get { return new SafeTokenHandle(IntPtr.Zero); }
        }

        [DllImport("kernel32", SetLastError = true), SuppressUnmanagedCodeSecurity, ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        private static extern bool CloseHandle(IntPtr handle);

        protected override bool ReleaseHandle()
        {
            return CloseHandle(handle);
        }
    }

    public class Impersonation
    {

        #region PRIVATE PROPERTIES

        #endregion

        #region PROTECTED PROPERTIES

        #endregion

        #region PUBLIC PROPERTIES

        public static SafeTokenHandle SafeTokenHandle = null;

        public static bool IsUserTokenCached
        {
            get
            {
                return SafeTokenHandle != null
                    && _isUserTokenCached == true;
            }
        }

        #endregion

        #region CTOR

        #endregion

        #region PRIVATE METHODS

        private static bool _isUserTokenCached = false;

        #endregion

        #region PROTECTED METHODS

        #endregion

        #region PUBLIC METHODS

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public extern static bool CloseHandle(IntPtr handle);

        public static void Impersonate(IAuthentication authentication, ProcessImpersonationDelegate d)
        {
            string insecurePassword = string.Empty;

            //IntPtr passwordBSTR = default(IntPtr);

            try
            {
                //Pull out the password.
                //passwordBSTR = Marshal.SecureStringToBSTR(authentication.Password);
                //insecurePassword = Marshal.PtrToStringBSTR(passwordBSTR);
            }
            catch
            {
                insecurePassword = "";
            }

            Impersonate(authentication.Username, authentication.Password, authentication.Domain, authentication.CacheUserCredentials, d);
        }

        public static void Impersonate(string userName, string password, string domain, bool cacheUserToken, ProcessImpersonationDelegate d)
        {
            const int LOGON32_PROVIDER_DEFAULT = 0;
            const int LOGON32_LOGON_INTERACTIVE = 2;

            bool returnValue = IsUserTokenCached;

            if (!IsUserTokenCached)
                returnValue = LogonUser(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out SafeTokenHandle);

            if (!returnValue)
                throw new SecurityException("Credentials invalid");

            if (!cacheUserToken)
            {
                _isUserTokenCached = false;

                using (SafeTokenHandle)
                {
                    WindowsIdentity newId = new WindowsIdentity(SafeTokenHandle.DangerousGetHandle());
                    using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
                    {
                        d.DynamicInvoke();
                    }
                }
            }
            else
            {
                _isUserTokenCached = true;

                WindowsIdentity newId = new WindowsIdentity(SafeTokenHandle.DangerousGetHandle());
                using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
                {
                    d.DynamicInvoke();
                }
            }
        }

        #endregion

        #region EVENTS

        #endregion

    }

    public delegate void ProcessImpersonationDelegate();



Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 05-Jun-2011 10:26:54   

Thanks for sharing! and I'm glad it worked OK simple_smile

There's a problem with your forum icon btw. It pops up a uid/pwd dialog, so I removed it from your profile.

Frans Bouma | Lead developer LLBLGen Pro