Coder Social home page Coder Social logo

flask-principal's Introduction

Flask-Principal

Identity management for Flask applications. This extension was originally written by Ali Afshar. Thanks to him for his great work. This is the new and official repository for this project.

Documentation

Pallets Community Ecosystem

Important

This project is part of the Pallets Community Ecosystem. Pallets is the open source organization that maintains Flask; Pallets-Eco enables community maintenance of related projects. If you are interested in helping maintain this project, please reach out on the Pallets Discord server.

flask-principal's People

Contributors

abdur-rahmaanj avatar aenglander avatar aliafshar avatar auha avatar buztard avatar coyotevz avatar dependabot[bot] avatar jyelloz avatar masom avatar mattupstate avatar mickey06 avatar phispi avatar s0undt3ch avatar sorki avatar techniq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flask-principal's Issues

Permission decorators

Add a view method decorator that can accept one or more permission class objects to control access. These permission classes will have a relationship with a specific HTTP resource.

Possible security hole in AnonymousIdentity implementaion

AnonymousIdentity instance uses 'anon' as a name that is equal to Identity instance with name 'anon'. If we have a user with that name then we have a security problem, because any anonymous (logged out) user gets 'anon' permissions.

Add documentation for using with flask-jwt

Hi Matt,

since you are already the original author of flask-jwt and since flask-principal has the option to go without session, could you perhaps add a little example that goes beside the more classical flask-login example which shows how to properly use this with flask-jwt?

If you lack the time to do so properly, could you roughly describe me the process in this report for now?

Consider adding ability to lazily load data for permission objects

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.

Issue with classes in sets

Hello,

I love this library but I have ran into an issue I thought I could pick you brain on.

In the example https://pythonhosted.org/Flask-Principal/#granular-resource-protection i implemented almost identically to how it states there but I have an issue. When calling if permission.can(): I get False back from the following line... https://github.com/mattupstate/flask-principal/blob/master/flask_principal.py#L333-L334

In order to make this work I had to implement the following.

class EditBlogPostPermission(Permission):
    def __init__(self, post_id):
        need = EditBlogPostNeed(unicode(post_id))
        super(EditBlogPostPermission, self).__init__(need)
        self.need = need

    def __eq__(self, other):
        """Override the default Equals behavior."""
        if isinstance(other, self.need.__class__):
            return self.need == other
        return False

    def __ne__(self, other):
        """Override the default Unequal behavior."""
        return self.need != other

    def __hash__(self):
        """Override the default Hash behavior."""
        return hash(self.need)

Do you have any suggestions on how to better implement this? I dont see any tests in your test file for this use case.

I also considered the following instead of the __eq__, __ne__, __hash__

    def allows(self, identity):
        for prov in identity.provides:
            if isinstance(self, type(prov)) and self.needs == prov.needs:
                return True
         
        return False

Small mistake in test_principal.py

This

    def test_and_permissions_view(self):
            self.assertRaises(PermissionDenied, self.client.open, '/g')

should be

    def test_and_permissions_view(self):
            self.assertRaises(PermissionDenied, self.client.open, '/h')

As it stands /h is never tested.

Declare permission that never allows anything

I'd like to declare a permission whose .can() method always returns False (background: it's returned from a factory function and the meaning is that some objects may not be edited/removed by anyone).

There are a couple of ways to do this:

  • subclass Permission and override the method;
  • crate a Need that is shared by all identites and return a permission that excludes it.

I prefer the need-based method because it retains the ability to perform permission algebra with the new permission. So then, how to add this need to the AnonymousIdentity? How about we define a Principal.get_default_identity method that subclasses can override?

skip_static only whitelists the app's static endpoint, not those from blueprints

I've seen a lot of issues cropping up related to the use of flask_principal or flask_security with blueprints where, even when skip_static is set, static files provided by blueprints will block and eventually thow a 500 with an error message along these lines:

sqlalchemy.exc.TimeoutError

sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30 (Background on this error at: http://sqlalche.me/e/3o7r)

While letting Flask serving static files is arguably not a good idea in production (though still very much useful in developpement), separating static files per blueprint has been a core feature of Flask for many years, one which I see no reason to ignore in an extension as widespread as this. The current workaround of putting all assets from blueprints in the root static folder goes against the idea of keeping blueprints as independent modules (blueprints can't have their internal static folder point outside of their URL prefix, so while it's easy to have the static directory point to /myapp/myblueprint/static, it's impossible to set it to /myapp/static/myblueprint) and isn't possible for extensions that wrap blueprints like flask-admin, since you can't rewrite their url_for routes.

Flask has some mechanisms that would allow for something like Flask-Principal to identify static folders from blueprints (though I'm not seeing a callback to automatically do that on blueprint registration).

Intersection of required and provided needs is not sufficient

The test for access in the allows method of the Permission class using the intersection of the required and provided needs in insufficient. The documentation says the needs in a permission "Represents needs, any of which must be present to access a resource". But with the intersection, if you have at least one need in common, the intersection will be not empty and the test succeeds.

I think we should use issubset instead:

def allows(self, identity):
        """Whether the identity can access this permission.

        :param identity: The identity
        """
        if not self.needs.issubset(identity.provides):
            return False

        if self.excludes and self.excludes.intersection(identity.provides):
            return False

        return True

Sane handling of identity_changed event raised inside identity_loaded event handler

Consider the case of a user account being disabled while that user has a session open.

Receiving a request from that session immediately after the account is disabled will be processed like...

  1. Flask.preprocess_request() calls the 'refore_request' functions
  2. Principal._on_before_request() loads the user identity from the session and calls set_identity()
  3. Principal.set_identity() raises an identity_loaded event
  4. MyApp.on_identity_loaded() reads the user account status from some database and decides to log-out the user.
  5. MyApp.on_identity_loaded() raises an identity_changed event with AnonymousIdentity()
  6. Principal._on_identity_changed() calls set_identity()
  7. Principal.set_identity() raises an identity_loaded event (Note, we are still in the MyApp.on_identity_loaded() handler from step 4)
  8. MyApp.on_identity_loaded() configures the App for anonymous access.
  9. Functions return, stack unwinds.

We see that changing identity inside the before_request cascade has resulted in a recursive call to Principal.set_identity()

Consider that the identity_loaded event may have multiple subscribers. Lets call them 'on_identity_loaded_A()' and 'on_identity_loaded_B()'. Now suppose on_identity_loaded_A() wants to reject the user.
The call sequence will look like:

set_identity(session_account)
    on_identity_loaded_A(session_account)
        set_identity(anonymous)
           on_identity_loaded_A(anonymous)
           on_identity_loaded_B(anonymous)
    on_identity_loaded_B(session_account)  # BIG Problem!

We see than on_identity_loaded_B() has been called with the session_account AFTER the identity has been changed to anonymous. Nothing good could come of this.

The fundamental problem is that blinker Signal.send() will send the session_account to all subscribers of the identity_loaded event, even AFTER the first subscriber has rejected that identity.

PROPOSAL

The solution I propose is to detect recursive calls to Principal.set_identity() and delay any changes to the identity until the current call returns.

In my code, I have sub-classed Principal and overridden the set_identity() method. I offer my code as a potential solution, but there are of course many ways of doing this. I'm not a Python wiz. I'm sure someone more experienced could improve on my work.

    def set_identity(self, identity):
        try:
            pending = self.set_identity_pending # throw exception if NOT recursion
            self.set_identity_pending = identity # take the most recent setting
            return # delay the identity change until the current one plays out
        except:
            pass

        self.set_identity_pending = identity
        while True:
            super().set_identity( identity )
            if self.set_identity_pending == identity:
                break
            identity = self.set_identity_pending

        del self.set_identity_pending

Struggling with using `http_exception=`

I'm starting to use Flask-Principal for adding granular resource protection to a REST API written using Flask-RESTful/Flask-RestPlus. For almost all of my permission checks I just want to abort with a 403 error when presented with insufficient permissions so I'm trying to use the decorators like so:

admin_permission = Permission(RoleNeed('admin'))

@app.route('/foo')
class Foo(Resource):
    @login_required
    @admin_permission.require(http_exception=403)
    def get(self):
        pass

However if I trigger a permission denied error, I end up getting:

Traceback (most recent call last):
  File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File ".../venv/lib/python2.7/site-packages/flask_restful/__init__.py", line 270, in error_router
    return original_handler(e)
  File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File ".../venv/lib/python2.7/site-packages/flask_restful/__init__.py", line 267, in error_router
    return self.handle_error(e)
  File ".../venv/lib/python2.7/site-packages/flask_restplus/api.py", line 379, in handle_error
    return super(Api, self).handle_error(e)
  File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File ".../venv/lib/python2.7/site-packages/flask_restful/__init__.py", line 270, in error_router
    return original_handler(e)
  File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1363, in handle_user_exception
    assert exc_value is e
AssertionError

If I don't use http_exception= then I get PermissionDenied exceptions with a 500 error code which aren't terribly useful. The only way I can seem to get things working is with:

admin_permission = Permission(RoleNeed('admin'))

@app.route('/foo')
class Foo(Resource):
    @login_required
    def get(self):
        if admin_permission.can():
            pass
        else:
            abort(403)

I'm concerned I'm going to end up writing the same block of code everywhere so I'd like to make use of http_exception= if possible. Is it possibly because I'm using Flask-RESTful/Flask-RestPlus which tries to JSON-ify responses perhaps?

Simplifying permission handling

About

Replace internal handling of permissions by replacing excludes by not needs

Flask-principal was maintaining a list of excludes and a list of needs and needed to update both.

Currently

p.needs.update(self.excludes)
p.excludes.update(self.needs)

In the future we just need to deal with needs.

Add Typing to Project

The project should support proper typing like the rest of the Pallets projects

See other Pallets projects

  • Add Typing to classes and functions
  • Add typing environment with mypy and pyright tests to tox
  • Add a run in Gitlab actions

Strange symlink? submodule? in docs - preventing pip install of main

I am trying to test against the 'main' branch - with the new changes (BTW - thank you for resurrecting this package).

Trying: pip install git+https://github.com/pallets-eco/flask-principal@main#egg=flask-principal
and after a bit - it times out:

fatal: clone of 'git://github.com/mitsuhiko/flask-sphinx-themes.git' into submodule path '/private/var/folders/tp/8sq0p0ld5_zfd8p8xywyyrfw0000gn/T/pip-install-ddhmpqtd/flask-principal_925399e419434c55b0786ccc75d81ed1/docs/_themes' failed
  Failed to clone 'docs/_themes'. Retry scheduled
  fatal: unable to connect to github.com:
  github.com[0: 140.82.116.4]: errno=Operation timed out

  fatal: clone of 'git://github.com/mitsuhiko/flask-sphinx-themes.git' into submodule path '/private/var/folders/tp/8sq0p0ld5_zfd8p8xywyyrfw0000gn/T/pip-install-ddhmpqtd/flask-principal_925399e419434c55b0786ccc75d81ed1/docs/_themes' failed
  Failed to clone 'docs/_themes' a second time, aborting

I have never seen this - but the docs directory seems to have a symlink or a sub-module? to another github repo (which is no longer in use). I believe the more 'modern' way of handling this is using requirements.

Lazy load Identity object

Currently, the identity object is loaded on each request using Principle._on_before_request and Principle.identity_loaders.

The problem is that the identity is not already required for each request and each access to the identity implies an access to the session data.

Ideally, the identity would only be loaded when needed.

Is this a planned feature? Something that I could help out with?

Raise TypeError if Flask.static_folder is None

When I use static_folder=None argument in Flask application, principal code raises TypeError.

app = Flask(app_name, static_folder=None)
principals = Principal(skip_static=True)

The use of static_folder=None is because a blueprint handle static files.

deque mutated during iteration

Hi,

we use flask-principal in a small application with blueprints. Authentication has its own blueprint. Apparently at random we have errors like the one below. Did you know that behavior? Here I read that I may get around that problem by creating a list and 'freeze' the deque. Should I give it a try?

  File
"/opt/venvs/geocalc-login/lib/python3.4/site-packages/flask_principal.py"
, line 479, in _on_before_request
    self.set_identity(identity)
  File
"/opt/venvs/geocalc-login/lib/python3.4/site-packages/flask_principal.py"
, line 419, in set_identity
    for saver in self.identity_savers:
RuntimeError: deque mutated during iteration

Bug in flask-login example

The flask-login example in the documentation is extremely helpful (common use case, I would think) but it has a significant bug in it!

The sample code shows Flask-Principal being registered before Flask-Login, but if you do this, then flask principal will send the identity_loaded message before Flask-Login has had a chance to load the current_user proxy. Therefore, the identity_loaded handler will always return the anonymous user!

I realize it's not meant to be a complete example, but it tripped me up as well as at least one person on Stack Overflow: http://stackoverflow.com/questions/18190067/flask-login-and-principal-current-user-is-anonymous-even-though-im-logged-in/18935921

It can be fixed simply by reversing the order of Principal(app) and login_manager = LoginManager(app).

why is there noway to create an 'perm = a or b'

sorry for creating a ticket for this, but idk where else to put this.

I've ran into this multiple times now and was wondering if there's a specific reason for this feature not being available which I'm overseeing, otherwise I could work on adding it maybe ;-)

Often I have stuff that is accessible with 2 different permissions (for example 2 different roles), but the or doesn't do an 'or', it instead does a 'difference' ... which is fine I guess (you build it in for a reason) but it would be nice if we could still have another way of combining 2 permissions as an 'or' !?

use mongoengine

principal is very good.
i do`t know how to use principal use mongodb.
can you update you doc,give a demo about mongoengine and principal?
thk.

Mongo based sessions do not allow dots as key names

Hi,

I have been using MongoEngineSessionInterface from flask-mongoengine library for server side sessions in a flask app. The flask principal extension seems to depend on storing identity.id and identity.auth_type in session and retrieving them later to store the identity of user in session. However, in a mongo based session this fails since mongo does not allow dots as key names.

I tried use_sessions=False setting but it was not very clear how to load the identity of the logged in user in that case.

I could rename them but would rather not create a custom fork of the library. Would you be willing to take in a pull request to change identity.id and identity.auth_type renamed to remove dots?

Is this library maintained?

I've noticed it is not updated since 2014
For instance, my code is affected by this bug: #30 but I've checked the code at it is not merged

So the question is the title: is this library maintained? and if not, what alternative did I have?

Thanks!

Flask 0.10.1, app_context and "_AppCtxGlobals object has no 'identity'"

I've got an issue that I don't quite know how to articulate but it's due to updates in Flask from 0.9 -> 0.10. I'm using Flask-Principal==0.4.0, Flask-Login==0.2.7.

I've got a permission-decorated function that's somethink like the below:

@admin_role.require(http_exception=401)
def do_stuff():
    return jsonify(code=200)

content_editor_role is defined as:

admin_role = Permission(RoleNeed('admin'))

Something changed between between flask==0.9 and flask==0.10.1 which is causing the below exception stack to be thrown on this decorated (permission-restricted) function:

  File "/Users/sundar/.virtualenvs/mmweb/lib/python2.6/site-packages/flask_principal.py", line 198, in _decorated
    with self:
  File "/Users/sundar/.virtualenvs/mmweb/lib/python2.6/site-packages/flask_principal.py", line 205, in __enter__
    if not self.can():
  File "/Users/sundar/.virtualenvs/mmweb/lib/python2.6/site-packages/flask_principal.py", line 193, in can
    return self.identity.can(self.permission)
  File "/Users/sundar/.virtualenvs/mmweb/lib/python2.6/site-packages/flask_principal.py", line 188, in identity
    return g.identity
  File "/Users/sundar/.virtualenvs/mmweb/lib/python2.6/site-packages/werkzeug/local.py", line 338, in __getattr__
    return getattr(self._get_current_object(), name)
AttributeError: '_AppCtxGlobals' object has no attribute 'identity'

Note that this happens ONLY when I run this from a flask-script through an app_context():

def run_script_task():
    with app.app_context():
        from myapp.views.api import cleanup
        cleanup.do_stuff()

And this basically is telling me that something is weird when I use the app_context to "fake" a user's identity.

I'm posting this here in case anyone knows what this might be from, since I'm at a bit of a loss. I'm going to start looking at the source to see if I can trace anything, but would appreciate any suggestions as well.

Thanks much!

Allow the possibility of an "Always deny" Permission to facilitate "safe by default" designs.

In my Flask application, I use class based Views. All views are derived from a base class.
The base class does not have a registered url rule and never receives requests directly.
My desire was to put an "Always Deny" permission on this base class to force any derived view
to explicitly declare permissions, thus creating a "safe by default" pattern.

I was surprised to find that Flask-Principal does not seem to have any (convenient) way of creating such a Permission.
The fundamental problem is that a Permission() without any Needs defaults to 'allow'
This is a little unintuitive given "A Permission is a set of requirements, any of which should be present for access to a resource."
But the chosen design is defensible, so probably an extra note in the documentation is sufficient to specify this corner case.

What is more problematic is that a Denial() without any Needs ALSO defaults to 'allow'. So we have a situation where
Permission() means 'allow' and Denial() also means 'allow'.

PROPOSAL

Part 1) Allow the default disposition (i.e. if permission.perms is an empty set) to be specified.

e.g.

class Permission(object):

    def __init__(self, *needs, default_permit=True):
        self.perms = {n: True for n in needs}
        self.default = default_permit

    def allows(self, identity):
        if self.needs and not self.needs.intersection(identity.provides):
            return False

        if self.excludes and self.excludes.intersection(identity.provides):
            return False

        if self.perms:
            return True

        return self.default

Part 2) Denial() should default to 'deny'.
This is more a restoration of sanity rather than a necessity. The change of Part 1, makes this less necessary.
Alternately, This change would make the change in Part 1 unnecessary as Denial() would be the missing "Always deny" permission.

e.g.

class Denial(Permission):

    def __init__(self, *needs, default_permit=False):  # default_permit=True would be backward compatible

This is a potentially breaking change. However.
The documentation does not say what will happen in this case.
There are no instances of Denial() in the unit tests (There ARE instances of Permission() in the unit tests)

Fixed per-role permissions

Hi, this is more of a question than a bug (potentially it may become RFE):
I have an application where I want to have:

  • users
  • possibly multiple roles for each user
  • "fixed" set of permissions (possibly growing in time) that I can assign/unassign to/from certain roles

Made-up example:

  • There is a developer role and a manager role
  • All developers have "change-code" permission and "create-new-repository" permission
  • All managers have "manipulate-team-members" permission
  • After a while, I decide that developers are creating too many repos, so I want to move the permission from developers to managers - just by changing permissions that are associated to these roles in DB, I don't want to touch application code

AFAICS this is not supported approach at this time, cause it would need adding a permission model with M:N relation to role model. I think this should be pretty easy to actually add to my application while still using flask-principal, but I wanted to ask if there's a preferred/recommended way of doing this or if you have some plans in this direction (or if I'm just missing something and doing everything wrong ;)). Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.