Writing a CherryPy Decorator for Authorization
Asked Answered
C

2

6

I have a cherrypy application and on some of the views I want to start only allowing certain users to view them, and sending anyone else to an authorization required page.

Is there a way I can do this with a custom decorator? I think that would be the most elegant option.

Here's a basic example of what I want to do:

class MyApp:
    @authorization_required
    def view_page1(self,appID):
        ... do some stuff ...
        return html

def authorization_required(func):
    #what do I put here?

Also can the authorization_required function when called as a decorator accept parameters like allow_group1, allow_group2? Or do I need a separate decorator for each group?

Ctenophore answered 21/7, 2010 at 19:8 Comment(2)
All those are possible, but what kind of authentication system are you using. Or planning on using? CherryPy has a couple of authentication methods build-in: cherrypy.org/wiki/BuiltinToolsLeila
I don't think I want to use anything built-in. We have some custom data stores I need to check against, etc.Ctenophore
L
4

Ok, in that case your decorator would look something like this:

# without any parameters
def authentication_required(f):
    @functools.wraps(f)
    def _authentication_required(*args, **kwargs):
        # Do you login stuff here
        return f(*args, **kwargs)
    return _authentication_required

# With parameters
def authentication_required(*allowed_groups):
    def _authentication_required(f):
        @functools.wraps(f)
        def __authentication_required(*args, **kwargs):
            # Do you login stuff here
            return f(*args, **kwargs)
        return __authentication_required
    return _authentication_required
Leila answered 21/7, 2010 at 19:18 Comment(7)
What does @functools.wraps do? Is that built into cherrypy?Ctenophore
Also, I guess I only return f(*args, **kwargs) when the login stuff has succeeded? If on the other hand the user is not authorized, would I call a cherry.redirect instead of returning?Ctenophore
Yes, correct. And the functools.wraps is a method that handles automatic copying of the function name, docs and other data when writing decorators. That way if you do help(method) on a decorated method, you still get the originals docs.Leila
I'm on Python 2.4 so I don't have functools. Is there a workaround?Ctenophore
You can simply copy the __name__, __module__ and __doc__ properties to your function. It seems that fumanchu has a more fitting solution though.Leila
Seems like with python 2.7 you need "@functools.wraps(f)" there instead.Christianity
@michael: my answer was written in the text box here without testing, must have missed that. Thanks for the heads upLeila
E
15

You really don't want to be writing custom decorators for CherryPy. Instead, you want to write a new Tool:

def myauth(allowed_groups=None, debug=False):
    # Do your auth here...
    authlib.auth(...)
cherrypy.tools.myauth = cherrypy.Tool("on_start_resource", myauth)

See http://docs.cherrypy.org/en/latest/extend.html#tools for more discussion. This has several benefits over writing a custom decorator:

  1. You get the decorator for free from the Tool: @cherrypy.tools.myauth(allowed_groups=['me']), and it already knows how to not clobber cherrypy.exposed on the same function.
  2. You can apply Tools either per-handler (with the decorator), per-controller-tree (via _cp_config) or per-URI-tree (in config files or dicts). You can even mix them and provide a base feature via decorators and then override their behavior in config files.
  3. If a config file turns your feature off, you don't pay the performance penalty of calling the decorator function just to see if it's off.
  4. You'll remember to add a 'debug' arg like all the builtin Tools have. ;)
  5. Your feature can run earlier (or later, if that's what you need) than a custom decorator can, by selecting a different "point".
  6. Your feature can run at multiple hook points, if needed.
Extenuatory answered 21/7, 2010 at 21:35 Comment(2)
I'm not sure if this would work for me because this code is using the RoutesDispatcher (instead of?) the expose decorator. I'm not sure why it was done that way.Ctenophore
Which dispatcher you use is not really an issue: any dispatcher worth its salt (including the Routes one) will work with Tools. The expose decorator does nothing more than set method.exposed = True. Again, this is required for all dispatchers and has no bearing on whether or not Tools work. Take it from the horse's mouth: Tools are the way to do this in CherryPy.Extenuatory
L
4

Ok, in that case your decorator would look something like this:

# without any parameters
def authentication_required(f):
    @functools.wraps(f)
    def _authentication_required(*args, **kwargs):
        # Do you login stuff here
        return f(*args, **kwargs)
    return _authentication_required

# With parameters
def authentication_required(*allowed_groups):
    def _authentication_required(f):
        @functools.wraps(f)
        def __authentication_required(*args, **kwargs):
            # Do you login stuff here
            return f(*args, **kwargs)
        return __authentication_required
    return _authentication_required
Leila answered 21/7, 2010 at 19:18 Comment(7)
What does @functools.wraps do? Is that built into cherrypy?Ctenophore
Also, I guess I only return f(*args, **kwargs) when the login stuff has succeeded? If on the other hand the user is not authorized, would I call a cherry.redirect instead of returning?Ctenophore
Yes, correct. And the functools.wraps is a method that handles automatic copying of the function name, docs and other data when writing decorators. That way if you do help(method) on a decorated method, you still get the originals docs.Leila
I'm on Python 2.4 so I don't have functools. Is there a workaround?Ctenophore
You can simply copy the __name__, __module__ and __doc__ properties to your function. It seems that fumanchu has a more fitting solution though.Leila
Seems like with python 2.7 you need "@functools.wraps(f)" there instead.Christianity
@michael: my answer was written in the text box here without testing, must have missed that. Thanks for the heads upLeila

© 2022 - 2024 — McMap. All rights reserved.