wiki:IdentityManagement
Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Version 69 (modified by anonymous, 11 years ago) (diff)

a little highlight correction

Identity Management

Here is a Tutorial for getting TurboGears identity management support up and running.

This Tutorial is written from the perspective of a fresh quick-started project

Part I: Identity Basics

A. Quick-Start

To begin with the tutorial, let's quick-start a learning project. Of course, everything you learn in the tutorial applies to existing projects as well.

$ tg-admin quickstart 

Name your project "identity_tutorial" and set the database uri in "identity_tutorial/dev.cfg" to point to a server and database you want to use.

After you specified the project name. a identity prompt will pop up:

Do you need Identity (usernames/passwords) in this project? [no]

This tutorial wouldn't go very far if we didn't enable identity, so go ahead and type "yes". Next, it will ask us to select one of providers:

Select your provider sqlobject or sqlalchemy [sqlobject]:

We should choose SQL Object, this will generate the identity-related code.

B. Setting up Identity

Identity Management can be used in controllers and in templates:

  • In controllers to implement access restrictions
  • In templates to adapt the appearance depending on the user's identity

To take advantage of identity, we need enable it.

Step 1 - Testing the login

Now let's check whether we have all set up correctly so far. Start the project as usual:

$ start-identity_tutorial

Visit the login page  http://localhost:8080/login. You should see a pretty login page with username and password fields. Try to log in. It should fail with an error message since you haven't added any users/groups/permisions yet.

You can access any other page without trouble, because we haven't protected those pages yet.

Step 2 - Adding an initial user and group

Of course, we will need at least one group and one user account to work with. Let's create these with CatWalk

Start Turbogears toolbox

$tg-admin toolbox

Enter CatWalk. You should see the TG_Group, TG_Permission, and TG_User classes on the left side.

a) Create a user

Select TG_User. Click the "Add TG_User+" button, then enter the user information. For example:

displayName : Jane Doe
userId : jdoe
emailAddress : jdoe@example.com
password : xxx

Click "Save".

b) Create an admin group

Select TG_Group. Click the "Add TG_Group+" button. Enter the displayName and groupId, for example:

displayName : Administrators
groupId :admin

Click "Save".

c) Add the new user to the admin group

Note that we are now in the "Browse" tab, with the new group displayed. Click the expansion triangle in front of "groups". This will reveal a "Manage Relations" link. Click on it. CatWalk will display two lists. Select the new user on the right list and click on "Add Selected" to move it to the left list. Then click "Save" to confirm the change.

C. Using Identity in the Controller

If you want to protect any method, add an @identity.require(...) decorator to the method. This decorator has a single argument, the predicate, which specifies the conditions to check. The identity module provides many predicates that you can use.

For example to protect the index page so that only members of the admin group can access it, we can use the identity.in_group("admin") predicate:

    @turbogears.expose(template=".templates.welcome")
    @identity.require(identity.in_group("admin"))
    def index(self):
        ...

Let's try this now. Visit  http://localhost:8080/. As the index page is protected, you will be redirected to the login page. Log in using the name and password of the account which you previosly created. Now you should see the index page.

Note that identity has added a small "Welcome, Jane Doe. Logout" at the top of the page.

D. Identity Predicates

Single Permission Checks

Checking that the user is logged in

@identity.require(identity.not_anonymous())

Checking access groups

@identity.require(identity.in_group("admin"))

Many people wanted something more flexible, any of the following are valid require decorators:

@identity.require(identity.in_all_groups("admin", "editor"))

@identity.require(identity.in_any_group("admin", "editor"))

Checking access permissions

@identity.require(identity.has_permission("edit"))

@identity.require(identity.has_all_permissions("edit", "delete", "update"))

@identity.require(identity.has_any_permission("edit", "delete", "update"))

Checking hosts

@identity.require(identity.from_host("127.0.0.1"))

@identity.require(identity.from_any_host(("127.0.0.1", "10.0.0.1")))

Combining Predicates

You can combine several predicates using identity.Any and identity.All:

     @identity.require(identity.Any(identity.in_group("admin"), identity.has_permission("edit")))

This decorator grants access to members of the "admin" group as well as any user who has the "edit" permission.

     @identity.require(identity.All(identity.from_host("127.0.0.1"), identity.has_permission("edit")))

     @identity.require(identity.All(identity.from_any_host(("127.0.0.1", "10.0.0.1")), identity.in_group("editor")))

Part II: Fine-Tuning Identity

A. Using Identity in Templates

Identity checks can also be used in kid templates to customize the appearance of the page depending on the user's identity. For example, you might show links to administrative functions only if the user is an administrator.

Note: Identity checks in the template don't provide any security, as it is always possible to enter URLs directly. Therefore you shouldn't rely on identity checks in the template for your site's security. Always check the identity in the controller as described above.

Step 1. Import turbogears.identity

Make sure you import turbogears.identity in your template (anywhere before you call the turbogears.identity)

<?python from turbogears import identity ?>

Step 2. Conditional display

Now we can use for example "py:if" attributes to display parts of the page depending on identity information:

Using access groups

<a py:if="'admin' in identity.current.groups" href="/admin">This is a link for admin</a>

Using access permissions

<div py:if="'write' in identity.current.permissions">This is a write permissions area</div>

#TODO: Describe how to display user information

Step 3. Displaying user information

B. More Access Checking Options

Sometimes, just restricting access to individual methods (pages) isn't enough. For example, you might want to protect an entire controller, or maybe your access permissions depend on the data viewed.

1. Protect your sub-directory

To restrict access to an entire controller (subdirectory), derive your Controller from identity.SecureResource? and add a require attribute at the class level.

class MySecureController(turbogears.Controller, identity.SecureResource):
    require = identity.in_group("admin")

    # etc. ... 

You can apply whatever decorators you want on the methods of the MySecureController? instance. So each method could have additional restrictions. And MySecureController? could have SecureObjects? as well. However, access to exposed methods of MySecureController? and any SecureObjects? would have to satisfy the authorisation requirements for MySecureController?.

2. Check the permissions explicitly

Let's say you are creating a web site where users can add their own content, like a blogging tool or a photo sharing site. Users should be able to edit their own content, but not the content added by other users. You can't do these checks in a decorator, as you need access to the actual data, which is only loaded in the method body. (And you don't want to load the data twice.) So you'll have to perform the identity checks in the method body.

Again, derive your controller from identity.SecureResource?. Perform your identity check at the method level. If the user doesn't have the required permissions, throw a suitable IdentityException?:

class MyController(controllers.Controller, identity.SecureResource):

     @turbogears.expose(html="mytemplate")
     def myFunction(self):
         if not ("admin" in identity.current.groups or
                 "super" in identity.current.groups):
             raise identity.GroupMembershipRequiredException(("admin", "super"))

This will work because SecureResource? wraps all exposed methods with code that checks permissions and traps IdentityExceptions?. So if your code, raises an IdentityException?, everything will be handled correctly.

Of course, you can then pull your authorisation logic out into a function that you call rather than copying and pasting it into each function that requires it.

You can also use the identity predicates in your own code:

     if identity.in_group("admin") and identity.has_permission("edit"):
         pass
     else:
         pass

3. Write your own decorator function

This is not for the faint at heart. But it gives you absolute flexibility.

Take a look at the decorators in turbogears/identity/conditions.py. They'll give you a head-start on what you'll have to do.

Also, to make your life easier, remember using turbogears.decorator. It will give you a solid basis and is used in other default decorators as well.


Part III: Advanced Identity

A. Using a Custom Identity Model

Identity model classes are now placed directly into the "model.py" of a quickstarted project. You can customize your own classes for users, groups, and/or permissions -- to add a few attributes to the user class, say an image of the user and a phone number -- or even a complete replacement.

There are some elements of the tables that you should not remove or rename.

B. Retrieve logged users' identity infomation

1. Retrieve user identity infomation

We can access the entire User object by accessing "turbogears.identity.current.user" to get user identity infomation. This gives you access to the user_name, display_name, email_address, and creation date.

2. Retrieve users’ group identity infomation

There are two ways to 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.group_name 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.


C. Adding user and group via tg-admin shell

Use "tg-admin shell" if you can't get Catwalk set up.

This section can also serve as an example how to add users and groups programmatically.

$ tg-admin shell

>>> from turbogears.identity.soprovider import *
>>> hub.begin()
>>> u=TG_User(user_name="jeff", email_address="jeff@metrocat.org",
            display_name="Jeff Watkins", password="xxxxx")
>>> g=TG_Group(group_name="admin", display_name="Administrators")
>>> hub.commit()
>>>

Add the user to admin group

$ tg-admin shell

>>> from turbogears.identity.model.somodel import *
>>> hub.begin()
>>> u=TG_User.get(1)
>>> g=TG_Group.get(1)
>>> u.addTG_Group(g)
>>> hub.commit()
>>>

D. Authenticating against an external password source

In some cases, you may want to authenticate against an external password source, while still using the standard Identity models for storing everything except passwords.

For example, authenticating against an existing Windows/Samba? domain controller means that your users can use the same password for your TurboGears project as they use to log in to Windows. (The same concept applies to LDAP, etc.)

For an example of how to do this, see the attached file "sosmbprovider.py", which subclasses the SQLObject provider, but validates user names and passwords against a Windows domain.

Attachments