Question about Security

Question about Security

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


Jav posted on Wednesday, June 30, 2010

My users are always already logged in using Microsoft Membership when they launch a Silverlight App and the following statement is already executed in the Application_AcquireRequestState in Global.asax:
               Csla.ApplicationContext.User = HttpContext.Current.User;. 

I was going to use the AppPrincipal from Demo 9 (Security Video), but I notice that in AppPrincipal there is again a Login.  What is best way to simply pass the existing  Csla.ApplicationContext.User  to the SL App?

I also notice that there is no Global.asax in the SL Web project.  Is that something one would add if needed?

Jav

RockfordLhotka replied on Wednesday, June 30, 2010

You can (and may need to) add Global.asax to the web project. Apparently the standard project template from Microsoft doesn't add it - probably to keep the web project as simple as possible.

Silverlight has no intrinsic security model, so CSLA added one to Silverlight. Strangely, Silverlight defines IPrincipal and IIdentity, but has no place to use them - very odd... So CSLA provides a place to store the value (Csla.ApplicationContext.User), and uses the principal for authorization.

Of course this does mean when your app starts you need to initialize the principal/identity on the client. CSLA provides three models for this:

  1. Go to the server and get the Windows identity from the server (Csla.Silverlight.Security.WindowsIdentity)
  2. Pass a username/pw to the server and validate it using the membership provider (Csla.Security.MembershipIdentity)
  3. Pass credentials to the server and get a custom identity (subclass CslaIdentity)

This third option is the key to your scenario - you just need a custom identity that goes to the server, copies the server identity information and returns to the client. You don't need to pass any credentials to the server of course, because the server already knows who you are - but you do need to copy the server's username and roles into a custom identity that can be kept and used on the client.

Odds are that you can't get the list of roles from the server's principal object, as there's no standard .NET API for doing such a thing. But you can always get the username - and that should be sufficient to reload the roles from the membership provider using the membership API. So that's what your custom identity object would do - sort of like Csla.Security.MembershipIdentity, but simpler.

One other thing you'll want to do (if you are using CSLA 4) is to tell the client to not pass the principal to the server on every data portal call (http://www.lhotka.net/cslabugs/edit_bug.aspx?id=736). It is a waste of bandwidth to send the principal to the server every time, since the server already knows who you are. By default the data portal sends the principal each time - but in CSLA 4 there's a config option you can set to tell the data portal to change that behavior.

Jav replied on Wednesday, June 30, 2010

Thank you for a detailed answer.

I have been pondering all this since this morning.  A few things are still making my head ache.
Right now my SL app is doing all kinds of data access, fetching and saving, without the Csla.Application.User having been set up within the SL App.  So it appears that the code on the server runs just fine without the Principal.  Hence it makes sense that the Principal doesn't need to go with every DataPortal call.

So the purpose of having Csla.Application.User in the SL App is entirely(??) to handle the Object and Properties Authorization - and that function depends entirely on the roles.  Since Identity only has two field, username and IsAuthenticated - and I would assume that the username will have to be passed to the SL app as a parameter - why not just Create an Identity object right on the client with that username and set IsAuthenticated to true.  With that one can create the Principal.  Of course I still need to get the roles from Membership database.  Since that DB allows one to add custom StoredProcs, is there any harm in doing just that to get a list of roles?

Jav

RockfordLhotka replied on Wednesday, June 30, 2010

Let's take a step back.

.NET defines a principal and identity. Basically the principal has roles, the identity has the username.The principal always contains exactly one identity.

In all these years,I've never found anyone at Microsoft who can explain why these are two objects. The closest I've gotten in terms of an answer is that Java did it first, and perhaps .NET copied Java.

Either way, it is really dumb, and it should be one object.

So whenever I implement a custom principal, it does nothing except delegate to the identity. And the identity contains everything, including the roles. In fact, the Csla.Security types BusinessPrincpalBase and CslaIdentity (and the related ICheckRoles interface) are designed this way.

The IsInRole() method in BusinessPrincipalBase delegates to the identity for role resolution if the identity implements ICheckRoles - which CslaIdentity does implement.

In other words, my thought is that it is really silly to have to implement a custom principal and custom identity, when all you really need is a custom identity.

The CSLA WindowsIdentity and MembershipIdentity types work as I describe above - fitting neatly inside a BusinessPrincipalBase (which should really be renamed...) so you don't need to create a custom principal at all.

Jav replied on Wednesday, June 30, 2010

Thanks Rocky.

I suppose that's the reason for my headaches too.

Jav

Jav replied on Thursday, July 01, 2010

Okay - Success.  I send in the Username as one of the parameters to the SL App.  In App.xaml.cs, I call AppPrincipal.SetupPrincipal(Username).  Here is the entire code in AppPrincipal:

 [Serializable]
  public class AppPrincipal : Csla.Security.BusinessPrincipalBase
  {
    public AppPrincipal()
    { }

    public AppPrincipal(Csla.Security.CslaIdentity identity)
      : base(identity)
    { }

#if SILVERLIGHT
    public static void SetupPrincipal(string username)
    {
         AppIdentity.GetAppIdentity(username, (o,e) =>
            {
                var principal = new AppPrincipal(e.Object);
                Csla.ApplicationContext.User = principal;
            });
       
    }
   
 #endif

  }

GetAppIdentity is the standard factory method to DataPortal_Fetch.  This is the code in AppIdentity.DataPortal_Fetch()

        public void DataPortal_Fetch(SingleCriteria<AppIdentity, string> criteria)
        {
            this.Name = criteria.Value;
            this.IsAuthenticated = true;
            using (var ctx = ConnectionManager<SqlConnection>.GetManager("MembershipConnectionString"))
            {
                using (var cm = ctx.Connection.CreateCommand())
                {
                    cm.CommandType = CommandType.StoredProcedure;
                    cm.Parameters.AddWithValue("@Uname", criteria.Value);
                    cm.CommandText = "Users_GetUserRoles";
                    using (var dr = new SafeDataReader(cm.ExecuteReader()))
                    {
                        if (this.Roles == null) { this.Roles = new Csla.Core.MobileList<String>(); }
                        while (dr.Read())
                        {
                            this.Roles.Add(dr.GetString("UserRole"));
                        }
                      }
                }
            }
        }

I appreciate your help and insight, Rocky.

Jav

Copyright (c) Marimer LLC