I have an app with relatively complex security/permissions requirements and I decided to build it modeled after SELinux.
The default Pyramid authz policy revolves around checking if a user belongs to a group, or has certain principal, the check of which is facilitated per view (with `permission` argument).
In my app I have several roles and each role can only access some resources, and only perform some actions upon those resources (read, write, delete, ...), and the permission is not static per view, but depends on data retrieved from database according to some identifier given through matchdict.
For example, "regular" users can create new and edit only own content of class X, Y, Z. Users belonging to a "editor" role can view other people's X, Y, Z but can't modify them except some properties thereof (like active/inactive). Perhaps editors can't write their own content, so they would only have `read` permission. Users belonging to a "billing" role can only view billing data of users, create invoices, activate/deactivate profiles, but not view or change their content, etc....
I decided to make a class which I reify as request.permits property (via config.set_request_property()), then do a following check:
resource = DBSession().query(Resource).get(resource_id)
if resource and not request.permit(resource, read=True):
raise Forbidden()
if not resource:
raise NotFound()
...
The permit() method is a __call__ method of the policy class. It does several things:
1. Checks the type of first parameter (the "tcontext" in SELinux lingo)
2. According to its type (isinstance), delegates check to a private method responsible for that resource class (content classes, menu links, ...)
3. The private method does whatever check required to see if the user (authenticated in the system with some principal like ID, the "scontext" in SELinux lingo) has access to the resource for given action (read, write, delete)
Example checks (which would be policy rules in SELinux):
# request.userauth is also an object created with
config.set_request_property()
if resource.owner_id == request.userauth.user_id and (read or
write):
return True
if "required_role" in request.userauth.roles and (read and not
write):
return True
etc... The default, of course, being no permission at all. It can be more DRY if the `permits` callable threw Forbidden() automatically, but I need it in the templates to return True or False which defines parts to be rendered. Alternatively I can supply a `nothrow=True` kwarg to return False instead of raising Forbidden().
This way I maintain the policy in single file, the policy can be "pluggable" if required (instantiate different policy depending on configuration) and it can even be compounded with the view_config's permission argument, and the custom authorization policy factory which implements the proper interface.
Too complicated? Overkill? Anyone doing something similar?
--
.oO V Oo.
Work Hard,
Increase Production,
Prevent Accidents,
and
Be Happy! ;)
I guess I'm doing something similar. At least as far as having
resource specific permissions. But I've chosen to implement this using
context factories. So the
resource_id = int(request.matchdict["resource_id"])
resource = DBSession().query(Resource).get(resource_id)
if not resource:
raise NotFound()
code which you have in the view is in my case part of the context
factory. Additionally, the context factory attaches a trivial __acl__
attribute to the context (allowing or denying the current user) to
make the standard authorization policy work.
On Thu, Aug 30, 2012 at 4:47 PM, Vlad K. <v...@haronmedia.com> wrote:
> Hello all,
> I have an app with relatively complex security/permissions requirements and
> I decided to build it modeled after SELinux.
> The default Pyramid authz policy revolves around checking if a user belongs
> to a group, or has certain principal, the check of which is facilitated per
> view (with `permission` argument).
> In my app I have several roles and each role can only access some resources,
> and only perform some actions upon those resources (read, write, delete,
> ...), and the permission is not static per view, but depends on data
> retrieved from database according to some identifier given through
> matchdict.
> For example, "regular" users can create new and edit only own content of
> class X, Y, Z. Users belonging to a "editor" role can view other people's X,
> Y, Z but can't modify them except some properties thereof (like
> active/inactive). Perhaps editors can't write their own content, so they
> would only have `read` permission. Users belonging to a "billing" role can
> only view billing data of users, create invoices, activate/deactivate
> profiles, but not view or change their content, etc....
> I decided to make a class which I reify as request.permits property (via
> config.set_request_property()), then do a following check:
> resource = DBSession().query(Resource).get(resource_id)
> if resource and not request.permit(resource, read=True):
> raise Forbidden()
> if not resource:
> raise NotFound()
> ...
> The permit() method is a __call__ method of the policy class. It does
> several things:
> 1. Checks the type of first parameter (the "tcontext" in SELinux lingo)
> 2. According to its type (isinstance), delegates check to a private method
> responsible for that resource class (content classes, menu links, ...)
> 3. The private method does whatever check required to see if the user
> (authenticated in the system with some principal like ID, the "scontext" in
> SELinux lingo) has access to the resource for given action (read, write,
> delete)
> Example checks (which would be policy rules in SELinux):
> # request.userauth is also an object created with
> config.set_request_property()
> if resource.owner_id == request.userauth.user_id and (read or write):
> return True
> if "required_role" in request.userauth.roles and (read and not write):
> return True
> etc... The default, of course, being no permission at all. It can be more
> DRY if the `permits` callable threw Forbidden() automatically, but I need it
> in the templates to return True or False which defines parts to be rendered.
> Alternatively I can supply a `nothrow=True` kwarg to return False instead of
> raising Forbidden().
> This way I maintain the policy in single file, the policy can be "pluggable"
> if required (instantiate different policy depending on configuration) and it
> can even be compounded with the view_config's permission argument, and the
> custom authorization policy factory which implements the proper interface.
> Too complicated? Overkill? Anyone doing something similar?
> --
> .oO V Oo.
> Work Hard,
> Increase Production,
> Prevent Accidents,
> and
> Be Happy! ;)
> --
> You received this message because you are subscribed to the Google Groups
> "pylons-discuss" group.
> To post to this group, send email to pylons-discuss@googlegroups.com.
> To unsubscribe from this group, send email to
> pylons-discuss+unsubscribe@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/pylons-discuss?hl=en.
On Fri, Aug 31, 2012 at 11:37 AM, Robert Forkel <xrotw...@googlemail.com> wrote:
> I guess I'm doing something similar. At least as far as having
> resource specific permissions. But I've chosen to implement this using
> context factories. So the
> resource_id = int(request.matchdict["resource_id"])
> resource = DBSession().query(Resource).get(resource_id)
> if not resource:
> raise NotFound()
> code which you have in the view is in my case part of the context
> factory. Additionally, the context factory attaches a trivial __acl__
> attribute to the context (allowing or denying the current user) to
> make the standard authorization policy work.
> On Thu, Aug 30, 2012 at 4:47 PM, Vlad K. <v...@haronmedia.com> wrote:
>> Hello all,
>> I have an app with relatively complex security/permissions requirements and
>> I decided to build it modeled after SELinux.
>> The default Pyramid authz policy revolves around checking if a user belongs
>> to a group, or has certain principal, the check of which is facilitated per
>> view (with `permission` argument).
>> In my app I have several roles and each role can only access some resources,
>> and only perform some actions upon those resources (read, write, delete,
>> ...), and the permission is not static per view, but depends on data
>> retrieved from database according to some identifier given through
>> matchdict.
>> For example, "regular" users can create new and edit only own content of
>> class X, Y, Z. Users belonging to a "editor" role can view other people's X,
>> Y, Z but can't modify them except some properties thereof (like
>> active/inactive). Perhaps editors can't write their own content, so they
>> would only have `read` permission. Users belonging to a "billing" role can
>> only view billing data of users, create invoices, activate/deactivate
>> profiles, but not view or change their content, etc....
>> I decided to make a class which I reify as request.permits property (via
>> config.set_request_property()), then do a following check:
>> resource = DBSession().query(Resource).get(resource_id)
>> if resource and not request.permit(resource, read=True):
>> raise Forbidden()
>> if not resource:
>> raise NotFound()
>> ...
>> The permit() method is a __call__ method of the policy class. It does
>> several things:
>> 1. Checks the type of first parameter (the "tcontext" in SELinux lingo)
>> 2. According to its type (isinstance), delegates check to a private method
>> responsible for that resource class (content classes, menu links, ...)
>> 3. The private method does whatever check required to see if the user
>> (authenticated in the system with some principal like ID, the "scontext" in
>> SELinux lingo) has access to the resource for given action (read, write,
>> delete)
>> Example checks (which would be policy rules in SELinux):
>> # request.userauth is also an object created with
>> config.set_request_property()
>> if resource.owner_id == request.userauth.user_id and (read or write):
>> return True
>> if "required_role" in request.userauth.roles and (read and not write):
>> return True
>> etc... The default, of course, being no permission at all. It can be more
>> DRY if the `permits` callable threw Forbidden() automatically, but I need it
>> in the templates to return True or False which defines parts to be rendered.
>> Alternatively I can supply a `nothrow=True` kwarg to return False instead of
>> raising Forbidden().
>> This way I maintain the policy in single file, the policy can be "pluggable"
>> if required (instantiate different policy depending on configuration) and it
>> can even be compounded with the view_config's permission argument, and the
>> custom authorization policy factory which implements the proper interface.
>> Too complicated? Overkill? Anyone doing something similar?
>> --
>> .oO V Oo.
>> Work Hard,
>> Increase Production,
>> Prevent Accidents,
>> and
>> Be Happy! ;)
>> --
>> You received this message because you are subscribed to the Google Groups
>> "pylons-discuss" group.
>> To post to this group, send email to pylons-discuss@googlegroups.com.
>> To unsubscribe from this group, send email to
>> pylons-discuss+unsubscribe@googlegroups.com.
>> For more options, visit this group at
>> http://groups.google.com/group/pylons-discuss?hl=en.
> I guess I'm doing something similar. At least as far as having
> resource specific permissions. But I've chosen to implement this using
> context factories.
Thanks for your reply.
That would work except it is limited to checks at routing / view mapping. The approach I'm having is a completely separate policy class that can be queried by any part of the code, specifically:
- view config to get general view permission
- view handlers to get permission based on loaded resource (this would also work well with traversal)
- templates to know which parts to render (menu options, content boxes, etc...)
- other parts of the code
with the policy neatly defined in single file / class. So when you query the policy, you basically ask it to:
1. verify action named X (eg. "article.edit")
2. with source context Y (eg. user profile ID and/or user's role(s) )
3. with target context Z (eg. SQLA article model)
--
.oO V Oo.
Work Hard,
Increase Production,
Prevent Accidents,
and
Be Happy! ;)