Identity Management for TurboGears
I just committed the code for the TurboGears identity management support (revision 89). And because this is such new code, I thought it might be helpful to include a short How To for getting everything up and running.
This How To is written from the perspective of a fresh quick-started project, but most everything applies for existing projects. 1. Create new project (idtest). Set dburi.
2. Edit idtest.egg-info/sqlobject.txt
db_module=idtest.model, turbogears.identity.model.somodel
3. Create login.kid
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'">
<head>
<meta content="text/html; charset=UTF-8"
http-equiv="content-type" py:replace="''"/>
<title>Login to TurboGears</title>
</head>
<body>
<h2>Login</h2>
<p>${message}</p>
<form action="${previous_url}" method="POST">
<label for="user_name">User Name:</label>
<input type="text" id="user_name" name="user_name"/><br/>
<label for="password">Password:</label>
<input type="password" id="password" name="password"/><br/>
<input type="submit" value="Login"/>
</form>
</body>
</html>
4. Create secured.kid
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'">
<head>
<meta content="text/html; charset=UTF-8"
http-equiv="content-type" py:replace="''"/>
<title>Welcome to Secured TurboGears</title>
</head>
<body>
<h2>Secure!</h2>
<p>This page is secured.</p>
</body>
</html>
5. Modify controllers.py
from turbogears import identity
import cherrypy
@turbogears.expose( html="idtest.templates.login" )
def login( self, *args, **kw ):
if hasattr(cherrypy.request,"identity_exception"):
msg= str(cherrypy.request.identity_exception)
else:
msg= "Please log in"
cherrypy.response.status=403
return dict( message=msg, previous_url=cherrypy.request.path )
@turbogears.expose( html="idtest.templates.secured" )
@identity.require( group="admin" )
def secured( self ):
return dict()
6. Turn on Identity management and configure failure url in dev.cfg
[global]
identity.on=True
identity.failure_url="/login"
7. Create the database
tg-admin sql create
8. Create a user and group
tg-admin shell
Python 2.4.1 (#2, Mar 31 2005, 00:05:10)
[GCC 3.3 20030304 (Apple Computer, Inc. build 1666)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from turbogears.identity.model.somodel import *
>>> hub.begin()
>>> u=User( userId="jeff", emailAddress="jeff@metrocat.org",
displayName="Jeff Watkins", password="xxxxx" )
>>> g=Group( groupId="admin", displayName="Administrators" )
>>> hub.commit()
>>>
9. Start project and visit secured page and login. Should fail with message:
Not member of group: admin
10. Add user to admin group
tg-admin shell
Python 2.4.1 (#2, Mar 31 2005, 00:05:10)
[GCC 3.3 20030304 (Apple Computer, Inc. build 1666)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from turbogears.identity.model.somodel import *
>>> hub.begin()
>>> u=User.get(1)
>>> g=Group.get(1)
>>> u.addGroup(g)
>>> hub.commit()
>>>
11. Revisit secured page and login. Should succeed.
Update: There is now a sample identity project which demonstrates this and some of the latest features of the identity framework. It’s actually the project I use to test features of the framework.
Comments
I am starting to work with turbogears and I am missing this feature to implement what I want.
This seems to be very well detailed tank you for your effort to formalize this how to. However I am still missing something how can I add this to my existing sqlite database. I think that I need to add something to my model.py and then run again tg-admin sql create. If my assumption is correct could you help me to find what exactly is the something?
Thank you
Very nice, thank you.
To preserve the session state after login I modified your login function from:
return dict( message=msg, previous_url=cherrypy.request.path )
To:
return dict( message = msg, previous_url = turbogears.url( cherrypy.request.path, cherrypy.request.paramMap ) )
Christopher, I’m glad that worked for you. I’m planning to update the How To with a better login form that does exactly what you suggested. I assume you use a py:for attribute on the template side of things?
I confess I don’t know Kid very well (and don’t like it much either).
Unfortunately, I’m not exactly certain what must be done to add the model to your existing database. You could try running the sql-admin commands directly. I know you can specify a module to sql-admin and it will create tables for the classes the module contains.
No, I left the login form unchanged. It was a GET request that triggered the login prompt, so just setting previous_url correctly key did the trick.
Question: how do I retrieve the userId in my application code? I can see the cookie parsing code in
identity_from_cookie()but I don’t really want to duplicate that, right?Actually you can access the entire User object by accessing
turbogears.identity.current.user. This gives you access to theuserId,displayName,emailAddress, and creation date.So if you have the proverbial welcome message:
Genius! Thank you.
So, if I wanted to access the users’ group info, how would I do that? For example, per group I’d like a different banner to be displayed. That should be rather simple, right?
Jared, there are two ways you can access the group information.
Via the current identity object:
Via the user object on the current identity:
Option number 2 only works if your using a Model that supports groups on the user object. So, with the default model you’ll be set. Other models might not work so well.
I’m not really certain how either option works in your Kid templates.
That’s the kind of information I like to see! Thanks!!
I’m planning on just sending a dictionary of user info to the kid templates.
Duplicate comments because I got a 500 Internal Server Error on the first try…
Just thought you’d like to know.
Hi, i’m trying to use you tutorial.
First point : if I follow your step the tg-admin sql create tells me there is nothing to do, So I fetch the somodel.py content and paste it into my model.py for sql create to actually create the table. Is this normal ? I thought the “db_module=idtest.model, turbogears.identity.model” would have include your model…
second point: I have my sql database, I get through the shell part I have a user which is in admin group. Then I start my qucikstart based app anf fetch http://localhost:8080/secured. I get “Not member of group: frozenset([’admin’])” then I type my user/pass and I get the same message again and again … How to debug or see any output there is no error but I never get logged in …
Thanks
Benoit, this seems to be another flaw in SQLObject. When I factored out the data model into a specific SQLObject model, this ran into a known design flaw in SQLObject.
If you are using the default model, you need to add turbogears.identity.model.somodel to the list of database modules. Sorry.
The second issue a bit more confusing. Perhaps you could email me (or post) the code for your controller…
I am actually having exactly the same problem as Benoit.
Adding turbogears.identity.model.somodel to the list of database modules did help, and the model db was created ok, but all i get at start is the “Not member of group: frozenset([‘admin’])” message, no matter what.
I am using trunk SVN checkout.
Ah ha! This is a bug I introduced when I added code to strip the submit button from the login form. It’s fixed in the latest SVN code. I apologise for the inconvenience. It seems the login page I posted doesn’t include a name for the login button (which should be login unless you specify otherwise in the config file).
The identity framework should allow you to leave the name off, but this bug was making it mandatory.
I had to replace ‘turbogears.identity.model’ with ‘turbogears.identity.model.somodel’ in the above set of steps to get it working. Other than that, it works great.
Oops, I just saw that the earlier comments have already mentioned that. Sorry for the noise.
Hi Jeff,
Silly question perhaps, but if I want to control the user attributes (and the table name used for the table), should I subclass the identify model(s) and include my own in their stead?
Hello, Does someone know how to logout a user ?
I’ve tried “identity.current.logout” as seen in the code but nothing hapen … :( if I reload the page the cookie hasn’t been erased and the user is still connected.
Next question does your system is based on cherryPy session ?
Thanks
Benoit, I wouldn’t expect the cookie to have been deleted after calling logout; however, I would expect the SecretToken to be invalid. I know this is a dumb question, but are you using the latest version of the code from SVN?
The Identity framework does not use CherryPy sessions. For the most part, sessions aren’t terribly scalable. And given that the identity framework has so little data, it doesn’t seem necessary. On the other hand, as someone else pointed out, not using sessions means we have to fetch all the user data on each request.
I think a post 1.0 feature of the framework should include an identity cache or something.
in which version of turbogears is your identity mangement integrated?
I use 0.8a4 and I can’t import it (”from turbogears import identity” fails).
The identity framework is present in the latest code in Subversion — sort of a pre-0.9.
Still stuck with my logout. I have the latest code from SVN, I’ve updated it this morning I get the same result.
What would the code look like for loging out a user ?
Thanks
Benoit, take a look at the sample project I’ve uploaded and let me know if this is roughly what you’ve been trying to do.
hummmm, how are your class for db (user/groups)?
If i do something like:
>>> from turbogears.identity.model.somodel import *
>>> hub.begin()
>>> u=User.get(1)
>>> g=Group.get(1)
>>> u.addGroup(g)
>>> u.addGroup(g)
>>> hub.commit()
Than i get a user with [’admin’, ‘admin’] groups right?
Some of the best documentation of tg admin that I have seen in a long while. One of the reasons that I avoid the Django crowd is because of a failure to focus on some of the deeper aspects of development.