Issue accessing custom properties from my custom identity

Issue accessing custom properties from my custom identity

Old forum URL: forums.lhotka.net/forums/t/12892.aspx


fujiiface posted on Friday, May 29, 2015

Hello,

Thank you in advance for any help provided here.  I read the answers on this post and still have a problem with my custom Principal/Identity implementation.

I am using Csla 4.1.0 and seem to be having a cast issue.  This is the same scenario as the OP in the linked thread where I am trying to get to custom properties that have been implemented into my CustomIdentity class.

When I try to cast Csla.ApplicationContext.User.Identity to CustomIdentity, I get "Unable to cast object of type 'System.Security.Principal.GenericPrincipal' to 'DMS.Library.Security.CustomIdentity.'

Here's the code in question:

public int UserOrgCount {

            get {

                var identity = (CustomIdentity)Csla.ApplicationContext.User.Identity;

                return identity.Orgs.Count();

            }

        }

I thought I followed the ebook pretty well and in fact everything seems to be working.  The only thing I want to get access to now is the Identity properties so that I can replace individual database calls with data that's already been loaded into my custom properties.

Let me know if there is any other information I can provide.

For reference, here is my CustomPrincipal and CustomIdentity objects:

 

using System;

using System.Security.Principal;

using Csla.Security;

using Csla.Serialization;

 

namespace DMS.Library.Security {

    [Serializable]

    public class CustomPrincipal : CslaPrincipal {

        private CustomPrincipal(IIdentity identity)

            : base(identity) { }

 

        /// <summary>

        /// 

        /// </summary>

        /// <param name="username">The user's username</param>

        /// <param name="password">The user's password</param>

        /// <param name="completed"></param>

        public static void BeginLogin(string username, string password, Action<Exception> completed) {

            DMS.Library.Security.CustomIdentity.GetCustomIdentity(username, password, (o, e) => {

                if (e.Error != null)

                    Logout();

                else

                    Csla.ApplicationContext.User = new CustomPrincipal(e.Object);

                completed(e.Error);

            });

        }

 

#if !SILVERLIGHT

 

        /// <summary>

        /// NOT IMPLEMENTED - Attempts to login the user with their username and password

        /// </summary>

        /// <param name="pUserName">The user's username</param>

        /// <param name="pPassword">The user's password</param>

        /// <remarks>

        /// As of 05/07/2015, this function is not used because it will create a circular execution of code.  

        /// Validation of credentials is handled within the ValidateUser function of each implemented Membership provider.

        /// Assuming the ValidateUser function returns true, the Principal's Load function is used instead

        /// </remarks>

        public static void Login(string username, string password) {

            throw new NotImplementedException("The CustomPrincipal Login method has not been implemented.  Refer to the remarks section of method's code for an explanation.");

            //var identity = DMS.Library.Security.CustomIdentity.GetCustomIdentity(username, password);

            //Csla.ApplicationContext.User = new CustomPrincipal(identity);

        }

 

        public static void Load(string username) {

            var identity = DMS.Library.Security.CustomIdentity.GetCustomIdentity(username);

            Csla.ApplicationContext.User = new CustomPrincipal(identity);

        }

 

#endif

 

        /// <summary>

        /// Replaces the current CustomPrincipal object with a new one that has an UnauthenticatedIdentity loaded up.

        /// </summary>

        public static void Logout() {

            Csla.ApplicationContext.User = new UnauthenticatedPrincipal();

        }

    }

}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Csla;
using Csla.Security;
using Csla.Serialization;
using DMS.Library.Admin;
namespace DMS.Library.Security {
    [Serializable]
    public class CustomIdentity : CslaIdentityBase<CustomIdentity> {
        
        #region Properties
            public static readonly PropertyInfo<string> EmailProperty = RegisterProperty<string>(c => c.Email);
            /// <summary>
            /// The user's E-mail
            /// </summary>
            public string Email {
                get { return GetProperty(EmailProperty); }
                private set { LoadProperty(EmailProperty, value); }
            }
            public static readonly PropertyInfo<Guid> UserIdProperty = RegisterProperty<Guid>(p => p.UserId);
            /// <summary>
            /// The user's internal identifier
            /// </summary>
            public Guid UserId {
                get { return GetProperty(UserIdProperty); }
                private set { LoadProperty(UserIdProperty, value); }
            }
            public static readonly PropertyInfo<string> FirstNameProperty = RegisterProperty<string>(c => c.FirstName);
            /// <summary>
            /// The user's first name
            /// </summary>
            public string FirstName {
                get { return GetProperty(FirstNameProperty); }
                private set { LoadProperty(FirstNameProperty, value); }
            }
            public static readonly PropertyInfo<string> LastNameProperty = RegisterProperty<string>(c => c.LastName);
            /// <summary>
            /// The user's last name
            /// </summary>
            public string LastName {
                get { return GetProperty(LastNameProperty); }
                private set { LoadProperty(LastNameProperty, value); }
            }
            public static readonly PropertyInfo<string> FullNameProperty = RegisterProperty<string>(c => c.FullName);
            /// <summary>
            /// The user's full name
            /// </summary>
            public string FullName {
                get { return GetProperty(FullNameProperty); }
                private set { LoadProperty(FullNameProperty, value); }
            }
            public static readonly PropertyInfo<string> HomeOrgStringProperty = RegisterProperty<string>(p => p.HomeOrgString);
            /// <summary>
            /// The user's Home OrgString
            /// </summary>
            public string HomeOrgString {
                get { return GetProperty(HomeOrgStringProperty); }
                private set { LoadProperty(HomeOrgStringProperty, value); }
            }
            public static readonly PropertyInfo<bool> IsApprovedProperty = RegisterProperty<bool>(p => p.IsApproved);
            /// <summary>
            /// The original membership IsApproved column
            /// </summary>
            /// <remarks>
            /// Value comes from the aspnet_Membership tables
            /// </remarks>
            public bool IsApproved {
                get { return GetProperty(IsApprovedProperty); }
                private set { LoadProperty(IsApprovedProperty, value); }
            }
            public static readonly PropertyInfo<List<string>> OrgsProperty = RegisterProperty<List<string>>(p => p.Orgs);
            /// <summary>
            /// Returns the list of Departments that the user belongs to
            /// </summary>
            public List<string> Orgs {
                get { return GetProperty(OrgsProperty); }
                private set { LoadProperty(OrgsProperty, value); }
            }
            public static readonly PropertyInfo<string> AppNameProperty = RegisterProperty<string>(p => p.AppName);
            public string AppName {
                get { return "AppName"; }
            }
    #endregion
        public static void GetCustomIdentity(string username, string password, EventHandler<DataPortalResult<CustomIdentity>> callback) {
            DataPortal.BeginFetch<CustomIdentity>(new UsernameCriteria(username, password), callback);
        }
#if !SILVERLIGHT
        /// <summary>
        /// Gets the user after authenticating them with their username and password
        /// </summary>
        /// <param name="pUserName">The user's employee number</param>
        /// <param name="pPassword">The user's password</param>
        /// <returns></returns>
        public static CustomIdentity GetCustomIdentity(string username, string password) {
            return DataPortal.Fetch<CustomIdentity>(new UsernameCriteria(username, password));
        }
        
        private void DataPortal_Fetch(UsernameCriteria criteria) {
            AuthenticationType = "Custom";
            using (var mgr = DataAccess.DalFactory.GetManager()) {
                var dal = mgr.GetProvider<DataAccess.IIdentityDal>();
                if (dal.VerifyUser(criteria.Username, criteria.Password))
                    LoadUserData(criteria.Username, dal);
            }
        }
        /// <summary>
        /// Gets the user based on their username
        /// </summary>
        /// <param name="pUserName">The user's employee number</param>
        /// <returns></returns>
        internal static CustomIdentity GetCustomIdentity(string username) {
            return DataPortal.Fetch<CustomIdentity>(username);
        }
        private void DataPortal_Fetch(string username) {
            AuthenticationType = "Custom";
            using (var mgr = DataAccess.DalFactory.GetManager()) {
                var dal = mgr.GetProvider<DataAccess.IIdentityDal>();
                LoadUserData(username, dal);
            }
        }
        /// <summary>
        /// Uses the DataAccess DalFactory to dynamically load the required membership provider
        /// </summary>
        /// <param name="pUserName">The user's employee number</param>
        /// <param name="dal">The Identity's data access object</param>
        private void LoadUserData(string pUserName, DataAccess.IIdentityDal dal) {
            var userData = dal.GetUser(pUserName);
            //Step 1 authentication loading - If the user exists in the membership tables, the user is temporarily authenticated
            this.IsAuthenticated = (userData != null);
            
            if (this.IsAuthenticated) {
                this.Name = userData.UserName;
                this.FirstName = userData.FirstName;
                this.LastName = userData.LastName;
                this.FullName = string.Format("{0} {1}", userData.FirstName, userData.LastName);
                this.Email = userData.Email;
                this.UserId = userData.UserId;
                this.HomeOrgString = userData.HomeOrgString;
                this.IsApproved = userData.IsApproved;
                //Step 2 authentication loading - Update the Identity's IsAuthenticated property based on the membership table
                this.IsAuthenticated = this.IsApproved;
                //Initialize the MobileList property and then load up the roles once we've gotten them
                this.Roles = new Csla.Core.MobileList<string>();
                string[] _roles = UserRole.GetRolesForUser(pUserName, string.Empty, true);
                foreach (var role in _roles) {
                    this.Roles.Add(role);
                }
                //Load up the Orgs the user belongs to
                this.Orgs = UserOrg.GetOrgsForUser(pUserName, true, false).ToList();
                //Step 3 authentication loading - Check to make sure that even if the user is authenticated with AD and exists in the membership table, the user has at least 1 Org and Role
                if (!(this.Orgs.Count() > 0 && this.Roles.Count() > 0)) {
                    this.IsAuthenticated = false;
                    throw new InvalidOperationException(string.Format("User {0} does not have access to any departments and/or roles.\n    Org Count: {1};\n    Role Count: {2}", this.Name, this.Orgs.Count(), this.Roles.Count()));
                }
            }
        }
#endif
    }
}

JonnyBee replied on Saturday, May 30, 2015

Hi,

The issue here is that you have lost your custom principal object and now has the default GenericPrincipal which is a built-in type in .NET.

This could be caused by some configuration error (AuthenticationType) or that your code has used async methods that does not transfer the "current" principal to the executing (background) thread. 

CSLA contains a custom BackgroundWorker and CslaTaskScheduler that will make sure to transfer the principal and ApplicationContext to background thread. 

fujiiface replied on Monday, June 01, 2015

Hi Jonny,

Thank you for your response and all of your support with CSLA.  Personally, I really appreciate it.

Regarding my issue, it looks like the problem has been resolved thanks to some of your suggestions.  Essentially, I did the following:

  1. Reverted my CustomPrincipal constructor back to accept a CustomIdentity parameter (instead of IIdentity)
  2. Removed my AuthorizeDataPortal settings from Web.config
  3. Confirmed that CslaAuthentication was set to "Custom"

To my knowledge, we are not running anything async.  Also, it appears the AuthorizeDataPortal configuration I had in place was not set up correctly.  As I recall (from when I would debug) within the AuthorizeDataPortal's Authorize method, I could never get to the CustomPrincipal.Load method.  It would always load an UnauthenticatedPrincipal.  Once I removed the concept of the AuthorizeDataPortal to avoid passing the Principal object over the wire, I was able to avoid "losing" my Csla.ApplicationContext.User.

 

As of right now, I believe this was the configuration error you were referring to in your previous post.  If anything comes up, I will follow up.  Thank you again for all of your support!

Copyright (c) Marimer LLC