views:

278

answers:

2

In this post, Nick suggested a decoartor:

http://stackoverflow.com/questions/1499832/python-webapp-google-app-engine-testing-for-user-pass-in-the-headers/1500047#1500047

I'm writing an API to expose potentially dozens of methods as web-services, so the decorator sounds like a great idea.

I tried to start coding one based on this sample: http://groups.google.com/group/google-appengine/browse%5Fthread/thread/ac51cc32196d62f8/aa6ccd47f217cb9a?lnk=gst&q=timeout#aa6ccd47f217cb9a

I need it compatible with Python 2.5 to run under Google App Engine (GAE).

Here's my attempt. Please just point the way to if I'm on the right track or not. Currently getting an error "Invalid Syntax" on this line: class WSTest(webapp.RequestHandler):

My idea is to pass an array of roles to the decorator. These are the only roles (from my db that should have access to each various web service).

def BasicAuthentication(roles=[]):
  def _decorator(func):
    def _wrapper(*args, **kwds): 
        logging.info("\n\n BasicAuthentication:START:__call__ \n\n") 
        auth = None 
        if 'Authorization' in self.request.headers: 
           auth = self.request.headers['Authorization']
        if not auth:
           self.response.headers['WWW-Authenticate'] = 'Basic realm="MYREALM"'
           self.response.set_status(401)
           self.response.out.write("Authorization required")
           logging.info ("\n\n  Authorization required \n\n") 
           return 

        (username, password) = base64.b64decode(auth.split(' ')[1]).split(':')
        logging.info ("\n\n username = " + username + "  password=" + password + "\n\n")         

        isValidUserPass = False 
        usersSimulatedRole = "Admin" 
        #check against database here...  
        if user == "test12" and password == "test34":
           isValidUserPass = True  
        isValidRole = False 
        if usersSimulatedRole in roles:
           isValidRole = True 
        #next check that user has one of the roles 
        #  TODO 

        if not isValidUserPass:
           self.response.set_status(403)
           self.response.out.write("Forbidden: Userid/password combination failed")

        logging.info("\n\n BasicAuthentication:END:__call__ \n\n") 
        return func(*args, **kwds) 
    return _wrapper
  return _decorator 


@BasicAuthentication(["Admin","Worker"])   #list of roles that can run this function 
class WSTest(webapp.RequestHandler):
  def get(self):
     logging.info("\n\n\n WSTest \n\n") 
     ...etc...

Thanks, Neal Walters

+2  A: 

Class decorators were added in Python 2.6.

You'll have to manually wrap the class or think of another solution to work under 2.5. How about writing a decorator for the get method instead?

lost-theory
Or just have `get` call a function or base-class-method to do the auth, or use a middleware? I don't really see what the big advantage of decorators is to this use-case.
bobince
Yep those are both good options. I like decorators because it's a nice declarative syntax (easy to read) for annotating functions / classes with extra behavior... But there are many strategies that give the same result.
lost-theory
+1  A: 

You need to write a method decorator, not a class decorator: As lost-theory points out, class decorators don't exist in Python 2.5, and they wouldn't work very well in any case, because the RequestHandler class isn't initialized with request data until after it's constructed. A method decorator also gives you more control - eg, you could allow GET requests unauthenticated, but still require authentication for POST requests.

Other than that, your decorator looks fine - just apply it to the relevant methods. The only change I would really suggest is replacing the .set_status() calls with .error() calls and remove the response.write calls; this allows you to override .error() on the RequestHandler class to output a nice error page for each possible status code.

Nick Johnson