A very nice, clean way to provide authorization for controller actions is system based on decorator usage.
put this code in /lib/decorators/authorize.py
1 2 3 4 5 6 7 8 9 10 11 | from decorator import decorator from someapp.lib.auth_conditions import NotValidAuth def authorize(valid, handler): def validate(func, self, *args, **kwargs): try: valid.check() except NotValidAuth, e: return handler(e) return func(self, *args, **kwargs) return decorator(validate) |
This is the main decorator that will be used to test permissions for specific actions. Basically it receives the conditions it has to check and a handler function reference to execute if the conditions are not met.
Ok, so we now have EVERYTHING needed to protect our actions (and controllers too, by decorating the _before_ method).
But now we need to know what type of permission we want to check, so we need to have some basic classes that will handle that for us.
So now we need to create the AllMet condition class and OneMet - those will handle every possible condition.
Now let's create /lib/auth_conditions/_init_.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | class NotValidAuth(Exception): pass class AllMet(object): message = u'All of these conditions have to be met: %s.' def __init__(self, *args): self.conditions = args def check(self): condition_messages = [] valid = True for condition in self.conditions: try: condition.check() except NotValidAuth, e: valid = False condition_messages.append(unicode(e)) if valid: return True raise NotValidAuth(self.message % ', '.join(condition_messages)) class OneMet(object): message = u'At least one of these conditions have to be met: %s.' def __init__(self, *args): self.conditions = args def check(self): condition_messages = [] valid = False for condition in self.conditions: try: condition.check() return True except NotValidAuth, e: condition_messages.append(unicode(e)) raise NotValidAuth(self.message % ', '.join(condition_messages)) class Not(object): message = u'All of these conditions have to be not met: %s.' def __init__(self, *args): self.conditions = args def check(self): condition_messages = [] valid = True for condition in self.conditions: try: condition.check() valid = False except NotValidAuth, e: condition_messages.append(unicode(e)) if valid: return True raise NotValidAuth(self.message % ', '.join(condition_messages)) |
From now on we can implement and check any condition we want to test.
Let's create a simple IsLogged condition - that will test if user is logged on to our app.
1 2 3 4 5 6 7 8 9 10 11 | from pylons import session from someapp.lib.auth_conditions import NotValidAuth class IsLogged(object): message = u'User must be logged' def check(self): if 'user' in session: return True raise NotValidAuth(self.message) |
Now let's put all this together to protect our controller to make it accessible only for logged users:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import pylons #bla bla bla, the usual stuff is here from someapp.lib.decorators.authorize import authorize from someapp.lib.auth_conditions import AllMet, OneMet from someapp.lib.auth_conditions.is_logged import IsLogged #this handler will get executed if condition is not met def handler(message): # let's do something here like throw redirect exception, # or maybe do a 401 class FooController(BaseController): @authorize(IsLogged(),handler) def __before__(self, controller, action): # often we need to remember to handle parent class __before__ super(FooController, self).__before__(controller, action) #another example how to protect edit_foo action @authorize(OneMet(IsModerator(), EditFooPerm()), handler) def edit_foo(self, controller, action): return "I'm protected" #another example how to protect edit_foo action @authorize(AllMet(HasMoney(), FooPerm()), handler) def buy_foo(self, controller, action): # action code |
Now this is all that is needed to protect your controllers and actions, now all you need to implement are your condition classes - for example use sqlalchemy to check group rights etc.
Another common use of our classes includes checking permissions INSIDE actions. This can be accomplished by simple try catch blocks we can use.
1 2 3 4 5 6 7 8 9 10 11 12 13 | from pylons import session from someapp.lib.auth_conditions import NotValidAuth from someapp.lib.auth_conditions.new_cond import NewCond class FooController(BaseController): #lets try to fetch the data based on user status def edit_foo(self, controller, action): try: NewCond().check() #do something here if user rights are sufficient (maybe admin user?) except (NotValidAuth, ),e: #do something else, maybe do ACL check or render simplified template? |