In GAE, restrict certain urls to Admin only is quite easy:
- url: /admin/.*
script: admin.app
login: admin
Now I am using Flask. I have to repeat the same code in every admin only function to tell if a user is admin, which makes the code long and redundant.
#app.route('/admin/somepage')
def AdminPage():
#Figure out if a user is admin
Is there some better way to do that?
You could use a app.before_request() hook to inspect the request and respond with a redirect when the requested path starts with /admin/ but authentication information is missing or insufficient.
def check_for_admin(*args, **kw):
if request.path.startswith('/admin/'):
if not user.is_admin():
return redirect(url_for('login_form'))
By returning a response object (such as produced by the flask.redirect() function from a before_request handler stops processing and the actual view is not invoked.
You can also use a decorator to mark admin views; this is more flexible (routes don't need to be all under one path) and more discoverable (each route has the decorator on it).
your AdminPage() should be a class inheriting from flask_admin BaseView, and you override the method is_accessible, like this:
from flask_admin import BaseView
class AdminPage(BaseView):
def is_accessible(self):
return current_user_is_admin()
#expose('/', methods=('GET', 'POST'))
def do_whatever(self):
# do it
return self.render("admin/whatever.html")
of course, your user class need to have a method (in the example it's called current_user_is_admin()) which evaluates True only if the current user is an administrator. You need to have this information anyway, regardless the way you implement this.
This is the proper way to use flask_admin, as far as I understand. You can define as complex, or as simple, permission rules as you like, per every admin function, just by changing the is_accessible() override.
Related
I'm trying to create a two-tiered authentication system with basic auth, using Flask-HTTPAuth. My application has two routes, a base route at / accessible to any logged in user, and an admin route at /admin accessible only to users who are (as you might expect) logged in as admins.
So I decided to implement this by chaining decorators, with the relevant part of the code as follows (where dbops is just a namespace that handles talking to the database):
#auth.verify_password
def verify_pw(lastname, password):
ln = lastname.lower()
if ln in dbops.list_users():
hashed_pw = dbops.find_hashed_password(ln)
return bcrypt.checkpw(password.encode('utf8'), hashed_pw.encode('utf8'))
return False
def must_be_admin(f):
#wraps(f)
def wrapper(*args, **kwargs):
if dbops.is_admin(auth.username()):
return f(*args, **kwargs)
return "Not authorized."
return wrapper
#core.route("/")
#auth.login_required
def dataentry():
return render_template("dataentry.html")
#core.route("/admin")
#must_be_admin
#auth.login_required
def admin():
return render_template("admin.html")
This works fine so long as anyone trying to log in as an admin user first visits the / route: it prompts for a username and password, and then the admin user can go to /admin and carry out logged-in admin tasks.
However, if an admin user first visits /admin it doesn't give a login prompt. It just throws, and after poking around in the debugger I've determined that auth.username() is returning an empty string. So, my guess is that for some reason, the inner decorator isn't being applied, hence the lack of a login prompt.
Does anyone know what might be going on here?
My first hypothesis was that this was an easy bug, because the inner function on the admin decorator wasn't being called until after the is_admin check. So I tried to fix that my calling the function---and thus presumably making auth.username() available--- before the check, as follows:
def must_be_admin(f):
#wraps(f)
def wrapper(*args, **kwargs):
dummy_to_get_username = f(*args, **kwargs)
if dbops.is_admin(auth.username()):
return dummy_to_get_username
return "Not authorized."
return wrapper
But that just caused the same behavior.
I see from this prior SO that the recommended way to do this from the library author is to just create two separate Flask-HTTPAuth objects. Which I can do, no problem. But clearly my mental model about how decorators are working are failing, so I'd like to solve this problem independent of getting the functionality I want working...
The correct order in which you apply decorators is sometimes hard to figure out without knowing what the decorator does, but unfortunately the wrong order will make the application behave incorrectly.
For decorators that do something "before" the view function runs, as in this case, you typically have to put the decorators in the order in which you want them to execute. So I think your code will do what you expect when you use Flask-HTTPAuth's login_required before your must_be_admin:
#core.route("/admin")
#auth.login_required
#must_be_admin
def admin():
return render_template("admin.html")
In this way, the credentials will be checked first, and if missing or invalid login_required will return a 401 error to the browser, which will make the login prompt appear. Only after credentials are determined to be valid you want to evaluate the admin decorator.
I was working on an application wherein I created a generic ListView. Now, while defining that view in my urls.py, I read from the documentation that I need to use the as_view() method as follows:
from django.conf.urls import patterns, include, url
from .views import BlogIndex
urlpatterns = patterns(
'',
url(r'^$', BlogIndex.as_view(), name="index"),
)
Now, I didn't really understood what the documentation had to say about this method. Can someone shed some light into this concept?
In Class-based views, you have to call as_view() function so as to return a callable view that takes a request and returns a response. Its the main entry-point in request-response cycle in case of generic views.
as_view is the function(class method) which will connect my MyView class with its url.
From django docs:
classmethod as_view(**initkwargs)
Returns a callable view that takes a request and returns a response:
You just can't use class-based views like you could in normal function-based views.
BlogIndex(request) # can't do this in case of CBVs
The above code is not valid if you want the CBVs to function properly. For that, you need to provide a view which is callable and then pass request to it. For example:
response = MyView.as_view()(request) # valid way
By calling the as_view() function on my view class MyView will give me a view which i will call with request parameter to initiate the request-response cycle.
In your case:
my_callable_view = BlogIndex.as_view() # returns a callable view
<function blog.views.BlogIndex>
Now, call this function and pass the request.
response = my_callable_view(request) # generate proper response
view function have different format than before because :
This view will actually be implemented as a class
We will be inheriting from an existing generic view function that already does most of what we want this view function to do, rather
than writing our own from scratch.
Class method as_view()- this does all the work of creating an instance of the class, and making sure that the right handler methods
are called for incoming HTTP requests.
ref : https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views
Maybe I can try to write the as_view with pseudo-code:
class ViewClass():
#other stuff
def as_view(self):
return self.render(*args)
It return the render function inside the CBV.
So it actually is the same as path('some_path',views.some_view_function,name='some_name')
Of course, there there is actually much more things going on than only render function, for example to verify and save the content inside post queryDict, actually you need def post(): to handle the post, in function you just if request.method == 'POST' they are actually mutual.
To be specific, as_view() just generate a overall function, including if request.method =='POST': #some code Maybe the actual code doesn't work like that, but you could understand it this way if you are not prepared to contribute to django source code yourself.
Like this question: Best way to make Django's login_required the default
I'm using Flask-Login's login_required decorator now. Is there anyway to make it the default behavior in Flask?
I did this in my instruments project. I use the before_request decorator:
#app.before_request
def check_valid_login():
login_valid = 'user' in session # or whatever you use to check valid login
if (request.endpoint and
'static' not in request.endpoint and
not login_valid and
not getattr(app.view_functions[request.endpoint], 'is_public', False) ) :
return render_template('login.html', next=request.endpoint)
and I then created an is_public() decorator for the few places that would need to be accessible without login:
def public_endpoint(function):
function.is_public = True
return function
If you are using blueprints and need to protect an entire blueprint with a login, you can make the entire before_request to require login.
This is what I use for my CMS blueprint:
#cms.before_request
#login_required
def before_request():
if g.user.role != ROLE_ADMIN:
abort(401)
If you need only to check if the user is logged in (and not if the user has privileges) you can simply pass the function
This is a follow up ( bit more pythonic but thats debatable ) to #MalphasWats
already great answer.
Also includes an important security fix suggested by #nonagon.
Explanation of the vulnerability with 'static' in request.endpoint:
Imagine that there is route which can be user defiened in some way, like a profile link for example.
If the user sets his name lets say Static Joe, then:
"Static Joe" --slugifys--> /usr/profiles/static_joe.
This way making this route public. This is just asking for trouble.
Here is the route guard function which is appened before every request handling:
#app.before_request
def check_route_access():
if any([request.endpoint.startswith('static/'),
current_user.is_authenticated, # From Flask-Login
getattr(app.view_functions[request.endpoint],'is_public',False)]):
return # Access granted
else:
return redirect(url_for('users.login_page'))
( Flask-Login is an excellent module and makes session handling a breeze )
And here is the decorator ( #public_route ) which you can use to allow access to special pages that need public access by default. (register page, login page):
def public_route(decorated_function):
decorated_function.is_public = True
return decorated_function
I had to secure a REST API and I have solved finally like this:
#app.before_request
#auth.login_required
def login_required_for_all_request():
pass
(Actually I used also the connexion framework so I had to use: #app.app.before_request )
I'd like to check a condition before each request and call different views.
How is that achieved?
One solution I can think of is adding something to subscriber NewRequest, but I am stuck:
#subscriber(NewRequest)
def new_request_subscriber(event):
if condition:
#what now?
#subscriber(NewRequest)
def new_request_subscriber(event):
if condition:
raise pyramid.httpexceptions.HTTPFound(location=somelocation) ## to issue a proper redirect
More info can be found here:
http://pyramid.readthedocs.org/en/latest/api/httpexceptions.html#pyramid.httpexceptions.HTTPFound
Well you've given very little information about the "condition" or what you mean by "call different views", so I'll assume you do not want to invoke a redirect but instead you want the application to think a different URL is being requested. To do that you can look at pyramid_rewrite, which is pretty handy for these things, or you can just change the request's path within the NewRequest subscriber, since it is invoked before Pyramid dispatches to a view.
if request.path == '/foo':
request.path = '/bar':
config.add_route('foo', '/foo') # never matches
config.add_route('bar', '/bar')
Yet another option to "check a condition ... and call different views" is to use custom view predicates
From Cris McDonough's blog post:
def example_dot_com_host(info, request):
if request.host == 'www.example.com:
return True
That's a custom predicate there. It returns True if the hostname is www.example.com. Here's how we use it:
#view_config(route_name='blogentry', request_method='GET')
def get_blogentry(request):
...
#view_config(route_name='blogentry', request_method='POST')
def post_blogentry(request):
...
#view_config(route_name='blogentry', request_method='GET',
custom_predicates=(example_dot_com_host,))
def get_blogentry_example_com(request):
...
#view_config(route_name='blogentry', request_method='POST',
custom_predicates=(example_dot_com_host,))
def post_blogentry_example_com(request):
...
However, for your particular problem (display a sign in page if user has no permission to view the page) a better way to achieve this would be to set up permissions for views so the framework raises an exception when user has no permission, and then register a custom view for that exception which will show a sign in form.
I need to check user authorization in every view of one of my Django apps (I don't use Django's built in auth system) and redirect user to a "login please" page, if authorization has failed.
Code looks like this:
try:
admin_from_session = request.session['admin'];
admin = Administrator.objects.get(login = admin_from_session.login, password = admin_from_session.password, enabled=True);
except KeyError, Administrator.DoesNotExist:
return HttpResponseRedirect('/controlpanel/login')
Question is: how can I run this code at the beginning of every view, without repeating it every time?
If I would write my program on PHP, i would put this code in separate file and write something like this at the beginning of every page that requires authorization:
include("redirect_if_not_logged_in.inc.php");
The solutions I found was:
inclusion tags - doesn't do,
because I can't redirect anywhere
from there
custom function -
also doesn't do, because of the same
reason.
The task seems trivial, but I can't find a solution. I would be very grateful for any help.
Look at the source code for django.contrib.auth decorators. They do exactly what you want, but for the built-in Django authentication system (see the documentation). It shouldn't be hard to do something similar for your authentication system.
BTW, why don't you use the built-in auth? You can use it with custom authentication backends...
I found the answer I was looking for. Function decorators allow to run a peace of code at the beginning of a function.
You must define a decorator function
def login_please_decorator(view_func):
"""
Redirect if admin was not logged in
"""
def _decorated(request, *args, **kwargs):
#Check authorization
try:
admin_from_session = request.session['admin'];
admin = Administrator.objects.get(login = admin_from_session.login, password = admin_from_session.password, enabled=True);
return view_func(request,*args, **kwargs);
except KeyError, Administrator.DoesNotExist:
return HttpResponseRedirect('/cp/login?ret=' + request.path);
return _decorated
And decorate a view using this function name:
#login_please_decorator
def some view(request):
# do something ...
# ...
Ludwik Trammer, bugspy.net, thank you for your help.
Function decorators comes to mind
Take a look at the User authentication page here http://docs.djangoproject.com/en/dev/topics/auth/
Read on to "The login_required decorator".
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
You can setup where the user is to be redirected if not authenticated via the setting "settings.LOGIN_URL".
At the page there is also an example for a special authentication template you can style to anything you like!