2005-09-09 19:34

Authenticated Pages in CherryPy

CherryPy is a cool, pythonic, simple, quick, fun way to write web applications.

I often use CherryPy to write custom web admin tools for customers. Suppose you want to provide them with a simple way for password management. Usually I have the following requirements:

  • It must be simple ( not webmin )
  • It must not be a terminal session (bye ssh :-( )
  • It must not be a graphical session (sadly, that leaves out PyQt :-( )
  • It needs to do custom stuff: set the samba password at the same time, send a mail warning about the next forced change, whatever.

Someday I may be able to use a single-app freeNX session, but right now that's a bit too much problem for different reasons.

So, I wrote a CherryPy page. Over time, I have become quite fond of it, and wrote a bunch of small tools around it. One of them was a way to login the user into the site using the system's users and passwords. Now I got to throw it away :-)

The new CherryPy 2.1 has a mechanism for implementing password-protected pages, called the Session authenticate filter which is sadly not documented yet anywhere I can find.

So, here is my attempt, so people googling it up can use it. Excuse me:

cherrypy sessionauthenticatefilter cherrypy sessionauthenticatefilter cherrypy sessionauthenticatefilter cherrypy sessionauthenticatefilter cherrypy sessionauthenticatefilter cherrypy sessionauthenticatefilter cherrypy sessionauthenticatefilter cherrypy sessionauthenticatefilter cherrypy sessionauthenticatefilter cherrypy sessionauthenticatefilter

That should do it :-)

What you need first is a function that takes a username and password, and returns None on success, or an error message for the failure.

For example, I can adapt something I wrote earlier using checkpassword-pam

def validPass(name,password):
                cmd='/usr/bin/checkpassword-pam -s xdm -- /bin/true 3<&0'
                p=os.popen(cmd,'w')
                s='%s\000%s\000xxx\000'%(name,password)
                print cmd,s
                p.write(s)
                r=p.close()
                if r==None: #Success
                        return None
                else:
        return "Login Incorrect"

Also, you may want a function that returns the login screen. If you do, remember the following:

  1. It must set the form action to doLogin
  2. The user field should be called login
  3. The password field should be called password
  4. You will take a fromPage argument that you should pass through, so the user will end on the page he wants.
  5. You will take a errorMsg argument which is probably the result of a failed previous login. Display it red or something like it. Unless it's empty, in which case it should not be visible.

Here's mine.

def loginScreen(fromPage, login = '', errorMsg = ''):
content="""
<form method="post" action="doLogin">
<div align=center>
        <span class=errormsg>%s</span><p>
        <table >
        <tr>
        <td>
                Login:
        <td>
                <input type="text" name="login" value="%s" size="40"/>
        <tr>
        <td>
                Password:
        <td>
        <input type="password" name="password" size="40"/>
        <input type="hidden" name="fromPage" value="%s"/>
        <tr>
        <td colspan=2 align=right>
        <input type="submit" value="Login" />
        </table>
        </div>
</form>
""" % (errorMsg, login, fromPage)
title='Login'
return renderTemplate(file='logintemplate.html')

Although I am using a template to display it nicely and with the right style, it should be pretty obvious how it works.

You could use the default login screen provided by the filter. While it works, it's just ugly.

Then you need to apply the filter to the set of your pass-protected pages. Suppose you want the whole site to be protected, except for your /static directory, which contains the stylesheet, images and such. Then you put this in your configuration file:

[/]
sessionAuthenticateFilter.on=True

[/static]
sessionAuthenticateFilter.on=False

Next thing is to hook the session authenticate filter to your custom auth code. In your app, do the following. It seems that you can't do this in the config file, though, so do it in code.

    settings={
            '/': {
                                    'sessionAuthenticateFilter.checkLoginAndPassword': validPass,
                'sessionAuthenticateFilter.loginScreen':loginScreen
        }
}
cherrypy.config.update(settings)

And that's it. Now your site is password protected. You can even have different authentication schemes for different pieces of the site, by resetting the hooks for the folder you prefer to a different function.

Also, I think this is a good example of why I like CherryPy. This mechanism is both flexible, powerful and simple.

Comments

Comments powered by Disqus

Contents © 2000-2018 Roberto Alsina