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:
@view_config(route_name="some_route", ...):
def some_view(request):
resource_id = int(request.matchdict["resource_id"])
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! ;)