pallets-eco / flask-principal Goto Github PK
View Code? Open in Web Editor NEWIdentity management for Flask applications
License: MIT License
Identity management for Flask applications
License: MIT License
Howdy there,
It'd be nice to include LICENSE in your sdist (might need to set it in your MANIFEST.in)
Thanks for your work!
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?
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?
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
In the "Authentication providers" example identity.name should be replaced by identity.id
In the "User Information providers" example the hasattr(current_user, 'id') test doesn't exclude anonymous users anymore. Because all users have the id attribute now.
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.
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.
The project should support proper typing like the rest of the Pallets projects
See other Pallets projects
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...
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.
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
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?
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.
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).
Should read Flask-principal instead of Flask-IdentityContext
I am getting the following error:
AttributeError: 'NoneType' object has no attribute 'static_path'
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.
To help packaging, especially legal review, it would be nice to have the LICENSE file included in pypi distribution.
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
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'.
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)
Instead of writing a full login form example, it would be much simpler and more general to integrate the two using Flask-Login's signals. It has signals for users logging in and logging out.
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)
.
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!
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.
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.
Hi, this is more of a question than a bug (potentially it may become RFE):
I have an application where I want to have:
Made-up example:
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!
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:
Permission
and override the method;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?
One would think that setting principals.use_session to True would enable session use at any time.
But, since it is only checked during init_app, this is not the case.
For tools like caniusepython3.com it would be neat if you'd add classifiers to your python package so we can check if there is python 3 support.
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
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!
just curious
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.
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' !?
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.
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.