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?

Greg
  • 45,306
  • 89
  • 231
  • 297
  • 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: http://www.cherrypy.org/wiki/BuiltinTools – Wolph Jul 21 '10 at 19:12
  • I don't think I want to use anything built-in. We have some custom data stores I need to check against, etc. – Greg Jul 21 '10 at 19:14

2 Answers2

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.
The Tahaan
  • 6,915
  • 4
  • 34
  • 54
fumanchu
  • 14,419
  • 6
  • 31
  • 36
  • 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. – Greg Jul 21 '10 at 23:08
  • 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. – fumanchu Jul 23 '10 at 06:14
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
Wolph
  • 78,177
  • 11
  • 137
  • 148
  • 2
    What does @functools.wraps do? Is that built into cherrypy? – Greg Jul 21 '10 at 19:21
  • 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? – Greg Jul 21 '10 at 19:23
  • 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. – Wolph Jul 21 '10 at 19:37
  • I'm on Python 2.4 so I don't have functools. Is there a workaround? – Greg Jul 21 '10 at 19:55
  • You can simply copy the `__name__`, `__module__` and `__doc__` properties to your function. It seems that fumanchu has a more fitting solution though. – Wolph Jul 21 '10 at 22:21
  • Seems like with python 2.7 you need "@functools.wraps(f)" there instead. – Michael Feb 11 '15 at 20:25
  • @michael: my answer was written in the text box here without testing, must have missed that. Thanks for the heads up – Wolph Feb 11 '15 at 21:22