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!
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.
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.
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 am looking into adding RSS feeds to one of my Django apps and I would like to be able to have them be authenticated.
I want to use the new syndication framework in Django 1.2. I've read the docs on how to do this and have the basic feeds setup.
I am new to authenticating feeds, so I am not sure what the best approach to take is or what my options really are.
Each user has a unique sub domain and I would like the URL structure to look something like this: http://mysubdomain.mysite.com/myapp/rss/ if possible.
I don't want the feeds to be publicly available, is it possible to use the users username and password for the authentication? Have you found that most feed readers support this? If it's not possible to authenticate for each user, should I try to use a uuid to give them a unique url or is that not secure enough?
As you can probably tell I am not sure what direction to take with this, so any advice on the best way to do this would be very much appreciated.
Thanks
This is an old thread, but I recently encountered the same question. I solved it by overloading the __call__ method of the Feed object:
from django.http import HttpResponse
class ArticleFeed(Feed):
"snip [standard definitions of title, link, methods...]"
def __call__(self,request,*args,**kwargs):
if not request.user.is_authenticated():
return HttpResponse(status=401)
else:
return super().__call__(request,*args,**kwargs)
Have you tried wrapping the syndication view django.contrib.syndication.views.feed into a view that requires login? RSS feeds should normally be fetched over HTTP, so this should work!
# Import Django's standard feed view.
from django.contrib.auth.decorators import login_required
from django.django.contrib.syndication.views import feed
# Wrap it in a new feed view that requires authentication!
private_feed = login_required(feed)
Caveat: I've never tried this!
Edit!
To be safe with RSS readers that don't support redirection, return a HTTP 401 status code with the following:
authentication_url = '/accounts/login'
def feed_safe_login_required ( view ):
def _ ( request, *args, **kwargs ):
if not request.user.is_authenticated:
return HttpResponseNotAuthorized, authentication_url
return _
feed = feed_safe_login_required(django.contrib.syndication.views.feed)
Where HttpResponseNotAuthorized is as defined in this django snippet.