Confused about authorizationConfused about authorization
Old forum URL: forums.lhotka.net/forums/t/4600.aspx
Cine posted on Wednesday, April 02, 2008
I've been trying to get my head around the authorization classes in CSLA (3.5), and I am having some problems.
Let me illustrate with a simple example: I have a class called User and some use cases:
Use case 1: The administrator can create a new User object.
Use case 2: The administrator can delete any User objects.
Use case 3: The administrator can change the password of any User object.
Use case 4: The user authenticated by the User object may edit the password of her own User object.
Use case 5: The user authenticated by the User object may delete her own User object.
Use case 6: The exception to uc5 are users in the role "simpleuser", who may not delete themselves.
The authentication we handle with the IIdentity and IPrincipal classes, so no need to worry about that part in my simple design.
UC1: In the GUI with the user list (presuming we have one such) I can use CanCreateObject(typeof(User)) to check if I can show the create button. I can set the rules for this in User.AddObjectAuthorizationRules by using AuthorizationRules.AllowCreate(typeof(User), "administrator"). Also when calling Dataportal.Create this will be double checked. Right so far?
UC2: Add AuthorizationRules.AllowDelete(typeof(User), "administrator") inside User.AddObjectAuthorizationRules. Similar to UC1
UC3: Add AuthorizationRules.AllowWrite(PasswordProperty, "administrator") inside the User.AddAuthorizationRules. In the GUI I can call userobj.CanWriteProperty("Password") to check if I can update the password, and should I forget this then whenever I access GetProperty(PasswordProperty) I will have my access double checked.
UC4: It seems the idea was to put this inside the User.AddInstanceAuthorizationRules, however it is called _before_ I put any values in my object and thus I cannot tell if the ID of the object is the same as the ID of the current IPrincipal. I could add a custom function MyAddInstanceAuthorizationRules which I could call after I initialize all my values, which could then add if (Csla.ApplicationContext.User.Identity.Name == this.Name) AuthorizationRules.InstanceAllowWrite(PasswordProperty, "everybody"). However I fear that if I should ever want to cache my User objects, this will fail miserably. Any hints?
UC5: No clue how to work around this, any hints?
UC6: The solution seems to be simple enough: Add AuthorizationRules.DenyDelete(typeof(User), "simpleuser") inside User.AddObjectAuthorizationRules. However this doesn't work since the check to see if I am allowed will only check the AllowDeleteRules due to UC2. Any hints?
I tried searching the achieves, but I couldn't find anything really. If I missed something, please let me know.
ajj3085 replied on Wednesday, April 02, 2008
UC1 - 3 should use one set of classes, maybe in a .Admin namespace.
UC4 - 6 should use an independent set of classes. Users can ONLY get thier own user object (User.GetCurrent() perhaps).
Cine replied on Wednesday, April 02, 2008
I found a simple solution for UC4:
public override bool CanWriteProperty(string propertyName)
{
IIdentity identity = ApplicationContext.User.Identity;
if (propertyName == PasswordProperty.Name &&
identity.IsAuthenticated &&
identity.Name == ReadProperty(NameProperty))
return true;
return base.CanWriteProperty(propertyName);
}
However, I still have no idea how I could possible achieve UC5 or 6.
UC5 is possible if I want to make a special case of it in the GUI and fetch the object for myself exclusively, however if I want to just "catch" this special case whenever I happen to have "myself", I don't see a way.
Anyone tried to implement this?
Cine replied on Thursday, April 03, 2008
I can hack the csla code a bit to get what I desire.
By implementing an interface (ICanDecideEditAndDeleteAuthorization) I can now have my object override the decision made deep within Security.AuthorizationRules.
My code for UC5 and 6 then becomes these two functions in my User object:
bool ICanDecideEditAndDeleteAuthorization.IsDeleteAllowed(bool rolesSaysAllowed)
{
IIdentity identity = ApplicationContext.User.Identity;
if (!identity.IsAuthenticated)
return rolesSaysAllowed;
bool ispartofsimleuser = FunctionToDetermineIfUserIsIndeedThis();
if (ispartofsimleuser)
return false;
bool ownedbyuser = identity.Name == ReadProperty(NameProperty);
if (ownedbyuser)
return true;
return rolesSaysAllowed;
}
bool ICanDecideEditAndDeleteAuthorization.IsEditAllowed(bool rolesSaysAllowed)
{
IIdentity identity = ApplicationContext.User.Identity;
if (!identity.IsAuthenticated)
return rolesSaysAllowed;
bool ownedbyuser = identity.Name == ReadProperty(NameProperty);
if (ownedbyuser)
return true;
return rolesSaysAllowed;
}
I don't know enough about bindable lists (yet) to figure out how to make it change when I change my selection though.
Property changes on: .
___________________________________________________________________
Name: svn:ignore
+ *.suo
Property changes on: Csla
___________________________________________________________________
Name: svn:ignore
+ bin
obj
Index: Csla/Csla.csproj
===================================================================
--- Csla/Csla.csproj (revision 2235)
+++ Csla/Csla.csproj (working copy)
@@ -120,6 +120,7 @@
<Compile Include="FilteredBindingList.cs">
<SubType>Code</SubType>
</Compile>
+ <Compile Include="ICanDecideEditAndDeleteAuthorization.cs" />
<Compile Include="Linq\CslaQueryProvider.cs" />
<Compile Include="Linq\IIndex.cs" />
<Compile Include="Linq\IIndexSearchable.cs" />
Index: Csla/BusinessListBase.cs
===================================================================
--- Csla/BusinessListBase.cs (revision 2235)
+++ Csla/BusinessListBase.cs (working copy)
@@ -126,7 +126,7 @@
{
get
{
- bool auth = Csla.Security.AuthorizationRules.CanEditObject(this.GetType());
+ bool auth = Csla.Security.AuthorizationRules.CanEditObject(this.GetType(), this as ICanDecideEditAndDeleteAuthorization);
return (IsDirty && IsValid && auth);
}
}
Index: Csla/ICanDecideEditAndDeleteAuthorization.cs
===================================================================
--- Csla/ICanDecideEditAndDeleteAuthorization.cs (revision 0)
+++ Csla/ICanDecideEditAndDeleteAuthorization.cs (revision 0)
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Csla
+{
+ public interface ICanDecideEditAndDeleteAuthorization
+ {
+ bool IsDeleteAllowed(bool rolesSaysAllowed);
+ bool IsEditAllowed(bool rolesSaysAllowed);
+ }
+}
Index: Csla/BusinessListBase.cs
===================================================================
--- Csla/BusinessListBase.cs (revision 2235)
+++ Csla/BusinessListBase.cs (working copy)
@@ -126,7 +126,7 @@
{
get
{
- bool auth = Csla.Security.AuthorizationRules.CanEditObject(this.GetType());
+ bool auth = Csla.Security.AuthorizationRules.CanEditObject(this.GetType(), this as ICanDecideEditAndDeleteAuthorization);
return (IsDirty && IsValid && auth);
}
}
Index: Csla/Core/BusinessBase.cs
===================================================================
--- Csla/Core/BusinessBase.cs (revision 2235)
+++ Csla/Core/BusinessBase.cs (working copy)
@@ -319,11 +319,11 @@
{
bool auth;
if (IsDeleted)
- auth = Csla.Security.AuthorizationRules.CanDeleteObject(this.GetType());
+ auth = Csla.Security.AuthorizationRules.CanDeleteObject(this.GetType(), this as ICanDecideEditAndDeleteAuthorization);
else if (IsNew)
auth = Csla.Security.AuthorizationRules.CanCreateObject(this.GetType());
else
- auth = Csla.Security.AuthorizationRules.CanEditObject(this.GetType());
+ auth = Csla.Security.AuthorizationRules.CanEditObject(this.GetType(), this as ICanDecideEditAndDeleteAuthorization);
return (IsDirty && IsValid && auth);
}
}
Index: Csla/Csla.csproj
===================================================================
--- Csla/Csla.csproj (revision 2235)
+++ Csla/Csla.csproj (working copy)
@@ -120,6 +120,7 @@
<Compile Include="FilteredBindingList.cs">
<SubType>Code</SubType>
</Compile>
+ <Compile Include="ICanDecideEditAndDeleteAuthorization.cs" />
<Compile Include="Linq\CslaQueryProvider.cs" />
<Compile Include="Linq\IIndex.cs" />
<Compile Include="Linq\IIndexSearchable.cs" />
Index: Csla/DataPortal/Client/DataPortal.cs
===================================================================
--- Csla/DataPortal/Client/DataPortal.cs (revision 2235)
+++ Csla/DataPortal/Client/DataPortal.cs (working copy)
@@ -349,7 +349,7 @@
{
methodName = "DataPortal_Execute";
operation = DataPortalOperations.Execute;
- if (!Csla.Security.AuthorizationRules.CanEditObject(objectType))
+ if (!Csla.Security.AuthorizationRules.CanEditObject(objectType, obj as ICanDecideEditAndDeleteAuthorization))
throw new System.Security.SecurityException(string.Format(Resources.UserNotAuthorizedException,
"execute",
objectType.Name));
@@ -360,7 +360,7 @@
if (tmp.IsDeleted)
{
methodName = "DataPortal_DeleteSelf";
- if (!Csla.Security.AuthorizationRules.CanDeleteObject(objectType))
+ if (!Csla.Security.AuthorizationRules.CanDeleteObject(objectType, obj as ICanDecideEditAndDeleteAuthorization))
throw new System.Security.SecurityException(string.Format(Resources.UserNotAuthorizedException,
"delete",
objectType.Name));
@@ -377,7 +377,7 @@
else
{
methodName = "DataPortal_Update";
- if (!Csla.Security.AuthorizationRules.CanEditObject(objectType))
+ if (!Csla.Security.AuthorizationRules.CanEditObject(objectType, obj as ICanDecideEditAndDeleteAuthorization))
throw new System.Security.SecurityException(string.Format(Resources.UserNotAuthorizedException,
"save",
objectType.Name));
@@ -386,7 +386,7 @@
else
{
methodName = "DataPortal_Update";
- if (!Csla.Security.AuthorizationRules.CanEditObject(objectType))
+ if (!Csla.Security.AuthorizationRules.CanEditObject(objectType, obj as ICanDecideEditAndDeleteAuthorization))
throw new System.Security.SecurityException(string.Format(Resources.UserNotAuthorizedException,
"save",
objectType.Name));
Index: Csla/ICanDecideEditAndDeleteAuthorization.cs
===================================================================
--- Csla/ICanDecideEditAndDeleteAuthorization.cs (revision 0)
+++ Csla/ICanDecideEditAndDeleteAuthorization.cs (revision 0)
@@ -0,0 +1,8 @@
+namespace Csla
+{
+ public interface ICanDecideEditAndDeleteAuthorization
+ {
+ bool IsDeleteAllowed(bool rolesSaysAllowed);
+ bool IsEditAllowed(bool rolesSaysAllowed);
+ }
+}
Index: Csla/Security/AuthorizationRules.cs
===================================================================
--- Csla/Security/AuthorizationRules.cs (revision 2235)
+++ Csla/Security/AuthorizationRules.cs (working copy)
@@ -816,13 +816,25 @@
return result;
}
- /// <summary>
+ /// <summary>
+ /// Gets a value indicating whether the current user
+ /// is allowed to edit (save) an instance of the business
+ /// object.
+ /// </summary>
+ /// <param name="objectType">Type of business object.</param>
+ public static bool CanEditObject(Type objectType)
+ {
+ return CanEditObject(objectType, null);
+ }
+
+ /// <summary>
/// Gets a value indicating whether the current user
/// is allowed to edit (save) an instance of the business
/// object.
/// </summary>
/// <param name="objectType">Type of business object.</param>
- public static bool CanEditObject(Type objectType)
+ /// <param name="obj">Allows the object to override the global authorization decision.</param>
+ public static bool CanEditObject(Type objectType, ICanDecideEditAndDeleteAuthorization obj)
{
bool result = true;
var principal = ApplicationContext.User;
@@ -841,16 +853,30 @@
result = false;
}
}
+ if (obj != null)
+ result = obj.IsEditAllowed(result);
return result;
}
- /// <summary>
+ /// <summary>
+ /// Gets a value indicating whether the current user
+ /// is allowed to delete an instance of the business
+ /// object.
+ /// </summary>
+ /// <param name="objectType">Type of business object.</param>
+ public static bool CanDeleteObject(Type objectType)
+ {
+ return CanDeleteObject(objectType, null);
+ }
+
+ /// <summary>
/// Gets a value indicating whether the current user
/// is allowed to delete an instance of the business
/// object.
/// </summary>
/// <param name="objectType">Type of business object.</param>
- public static bool CanDeleteObject(Type objectType)
+ /// <param name="obj">Allows the object to override the global authorization decision.</param>
+ public static bool CanDeleteObject(Type objectType, ICanDecideEditAndDeleteAuthorization obj)
{
bool result = true;
var principal = ApplicationContext.User;
@@ -869,6 +895,8 @@
result = false;
}
}
+ if (obj != null)
+ result = obj.IsDeleteAllowed(result);
return result;
}
Index: Csla/Wpf/CslaDataProviderCommandManager.cs
===================================================================
--- Csla/Wpf/CslaDataProviderCommandManager.cs (revision 2235)
+++ Csla/Wpf/CslaDataProviderCommandManager.cs (working copy)
@@ -106,7 +106,7 @@
if (list != null)
{
result = list.AllowNew;
- if (result && !Csla.Security.AuthorizationRules.CanEditObject(ctl.Provider.Data.GetType()))
+ if (result && !Csla.Security.AuthorizationRules.CanEditObject(list.GetType(), list as ICanDecideEditAndDeleteAuthorization))
result = false;
}
}
@@ -130,7 +130,7 @@
if (list != null)
{
result = list.AllowRemove;
- if (result && !Csla.Security.AuthorizationRules.CanEditObject(ctl.Provider.Data.GetType()))
+ if (result && !Csla.Security.AuthorizationRules.CanEditObject(list.GetType(), list as ICanDecideEditAndDeleteAuthorization))
result = false;
}
}
Cine replied on Thursday, April 03, 2008
Unittests for above:
using System;
using System.Security.Principal;
using Csla.Security;
using NUnit.Framework;
using TestClass = NUnit.Framework.TestFixtureAttribute;
using TestInitialize = NUnit.Framework.SetUpAttribute;
using TestCleanup = NUnit.Framework.TearDownAttribute;
using TestMethod = NUnit.Framework.TestAttribute;
namespace Csla.Test.Authorization
{
[TestClass]
public class ICanDecideEditAndDeleteAuthorizationTest
{
private const string MYUSERNAME = "me";
[Serializable]
public class MyUser : BusinessBase<MyUser>, ICanDecideEditAndDeleteAuthorization
{
private MyUser()
{
/* Use factory method */
}
private static readonly PropertyInfo<string> NameProperty = RegisterProperty(typeof(MyUser), new PropertyInfo<string>("Name"));
public string Name
{
get { return GetProperty<string>(NameProperty, NoAccessBehavior.ThrowException); }
set { SetProperty<string>(NameProperty, value, NoAccessBehavior.ThrowException); }
}
private static readonly PropertyInfo<string> PasswordProperty = RegisterProperty(typeof(MyUser), new PropertyInfo<string>("Password"));
public string Password
{
get { return GetProperty<string>(PasswordProperty, NoAccessBehavior.ThrowException); }
set { SetProperty<string>(PasswordProperty, value, NoAccessBehavior.ThrowException); }
}
protected override void AddAuthorizationRules()
{
AuthorizationRules.AllowWrite(PasswordProperty, "administrator");
}
static public void AddObjectAuthorizationRules()
{
AuthorizationRules.AllowCreate(typeof(MyUser), "administrator");
AuthorizationRules.AllowDelete(typeof(MyUser), "administrator");
}
public static MyUser NewMyUser()
{
return DataPortal.Create<MyUser>();
}
[RunLocal]
protected override void DataPortal_Create()
{
LoadProperty<string>(NameProperty, MYUSERNAME);
LoadProperty<string>(PasswordProperty, "me");
ValidationRules.CheckRules();
}
public override bool CanWriteProperty(string propertyName)
{
IIdentity identity = ApplicationContext.User.Identity;
if (propertyName == PasswordProperty.Name &&
identity.IsAuthenticated &&
identity.Name == ReadProperty(NameProperty))
return true;
return base.CanWriteProperty(propertyName);
}
#region ICanDecideEditAndDeleteAuthorization Members
bool ICanDecideEditAndDeleteAuthorization.IsDeleteAllowed(bool rolesSaysAllowed)
{
IIdentity identity = ApplicationContext.User.Identity;
if (!identity.IsAuthenticated)
return rolesSaysAllowed;
bool ownedbyuser = identity.Name == ReadProperty(NameProperty);
if (ownedbyuser)
return true;
return rolesSaysAllowed;
}
bool ICanDecideEditAndDeleteAuthorization.IsEditAllowed(bool rolesSaysAllowed)
{
IIdentity identity = ApplicationContext.User.Identity;
if (!identity.IsAuthenticated)
return rolesSaysAllowed;
bool ownedbyuser = identity.Name == ReadProperty(NameProperty);
if (ownedbyuser)
return true;
return rolesSaysAllowed;
}
#endregion
}
private MyUser u;
private IPrincipal origprincipal;
[TestInitialize]
public void setup()
{
origprincipal = ApplicationContext.User;
ApplicationContext.User = GetAdmin();
u = MyUser.NewMyUser();
ApplicationContext.User = origprincipal;
}
[TestCleanup]
public void cleanup()
{
ApplicationContext.User = origprincipal;
}
private static GenericPrincipal GetAdmin()
{
return new GenericPrincipal(new GenericIdentity("administrator"), new[] { "administrator" });
}
private static GenericPrincipal GetRandom()
{
return new GenericPrincipal(new GenericIdentity("random"), new[] { "somegroup" });
}
private static GenericPrincipal GetMe()
{
return new GenericPrincipal(new GenericIdentity(MYUSERNAME), null);
}
[TestMethod]
public void Delete_ShouldNotAllowRandomDeleteOfObjectByTypeOrByObject()
{
ApplicationContext.User = GetRandom();
Assert.IsFalse(AuthorizationRules.CanDeleteObject(typeof(MyUser)));
Assert.IsFalse(AuthorizationRules.CanDeleteObject(typeof(MyUser), u));
}
[TestMethod]
public void Delete_ShouldAllowAdminDeleteOfObjectByTypeOrByObject()
{
ApplicationContext.User = GetAdmin();
Assert.IsTrue(AuthorizationRules.CanDeleteObject(typeof(MyUser)));
Assert.IsTrue(AuthorizationRules.CanDeleteObject(typeof(MyUser), u));
}
[TestMethod]
public void Delete_ShouldNotAllowDeleteOfObjectByTypeButShouldAllowSelfViaObject()
{
ApplicationContext.User = GetMe();
Assert.IsFalse(AuthorizationRules.CanDeleteObject(typeof(MyUser)));
Assert.IsTrue(AuthorizationRules.CanDeleteObject(typeof(MyUser), u));
}
[TestMethod]
public void Password_ShouldNotAllowRandomToWritePassword()
{
ApplicationContext.User = GetRandom();
Assert.IsFalse(u.CanWriteProperty("Password"));
}
[TestMethod]
public void Password_ShouldAllowAdminToWritePassword()
{
ApplicationContext.User = GetAdmin();
Assert.IsTrue(u.CanWriteProperty("Password"));
}
[TestMethod]
public void Password_ShouldAllowSelfToWritePassword()
{
ApplicationContext.User = GetMe();
Assert.IsTrue(u.CanWriteProperty("Password"));
}
}
}
Cine replied on Friday, April 11, 2008
Is this possible in any other way than changing CSLA sourcecode?
sergeyb replied on Friday, April 11, 2008
Does every user have access to the list of all users or just
his/her own object?
Sergey Barskiy
Senior Consultant
office: 678.405.0687 |
mobile: 404.388.1899
Microsoft Worldwide Partner of the Year | Custom
Development Solutions, Technical Innovation
From: Cine
[mailto:cslanet@lhotka.net]
Sent: Friday, April 11, 2008 8:10 AM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] Confused about authorization
Is this possible in any other way than changing CSLA
sourcecode?
ajj3085 replied on Friday, April 11, 2008
Are you trying to use one object to satisfy all six use cases? As I stated earlier, you're probably better off having a completly seperate class to handle the user creating or deleting their own profile. It's a very different use case than your first three use cases, so you should have a seperate set of classes to handle it.
Cine replied on Sunday, April 13, 2008
My overall use-case is "display users in grid", for the purpose of simplification everyone has access to the grid and all users. I cannot figure out how to put all my behaviour in one place, when I have both a grid view, and I will eventually also have a details view.Perhaps my problem is I am still to data-centric. I am trying to figure
out how to build my architecture when the behaviour has to be one
place.
One thing I do definately want to avoid is a special check in the upper layers to work with "myself". Why would I want to make a different implementation for the administrative user editing a existing user, a new user or a user editing himself? For me it seems like the same GUI working on the same object, just different users doing so.
I could always make a special function on my CSLA object to determine if I can delete, and can change the password and then double check these in the setter and delete functions. However, I do not see how this is vastly different from changing CSLA's build-in features to do it for me, and thereby avoiding yet-another-authorization-layer.
Ajj3085, You are right about not having all the behaviour in one class though, I would choose to extract the delete and changepassword use-cases into their own classes, ChangePasswordCommand and a similar DeleteMyUserCommand, and then from the MyUser object delegate the responsibility to those classes. However, that doesn't change the interface of the MyUser object. Correct me if I am wrong.
Torb replied on Thursday, June 05, 2008
hi
had the same problem with UC4.
one solution might be:
1. Set instance rules after fetch:
private void PostAddInstanceAuthorizationRules()
{
if (ApplicationContext.User.Identity is ZIdentity)
loggedInUser = ((ZIdentity)ApplicationContext.User.Identity).UserId;
if (loggedInUser != 0 && loggedInUser == _userId)
{
//get the users first role
string firstRole = string.Empty;
ZIdentity zIdent = Csla.ApplicationContext.User.Identity as ZIdentity;
if (zIdent != null && zIdent.Roles.Count > 0)
{
firstRole = zIdent.Roles[0].Identifier;
AuthorizationRules.InstanceAllowRead("Password", firstRole);
AuthorizationRules.InstanceAllowWrite("Alias", firstRole);
AuthorizationRules.InstanceAllowWrite("Password", firstRole);
...
2.
it seems like the GetReadProperty() doesnt work as expected.. (bug?) so, i did this:
public override bool CanReadProperty(string propertyName)
{
//for property "password"
bool canread = base.CanReadProperty(propertyName); //Is False
bool canReadByAuthRules = AuthorizationRules.IsReadAllowed(propertyName); // is True!
if(canReadByAuthRules)
return canReadByAuthRules;
//Old stuff...
//if (propertyName.Equals("Password") &&
// (canread || (loggedInUser == _userId)))
// return true;
return base.CanReadProperty(propertyName);
}
ajj3085 replied on Thursday, June 05, 2008
For that you may want to override CanWriteProperty / CanReadProperty.
Copyright (c) Marimer LLC