I know there have been a few other discussions about per-instance authorization solutions, but I am still working out a few issues and hope to re-visit the topic in a more complete way...
Let's use the "CanEditObject()" method as the basis for our discussion. In my Quote class, as with all other classes, this method is static and is implemented simply as:
return ApplicationContext.User.IsInRole("Account Manager");
which works great for most applications. However, in my case, there are times when the object's state affects the response we want to provide from this method. For example, here would be the full use-case statement:
So, I need the CanEditObject method to be more like:
public static System.Boolean CanEditObject()
{
if (ApplicationContext.User.IsInRole("Account Manager"))
return (State == QuoteStatus.Draft)
return false;
}
The problem is that the State property is not static - and can't be as this is a per-instance value. So, how to accomplish this then?
In other posts there was a suggestion to implement two classes, Quote and SubmittedQuote, that would return different responses to the authorization methods. I.e. Quote would simply check IsInRole and SubmittedQuote would always return false. However, when putting this into action there is a problem implementing this as we are displaying a list of QuoteInfo objects from which the user has selected to open one of the listed records (by double-clicking the row, for instance). We pass the ID value from the selected row to the new form which then calls Quote.GetQuote(ID) to instantiate our business object. It seems like we are adding extra code and complications in order to differentiate which type to instantiate.
And what if there are multiple properties that determine whether the object can be edited by the current user? Perhaps not only the object's state matters but also the dollar value. Let's say that a Quote that has been submitted for approval worth more than $1 Million can only be updated by the VP of Sales whereas any Quote with a lower dollar value would be accessible to the Sales Manager for approval.
To further complicate the discussion, we are in the design phase on another application that will require us to implement more traditional access control (per object) security whereby each object will maintain a list of users that are allowed to perform actions upon it. I don't see how we are to have separate classes for each of these cases. Nor can the ACL's be static as they will vary from instance to instance.
How best to accomplish what I've described? Any suggestions? Anyone had any success in these areas???
Thanks in advance.
Actually the CanReadProperty/CanWriteProperty/CanExecuteMethod part isn't a problem because those are not static methods so I can make conditional checks with other properties of the object. My issue has to do with the static CanAddObject, CanEditObject, CanRemoveObject and CanReadObject methods used by the UI to enable/disable menu items, display forms as read-only, etc.
For example, continuing the Quote scenerio described above, when we display our Quote Details (web) form, we use the CanEditObject method to determine if the FormView is in ReadOnly or Edit mode. Our business rules, as shown before, dictate that we:
Which brings up a second question that has plauged me for a while. I do not make much use of the CanReadProperty, etc. methods because access to the object is blocked by the static methods. However, I understand that having the AuthorizationRules defined provides a safety net in case access is granted despite denial by the static methods - as would be the case if the UI developer failed to include the check! What do you do then? Do you throw an exception? Display an error message?
About 90% of the applications I work on are web apps, so handling an exception is a bit more cumbersome than with Windows apps. And, since I make use of the various xView controls, we are not binding the enabled/disabled state of the individual edit controls to their corresponding properties but rather using the static methods to determine the appropriate template to display.
Make sense?
I do not use the Auth rules very much. But when I was looking in to them I decided that for my web app it would make more sense to just blank out the value if CanReadProperty was False. This made more sense to me than throwing an exception and then trying to handle it in the UI. Just let the user see all the values they are supposed to see and blank out the rest.
'CanReadProperty("Desc", True) ' this causes an Exception to be thrown if user is not allowed to Read the Property.==========================================================================
Public
Class Check Private Shared Function HasPermission(ByVal permission As String) As BooleanRather than write this kind of code in each BO I decided to centralize it so any BO can call it.My user.HasPermission is similar to IsInRole but much finer grained.
I realize this doesn't answer the question about how you also deal with the state of the object. Not sure how you can do that without an instance of the object. Sort of a Catch-22.
Finally - Rocky may enhance IsReadAllowed so that the *default* implementation is the same as today - call IsInRole. But he will do it using a Delegate. This wa y we can write our own IsInRole boolean function and use a dfiferent delegate (which is hooked up on App start). This delegate could return my HasPermission value instead of IsInRole which would allow me to again have finer control over this mechanism.
Joe
I still don't see the value in the CanReadProperty and CanWriteProperty methods except as a backup in case the UI developer forgets to check CanReadObject/CanEditObject. I have never run into a case where I need to allow a user access to only a portion of an object's properties while denying others - the user can either read or not and can either edit or not. And with regards to the latter, we already have a safe-guard check in the Save method where we make sure that the user has edit rights before allowing the save. So, again, I'm not looking for answers how to implement these methods as they don't serve my purposes.
Furthermore, we don't really have an equivalent control for web apps as the RWAuth Windows control. In web apps, we make use of the built-in features by utilizing the various xView controls which allow us to define templates for editing and read-only views of the data. This is done "globally", so to speak, in that we are either in edit mode or read-only mode. Again going back to what I referred to before about not working on a per-property basis - as the RWAuth allows us to do in Windows apps (but I've never used cuz it was never applicable).
If anyone has examples of use-cases where you would want to be able to grant edit access to some fields and deny for others within the same instance of a BO, please pass them along because this is one big gray area for me.
Let me try to make a more concrete example to further the discussion.
In our application, object instances are "owned" by their creator. As such, the creator will always have full rights to the object. He can view it, edit it and delete it. During the object's lifetime, the creator may need to allow others to work with the object, so it can be temporarily "assigned" to another user. This user is granted view and edit rights so long as they are assigned to it. Furthermore, the object has a list of users and roles in which it has been "shared." Any user in this list (or user belonging to a role that is included) is granted read rights. Anyone else can do nothing with the object.
So, our authorization logic is as follows:
The only authorization method that is not per-instance is the CanCreateObject method which does the simple IsInRole check.
Again, it doesn't make sense for me to implement these rules on each and every individual property accessor. Besides the performance impact of having to evaluate this everytime a property is read or written to, the amount of excess code...!!!
But, you can clearly see that three out of the four authorization methods rely on instance property values. So, how to implement?
Do I make each of the authorization methods non-static? If so, I am now bound to only checking per-instance and no longer have per-type authorization as I still would use for the CanCreateObject method.
Does this example help to illustrate the problem/question better?
SonOfPirate:I still don't see the value in the CanReadProperty and CanWriteProperty methods except as a backup in case the UI developer forgets to check CanReadObject/CanEditObject. I have never run into a case where I need to allow a user access to only a portion of an object's properties while denying others - the user can either read or not and can either edit or not. And with regards to the latter, we already have a safe-guard check in the Save method where we make sure that the user has edit rights before allowing the save. So, again, I'm not looking for answers how to implement these methods as they don't serve my purposes.
SonOfPirate:Furthermore, we don't really have an equivalent control for web apps as the RWAuth Windows control. In web apps, we make use of the built-in features by utilizing the various xView controls which allow us to define templates for editing and read-only views of the data. This is done "globally", so to speak, in that we are either in edit mode or read-only mode. Again going back to what I referred to before about not working on a per-property basis - as the RWAuth allows us to do in Windows apps (but I've never used cuz it was never applicable).
So the only question here is a matter of scope; currently I imagine you're using the static CanEditObject to determine which template to use, but I can easily see using CanWriteProperty if you wanted to only lock portions of the objects but still allow edit access.
I could see a use case where a sales person could modify some customer information, such as address data, but not other aspects, such as the customer's credit limit, payment terms, etc.SonOfPirate:If anyone has examples of use-cases where you would want to be able to grant edit access to some fields and deny for others within the same instance of a BO, please pass them along because this is one big gray area for me.
SonOfPirate:The only authorization method that is not per-instance is the CanCreateObject method which does the simple IsInRole check.Again, it doesn't make sense for me to implement these rules on each and every individual property accessor. Besides the performance impact of having to evaluate this everytime a property is read or written to, the amount of excess code...!!!
But, you can clearly see that three out of the four authorization methods rely on instance property values. So, how to implement?
Do I make each of the authorization methods non-static? If so, I am now bound to only checking per-instance and no longer have per-type authorization as I still would use for the CanCreateObject method.
The only solution I can think of is use Csla and the CanReadProperty / CanWriteProperty, or your static methods will have to take parameters that give enough information to get a result. The parameterless static methods would return in general what someone may do.
For example, CanLoadObject would mean the user can load at least some instances. If CanLoadObject is false, then I would think the static factory get methods would throw exceptions always, and your UI would not even show links to pages which load the object details.I just stumbled across a great blog post related to Microsoft CRM security that has given me a few new thoughts on how to implement this within Csla.
In CRM, authorization is broken into two parts: PrivilegeCheck and AccessCheck.
A PrivilegeCheck would do exactly what the existing static methods are doing: check IsInRole and return true/false based on whether the current user MAY be able to perform the requested action. For instance, if I have a user that is in the Account Managers role, they may be able to edit a Quote object so we would enable the Properties toolbar button and allow them to open the Quote details form/page.
An AccessCheck is where the instance part comes in that I am talking about. An AccessCheck is what determines whether or not the user can actually perform that action on the specific instance of a BO. This would be where we would have to apply the authorization rules that I described and is what would tell me to display the form as read-only and/or disable the Save button, etc.
What dawned on me in reading this is that my problems may be solved as simply as creating overloaded versions of the static methods that accept the instance being checked as a parameter. For example, to implement the CanReadObject methods given the rules I described previously, I'd have:
public static System.Boolean CanReadObject()
{
return ApplicationContext.User.IsInRole("Account Manager");
}
public static System.Boolean CanReadObject(Quote q)
{
if (CanReadObject())
{
if (ApplicationContext.User.Equals(q.Owner))
return true;
if (ApplicationContext.User.Equals(q.AssignedTo))
return true;
if (q.ShareWith.Contains(ApplicationContext.User))
return true;
// Some check to see if any of the user's roles are in the
// share list (have to figure out later)
return false;
}
}
In my UI code, to determine if my FormView, for example, should be displayed in edit or read-only mode, I'd use:
if ((Quote.CanCreateObject()) && (DataItem.IsNew))
FormView1.ChangeMode(FormViewMode.Insert);
else if (Quote.CanEditObject(DataItem))
FormView1.ChangeMode(FormViewMode.Edit);
else
FormView1.ChangeMode(FormViewMode.ReadOnly);
This seems pretty consistant with the description of how it is done within CRM and appears to accomplish what I am after. I will have to go back through my use cases to see that it fits across the board, but I think it will do the trick.
Thoughts?
SonOfPirate:A PrivilegeCheck would do exactly what the existing static methods are doing: check IsInRole and return true/false based on whether the current user MAY be able to perform the requested action. For instance, if I have a user that is in the Account Managers role, they may be able to edit a Quote object so we would enable the Properties toolbar button and allow them to open the Quote details form/page.An AccessCheck is where the instance part comes in that I am talking about. An AccessCheck is what determines whether or not the user can actually perform that action on the specific instance of a BO. This would be where we would have to apply the authorization rules that I described and is what would tell me to display the form as read-only and/or disable the Save button, etc.
Hmm, that sound very similar to what I was getting at.
SonOfPirate:What dawned on me in reading this is that my problems may be solved as simply as creating overloaded versions of the static methods that accept the instance being checked as a parameter.
Pirate, why the static method though (I know, I threw something similar out there as well..)?
Anyway, it sounds like you're making progress, so that's good
AndyLooks like you combined my advice with Andy's. <g>
I said you needed an instance of the BO and Andy said: "your static methods will have to take parameters that give enough information to get a result. The parameterless static methods would return in general what someone may do."
Your detailed explanation was very valuable - it made the "advice" much clearer on how to do it and why.
I am not sure that the sample code at the end matched you original use cases though.
Also - you didn't show *when* you instanitate the instance of the DataItem which is a Quote object.
In your sample code you already had it. Why would you bother creating it if the user wasn't allowed to do something with it? (The paremeterless check.) Once you determine they might be able to do something with it, it makes snese to create an instance and find out exactly what they are allowed to do.
Joe
First answer: the static methods follow the Csla approach and are declared as such so that the current user can be authorized without instantiating an object of the specific type. For instance, if I have a New Project menu item, I can check Project.CanCreateObject() and set the menu item's enabled or visible property accordingly. Certainly not someplace you'd want to instantiate an object.
As for having both of the methods I described static, it is mostly for consistency.
Second question, the sample code I shown at the end of my last post is from a real-world application that is (now) running this exact code. I did not copy the rest of the code from the class because it is not relevant and is quite large. Nonetheless, the DataItem property returns the BO that the form is bound to - in this case a Quote object. Since the form is used to display the BO for inserting (creating), editing or in read-only mode, you have to instantiate an object. The only right that isn't included in this is the right to view the object (via CanReadObject). This check is performed in another event handler and throws an exception if the user isn't allowed to view the object. Otherwise, with that right granted, the form is going to be displayed in one of its three modes so we will always need to instantiate the object.
And, ultimately, it was intended as a possible use of the code explained above it and not meant to be comprehensive.
use-cases of a user getting read access to only certain properties:
What about an application that holds personal details like salary - say an HR application? Most users should only be able to view 'public' information like e-mail address, emergency contact details etc. but not be able to see whether they get paid more than the person they are enquiring about! Only HR and high up manager types should be able to see salary details.
Another one is credit card details (held no doubt in an encrypted format and using SecureString!) which should not be visible to most people but may need to be visible to one or two high up managers. As mentioned above, in this instance you might not raise an error but return something different. It might be that some people are able to see all the card numbers but most can see only the last 4 digits.
Here in the UK we are more strict on other personal information like home address etc. than I believe is the default case in the States so this may be another area where this kind of per-property read enablement is beneficial. Again, a return of blank details or some suitable message may be more appropriate than raising an error.
SonofPirate,
What did you end up doing?
I have a very similar situation in which I have a User object that can be edited by:
1) Administrator
2) User Admin
3) The user represented by the object (i.e., a user can edit his or her own record)
In the first two cases, I can simply use the static CanEditObject() method. But in the third case, I would need an instance of the object to make the determination that the user was editing his or her own object.
I know this post is from a while back, but I'm looking at the problem as a row level database security situation.
For my two cents it would seem that the having an overloaded CanEditObject(typeof(CSLA BusinessObject)) means that the cat is out of the bag and another CSLA business object (Collection/list etc) has already read and retrive the data incorrectly for a user role.
Being a defensive programmer I can see other ways to stop users from reading/editing and use the CSLA AuthorizationRules.
During the DataPortalFetch methods, reading the data and then calling throwing Security.SecurityExceptions or explicity setting the AuthorisationRules after the data retrieval attempt. This way the BO isn't going to pass information back to the client application and expose data.
Alternatively the SQL text/storedprocedure used by the dataportal fetch could pass security information and then also "block" returning data if the user wasn't allow to get at it.
If the CSLA solution was exposed to the world via a Web service, there is no way I would allow the BObject to pass information out.
Example code that applies AuthorizationRules in another part of the business object ( I don't see why that's not a logical and easiest thing to do?).
Dim DenyPaidRecordUsers As String() = {My.Resources.Role_User}
Dim EditPaidRecordsUsers As String() = {My.Resources.Role_Admin}
If HasBeenPaided Then
With AuthorizationRules ' More importantly if a record has been paid general users can not edit or delete it..DenyExecute(
"DeleteRecord", DenyPaidRecordUsers).AllowExecute(
"DeleteRecord", EditPaidRecordsUsers) ' In defensive mode, lets block the user from saving the record (just case they have loaded a record [or created one from scratch]).DenyExecute(
"Save", DenyPaidRecordUsers).AllowExecute(
"Save", EditPaidRecordsUsers) ' If there is a payment, then don't allow all the fields to be edited. ' Admin user role should be allowed to change some information. .AllowWrite("SaleDate", EditPaidRecordsUsers) .DenyWrite("SaleAccountBal", DenyPaidRecordUsers) End With End If
Let me add to my previous post. The CSLA 3.x AuthorizationRules add entries only, so you can't change them after you create the object. I do however need to change authorization based on OB instance data. To get the Deny properties to work I've had to use AuthorizationRules.InstanceDenyWrite("PropertyName") and Shadow the CanWriteProperty("propertyName",Boolean) as follows
'''
<summary> ''' Need to change this call to ensure that Deny property entries took preference over Write authorization. ''' </summary> ''' <param name="propertyName"></param> ''' <param name="throwOnFalse"></param> ''' <returns></returns> ''' <remarks></remarks> Shadows Function CanWriteProperty(ByVal propertyName As String, ByVal throwOnFalse As Boolean) As Boolean Dim result As Boolean = CanWriteProperty(propertyName) If throwOnFalse AndAlso result = False Then Dim ex As New System.Security.SecurityException( _ String.Format("{0} ({1})", "Property set not allowed", propertyName))ex.Action = System.Security.Permissions.SecurityAction.Deny
Throw ex End If Return result End Function ''' <summary> ''' CSLA tests for write/read authorisation and ignores deny ''' As we want to apply instance authorisation (rules that look as BO data) we add deny(Property) calls ''' and then check to see if a property was denied and make sure it has priority. ''' </summary> ''' <param name="propertyName"></param> ''' <returns></returns> ''' <remarks></remarks> Shadows Function CanWriteProperty( _ ByVal propertyName As String) As Boolean Dim result As Boolean = MyBase.CanWriteProperty(propertyName) ' Deny authorization should be check for the property ' In most security mechanisms deny overpowers and read/write privalages. If AuthorizationRules.IsWriteDenied("SaleProgress") Thenresult =
False End If Return result End FunctionCopyright (c) Marimer LLC