I am trying to create a security system for my toolbox of applications that I am building. I previously had an acl, but the problem with that was I couldn't control on an application based level.
I think the picture below describes how I have it setup in the database.
Basically, what I think I need is to pass the application GUID to the groupfinder()
function, and I am not sure how I should be going about this.
Anybody have any ideas?
The groupfinder
and the context factory
in Pyramid are intended to operate separately and in isolation. The former is used to define the "current user" and the latter is used to define the "current resource". These two items are finally combined with a permission
via the authorization policy's permits
function to determine whether the "current user" has "permission" to operate on this "resource".
The groupfinder
is responsible for identifying the user. This means converting them into a set of principals that can be used later.
The context factory
determines the current resource (context)... so this should normally be different per url (remember the R in URL is resource) so the url normally defines the resource.
The view attached to a url defines the permission
needed (for the operation being performed by the view).
Step 2 here is the hardest for people to grok when coming to Pyramid. How do I define this resource for each URL? It is covered in the url dispatch tutorial [1] but I will explain quickly here.
As I said above, each route usually represents a resource so we can put that into code by defining an object for that route. This is done via the factory
argument to config.add_route(..., factory=...)
and it can do lots of things with the route to determine what the current resource is.
def page_factory(request):
# I'm attached to a page so I can grab the matchdict
pageid = request.matchdict['pageid']
page = request.db.query(Page).get(pageid)
if page is None:
raise HTTPNotFound
return page
config.add_route('page', '/pages/{pageid}', factory=page_factory)
This snippet defines a route with a page factory and every view attached to this route is going to have the page as the context (request.context
).
The Page
object here has an ACL defining what users/groups (principals) are allowed to do what (permissions) on it.
class Page(Base):
__tablename__ = 'page'
# a bunch of columns
def __acl__(self):
return [
(Allow, f'app:{self.app.id} user:{self.owner_id}', 'edit'),
(Allow, f'app:{self.app.id}', 'read'),
]
Assuming this page is attached to app1, we can easily define a groupfinder saying that anyone in app1 can read it:
def groupfinder(userid, request):
user = request.db.query(User).get(userid)
if user is not None:
principals = []
for app in user.apps:
principals += [
f'app:{app.id}',
f'app:{app.id} user:{user.id}',
]
for group in app.groups:
principals += [f'app:{app.id} group:{group.id}']
return principals
The groupfinder doesn't know about pages but it describes the user with enough granularity that the page can match them up with the operations it allows.
[1] https://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/tutorials/wiki2/authorization.html