login required with some public endpoints - better way to do it?
- From:
- Malphas Wats
- Date:
- 2012-10-17 @ 09:25
Hi,
I've been playing with a new app, where almost all routes require a
login. I decided the best way to do this was to use @before_request to
check that there is a user logged in:
@app.before_request
def check_login():
if ('logged_in' not in session and
request.endpoint not in PUBLIC_ENDPOINTS):
return render_template('login.html', next=request.endpoint)
PUBLIC_ENDPOINTS is simply a list of endpoints that don't need a login
('static', for example).
This works, but I'm not overly keen on the managing of registering
public endpoints, it just sort of feels clumsy - any time I add a new
one, I have to edit the list, which is probably in a different .py
file.
Ideally, I'd like to be able to decorate public methods, so I can keep
all of the code together, something like this
@app.route('/my/public/route')
@public_endpoint
def my_public_thing_5():
pass
but I'm pretty sure that I can't do it that way, because of the way
@before_request works, but I don't know enough about decorators (and
requests) yet to know for sure. I have a working solution to my
problem, but I just thought I'd investigate a bit more to see if I
can't get something a little more elegant (the solution I found that
inspected the module source-code looking for @decorators doesn't
strike me as particularly elegant!). I also don't want to have to have
*everything* decorated with either @public or @private.
Thanks
-Mike
Re: login required with some public endpoints - better way
to do it?
- From:
- Simon Sapin
- Date:
- 2012-10-17 @ 09:38
Le 17/10/2012 11:25, Malphas Wats a écrit :
> Ideally, I'd like to be able to decorate public methods, so I can keep
> all of the code together, something like this
>
> @app.route('/my/public/route')
> @public_endpoint
> def my_public_thing_5():
> pass
Hi,
A decorator could add a "marker" attribute on the function:
def public_endpoint(function):
function.is_public = True
return function
Then, you can test if a function has this marker:
if getattr(function, 'is_public', False):
To get the function from the endpoint, the app maintains a dict:
app.view_functions[endpoint]
Putting it all together:
@app.before_request
def check_login():
if not ('logged_in' in session or
getattr(app.view_functions[request.endpoint],
'is_public', False)):
return ...
Cheers,
--
Simon Sapin
Re: login required with some public endpoints - better way to
do it?
- From:
- Malphas Wats
- Date:
- 2012-10-17 @ 15:29
On Wed, Oct 17, 2012 at 10:38 AM, Simon Sapin <simon.sapin@exyr.org> wrote:
> Putting it all together:
>
> @app.before_request
> def check_login():
> if not ('logged_in' in session or
> getattr(app.view_functions[request.endpoint],
> 'is_public', False)):
> return ...
Thanks Simon, This does the trick, with one small change - if a
request is made that generates a 404 (like the forever annoying
automatic request for /favicon.ico), it ends up without an endpoint,
which causes a KeyError in app.view_functions. I just added a check at
the start of the if to make sure there was an endpoint:
if request.endpoint and not ('logged_in' ...)
I have gone away and read a little more about decorators too - I had
the way they work wrong in my head a bit, thinking they only got
called when the function they were decorating actually got called, but
I realise now that they get evaluated at 'compile' time, so anything
they do is valid without the function ever having been called.
Thank you
-Mike
Re: login required with some public endpoints - better way to
do it?
- From:
- Kerem Ulutaş
- Date:
- 2012-10-17 @ 15:35
Take a look at Flask-Login extension:
http://packages.python.org/Flask-Login/
With the help of a decorator (
http://packages.python.org/Flask-Login/#how-it-works) you can easily
implement this functionality.
2012/10/17 Malphas Wats <malphas@subdimension.co.uk>
> On Wed, Oct 17, 2012 at 10:38 AM, Simon Sapin <simon.sapin@exyr.org>
> wrote:
>
> > Putting it all together:
> >
> > @app.before_request
> > def check_login():
> > if not ('logged_in' in session or
> > getattr(app.view_functions[request.endpoint],
> > 'is_public', False)):
> > return ...
>
> Thanks Simon, This does the trick, with one small change - if a
> request is made that generates a 404 (like the forever annoying
> automatic request for /favicon.ico), it ends up without an endpoint,
> which causes a KeyError in app.view_functions. I just added a check at
> the start of the if to make sure there was an endpoint:
>
> if request.endpoint and not ('logged_in' ...)
>
> I have gone away and read a little more about decorators too - I had
> the way they work wrong in my head a bit, thinking they only got
> called when the function they were decorating actually got called, but
> I realise now that they get evaluated at 'compile' time, so anything
> they do is valid without the function ever having been called.
>
> Thank you
> -Mike
>
--
Blog'umu okudunuz mu? http://www.ulutas.gen.tr
The box said "Requires Windows 95, NT, or better", so I installed Linux.
Re: login required with some public endpoints - better way to do it?
- From:
- Malphas Wats
- Date:
- 2012-10-17 @ 18:06
On Wednesday, October 17, 2012, Kerem Ulutaş <1151986@gmail.com> wrote:
> Take a look at Flask-Login extension:
> http://packages.python.org/Flask-Login/
>
> With the help of a decorator (
> http://packages.python.org/Flask-Login/#how-it-works) you can easily
> implement this functionality.
>
>
Thanks Kerem,
I've used the @login_required method previously, but by the time I've
finished the app, it ends up with all the methods decorated, which works,
but you have to remember to add the decorator. I ended up with some very
strange entries in my database when one of my users found a URL I had
forgotten to @require_login :)
What I was looking for (and have found) was a way to reverse it to
basically @dont_require_login :)
Re: login required with some public endpoints - better way
to do it?
- From:
- Simon Sapin
- Date:
- 2012-10-17 @ 15:48
Le 17/10/2012 17:29, Malphas Wats a écrit :
> I have gone away and read a little more about decorators too - I had
> the way they work wrong in my head a bit, thinking they only got
> called when the function they were decorating actually got called, but
> I realise now that they get evaluated at 'compile' time, so anything
> they do is valid without the function ever having been called.
Python decorators work like this:
@EXPR
def foo(...):
...
is the same as:
def foo(...):
...
foo = EXPR(foo)
where EXPR can be any Python expression, including one with a method
call like app.route(...)
Everything else about decorators can be inferred from here, but you can
find lots of very good explanations online.
Cheers,
--
Simon Sapin