Skip to content
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

Open
mattupstate opened this issue Aug 28, 2012 · 12 comments
Open

Comments

@mattupstate
Copy link
Collaborator

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.

@wshrdryr
Copy link

about to crack a million permissioned resources here, so yes i'd vote for this

@mattupstate
Copy link
Collaborator Author

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.

@wshrdryr
Copy link

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?

@mattupstate
Copy link
Collaborator Author

I'm not convinced a fundamental redesign is required. Flask-Principal is already very minimal in its design.

@wshrdryr
Copy link

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.

@mattupstate
Copy link
Collaborator Author

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 app.before_request handler but obviously this is not scalable if there are thousands of resources a user may be able to access.

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.

@metatoaster
Copy link

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 Permission class should be a subclass of a generic implementation (say PermissionBase) with the allows method that raises NotImplementedError, then giving a clearer indication to integrators to create their own permission items.

This also allows the implementation of a more clear set of operators on permission objects, where by default a & operation on permission objects will yield a new permission object that requires both preceding ones, and then a set of | to or a set of permissions that specifies any of those permissions.

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 role_map dictionary for the roles needed. Yes it is possible to have millions of blog entries without having to bog down checks when a smarter Permission class is built to guard a view.

@mattupstate
Copy link
Collaborator Author

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

@lyschoening
Copy link

+1

@DerekDomino
Copy link

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

@mattupstate
Copy link
Collaborator Author

@DerekDomino unfortunately I do not. I am, however, experimenting with a different approach to an ACL system over here

@frol
Copy link

frol commented Nov 9, 2015

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 permission! Even though it is mentioned that it is intended for Flask, it is a general purpose Python module with zero dependencies. It is much easier than Flask-Principal, which I couldn't get right (it works, but I tried to wrap it to make it simple and good-looking to maintain permissions in future) after a day of hardwork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

6 participants