permission per object

64 views
Skip to first unread message

Antonio Beamud Montero

unread,
Feb 16, 2012, 10:12:25 AM2/16/12
to pylons-...@googlegroups.com
Hi, what's the best way to implement a permission per object in pyramid
using SQLAlchemy to store users info, and using url distpatch.
For example, If a user has three tasks assigned, he can edit two of
them, but only view the 3rd, or view tasks of other users (if they
want), but not edit them.

Greetings.

Robert Forkel

unread,
Feb 16, 2012, 10:44:48 AM2/16/12
to pylons-...@googlegroups.com
i went with a custom context factory [1] per route; i.e. i have routes
for patterns like "/task/{id}/edit" or "/task/{id}/view" and register
context factories for these which retrieve the task object from the db
and compute the permissions, i.e. attach an appropriate __acl__ object
to the task object.
regards
robert

[1] http://docs.pylonsproject.org/projects/pyramid/en/1.2-branch/narr/urldispatch.html#route-factories

> --
> You received this message because you are subscribed to the Google Groups
> "pylons-discuss" group.
> To post to this group, send email to pylons-...@googlegroups.com.
> To unsubscribe from this group, send email to
> pylons-discus...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/pylons-discuss?hl=en.
>

Antonio Beamud Montero

unread,
Feb 16, 2012, 11:36:38 AM2/16/12
to pylons-...@googlegroups.com
El 16/02/12 16:44, Robert Forkel escribi�:

> i went with a custom context factory [1] per route; i.e. i have routes
> for patterns like "/task/{id}/edit" or "/task/{id}/view" and register
> context factories for these which retrieve the task object from the db
> and compute the permissions, i.e. attach an appropriate __acl__ object
> to the task object.
> regards
> robert
>
> [1] http://docs.pylonsproject.org/projects/pyramid/en/1.2-branch/narr/urldispatch.html#route-factories
>

Ok, but if you want to show the list of tasks in the index user page,
how do you check the tasks permission, for example to show a icon near
the task description, in case you can edit that task?

A lot of thanks.

PD: My conclusion is I need to created a role or group attribute in the
user-task relation, to see if the user has the correct role/group
assigned to this object...

Robert Forkel

unread,
Feb 16, 2012, 11:45:54 AM2/16/12
to pylons-...@googlegroups.com

My context factories can be used in a hybrid way. As regular context factories they retrieve the object according to matchdict. But they can be passed the object as well and then just compute the permission. So in the Index i'd loop over all possible tasks and ask the factories for each route whether the User gas this permission.

Am 16.02.2012 17:36 schrieb "Antonio Beamud Montero" <antonio...@gmail.com>:

El 16/02/12 16:44, Robert Forkel escribió:


i went with a custom context factory [1] per route; i.e. i have routes
for patterns like "/task/{id}/edit" or "/task/{id}/view" and register
context factories for these which retrieve the task object from the db
and compute the permissions, i.e. attach an appropriate __acl__ object
to the task object.
regards
robert

[1] http://docs.pylonsproject.org/projects/pyramid/en/1.2-branch/narr/urldispatch.html#route-factories


Ok, but if you want to show the list of tasks in the index user page, how do you check the tasks permission, for example to show a icon near the task description, in case you can edit that task?

A lot of thanks.

PD: My conclusion is I need to created a role or group attribute in the user-task relation, to see if the user has the correct role/group assigned to this object...


On Thu, Feb 16, 2012 at 4:12 PM, Antonio Beamud Montero
<antonio...@gmail.com>  wrote:

Hi, what's the best way to implement a permission per object in pyramid
using SQLAlchemy to store users info, and using url distpatch.
For example, If a user has three tasks assigned, he can edit two of them,
but only view the 3rd, or view tasks of other users (if they want), but not
edit them.

Greetings.

--
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.

--
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.

Mike Orr

unread,
Feb 16, 2012, 2:44:47 PM2/16/12
to pylons-...@googlegroups.com
On Thu, Feb 16, 2012 at 8:45 AM, Robert Forkel <xrot...@googlemail.com> wrote:
> My context factories can be used in a hybrid way. As regular context
> factories they retrieve the object according to matchdict. But they can be
> passed the object as well and then just compute the permission. So in the
> Index i'd loop over all possible tasks and ask the factories for each route
> whether the User gas this permission.

How do you do this?

--
Mike Orr <slugg...@gmail.com>

Robert Forkel

unread,
Feb 17, 2012, 4:33:53 AM2/17/12
to pylons-...@googlegroups.com
It works as follows: I register context factories per route (as named
utility in the registry) and also use the route names as permission
names. The only thing the context factories need to compute
permissions is the logged-in user which is attached to the request and
the context object which they will either retrieve from the db
according to the request's matchdict or lookup as attribute on the
request. So to compute whether a user has permission to edit task 1,
I'd retrieve task 1 from the db, lookup the context factory for
'task.edit', attach the task object to the current request (possibly
to route task.index) and check the permission for this artificial
request.
The code I use looks similar to below (i've stripped out further
complicating details). The concrete authorization context for
'task.edit' would subclass AuthzContext, setting __object_class__ to
Task, and actually implement the allow_if method.

class AuthzContext(object):

implements(IAuthzContext)
__object_class__ = None

def __init__(self, request):
self.request = request
self.object = None

# make sure the reified attribute 'user' is instantiated
self.user = request.user

#
# while it is the main responsibility of a AuthzContext to determine
# the correct context object and attach ACEs to it, there's a shortcut
# which allows passing in the context object as attribute of the
# request. this mechanism can be used to compute permissions
"in advance"
# i.e. without actually preforming a request. this allows us to keep
# view permissions and links in sync.
#
if getattr(request, 'authz_object', False):
self.object = request.authz_object
else:
self.object =
self.__object_class__.get(int(request.matchdict['id']), default=None)

if not self.object:
raise HTTPNotFound()

self.object.__acl__ = []#[(Allow, 'group:admin', ALL_PERMISSIONS)]

#
# call the convenience methods which may be used to set ACEs:
#
if self.allow_if():
self.allow()
else:
self.deny()

def allow_if(self):
return False

def allow(self, permission=None):
if self.request.user:
self.object.__acl__ = [(Allow, self.request.user.login,
permission or getattr(self, 'name', 'none'))]
else:
# no user logged in but we still have to allow access:
self.object.__acl__ = [(Allow, Everyone, permission or
getattr(self, 'name', 'none'))]

def deny(self, permission=None):
if self.request.user:
self.object.__acl__ = [(Deny, self.request.user.login,
permission or getattr(self, 'name', ALL_PERMISSIONS))]
else:
self.object.__acl__ = [(Deny, Everyone, permission or
getattr(self, 'name', 'none'))]

def context_factory(authz_context):
"""We must make sure a new AuthzContext instance is created for
each request.
Since __init__ must not return anything, we must use a helper function to
mediate.
"""
def context(request):
_authz_context = authz_context(request)
return _authz_context.object
return context


def has_permission_on_object(permission, request, object_):
"""Compute object-specific permissions ahead of time, i.e. without
the object
being determined from the request's properties. We still use the context
factory to assign the same __acl__ to the object as would happen
with a "real"
request. Thus, use this function to check whether a user (detemined by
request) will have access to a specific action (determined by permission) on
an object_.
"""
context = request.registry.queryUtility(IAuthzContext,
name=permission, default=None)
if context:
request.authz_object = object_
res = has_permission(permission,
context_factory(context)(request), request)
request.authz_object = None
else:
res = has_permission(permission, RootFactory, request)
return res

> --
> You received this message because you are subscribed to the Google Groups "pylons-discuss" group.

> To post to this group, send email to pylons-...@googlegroups.com.
> To unsubscribe from this group, send email to pylons-discus...@googlegroups.com.

Antonio Beamud Montero

unread,
Feb 17, 2012, 7:52:19 AM2/17/12
to pylons-...@googlegroups.com
El 17/02/12 10:33, Robert Forkel escribi�:

> It works as follows: I register context factories per route (as named
> utility in the registry) and also use the route names as permission
> names. The only thing the context factories need to compute
> permissions is the logged-in user which is attached to the request and
> the context object which they will either retrieve from the db
> according to the request's matchdict or lookup as attribute on the
> request. So to compute whether a user has permission to edit task 1,
> I'd retrieve task 1 from the db, lookup the context factory for
> 'task.edit', attach the task object to the current request (possibly
> to route task.index) and check the permission for this artificial
> request.
I'm not tested it, but is not simpler something like this?
User and Task and a relation between them with de permission available
for this user.

class Task(Base):
...
users = orm.relationship(UserTask,
backref='tasks')

def __acl__(self):
acl = []
for o in self.users:
acl.append((Allow, o.user.username, o.permission))
return acl

class UserTask(Base):
__tablename__ = 'user_task'
task_id = Column(Integer, ForeignKey(Task.id), primary_key=True)
user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
user = user = orm.relationship("User", backref="tasks")
permission = Column(Unicode(80))

And to show in my index page, all my tasks, with an edit link (if
apply), we can check the permission with:

d = {}
q = DBSession.query(Task).join('user', 'users')
res = q.filter(and_(User.username = request.user.username)
for t in res:
d[t.id] = None
if has_permission('edit', t, request):
d[t.id] = 'edit'
return d

I'll test this weekend... any drawbacks?

To manage url's like /tasks/1/edit I need a Factory, of course.
Greetings.

Robert Forkel

unread,
Feb 17, 2012, 9:18:06 AM2/17/12
to pylons-...@googlegroups.com
in my system the computation of the permissions is influenced by many
variables, like status of an object, whether a user has delegated her
responsibilities to another user, etc. So i really have to compute the
permissions dynamically instead of updating them in the db whenever
one of these variables changes.

On Fri, Feb 17, 2012 at 1:52 PM, Antonio Beamud Montero
<antonio...@gmail.com> wrote:
> El 17/02/12 10:33, Robert Forkel escribió:

Brian Brinegar

unread,
Feb 17, 2012, 11:31:38 AM2/17/12
to pylons-...@googlegroups.com
Here's an approach that's worked for me.

First, I implement a root factory with a group_finder method.
Something like this:

class RootFactory(object):
__acl__ = [
(Allow, Everyone, 'view'),
]

def group_finder(self, userid):
return []

This is registered with the configuration as the default factory. I
specify a callback for my authentication policy that delegates the
group_finder to the current context.

def group_finder(userid, request):
return request.context.group_finder(user_id)

...

authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=group_finder)

By default, this calls the RootFactory group_finder which assigns no
additional roles roles/groups.

For specific routes I add factories to load the appropriate task
instance. Since the group_finder is context aware it calls the
group_finder method of the task object.

My ACLs are defined at class level in the model

__acl__ = [
( Allow, 'group:owner', 'edit')
]

def group_finder(self, user_id):
if user_id == self.owner_id:
return ['group:owner']
else:
return []

You can check the groups of a user by calling the group_finder method
of an instance.

Writing this, it might make sense to have a mix-in class which
implements a default group_finder and has_group method which you could
throw into your models.

This approach has worked for me, but I doubt it's ideal. I'd love to
see more detail on instance level authorization added to the
documentation.

Brian

Graham Higgins

unread,
Feb 17, 2012, 11:55:39 AM2/17/12
to pylons-...@googlegroups.com
On Friday, 17 February 2012 16:31:38 UTC, Brian wrote:

I'd love to
see more detail on instance level authorization added to the
documentation.

Noted.

Cheers,

Graham.

Antonio Beamud Montero

unread,
Mar 8, 2012, 12:13:08 PM3/8/12
to pylons-...@googlegroups.com
Am 16.02.2012 17:36 schrieb "Antonio Beamud Montero" <antonio...@gmail.com>:
El 16/02/12 16:44, Robert Forkel escribió:
i went with a custom context factory [1] per route; i.e. i have routes
for patterns like "/task/{id}/edit" or "/task/{id}/view" and register
context factories for these which retrieve the task object from the db
and compute the permissions, i.e. attach an appropriate __acl__ object
to the task object.
regards
robert

I'm back again.. Now, with a permission-per-object implementation working :). The problem is not efficient, for example, in the case of:

task/{id}/edit

A TaskFactory, check all the tasks permissions or parent tasks with permissions to check if that user can edit this task (this can be very complex and heavy, if you have a big tree). This check is only to return the __acl__ dinamically with permission for this user.
Then, in the edit view, we need to get again all the tasks to show a select in the edit template...
Two times the same heavy queries.
Can I check the permissions only in the view? Or reuse the info I get in the TaskFactory? Any drawbacks?

Greetings.
Reply all
Reply to author
Forward
0 new messages