Flat User Management in Zope

Many applications are surprisingly easy to create in Zope. This article will teach you how to create a simple user database using Zope objects for data storage.

Introduction

Today, many websites feature a database of users. Viewers can register accounts required to access privileged areas of the website. For example, a clan could reserve pages on game tactics for members of the clan, rather than anyone and everyone who just might happen to come across their page.

This article will teach you how to create a simple user database using Zope objects for data storage rather than a relational database. You will learn how to create a registration system, a login system and a verification system to handle access to privileged pages. We will use Zope Page Templates and Python to do this.

The Blueprints

First, we will build a registration system. Users will access the registration Zope Page Templates and fill out a form with their desired username and password. After submitting the form, the data will be handled by a Script ( Python ) object. The script will check to see if the username submitted already exists. If it doesn’t, a file will be created with the user’s username as the filename. A password property will be added to the file containing their hashed password.

A login system will then need to be built. Users will access the login Zope Page Template and enter their username and password in a form. When they submit the form, their username and password will be sent to a Script ( Python ) object, which will try it against the username’s file. If the correct password is provided, two cookies will be set: a cookie containing the user’s username and a cookie containing the user’s hashed password.

When a user attempts to access a private page, the username and password provided in their cookies will be tried against the username’s file. If the password matches, the user will be permitted to access the file.

{mospagebreak title=Registration}

We’ll first create the Zope Page Template containing the login form. The page will also need to check whether the user has already submitted data. This will be done using a Script ( Python ) object –- the same object which will be responsible for creating the accounts of users.

Create a file named register_html and insert the following code into it:

<html>
   <head>
      <title>Register</title>
   </head>
   <body tal:define=’regStatus container/register’>
      <span tal:condition=’regStatus’>
         <span tal:replace=’regStatus’></span>
      </span>
      <span tal:condition=’not:regStatus’>
         <form method=’POST’>
            <label>Desired Username:</label><br />
            <input type=’text’ name=’username’><br />
            <label>Password:</label><br />
            <input type=’text’ name=’password’><br />
            <label>E-Mail:</label></br />
            <input type=’text’ name=’email’><br />
            <input type=’submit’ value=’Register’>
         </form>
      </span>
   </body>
</html>

The file first defines a variable named “regStatus” with a value based upon the result of a file named register. If the file returns a non-zero value, the value is displayed. If it doesn’t, the registration form is displayed.

Before we create the register file, we need to create an external method. Since Zope forbids Script ( Python ) objects from using the “md5″ module to hash data such as user passwords, we need to create a separate script to do this for us. External methods can contain any code. Fortunately, external methods are a breeze to create and use. In your Zope instance’s folder ( not the document root ), there should be a folder called Extensions. In this folder, create a file named hash.py with this as its contents:

import md5

def hash ( text ):
   password = md5.new()
   password.update ( text )
   password = password.hexdigest()
   return password

This will use the “md5″ module to hash data given to it.

Next, create an External Method object in the same directory as our registration page.

We’re now ready to move on. First, create a Folder object called users. This will store user data. Next, create a Script ( Python ) object named register. Add the following data to it:

if context.REQUEST.has_key ( ‘username’ ) and context.REQUEST.has_key ( ‘password’ ) and context.REQUEST.has_key ( ‘email’ ):
   if hasattr ( container.users, context.REQUEST.username ):
      return ‘Sorry, someone else has registered with your username.’
   password = container.hash ( context.REQUEST.password )
   container.users.manage_addProduct [ 'OFSP' ].manage_addFile ( context.REQUEST.username )
   userObject = getattr ( container.users,context.REQUEST.username )
   userObject.manage_addProperty ( ‘password’, password, ‘string’ )
   userObject.manage_addProperty ( ‘email’, context.REQUEST.email, ‘string’ )
   return ‘Your account has been created.’
return False

The script first checks to see if the user has filled in the required values. If the user hasn’t, the script returns “False” and ends execution. It then checks to see if the username has been taken. If it has, it returns a message saying so and ends execution. Finally, if everything has gone right, it sends the hash object the user’s password. The hash object hashes the user’s password and returns it. The script then adds a File object with the user’s username as its filename. A property called password is added to the File object containing the user’s password. An property for the user’s email is also added. The script then returns a success message.

The registration system is now complete. If you have followed the article correctly, you can now test it out.

{mospagebreak title=Login}

We now need to create a login system that allows the user to login with his or her username and password. Create a Zope Page Template object named login_html. The login_html object is almost identical to the register_html object in the previous section:

<html>
   <head>
      <title>Login</title>
   </head>
   <body tal:define=’logStatus container/login’>
      <span tal:condition=’logStatus’>
         <span tal:replace=’logStatus’></span>
      </span>
      <span tal:condition=’not:logStatus’>
         <form method=’POST’>
            <label>Username:</label><br />
            <input type=’text’ name=’username’><br />
            <label>Password:</label><br />
            <input type=’password’ name=’password’><br />
            <input type=’submit’ value=’Login’>
         </form>
      </span>
   </body>
</html>

However, instead of calling the register object, it calls an object named login. Create a Script ( Python ) object called login:

if context.REQUEST.has_key ( ‘username’ ) and context.REQUEST.has_key ( ‘password’ ):
   if not hasattr ( container.users, context.REQUEST.username ):
      return ‘No such user exists.’
   password = container.hash ( context.REQUEST.password )
   userObject = getattr ( container.users,context.REQUEST.username )
   if password != userObject.password:
      return ‘You have specified an incorrect password.’
   context.REQUEST.RESPONSE.setCookie ( ‘username’, context.REQUEST.username )
   context.REQUEST.RESPONSE.setCookie ( ‘password’, password )
   return ‘You have successfully logged in!’
return False

Like the register object, the login object first checks to see if the user has submitted the appropriate values. If the user hasn’t, the script returns “False” and stops execution. This signals the login_html object to display the login form. If the user has supplied the appropriate values, the script checks to see if the username even exists. If the username does not exist, the script returns a message saying so. If the username does exist, the script uses the hash object to, once again, hash the user’s password. It then check to see if the supplied password and the stored password match. If they do, it sets one cookie containing the user’s username, and another cookie containing the user’s hashed password. It then returns a success message.

The login system is now done, and it will work in conjunction with the registration system. Feel free to test both systems out.

{mospagebreak title=Verification}

Our final step in creating our user database is to create a system that will challenge the user’s username and password when he or she tries to access a restricted page, permitting him or her to view the page only if their username and password match the stored data. This is easily done by checking the user’s cookies. Create a Script ( Python ) object named verify with the following code:

if context.REQUEST.has_key ( ‘username’ ) and context.REQUEST.has_key ( ‘password’ ):
   if not hasattr ( container.users, context.REQUEST.username ):
      return False
   userObject = getattr ( container.users,context.REQUEST.username )
   if context.REQUEST.password != userObject.password:
      return False
   userData = { ‘email’ : userObject.email }
   return userData
return False

First, the script checks to see if the two cookies containing the user’s username and password are set. If they are, the script moves on to check if the supplied uesrname exists. If it does, the script checks the passwords against each other. If they match, a dictionary is returned containing the user’s email address.

We’ll now create a page to test out our system. Create a Zope Page Template object named private_html:

<html>
   <head>
      <title>Private Area</title>
   </head>
   <body tal:define=’verifyStatus container/verify’>
      <span tal:condition=’not:verifyStatus’>
         Uh oh! You don’t have permission to access this area!
      </span>
      <span tal:condition=’verifyStatus’>
         Welcome to the ultra-secret club.<br /><br />
         Your e-mail address is:<br />
         <span style=’font-weight: bold;
              ‘tal:content=’verifyStatus/email’></span>
      </span>
   </body>
</html>

The page calls the verify object and stores the data returned in a variable called verifyStatus. If the user supplied the correct information, verifyStatus will be a dictionary containing his or her email address. The email address is printed if the user has access. If not, the user is presented with an error message.

Conclusion

Our system is now complete. If you wish, you can add a field to the registration form asking for other details. This can be accomplished in the same way we added a field and property for the user’s email address. Don’t forget to add the property to the dictionary in verify.

As you probably noticed, user management systems are suprisingly easy to create in Zope, as are many other types of applications. A user database could also be implemented using a relational database, such as MySQL. You are not restricted to using flat files for storage.

Good luck!

Google+ Comments

Google+ Comments