Dalke Scientific Software: More science. Less time. Products

Logins and authentication

Now you have a taxonomy browser that anyone can edit. Is that what you really want? Is it okay for someone to add "puddy cat" to the Felis silvestris (tax id 9683) entry? Or add "buy Viagr* at example.com" to the entry for humans? The answer could be yes if the application is only accesible by a few people. But if you expect it will be popular in the future then you need to support security early in the development process.

TurboGears comes with a simple, flexible identity authentication system. You don't have to use it if you don't want to - it's just a lot easier. Unfortunately it isn't quite as simple as saying "enable user authentication" and everything works. It needs a data model in the database and a few other configuration changes to work.

The easiest way to get started with it is to redo the TurboGears quickstart. I had hoped I could write over my existing project and use subversion to figure out my changes but that didn't work. Here's what happened when I tried it:

[~/nbn/taxonomy] % tg-admin quickstart TaxonomyServer
Enter package name [taxonomyserver]: 
Do you need Identity (usernames/passwords) in this project? [no] yes
A directory called 'TaxonomyServer' already exists. Exiting.
Instead I renamed the existing project directory to "TaxonomyServer.old", did the new quickstart, and renamed the directories a bit more:
[~/nbn/taxonomy] % mv TaxonomyServer TaxonomyServer.old 
[~/nbn/taxonomy] % tg-admin quickstart TaxonomyServer
Enter package name [taxonomyserver]: 
Do you need Identity (usernames/passwords) in this project? [no] yes
 ... lots of output removed ...
[~/nbn/taxonomy] % mv TaxonomyServer TaxonomyServer.tmp 
[~/nbn/taxonomy] % mv TaxonomyServer.old/ TaxonomyServer

NOTE! You do not need to do this. You can start a brand new project and copy in only those parts from the old project that you want. I'm showing you all this so you have some idea of how to do this sort of thing.

I used 'diff' to figure out the differences between the two directories. To minimize the number of differences I removed some Python byte-compiled files (the .pyc files) and my editor's backup files

[~/nbn/taxonomy] % rm TaxonomyServer/*.pyc
[~/nbn/taxonomy] % rm TaxonomyServer/*/*.pyc
[~/nbn/taxonomy] % rm TaxonomyServer/*~
[~/nbn/taxonomy] % rm TaxonomyServer/*/*~
[~/nbn/taxonomy] % rm TaxonomyServer/*/*/*~
[~/nbn/taxonomy] % diff -r TaxonomyServer.tmp TaxonomyServer | less
It changed two files that I had never modified from the original quickstart so I copied the new files over into the existing project
[~/nbn/taxonomy] % cp TaxonomyServer.tmp/taxonomyserver/config/app.cfg TaxonomyServer/taxonomyserver/config/app.cfg 
[~/nbn/taxonomy] % cp TaxonomyServer.tmp/taxonomyserver/json.py TaxonomyServer/taxonomyserver/json.py 
You should read the 'app.cfg' file to see what it does.

It did not create any new files so the remaining differences are changes to dev.cfg, controllers.py and model.py.

There's only one change to 'dev.cfg' which is to revert my change to the database filename. I'll keep my edited file.

In controllers.py it did a "from turbogears import identity" and added two new functions, "login" and "logout". I'll copy them over into my project's controller.py. NOTE: your login and logout interface can look different than this.

The biggest change was to model.py to add the data model for identity and visit tracking. I copied the five new classes to the bottom of my model.py file. I also added

from datetime import datetime
from turbogears import identity
at the top of model.py

With that I restarted the server and got it working again. I reloaded one of my taxonomy browser pages and noticed the "Login" button on the upper right. That takes me to a fancy login window, but of course I have no account on the machine.

The User/Group data model

The TurboGears account configuration information is stored in tables in your database. If they don't already exist then TG will add them for you.

The model is pretty simple. Permissions are given to groups and not to individual people. People can be in multiple groups, because people may have multiple roles in a project. Using groups simplifies administration because usally there are many fewer distinct roles on a project than there are people. For example, you can have groups like "curator" and "administrator", each with special privledges. Some people may be both curators and administrators while others may only have one, or no roles to play.

TurboGears also allows anonymous users.

I'm going to create a group named "curator" and add myself as a user in the group. If you can get the TurboGears toolbox working this would be very easy in CatWalk because it's all GUI driven. Instead I'll do it the old-fashioned way: type it.

>>> from model import Group, User
>>> from model import hub
>>> g = Group(group_name="curators", display_name="Taxonomy Curators")
>>> g
<Group 1 group_name=u'curators' display_name=u'Taxonomy Curators'
>>> u = User(user_name="dalke", display_name="Andrew Dalke",
...     password="12345", email_address="dalke@dalkescientific.com")
2006-07-24 00:02:59,107 turbogears.identity.soprovider INFO Succesfully loaded "taxonomyserver.model.User"
2006-07-24 00:02:59,109 turbogears.identity.soprovider INFO Succesfully loaded "taxonomyserver.model.Group"
2006-07-24 00:02:59,111 turbogears.identity.soprovider INFO Succesfully loaded "taxonomyserver.model.Permission"
2006-07-24 00:02:59,114 turbogears.identity.soprovider INFO Succesfully loaded "turbogears.identity.soprovider.TG_VisitIdentity"
2006-07-24 00:02:59,116 turbogears.identity.soprovider INFO Identity provider not enabled, and no encryption algorithm specified in config.  Setting password as plaintext.
>>> u
<User 1 user_name=u'dalke' email_address="u'dalke@dalkescie...'" display_name=u'Andrew Dalke' password=u'12345' created='datetime.datetime...)'>
>>> g.addUser(u)
>>> hub.commit()

Now I'll see if it works. Head back to the login window ... Cool beans! I'm logged in and the upper right-hand corner says "Welcome Andrew Dalke. Logout".

Where does that bit of text come from? Look in the master.kid template for the answer. Again, your interface doesn't need to look like this. You could, for example, ask for the username and password on the corner of every page and not use a special login page.

Because you can change the user/group table just like changing anything else in the database it's straight-forward to add a registration page, password administration page, etc. in TurboGears.

Restricting pages

The authentication system uses the decorator @identity.require to annotate functions and restrict them to a given group, a set of groups, a host name, and a few other possibilities. I'll modify the controller.py file to say that 'edit_alises' and 'update_aliases' can only be accessed by members of the 'curator' group.

    def edit_aliases(self, tax_id):
        return dict(taxon=Taxonomy.get(tax_id))

    def update_aliases(self, tax_id, alias=[]):
        # one input alias -> a string
        # multiple input aliases -> a list of strings
        if isinstance(alias, basestring):
            aliases = [alias]
            aliases = alias
It worked (after I fixed a couple of mistakes in what I did). That is, I can go to those pages without problems when I'm logged in as "dalke" but cannot when I'm logged out.

I'm not quite done yet. If I'm not logged in, or I'm not a member of the "curators" group then I don't want to see the "edit aliases" link on the detail page. I'll change the template for that.

The current identity information is always accessible via the turbogears.identity.current object. This works even on multi-threaded systems by using thread-local variables. Don't worry if you don't know what that means. To get the groups for the logged-in person use "current.groups" and for the actual user record use "current.user". (For example, the person's name is "current.user.display_name".)

This is a Python object so I can pass it in through the template's dictionary or have the template get the object itself. The documentation recommends the latter.

A template can embed Python code by using the special <? syntax ?>. Put the following somewhere near the top of the details.kid template. I put it just after the <body> tag:

<?python from turbogears import identity ?>
Now the template can use 'curators' in identity.current.groups as the test to see if the current user is allowed to see the edit page. Here's the latest code to show the list of aliases and only show curators the link to 'edit_aliases'. I'm showing the first part of the HTML body so you can see things in context:

<?python from turbogears import identity ?>

<a href="/">Start a new search</a>
Taxon identifier: ${taxon.id}<br />
Genetic Code: ${taxon.genetic_code.name}<br />
Aliases: ${", ".join([alias.alias for alias in taxon.aliases])}
<span py:if="'curators' in identity.current.groups" py:strip="True">
[<a href="/edit_aliases?tax_id=${taxon.id}">edit aliases</a>]
</span>${identity.current.user.display_name}<br />

Try it out. Give it a whirl.

Visit tracking

When you enable logins you also enable visit tracking. To turn it off or configure it edit the file config/app.cfg under your application directory. (That's the one with model.py in it.) Every person visiting your server gets a cookie. That's a bit of text passed back and forth between the browser and the server. If the server gets an old cookie back in less than 20 minutes then it assumes the user is still active and it returns an updated cookie.

TG stores the tracking information in the database so you can see who logged in and roughly for how long. Here's one example:

sqlite> select * from tg_visit;
1|1c4db003925d9bdaa7f5c094d1799b6bd4be85ef|2006-07-23 23:45:50|2006-07-24 00:41:19
sqlite> select * from tg_visit_identity;
The first table says that a user first visited at 11:45pm and was still active at 12:21am (the second timestamp is the expiration, which is configured to be 20 minutes after the last visit). The second table says the vistor was user 1 (that's me!).


One last thing. TurboGears' security model is a bit more flexible than what I described. Users are members of groups and groups have permissions, which are strings. Often these are things like "read", "write", and "edit" which break things up by function. Why? Consider a curational database with quarterly releases. When doing the release you may want to shut off write access to the database to keep it stable. It's easier to remove "edit" permission from the "curators" group than it is to move everyone to a different group.

Copyright © 2001-2013 Andrew Dalke Scientific AB