-
-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider adding ability to lazily load data for permission objects #6
Comments
about to crack a million permissioned resources here, so yes i'd vote for this |
Any thoughts on how to implement this? I did something at one point in another project of mine where the permission object is created on the fly in a decorator. I used a class property that matches the url parameter. For example: from collections import namedtuple
from functools import partial
from flask.ext.principal import Permission as _Permission
BlogPostNeed = namedtuple('BlogPostNeed', ['method', 'value'])
EditBlogPostNeed = partial(BlogPostNeed, 'edit')
class Permission(_Permission):
name = None
need = None
class BlogPostPermission(Permission):
uri_parameter_key = 'blog_post_id'
class EditBlogPostPermission(BlogPostPermission):
name = 'edit'
need = EditBlogPostNeed
def __init__(self, blog_post_id):
super(EditBlogPostPermission, self).__init__(self.need(unicode(blog_post_id)))
# Decorator...
def accepted_permissions(*perms):
def wrapper(fn):
@wraps(fn)
def decorated(*args, **kwargs):
for clazz in perms:
if clazz(kwargs[clazz.uri_parameter_key]).can():
return fn(*args, **kwargs)
abort(403)
return decorated
return wrapper
# In action:
@accepted_permissions(EditBlogPostNeed)
@app.route('/posts/<blog_post_id>', methods=['PUT', 'PATCH'])
def edit_blog_post(blog_post_id):
# .... do stuff ....
return render_template(...) But all the needs are loaded before each request still. I'm still not sure how I would lazily check for one resource. |
Yes, on reflection it seems like a fundamental redesign is required to get there. Perhaps so fundamental that it ought to be a different extension? I'm thinking this needs base classes for Principal and Resource that have some SQLA cascading magic so the decorators have some data structures to check at runtime. I'd be happy to hear of a clever way to avoid going that far but if you plan to have granular permissions, you need to define a storage layer too, right? |
I'm not convinced a fundamental redesign is required. Flask-Principal is already very minimal in its design. |
Okay, given that, Are we talking about an ACL-based system? I have assumed so up to this point but perhaps you see a different way to do this. |
I would say Flask-Principal gives you to the tools to build an ACL system already. Permissions are attached to the identity at the developer's discretion. The problem I'd like to solve here is how and when to check the permissions for a given resource to the identity. The easiest way is to do this add all the permissions to the identity in a So when in the context of a request should the permission(s) for a resource be created and checked? The obvious approach to me is a decorator of sorts like I have above but I developed that in a vacuum and I'm not sure it will serve the use case of anyone else. |
I don't think a redesign is needed at all. Granular access control on resources generally requires an understanding of the associated mission specific implementation of data structures, which flask-principal, being a generic framework, would be completely agnostic about them. That said, this generic implementation allows the overriding of these classes for suitable purposes, even for lazily loaded permission checks. The current This also allows the implementation of a more clear set of operators on permission objects, where by default a Now to solve that original problem posed here: I have an example that sort of addresses this. class BlogEntryEditPermission(Permission):
"""
Lazily loaded permission check.
"""
def allows(self, identity):
# current entry is already in the request
blog_id = request.view_args.get('blog_id')
blog = blogs.get(blog_id)
if not blog:
# free sanity check.
abort(404)
result = blog.identity == identity.id
return result
blog_edit = BlogEntryEditPermission()
# ...
@app.route('/blog/edit/<int:blog_id>')
@blogger.require()
@blog_edit.require()
def blog_edit(blog_id):
return 'Editing blog entry: %s' % blog_id Since the permission checks happen within a flask request context, the view has all the values needed to do pin-point checks on what needs to be done. I have the example posted as a gist. Once you started the server, have your client open to the /login/${userid} endpoint to log in as the user, specified at the |
@metatoaster Thanks for your input. This is certainly one way of going about it and should work for many implementations. I've started working on a simple ACL/ACE system for Flask-Security that, once I'm done with it, I could provide an example, along with yours, in the documentation on how to use Flask-Principal in more "advanced" ways. |
+1 |
@mattupstate Do you have any update on an example of ACL with Flask-Principal? Having examples would greatly help in understanding concepts of Flask-Principal. |
@DerekDomino unfortunately I do not. I am, however, experimenting with a different approach to an ACL system over here |
Just for your info, looking for a solution of simple permissions handling in my Flask app, I have found a really simple, lightweight and powerful module |
Considering the hypothetical example in the current documentation for granular resource protection, what if a user has hundreds, even thousands of posts or some other sort of attribute. Perhaps adding a way to lazily load some sort of condition in the permission object can avoid loading all that data and inflating the identity on every request.
The text was updated successfully, but these errors were encountered: