Re: Model-View-Controller and user permissions
Re: Model-View-Controller and user permissions
- Subject: Re: Model-View-Controller and user permissions
- From: Ramsey Gurley <email@hidden>
- Date: Tue, 19 Jul 2011 08:50:38 -0700
Hi Amy,
On Jul 19, 2011, at 5:55 AM, Amy Worrall wrote:
> Hi! I have a question about good app design.
>
> I know that, as mentioned in the WOWODC videos I've watched, a common
> beginner mistake is to put most of the logic in the page components.
> Indeed, I've been guilty of that myself in apps I've made in the past.
> I know the fundamentals of MVC from my background in Cocoa.
>
> So suppose I have an app where users have their own profile. Each user
> can edit his own profile, whereas an admin can edit any profile.
>
> Where abouts does the logic go to check if someone is authorised to
> edit a particular profile?
You're right. This is definitely not view code. There is no one correct answer to this question. Every approach has tradeoffs. The main question you have to ask yourself is this: Is this model or control logic?
If it is model logic, it never changes with that particular model. An admin is always able to edit any profile. If you reuse this user framework in another app, the same rule always applies. If that is the case, then your answer is model logic.
If however there are grey areas, then it becomes control logic. Consider a Photo entity. In a photo sharing application, maybe everyone can read Photos. However, in a medical imaging app, only certain people are allowed to read certain photos due to privacy regulations.
In that case, the authorization logic is control code. Ideally, you would not want to put access control code on your Photo entity, because that code needs to change based on the application where the framework will be used. If you put the logic in the model, then you will end up rewriting your photo model every time you need photos in a new app.
> Should there be a method on the Session, to
> return a boolean for "can edit this profile"? If that's the case, from
> where is that method called?
If you do it that way, you'll be tying your view components to a particular implementation in your session. So, while it may work, it may not be the best approach. I would suggest creating a reusable java interface in your view framework if you do it this way. If you want to use these views in another application, you will know you have to implement the session interface you produced.
> I know I could do it by having the page component call the
> authorisation method, and return an error page instead if it goes
> wrong. But that seems to tie the logic too much to my view: what if I
> come to add a REST API later? I'd need to duplicate my permissions
> logic, since it wouldn't be using the WOComponent that outputs the
> HTML page. Ditto if I add another page elsewhere that happens to be
> able to make a profile change (say, allowing an inline name change on
> another otherwise unrelated page). Ideally I think the data model
> itself should be able to reject an edit if it's performed without
> permission, but then we get into problems since the data model
> shouldn't know about the session.
>
> Also, I'm considering using Direct To Web (at least to some extent)
> for this project. I've never used it for anything more than an admin
> interface (i.e. one global login, if you're in then you can edit
> everything). If I were using Direct To Web, is the answer to the above
> question the same?
The approach Direct To Web takes is to provide a delegate object on the page component to handle control code. Page components that need to trigger control code will call nextPage() on the delegate to return the next page. Obviously, you can see how it can become confusing if every button on a page calls nextPage(), but expects different results.
ERDirectToWeb extends this with branching delegates. Instead of having a single nextPage() method, a branching delegate has multiple methods. So the delegate may have an editUser(), inspectUser(), deleteUser() method. This makes it much easier to produce different results for different interface elements. Whether or not these control methods are available in the interface is determined by the branchChoices RHSKey in the rule system. So, continuing with the example, you could have a couple of rules like
100: *true* => branchChoices = (inspectUser)
105: session.objectStore.user.isAdmin => branchChoices = (inspectUser, editUser, deleteUser)
Now, if the user in the session's objectStore is an admin, then inspect, edit, and delete will be available in the interface. Otherwise, only inspect is available.
Personally, I find this approach the simplest. The main problem I run into here is rule system caching. ERD2W does a fantastic job of caching to keep the rule system really fast, but in the case of auth logic, it isn't hard to imagine ways that cached values can become invalid.
My approach then, was to put together an auth framework to extend this a bit further and remove the rule system caching from the equation. The framework is available in one of my github repositories.
https://github.com/nullterminated/ponder
ERAuth manages authentication and authorization. It manages authorization for CRUD logic with the CRUDAuthorization class. This class provides generic methods like
public Boolean canReadProperty(EOEnterpriseObject eo, String keyPath) {
return Boolean.FALSE;
}
Using a special ERASelector class, these methods can be overridden by methods with different signatures like:
public Boolean canReadProperty(User eo, String keypath) {
if(User.USERNAME_KEY.equals(keypath)) { return true; }
if(isActor(eo)) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
}
So that if the method is called for the class User, then the special User method is used. Otherwise, the ERASelector falls back to the User's parent entity until it reaches the base EOEnterpriseObject method. This allows me to provide special logic for specific entities without a huge if block like:
if("User".equals(entity.name())) ...
else if
else if
else if
baaaarrrrffff
This approach also provide the ability to set auth controls all the way down to specific attributes. Using a method like this, it isn't hard to imagine a list view where table cells have a 'lock' icon only on certain rows and columns, depending on a user's auth privileges. Your doctor can read your photo's, but he can't read your neighbors... and all of the logic is encapsulated in a single auth delegate class.
The primary downside to ERAuth is that it requires support built into the view components. Right now, only one 'look' framework can use it... R2D2W. It's pretty new code as well, so don't be surprised if you find a bug or three (^_^)
Ramsey
>
> Thanks for your help,
>
> Amy
> _______________________________________________
> Do not post admin requests to the list. They will be ignored.
> Webobjects-dev mailing list (email@hidden)
> Help/Unsubscribe/Update your Subscription:
>
> This email sent to email@hidden
Attachment:
smime.p7s
Description: S/MIME cryptographic signature
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Webobjects-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden