librelist archives

« back to archive

login required with some public endpoints - better way to do it?

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: [flask] 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: [flask] 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: [flask] 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: [flask] 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