I'm currently working on an application that requires authorisation to work properly. I've implemented authentication using the examples, while adding some stuff to them.
Now, I have to do proper authorization. As suggested, I can limit what can be accessed by what users in Site::forwardRequest(). But, this will only work on a Type/Handler level.
What I want to do, is distinguish admin and normal users (perhaps more). Admins should be able to do just about anything, normal users should be able to edit their own data. I've accomplished this by limiting normal users to pntType=Person and id=their own id.
Furthermore, there are a number of properties on a Person that should not be modifiable by a normal user (such as the Person's permissions and its accountname). I could just mark these as read-only or hidden, but that will mean even admins cannot edit the properties. So, my second try was to make them readonly or hidden in Person::initPropertyDescriptors() depending on whether an admin was logged in, but that didn't really work out, because the authorization system uses the Person class. But, we can't use the Person class yet, since it is not initialized yet... Any thoughts?
Also, I was thinkint it might be good to have some authorisation framework inside peanuts? For example, introduce some abstract authorisation manager that will decide what is allowed an what is not, based on (arbitrary) permission objects in the various classes/peanuts? Has any thought been given to this already?
Matthijs
matthijs
2006-09-17 15:05:38
Okay, I've been thinking about this, and I'm proposing the following.
I think that for proper authorization, we can split everything in two. First, a low level layer that says "This property is read only" or "A new object of this type can be created". On top of that, there is a higher level layer, that can express things like "Admins can edit Persons, but normal users can edit only themselves".
The lower level is already mostly implemented in peanuts, through the readonly and visible properties of the propertydescriptors. The higher level would not be implemented by peanuts, only supported with proper callbacks and properties. More on that further on.
So, the lower level will consist of the following properties:
PropertyDescriptor::readOnly
PropertyDescriptor::visible
ClassDescriptor::creatable
PntObject::Deletable
Using these, one should be able to express most authorization schemes. More
complicated schemes can probably be expressed to, through properly overriding
some methods.
If all code in the peanuts framework respects these properties, everything
should be allright (just as is the case with the first two properties now).
Now, the higher level should be implemented by the application itself. It
should define some way to specify permissions and translate them to the above
properties. To support this, we introduce the concept of a SecurityManager. It
will perform the translation part. Also, we will give PropertyDescriptors,
ClassDescriptors and PntObjects a new property called "ACL" (for access
control list). This property should be set by the application and read by the
SecurityManager. Peanuts does not care about what is in it (or what type the
property has, even).
The last thing Peanuts will do, is call the SecurityManager at the proper
times. For example, when the visibility of a PropertyDescriptor is queried,
the SecurityManager should be consulted about what to return (or a default is
used if no SecurityManager is available). To prevent the SecurityManager from
getting called every time getVisible() is called, we cache the result in the
visible property (where currently visibility is already stored). This has the
added effect that, if we explicitely set the visibility of a
PropertyDescriptor, the SecurityManager will not get consulted (serving as a
manual override as well as backwards compatibility).
All this about visibility applies to the other four low level properties as
well. So, we get a SecurityManager that needs the following methods:
isCreatable ( ClassDescriptor )
isDeleteable ( PntObject )
isVisible ( PntPropertyDescriptor, PntObject )
isWritable ( PntPropertyDescriptor, PntObject )
I think this SecurityManager should be available throught the Site object, so
we add a getSecurityManager() method to PntSite (returning a default
SecurityManager by default, which should be overrided in the application's
Site class).
Any comments on how this might work out? I'll try to implement some of this,
to see how it works and if it solves my problems soon.
Post Edited (09-17-06 15:08)
matthijs
2006-09-17 16:32:33
After fifteen minutes of fiddling around with the code, I've already found a few flaws.
Firstly, there should also be a property PntObject::visible, which can be used to hide objects alltogether. This requires a method SecurityManager::isObjectVisible. To keep consistency, the SecurityManager methods should be:
isClassCreatable
isObjectDeletable
isObjectVisible
isPropertyVisible
isPropertyWritable
Secondly, calling the isPropertyVisible methods with a PntObject argument is non trivial, since visibility is defined for each property, not for each property/object combination. Without this, however, one cannot express permissions such as "Normal users may view all person's name, but only edit their own".
A workaround that springs to mind just now, is to add a readonly property to objects, that overrides the readonly property of all the object's properties. In the example above, this would mean that the 'name' property is writable to everybody, but a all person's readonly properties are false, except for the person object that is currently logged in.
Still, this will not solve the problem for more complicated combinations of permissions, such as: "Everybody can edit all Person's property A, but only edit their own property B" (although I have not found a real example of where this kind of permission would be required, I'm pretty positive that it is useful).
To also be able to express this last type of permission, we need to assign the
properties of PropertyDescriptors to combinations of PropertyDescriptor and
PntObject instead. A simple way to do this, is to make
PropertyDescriptor::visible a assoc array, indexed by the PntObjects' ID. This
still allows for manual overriding and backwards compatibility, since you can
just store a boolean instead of an assoc array then. Downside to this is that
it might be a little to complicated.
A better approach might be to store these properties in the PntObject, using
an assoc array indexed by property name, since this is a little more
intuitive.
Whatever the case, the PntPropertyDescriptor::getVisible method should get an
extra PntObject parameter (not sure what the full implications of this
are...).
So, I'm off exploring the code a little further...
matthijs
2006-09-17 18:14:57
Okay, to make visible a PntObject/PropertyDescriptor property instead of just
a PropertyDescriptor property, getVisible needs an extra PntObject argument.
isVisible is called by PntObjectReportPage::getMultiPropNames,
PntClassDescriptor::getUiPropertyDescriptors,
PntPage::addMultiValuePropertyButtons. The first two should just get an extra
PntObject parameter, the last one already has the PntObject to pass to
isVisible.
The getMultiPropNames is only called from one place, where the PntObject is
available. getUiPropertyDescriptors is called from
PntClassDescriptor::getUiColumnPaths, PntClassDescriptor::getUiFieldPaths and
PntObjectReportPage::getFormTextPaths, which all should get an extra PntObject
argument.
So, these methods are called from a number of places, which should also
require updating. Don't have time to continue this right now, I'll get back to
this. For now, it seems this change is non-trivial, but does not pose any
problems so far...
henk
2006-09-20 01:09:41
Hi Matthijs,
Sorry for the late reaction. Of course i have been thinking about an authorization framework in phpPeanuts, but i simply never needed it. PhpPeanuts development is in practice driven by my needs in commercial development. However, i suggest others to do the same (develop for your own needs, but share reusable code), so i appreciate your effort.
For a first authorization extension to phpPeanuts i would like to separate code that is part of the authorization system from both phpPeanuts as it is, as well as from domain classes of specific applications, like your 'Person'. In other words, i suggest you create a PntUser class whose instances will store information about the user, like name, password(hash), etc, as well as generic information about what the particular user may do, like the names of the classes he is authorized to access (in this case 'User').
Then create a subclass User for defining specific data you need for security in your own application. This could for example be the id of the Person object the particular user may access and edit). But a better idea may be to add a property userId to Person. Of course each Person object has to store the userId of the current user when the object is first saved. The simpelest way to allow it to find out about the current user is to mat that information accessable in $site. (I agree this is not nice architecture, but currently the user interface Controller and the application base are still close coupled in Site).
Then let the SecurityManager object decide which requests can be permitted and which not. This has the advantage that you may later extend your application with other classes whose instances may be accessed by certain users without having to change the User class. You may for example simply add another property userId to these new classes and store the id of the user when the object is first saved.
Of course only users with administrative rights are allowed to access User objects. In fact the administrators may not even have their own 'Person' object. Or maybe they may have multiple Person objects around for testing. Both are possible withing the above design*.
I guess this will eliminate your need for hiding/showing specific properties to specific users. For a first authorization subsystem that would be good enough too. But just for fun let's look into this anyhow. I guess making PropertyDescriptor::getVisible return $this->visible && $site->securityManager->isPropertyVisible($this) will do the trick, but only if all instances of Person are to be treated the same. A more powerfull and elegant solution would be to adapt the code of the various subclasses of PntPage and PntPagePart to call $this->getSecurityManager()->isPropertyVisible($property, $requestedObject, $this). This will give the securityManager very detailed control. Of course getSecurityManager must allways return an instance of a subclass of PntSecurityManager, if no specific security is required, it may be set to an instance of NoSecurityManager. That will allways return $property->getVisible().
This was quite a long story, i hope it makes sense to you. If you have built something in this style please consider to send me your code to incorporate into next version of phpPeanuts. (I can not promise i will use all of it, but at least it will motivate me to add this kind of function to phpPeanuts, if possible in a way compatible to yours).
Greetings,
Henk Verhoeven,
www.phpPeanuts.org.
* Of course there is is a limitation to this design: only one User can be authorized to access a certain Person object. If you need several users to share the same access rights, consider to add UserGroups. An example of an instance of UserGroup may be 'Administrators'. To register which users are in which rights i suggest you also add a UserGroupMember class that defines properties 'user' (type: User), 'userId' (type: number), 'userGroup' (type: UserGroup) and 'userGroupId' (type: number). This will allow users to be member of any group and groups to have any user as member.
Once you have the UserGroup and UserGroupMember classes, you add a property 'userGroupId' to your Person class. You will need a specific user interface (Page class) for Administrators that allows them to set this property, that is never shown by the normal user interfaces.
Post Edited (09-20-06 22:50)
matthijs
2006-10-08 18:10:57
> Sorry for the late reaction.
No problem, I appreciate all of your efforts. I've not replied again
until now, because I only now found your reply. Since you edited your
post, I was not notified by email, nor did the post count increase, so
I've been in the illusion that you had not made your reply yet.
Anyway, I found it now :-)
I've already partly implemented something similar to what you
describe, only instead of using a (Pnt)User class, I'm exposing the
currently logged in Person object directly. I can see the merit of
putting a User class in between, so I will probably implement that. At
first thought, I would say (Pnt)User is a non-persistent class that
wraps a (persistent) domain model class (say Person). Yet, from the
rest of the post, I get the idea that you would make PntUser
persistent? This would mean it shouls inherit from PntDbObject, which
is not really what I would consider elegant (nor flexible).
> For a first authorization extension to phpPeanuts i would like
> to separate code that is part of the authorization system from
> both phpPeanuts as it is, as well as from domain classes of
> specific applications, like your 'Person'. In other words, i
> suggest you create a PntUser class whose instances will store
> information about the user, like name, password(hash), etc, as
> well as generic information about what the particular user may
> do, like the names of the classes he is authorized to access
> (in this case 'User').
I'm not sure this is such a good idea, though. In particular, I was
trying to completely separate the authoristion mechanism from peanuts.
The peanuts framework should know that User objects exist and that
there is a SecurityManager that knows how to handle them, but the
framework should not assume anything else. In my application, I would
be implementing authentication against a phpbb forum database, so
storing a password in the User object would not really make sense.
What might be a good idea to ship an example implementation SimpleUser
and SimpleSecurityManager that implement simple
authentication/authorization like you describe (though I will not
really look at this, since it does not suit my needs, I will keep the
option in mind).
> Then let the SecurityManager object decide which requests can
> be permitted and which not. This has the advantage that you may
> later extend your application with other classes whose
> instances may be accessed by certain users without having to
> change the User class. You may for example simply add another
> property userId to these new classes and store the id of the
> user when the object is first saved.
Currently, I have implemented request based authorization directly in
my Site class, but this should indeed go into the SecurityManager (and
be called from within PntSite I guess?).
> Of course only users with administrative rights are allowed to
> access User objects. In fact the administrators may not even
> have their own 'Person' object. Or maybe they may have multiple
> Person objects around for testing. Both are possible withing
> the above design*.
I don't really see how having multiple Person objects for one user
would be useful?
> I guess this will eliminate your need for hiding/showing
> specific properties to specific users. For a first
> authorization subsystem that would be good enough too.
It took me a while to fully understand what you were saying, but I
think it is really a workaround for what I want to achieve. What you
are suggesting, really, is separating my Person class into a Person
and a user Class (having a 1-on-1 relationship). The Person object
would contain the public, user-modifiable properties, such as name and
address. The user object would contain the administrative properties
such as accountName that would be hidden from ordinary users by
denying access to the User object alltogether.
I would not say this is really an elegant solution, since it has now
become impossible for admin users to view all a person's properties at
once.
Also, I will be implementing more objects that will need a similar
authorisation (users should be able to submit articles, which have a
comments field only visible to admin users, for example). In other
words, I do actually need property visibility on a per-user basis.
> But just for fun let's look into this anyhow. I guess making
> PropertyDescriptor::getVisible return $this->visible &&
> $site->securityManager->isPropertyVisible($this) will do the trick,
> but only if all instances of Person are to be treated
> the same.
I have this implemented now and it works well enough for my initial
version.
> A more powerfull and elegant solution would be to adapt the code of
> the various subclasses of PntPage and PntPagePart to call
> $this->getSecurityManager()->isPropertyVisible($property,
> $requestedObject, $this). This will give the securityManager very
> detailed control. Of course getSecurityManager must allways return
> an instance of a subclass of PntSecurityManager, if no specific
> security is required, it may be set to an instance of
> NoSecurityManager. That will allways return $property->getVisible().
My intended implementation would add the $requestedObject parameter to
PropertyDescriptor::getVisible(), which would pass it on to
SecurityManager::isPropertyVisible (unless setVisible has been
called). This means that overriding the security manager is always
possible.
If I understand you correctly, you would propose to keep the current
property visibility code completely intact. On top of that, you would
make all Page(Part)s call the SecurityManager directly, to hide
certain properties from certain users?
> This was quite a long story, i hope it makes sense to you. If
> you have built something in this style please consider to send
> me your code to incorporate into next version of phpPeanuts. (I
> can not promise i will use all of it, but at least it will
> motivate me to add this kind of function to phpPeanuts, if
> possible in a way compatible to yours).
This has been my intention from the outset.
> * Of course there is is a limitation to this design: only one
> User can be authorized to access a certain Person object. If
> you need several users to share the same access rights,
> consider to add UserGroups. An example of an instance of
> UserGroup may be 'Administrators'. To register which users are
> in which rights i suggest you also add a UserGroupMember class
> that defines properties 'user' (type: User), 'userId' (type:
> number), 'userGroup' (type: UserGroup) and 'userGroupId' (type:
> number). This will allow users to be member of any group and
> groups to have any user as member.
This should indeed be kept in mind, but should be fully implementable
by the applications' SecurityManager and User classes (supported by
the UserGroup class you suggest).
> Once you have the UserGroup and UserGroupMember classes, you
> add a property 'userGroupId' to your Person class. You will
> need a specific user interface (Page class) for Administrators
> that allows them to set this property, that is never shown by
> the normal user interfaces.
What exactly would this property do? I would say a UserGroup has
n-to-n with User, which has 1-to-1 with Person? Or are you sugesting
that User would represent a permission instead of a user and/or
person, so User has a n-to-n with UserGroup which has 1-to-n with
Person?
Gr.
Matthijs
henk
2006-10-10 10:37:49
Hi Mathijs,
Sorry, i did not realize that you would not get an e-mail. As the forum moderator i get e-mails about any move i make, so i thought it would inform you about edits on reactions to your messages too.
>This would mean it shouls inherit from PntDbObject, which
> is not really what I would consider elegant (nor flexible).
Only if you want it to be persistent. Php is not Java, nothing will stop you or other developers from implementing a class that inherits from a different superclass but implements the same protocol (=unformalized interface). With php5 you could formalize the interface if you like. Howerver, to be edited directly using the default user interface it will need to be some kind of PntObject anyway. OTOH, if you implement authentication against a phpbb forum database, i guess you will already have the phpbb user interface for editing user objects so you will not need phpPeanuts for that.
>The peanuts framework should know that User objects exist and that
>there is a SecurityManager that knows how to handle them
Maybe the framework does not need to know at all about User objects. For performing security tasks only the SecurityManager needs to know. Then if the security manager decides to use an implementation of User that uses the framework, for persistancy or so, i don't see a problem with that. Furtermore, if in some user interface of a specific site, User objects are edited using the default user interface, no problem either, any site can make its own choices on how to edit eventual User objects.
> I would not say this is really an elegant solution, since it has
> now become impossible for admin users to view all
> a person's properties at once.
You have a point here. But if it's just for showing the admin users of a specific application different properties i would try to avoid adapting the framework. For example you could have made a subclass of Person that inherits its persistency from person and derives the extra property values from User. Maybe not the most elegant solution, but making frameworks user interface dynamically adapt to user rights is quite a big modification to phpPeanuts, it will be much more work to implement than a simple application specific subclass. In your case, as you actually need property visibility on a per-user basis throughout the application, it's a different story. But you only told me in last post.
> If I understand you correctly, you would propose to keep
> the current property visibility code completely intact.
PropertyDescriptors typically describe all (logical) instances of the described class. From that point of view, making the value of getVisible depend on the current user is confusing. Maybe its no problem with single request object lifetime like php has, but to me the idea seems too messy, i guess my Smalltalk background plays a role here ;-)
>This has been my intention from the outset.
> I have this implemented now and it works well enough for my initial
version.
Great, i will take a look at it when it is finished. Of course i can not include any phpbb code in phppeanuts, nor include any calls to phpbb code (the GPL license makes that unattractive) but i guess that will not be an issue because you use the phpbb database tables directly. Like you suggest, for a phpPeanuts package i will have to add some simple User class that makes its functionality complete if one does not use phpbb.
Greetings,
Henk Verhoeven.
matthijs
2006-10-10 22:52:45
> OTOH, if you implement authentication against a phpbb forum database, i guess
> you will already have the phpbb user interface for editing user objects so
> you will not need phpPeanuts for that.
Well, since I only use phpbb for simple authentication, I still need to keep
information about my users. But, I have my Persons objects for that.
> Maybe the framework does not need to know at all about User
> objects. For performing security tasks only the SecurityManager
> needs to know.
Good point. I will probably use a User object, but we'll not put
in the framework.
> In your case, as you actually need property visibility on a
> per-user basis throughout the application, it's a different
> story. But you only told me in last post.
I just used a simple example to illustrate my needs, but choosing
a Person property as an example probably was not the wisest ;-)
But, I do really need visibility based on (property, user)
instead just based on (property). I would even like to have
visibility based on (property, user, object), but I'm not sure I
really need that. Beside visibility, this also goes for
writability of properties, of course.
> > If I understand you correctly, you would propose to keep
> > the current property visibility code completely intact.
>
> PropertyDescriptors typically describe all (logical) instances
> of the described class. From that point of view, making the
> value of getVisible depend on the current user is confusing.
> Maybe its no problem with single request object lifetime like
> php has, but to me the idea seems too messy, i guess my
> Smalltalk background plays a role here ;-)
I can see your point here, yes. Though the use of the concept of
"current user" is exactly what makes the "visibility based on
(property, user)" easily implementable, while the "visibility
based on (property, user, object)" case is hard to implement.
For the latter case, knowledge about the object examined should
propagate all the way down to the PropertyDescriptors used, which
makes it complicated.
You make a good point, since information about the "current user"
should not be available so deep inside the (meta) model (in the
PropertyDescriptor). From a design point of view, it would be
better to make the getVisibility method take a user argument
instead. But, this will make the (property, user) case just as
complex as the (property, user, object) case (which might not be
bad, though).
You make a point about putting this functionality in the
SecurityManager instead (since we do not want to burder the
PropertyDescriptor with knowledge about the concept "User").
Initially I was reluctant to put this functionality in such a
high level class, since I want to put the authorisation at the
lowest possible level, to be absolutetly sure that no authorized
access was possible.
Looking at it again, putting it up high (in SecurityManager)
seems not so bad at all. I am wondering where exactly the current
getVisible (and getReadOnly) will fit in all this. One could
argue that they should disappear alltogether and take care of
visibility and writability only in SecurityManager. This poses
two problems AFAICS: Backward compatibility and it forces users
to always use the SecurityManager, even for simple rules.
So, keeping the old methods around seems like a good idea. The
(default) SecurityManager could probably just call these methods.
Other SecurityManagers could use these methods as overrides (ie,
allow visibility when it is allowed by normal rules _and_
getVisible() returns true), but that's up to the application
developers to decide.
This leaves the issue of the current users of getVisible() and
getReadOnly() (such as getUiPropertyDescriptors()). Should they
still use getVisible, or use the SecurityManager instead? Since
they mostly do not have enough information to do so, using
getVisible() will probably be the only option. Yet this does not
have to be a problem, as long as the list of visible/readable
items is filtered by the SecurityManager on a higher level. This
approach actually has the advantage that the SecurityManager has
the last "veto", so invisible properties can not become
accidentally visible by wrongly overriding
getUiPropertyDescriptors(), for example.
So, the theory sounds nice. The SecurityManager is consulted for
visibility and writability on a high level, giving it good
control. On a lower level, overrides or default values (depending
on the SecurityManager used) for visibility and writability can
be set.
Now only see if this will work in practice. I'm not sure if all
the places that should consult the SecurityManager have enough
context to actually do so, so there might be some rewriting of
control flow or propagation of information needed.
> Of course i can not include any phpbb code in phppeanuts, nor
> include any calls to phpbb code (the GPL license makes that
> unattractive) but i guess that will not be an issue because you
> use the phpbb database tables directly.
Indeed, the only phpBB specific code is a few lines of mysql
querying.
Gr.
Matthijs
Henk Verhoeven
2007-09-09 22:12:28
Hi Matthijs,
As you may have noticed 1.4.beta1 includes support for authorization, and a plugin is available for authentication. It is based on the ideas we discussed here. The security manager supports control down to the propery level, only the page and action classes do not yet implement that, so that currently only authorization per application and type are in effect. My intention is to add support for property level authorization in a later version.
The above discussion has been very usefull. Thank you very much for the ideas and inspiration!
Greetings,
Henk Verhoeven.
Add a Reply
Loading form, please wait
The website will not send you an e-mail when a reply is added to this topic
I'm currently working on an application that requires authorisation to work properly. I've implemented authentication using the examples, while adding some stuff to them.
Now, I have to do proper authorization. As suggested, I can limit what can be accessed by what users in Site::forwardRequest(). But, this will only work on a Type/Handler level.
What I want to do, is distinguish admin and normal users (perhaps more). Admins should be able to do just about anything, normal users should be able to edit their own data. I've accomplished this by limiting normal users to pntType=Person and id=their own id.
Furthermore, there are a number of properties on a Person that should not be modifiable by a normal user (such as the Person's permissions and its accountname). I could just mark these as read-only or hidden, but that will mean even admins cannot edit the properties. So, my second try was to make them readonly or hidden in Person::initPropertyDescriptors() depending on whether an admin was logged in, but that didn't really work out, because the authorization system uses the Person class. But, we can't use the Person class yet, since it is not initialized yet... Any thoughts?
Also, I was thinkint it might be good to have some authorisation framework inside peanuts? For example, introduce some abstract authorisation manager that will decide what is allowed an what is not, based on (arbitrary) permission objects in the various classes/peanuts? Has any thought been given to this already?
Matthijs
I think that for proper authorization, we can split everything in two. First, a low level layer that says "This property is read only" or "A new object of this type can be created". On top of that, there is a higher level layer, that can express things like "Admins can edit Persons, but normal users can edit only themselves".
The lower level is already mostly implemented in peanuts, through the readonly and visible properties of the propertydescriptors. The higher level would not be implemented by peanuts, only supported with proper callbacks and properties. More on that further on.
So, the lower level will consist of the following properties:
PropertyDescriptor::readOnly
PropertyDescriptor::visible
ClassDescriptor::creatable
PntObject::Deletable
Using these, one should be able to express most authorization schemes. More
complicated schemes can probably be expressed to, through properly overriding
some methods.
If all code in the peanuts framework respects these properties, everything
should be allright (just as is the case with the first two properties now).
Now, the higher level should be implemented by the application itself. It
should define some way to specify permissions and translate them to the above
properties. To support this, we introduce the concept of a SecurityManager. It
will perform the translation part. Also, we will give PropertyDescriptors,
ClassDescriptors and PntObjects a new property called "ACL" (for access
control list). This property should be set by the application and read by the
SecurityManager. Peanuts does not care about what is in it (or what type the
property has, even).
The last thing Peanuts will do, is call the SecurityManager at the proper
times. For example, when the visibility of a PropertyDescriptor is queried,
the SecurityManager should be consulted about what to return (or a default is
used if no SecurityManager is available). To prevent the SecurityManager from
getting called every time getVisible() is called, we cache the result in the
visible property (where currently visibility is already stored). This has the
added effect that, if we explicitely set the visibility of a
PropertyDescriptor, the SecurityManager will not get consulted (serving as a
manual override as well as backwards compatibility).
All this about visibility applies to the other four low level properties as
well. So, we get a SecurityManager that needs the following methods:
isCreatable ( ClassDescriptor )
isDeleteable ( PntObject )
isVisible ( PntPropertyDescriptor, PntObject )
isWritable ( PntPropertyDescriptor, PntObject )
I think this SecurityManager should be available throught the Site object, so
we add a getSecurityManager() method to PntSite (returning a default
SecurityManager by default, which should be overrided in the application's
Site class).
Any comments on how this might work out? I'll try to implement some of this,
to see how it works and if it solves my problems soon.
Post Edited (09-17-06 15:08)
Firstly, there should also be a property PntObject::visible, which can be used to hide objects alltogether. This requires a method SecurityManager::isObjectVisible. To keep consistency, the SecurityManager methods should be:
isClassCreatable
isObjectDeletable
isObjectVisible
isPropertyVisible
isPropertyWritable
Secondly, calling the isPropertyVisible methods with a PntObject argument is non trivial, since visibility is defined for each property, not for each property/object combination. Without this, however, one cannot express permissions such as "Normal users may view all person's name, but only edit their own".
A workaround that springs to mind just now, is to add a readonly property to objects, that overrides the readonly property of all the object's properties. In the example above, this would mean that the 'name' property is writable to everybody, but a all person's readonly properties are false, except for the person object that is currently logged in.
Still, this will not solve the problem for more complicated combinations of permissions, such as: "Everybody can edit all Person's property A, but only edit their own property B" (although I have not found a real example of where this kind of permission would be required, I'm pretty positive that it is useful).
To also be able to express this last type of permission, we need to assign the
properties of PropertyDescriptors to combinations of PropertyDescriptor and
PntObject instead. A simple way to do this, is to make
PropertyDescriptor::visible a assoc array, indexed by the PntObjects' ID. This
still allows for manual overriding and backwards compatibility, since you can
just store a boolean instead of an assoc array then. Downside to this is that
it might be a little to complicated.
A better approach might be to store these properties in the PntObject, using
an assoc array indexed by property name, since this is a little more
intuitive.
Whatever the case, the PntPropertyDescriptor::getVisible method should get an
extra PntObject parameter (not sure what the full implications of this
are...).
So, I'm off exploring the code a little further...
a PropertyDescriptor property, getVisible needs an extra PntObject argument.
isVisible is called by PntObjectReportPage::getMultiPropNames,
PntClassDescriptor::getUiPropertyDescriptors,
PntPage::addMultiValuePropertyButtons. The first two should just get an extra
PntObject parameter, the last one already has the PntObject to pass to
isVisible.
The getMultiPropNames is only called from one place, where the PntObject is
available. getUiPropertyDescriptors is called from
PntClassDescriptor::getUiColumnPaths, PntClassDescriptor::getUiFieldPaths and
PntObjectReportPage::getFormTextPaths, which all should get an extra PntObject
argument.
So, these methods are called from a number of places, which should also
require updating. Don't have time to continue this right now, I'll get back to
this. For now, it seems this change is non-trivial, but does not pose any
problems so far...
Sorry for the late reaction. Of course i have been thinking about an authorization framework in phpPeanuts, but i simply never needed it. PhpPeanuts development is in practice driven by my needs in commercial development. However, i suggest others to do the same (develop for your own needs, but share reusable code), so i appreciate your effort.
For a first authorization extension to phpPeanuts i would like to separate code that is part of the authorization system from both phpPeanuts as it is, as well as from domain classes of specific applications, like your 'Person'. In other words, i suggest you create a PntUser class whose instances will store information about the user, like name, password(hash), etc, as well as generic information about what the particular user may do, like the names of the classes he is authorized to access (in this case 'User').
Then create a subclass User for defining specific data you need for security in your own application. This could for example be the id of the Person object the particular user may access and edit). But a better idea may be to add a property userId to Person. Of course each Person object has to store the userId of the current user when the object is first saved. The simpelest way to allow it to find out about the current user is to mat that information accessable in $site. (I agree this is not nice architecture, but currently the user interface Controller and the application base are still close coupled in Site).
Then let the SecurityManager object decide which requests can be permitted and which not. This has the advantage that you may later extend your application with other classes whose instances may be accessed by certain users without having to change the User class. You may for example simply add another property userId to these new classes and store the id of the user when the object is first saved.
Of course only users with administrative rights are allowed to access User objects. In fact the administrators may not even have their own 'Person' object. Or maybe they may have multiple Person objects around for testing. Both are possible withing the above design*.
I guess this will eliminate your need for hiding/showing specific properties to specific users. For a first authorization subsystem that would be good enough too. But just for fun let's look into this anyhow. I guess making PropertyDescriptor::getVisible return $this->visible && $site->securityManager->isPropertyVisible($this) will do the trick, but only if all instances of Person are to be treated the same. A more powerfull and elegant solution would be to adapt the code of the various subclasses of PntPage and PntPagePart to call $this->getSecurityManager()->isPropertyVisible($property, $requestedObject, $this). This will give the securityManager very detailed control. Of course getSecurityManager must allways return an instance of a subclass of PntSecurityManager, if no specific security is required, it may be set to an instance of NoSecurityManager. That will allways return $property->getVisible().
This was quite a long story, i hope it makes sense to you. If you have built something in this style please consider to send me your code to incorporate into next version of phpPeanuts. (I can not promise i will use all of it, but at least it will motivate me to add this kind of function to phpPeanuts, if possible in a way compatible to yours).
Greetings,
Henk Verhoeven,
www.phpPeanuts.org.
* Of course there is is a limitation to this design: only one User can be authorized to access a certain Person object. If you need several users to share the same access rights, consider to add UserGroups. An example of an instance of UserGroup may be 'Administrators'. To register which users are in which rights i suggest you also add a UserGroupMember class that defines properties 'user' (type: User), 'userId' (type: number), 'userGroup' (type: UserGroup) and 'userGroupId' (type: number). This will allow users to be member of any group and groups to have any user as member.
Once you have the UserGroup and UserGroupMember classes, you add a property 'userGroupId' to your Person class. You will need a specific user interface (Page class) for Administrators that allows them to set this property, that is never shown by the normal user interfaces.
Post Edited (09-20-06 22:50)
No problem, I appreciate all of your efforts. I've not replied again
until now, because I only now found your reply. Since you edited your
post, I was not notified by email, nor did the post count increase, so
I've been in the illusion that you had not made your reply yet.
Anyway, I found it now :-)
I've already partly implemented something similar to what you
describe, only instead of using a (Pnt)User class, I'm exposing the
currently logged in Person object directly. I can see the merit of
putting a User class in between, so I will probably implement that. At
first thought, I would say (Pnt)User is a non-persistent class that
wraps a (persistent) domain model class (say Person). Yet, from the
rest of the post, I get the idea that you would make PntUser
persistent? This would mean it shouls inherit from PntDbObject, which
is not really what I would consider elegant (nor flexible).
> For a first authorization extension to phpPeanuts i would like
> to separate code that is part of the authorization system from
> both phpPeanuts as it is, as well as from domain classes of
> specific applications, like your 'Person'. In other words, i
> suggest you create a PntUser class whose instances will store
> information about the user, like name, password(hash), etc, as
> well as generic information about what the particular user may
> do, like the names of the classes he is authorized to access
> (in this case 'User').
I'm not sure this is such a good idea, though. In particular, I was
trying to completely separate the authoristion mechanism from peanuts.
The peanuts framework should know that User objects exist and that
there is a SecurityManager that knows how to handle them, but the
framework should not assume anything else. In my application, I would
be implementing authentication against a phpbb forum database, so
storing a password in the User object would not really make sense.
What might be a good idea to ship an example implementation SimpleUser
and SimpleSecurityManager that implement simple
authentication/authorization like you describe (though I will not
really look at this, since it does not suit my needs, I will keep the
option in mind).
> Then let the SecurityManager object decide which requests can
> be permitted and which not. This has the advantage that you may
> later extend your application with other classes whose
> instances may be accessed by certain users without having to
> change the User class. You may for example simply add another
> property userId to these new classes and store the id of the
> user when the object is first saved.
Currently, I have implemented request based authorization directly in
my Site class, but this should indeed go into the SecurityManager (and
be called from within PntSite I guess?).
> Of course only users with administrative rights are allowed to
> access User objects. In fact the administrators may not even
> have their own 'Person' object. Or maybe they may have multiple
> Person objects around for testing. Both are possible withing
> the above design*.
I don't really see how having multiple Person objects for one user
would be useful?
> I guess this will eliminate your need for hiding/showing
> specific properties to specific users. For a first
> authorization subsystem that would be good enough too.
It took me a while to fully understand what you were saying, but I
think it is really a workaround for what I want to achieve. What you
are suggesting, really, is separating my Person class into a Person
and a user Class (having a 1-on-1 relationship). The Person object
would contain the public, user-modifiable properties, such as name and
address. The user object would contain the administrative properties
such as accountName that would be hidden from ordinary users by
denying access to the User object alltogether.
I would not say this is really an elegant solution, since it has now
become impossible for admin users to view all a person's properties at
once.
Also, I will be implementing more objects that will need a similar
authorisation (users should be able to submit articles, which have a
comments field only visible to admin users, for example). In other
words, I do actually need property visibility on a per-user basis.
> But just for fun let's look into this anyhow. I guess making
> PropertyDescriptor::getVisible return $this->visible &&
> $site->securityManager->isPropertyVisible($this) will do the trick,
> but only if all instances of Person are to be treated
> the same.
I have this implemented now and it works well enough for my initial
version.
> A more powerfull and elegant solution would be to adapt the code of
> the various subclasses of PntPage and PntPagePart to call
> $this->getSecurityManager()->isPropertyVisible($property,
> $requestedObject, $this). This will give the securityManager very
> detailed control. Of course getSecurityManager must allways return
> an instance of a subclass of PntSecurityManager, if no specific
> security is required, it may be set to an instance of
> NoSecurityManager. That will allways return $property->getVisible().
My intended implementation would add the $requestedObject parameter to
PropertyDescriptor::getVisible(), which would pass it on to
SecurityManager::isPropertyVisible (unless setVisible has been
called). This means that overriding the security manager is always
possible.
If I understand you correctly, you would propose to keep the current
property visibility code completely intact. On top of that, you would
make all Page(Part)s call the SecurityManager directly, to hide
certain properties from certain users?
> This was quite a long story, i hope it makes sense to you. If
> you have built something in this style please consider to send
> me your code to incorporate into next version of phpPeanuts. (I
> can not promise i will use all of it, but at least it will
> motivate me to add this kind of function to phpPeanuts, if
> possible in a way compatible to yours).
This has been my intention from the outset.
> * Of course there is is a limitation to this design: only one
> User can be authorized to access a certain Person object. If
> you need several users to share the same access rights,
> consider to add UserGroups. An example of an instance of
> UserGroup may be 'Administrators'. To register which users are
> in which rights i suggest you also add a UserGroupMember class
> that defines properties 'user' (type: User), 'userId' (type:
> number), 'userGroup' (type: UserGroup) and 'userGroupId' (type:
> number). This will allow users to be member of any group and
> groups to have any user as member.
This should indeed be kept in mind, but should be fully implementable
by the applications' SecurityManager and User classes (supported by
the UserGroup class you suggest).
> Once you have the UserGroup and UserGroupMember classes, you
> add a property 'userGroupId' to your Person class. You will
> need a specific user interface (Page class) for Administrators
> that allows them to set this property, that is never shown by
> the normal user interfaces.
What exactly would this property do? I would say a UserGroup has
n-to-n with User, which has 1-to-1 with Person? Or are you sugesting
that User would represent a permission instead of a user and/or
person, so User has a n-to-n with UserGroup which has 1-to-n with
Person?
Gr.
Matthijs
Sorry, i did not realize that you would not get an e-mail. As the forum moderator i get e-mails about any move i make, so i thought it would inform you about edits on reactions to your messages too.
>This would mean it shouls inherit from PntDbObject, which
> is not really what I would consider elegant (nor flexible).
Only if you want it to be persistent. Php is not Java, nothing will stop you or other developers from implementing a class that inherits from a different superclass but implements the same protocol (=unformalized interface). With php5 you could formalize the interface if you like. Howerver, to be edited directly using the default user interface it will need to be some kind of PntObject anyway. OTOH, if you implement authentication against a phpbb forum database, i guess you will already have the phpbb user interface for editing user objects so you will not need phpPeanuts for that.
>The peanuts framework should know that User objects exist and that
>there is a SecurityManager that knows how to handle them
Maybe the framework does not need to know at all about User objects. For performing security tasks only the SecurityManager needs to know. Then if the security manager decides to use an implementation of User that uses the framework, for persistancy or so, i don't see a problem with that. Furtermore, if in some user interface of a specific site, User objects are edited using the default user interface, no problem either, any site can make its own choices on how to edit eventual User objects.
> I would not say this is really an elegant solution, since it has
> now become impossible for admin users to view all
> a person's properties at once.
You have a point here. But if it's just for showing the admin users of a specific application different properties i would try to avoid adapting the framework. For example you could have made a subclass of Person that inherits its persistency from person and derives the extra property values from User. Maybe not the most elegant solution, but making frameworks user interface dynamically adapt to user rights is quite a big modification to phpPeanuts, it will be much more work to implement than a simple application specific subclass. In your case, as you actually need property visibility on a per-user basis throughout the application, it's a different story. But you only told me in last post.
> If I understand you correctly, you would propose to keep
> the current property visibility code completely intact.
PropertyDescriptors typically describe all (logical) instances of the described class. From that point of view, making the value of getVisible depend on the current user is confusing. Maybe its no problem with single request object lifetime like php has, but to me the idea seems too messy, i guess my Smalltalk background plays a role here ;-)
>This has been my intention from the outset.
> I have this implemented now and it works well enough for my initial
version.
Great, i will take a look at it when it is finished. Of course i can not include any phpbb code in phppeanuts, nor include any calls to phpbb code (the GPL license makes that unattractive) but i guess that will not be an issue because you use the phpbb database tables directly. Like you suggest, for a phpPeanuts package i will have to add some simple User class that makes its functionality complete if one does not use phpbb.
Greetings,
Henk Verhoeven.
> you will already have the phpbb user interface for editing user objects so
> you will not need phpPeanuts for that.
Well, since I only use phpbb for simple authentication, I still need to keep
information about my users. But, I have my Persons objects for that.
> Maybe the framework does not need to know at all about User
> objects. For performing security tasks only the SecurityManager
> needs to know.
Good point. I will probably use a User object, but we'll not put
in the framework.
> In your case, as you actually need property visibility on a
> per-user basis throughout the application, it's a different
> story. But you only told me in last post.
I just used a simple example to illustrate my needs, but choosing
a Person property as an example probably was not the wisest ;-)
But, I do really need visibility based on (property, user)
instead just based on (property). I would even like to have
visibility based on (property, user, object), but I'm not sure I
really need that. Beside visibility, this also goes for
writability of properties, of course.
> > If I understand you correctly, you would propose to keep
> > the current property visibility code completely intact.
>
> PropertyDescriptors typically describe all (logical) instances
> of the described class. From that point of view, making the
> value of getVisible depend on the current user is confusing.
> Maybe its no problem with single request object lifetime like
> php has, but to me the idea seems too messy, i guess my
> Smalltalk background plays a role here ;-)
I can see your point here, yes. Though the use of the concept of
"current user" is exactly what makes the "visibility based on
(property, user)" easily implementable, while the "visibility
based on (property, user, object)" case is hard to implement.
For the latter case, knowledge about the object examined should
propagate all the way down to the PropertyDescriptors used, which
makes it complicated.
You make a good point, since information about the "current user"
should not be available so deep inside the (meta) model (in the
PropertyDescriptor). From a design point of view, it would be
better to make the getVisibility method take a user argument
instead. But, this will make the (property, user) case just as
complex as the (property, user, object) case (which might not be
bad, though).
You make a point about putting this functionality in the
SecurityManager instead (since we do not want to burder the
PropertyDescriptor with knowledge about the concept "User").
Initially I was reluctant to put this functionality in such a
high level class, since I want to put the authorisation at the
lowest possible level, to be absolutetly sure that no authorized
access was possible.
Looking at it again, putting it up high (in SecurityManager)
seems not so bad at all. I am wondering where exactly the current
getVisible (and getReadOnly) will fit in all this. One could
argue that they should disappear alltogether and take care of
visibility and writability only in SecurityManager. This poses
two problems AFAICS: Backward compatibility and it forces users
to always use the SecurityManager, even for simple rules.
So, keeping the old methods around seems like a good idea. The
(default) SecurityManager could probably just call these methods.
Other SecurityManagers could use these methods as overrides (ie,
allow visibility when it is allowed by normal rules _and_
getVisible() returns true), but that's up to the application
developers to decide.
This leaves the issue of the current users of getVisible() and
getReadOnly() (such as getUiPropertyDescriptors()). Should they
still use getVisible, or use the SecurityManager instead? Since
they mostly do not have enough information to do so, using
getVisible() will probably be the only option. Yet this does not
have to be a problem, as long as the list of visible/readable
items is filtered by the SecurityManager on a higher level. This
approach actually has the advantage that the SecurityManager has
the last "veto", so invisible properties can not become
accidentally visible by wrongly overriding
getUiPropertyDescriptors(), for example.
So, the theory sounds nice. The SecurityManager is consulted for
visibility and writability on a high level, giving it good
control. On a lower level, overrides or default values (depending
on the SecurityManager used) for visibility and writability can
be set.
Now only see if this will work in practice. I'm not sure if all
the places that should consult the SecurityManager have enough
context to actually do so, so there might be some rewriting of
control flow or propagation of information needed.
> Of course i can not include any phpbb code in phppeanuts, nor
> include any calls to phpbb code (the GPL license makes that
> unattractive) but i guess that will not be an issue because you
> use the phpbb database tables directly.
Indeed, the only phpBB specific code is a few lines of mysql
querying.
Gr.
Matthijs
As you may have noticed 1.4.beta1 includes support for authorization, and a plugin is available for authentication. It is based on the ideas we discussed here. The security manager supports control down to the propery level, only the page and action classes do not yet implement that, so that currently only authorization per application and type are in effect. My intention is to add support for property level authorization in a later version.
The above discussion has been very usefull. Thank you very much for the ideas and inspiration!
Greetings,
Henk Verhoeven.