How to update principals of AuthTktAuthenticationPolicy?

36 views
Skip to first unread message

Aleksander Philips

unread,
Aug 13, 2016, 3:53:09 PM8/13/16
to pylons-discuss
Hello,

My project has included some simple authentication with route factory:
# security.py
def effective_principals(self, request):
        principals = [Everyone]
        user
= request.user
       
if user is not None:
            principals
.append(Authenticated)
            principals
.append(str(user.user_id))
       
return principals



# routes.py
config
.add_handler('calendar', 'calendar/{action}', factory=calendar_factory,
                   handler
=CalendarView)

def calendar_factory(request):
   
if 'calendar_id' not in request.params:
       
return CalendarResource(Calendar())
    calendar_id
= request.params.get('calendar_id')
    calendar
= request.dbsession.query(Calendar)\
       
.filter_by(calendar_id=calendar_id).first()
   
if calendar is None:
       
raise HTTPNotFound

   
return CalendarResource(calendar)


class CalendarResource():
   
def __init__(self, calendar):
       
self.calendar = calendar

   
def __acl__(self):
       
return [
           
(Allow, Authenticated, 'view_calendar'),
           
(Allow, Everyone, 'add_calendar'),
           
(Allow, str(self.calendar.user_id), 'edit_calendar')
       
]

which works nice if user requests own calendar, but not if he tries to get shared one. I tried to append request.effective_principals like this:
    calendar_permissions = request.dbsession.query(CalendarPermission.cp_id, CalendarPermission.perm_type)\
       
.filter_by(calendar_id=calendar_id)\
       
.all()
    request
.effective_principals.append(calendar_permissions)


but it doesn't appends effective_principals list. I got stuck hardly there and I really can't find a way to update dynamically principals. I would be grateful guys if you can help me resolve this problem.

Mike Orr

unread,
Aug 14, 2016, 2:47:35 PM8/14/16
to pylons-...@googlegroups.com
This sounds like a variation of record-specific permissions. I
structured it slightly differently in my application, which has both
advantages and disadvantages, but it might help.

My problem was that most users have read-only access to all incidents,
a few can read only certain incidents, and a few can create and edit
them. There's also another level of objects below them, and they may
have read or read-write access tot that.

All the principals are based on the user object. It has booleans and
role strings that imply certain principals, and integer arrays
(PostgreSQL ARRAY) telling which incidents they have incident-specific
permissions on. That might be analogous to a shared calendar. The user
object has a method, 'get_auth_groups()'. This returns a list of
principal strings, which includes groups like 'g:foo' and
'g:incident-1234-contrib'.

My authentication pollicy has a 'groupfinder' function that looks up a
user by ID, calls its '.get_auth_groups()' method, and returns the
result. I cache the result in Redis with a 15-minute TTL, but that's
optional.

The application uses URL Dispatch but has a small resource tree for
ACLs. From memory the code is something like this:

class Resource(object):
def __init__(self, parent, name):
self.__parent__ = parent
self.__name__ = name

class RootResource(Resource):
__acl__= [
(Allow, "g:responder", "view"), # View all incidents
]

def getitem(self, key):
return IncidentResource(self, key)

class IncidentResource(Resource):
def __acl__(self):
ret = []
principal = "g:incident-{}".format(self.__name__)
ret.append((Allow, principal, "incident") # View this incident.
principal = "g:incident-{}.contrib".format(self.__name__)
ret.append((Allow, principal, ("post.create", "post.edit"))
# Create and edit posts in this incident

def get_root(request):
return RootResource(None, "")

The top-level root factory is 'get_root'. Routes pertaining to a
particular incident have an "{incident}" routing variable (incident
ID), and their views are configured with 'traverse="/{incident}". The
net result is is that when the user goes to an incident URL or under
it, the context is for that incident ID. The user is granted
"incident" permission if they have the the global "g:responder"
principle or the incident-specific "g:incident-1234" principal.

The disadvantages of this approach is that you're doubling up on the
meaning of the "incident" permission, and it's cumbersome to parse
principal strings to determine which incidents to show links to. I ran
into both problems. My menu bar showed some items if the request has
"incident" permission, but that turned out to be wrong with
limited-incident users because they shouldn't have those menu items.
The home page also wants to show links to permitted incidents, and I
highly oppose parsing things out of strings unnecessarily (i.e., the
permitted incident IDs from the principals). So for those things in
the home page and layout, I go directly to the lower-level 'user'
attributes to determine which links to show, bypassing the Pyramid
principals. They should be the same if the code is correct because the
views use the user attributes, and 'get_auth_groups' uses the same
user attributes to calculate the principals.
> --
> You received this message because you are subscribed to the Google Groups
> "pylons-discuss" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to pylons-discus...@googlegroups.com.
> To post to this group, send email to pylons-...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/pylons-discuss/1c20670e-6a5e-41b4-91c7-f0bc0d7f9071%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



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

Aleksander Philips

unread,
Aug 17, 2016, 10:02:53 AM8/17/16
to pylons-discuss
I resolved the problem by creating custom authorization policy. I'm not sure is it secure, but at this moment works good.

In resource class the only modification is extended custom tuple like:
(Allow, self.calendar_permissions('edit'), 'edit_calendar', 'bool')
where calendar_permission function returns 'bool' value.

My authorization policy class has default_acl field and permits function:
    default_acl = [
       
(Allow, Everyone, 'login_view'),
       
(Allow, Authenticated, 'auth_user'),
   
]

   
def permits(self, context, principals, permission):
        request
= context.request
       
if request.user:
            acl
= [x for x in context.__acl__() + self.default_acl if x[2] == permission]

           
for access in acl:
               
if len(access) == 4:
                   
if access[3] == 'bool':
                       
return access[1]
               
else:
                   
if access[1] in principals:
                       
return True
           
return True
       
elif type(context) is RootFactory\
       
and request.route_url('login') in request.url\
       
and permission == 'login_view':
           
return True
       
raise HTTPForbidden
Reply all
Reply to author
Forward
0 new messages