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.

26 Comments

  1. Very Interesting
    Posted 24 Oct 2005 at 1:27 pm | Permalink

    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

  2. Christopher Kelly
    Posted 25 Oct 2005 at 4:29 pm | Permalink

    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 ) )

  3. Posted 25 Oct 2005 at 6:37 pm | Permalink

    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).

  4. Posted 25 Oct 2005 at 6:38 pm | Permalink

    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.

  5. Christopher Kelly
    Posted 25 Oct 2005 at 10:02 pm | Permalink

    I assume you use a py:for attribute on the template side of things?

    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?

  6. Posted 25 Oct 2005 at 10:09 pm | Permalink

    Actually you can access the entire User object by accessing turbogears.identity.current.user. This gives you access to the userId, displayName, emailAddress, and creation date.

    So if you have the proverbial welcome message:

    from turbogears import identity
    @turbogears.expose( html="welcome.kid" )
    def welcome( self ):
        return dict( user_name=identity.current.user.user.displayName )
    
  7. Christopher Kelly
    Posted 25 Oct 2005 at 10:56 pm | Permalink

    Genius! Thank you.

  8. Posted 27 Oct 2005 at 2:41 pm | Permalink

    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?

  9. Posted 27 Oct 2005 at 4:10 pm | Permalink

    Jared, there are two ways you can access the group information.

    1. Via the current identity object:

      from turbogears import identity
      if 'admin' in identity.current.groups:
          pass
      
    2. Via the user object on the current identity:

      from turbogears import identity
      if 'admin' in [g.groupId for g in identity.current.user.groups]:
          pass
      

    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.

  10. Posted 28 Oct 2005 at 4:45 pm | Permalink

    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.

  11. Posted 28 Oct 2005 at 4:47 pm | Permalink

    Duplicate comments because I got a 500 Internal Server Error on the first try…

    Just thought you’d like to know.

  12. Benoit Masson
    Posted 31 Oct 2005 at 10:41 pm | Permalink

    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

  13. Posted 1 Nov 2005 at 6:36 pm | Permalink

    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…

  14. eli yukelzon
    Posted 2 Nov 2005 at 5:31 am | Permalink

    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.

  15. Posted 2 Nov 2005 at 8:23 am | Permalink

    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.

  16. Posted 2 Nov 2005 at 10:28 am | Permalink

    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.

  17. Posted 2 Nov 2005 at 10:32 am | Permalink

    Oops, I just saw that the earlier comments have already mentioned that. Sorry for the noise.

  18. Garrett Smith
    Posted 2 Nov 2005 at 3:37 pm | Permalink

    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?

  19. Posted 6 Nov 2005 at 8:21 am | Permalink

    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

  20. Posted 6 Nov 2005 at 8:25 am | Permalink

    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.

  21. joerg
    Posted 6 Nov 2005 at 8:51 am | Permalink

    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).

  22. Posted 6 Nov 2005 at 9:05 am | Permalink

    The identity framework is present in the latest code in Subversion — sort of a pre-0.9.

  23. Posted 12 Nov 2005 at 9:03 pm | Permalink

    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

  24. Posted 13 Nov 2005 at 9:16 am | Permalink

    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.

  25. sharky
    Posted 7 Dec 2005 at 7:21 am | Permalink

    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?

  26. DWSmall
    Posted 27 Jun 2007 at 3:13 am | Permalink

    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.