= Identity Management = '''This was ripped from [http://metrocat.org/nerd/2005/10/identity-management-for-turbogears Jeff Watkins' blog].''' ...with a little changes to play nice with latest svn [as of 9th Jan 2006] 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. == Quick start example == === Step 1 - Create new project === {{{ #!python $ tg-admin quickstart }}} Name your project idtest and set the dburi in dev.cfg to point to a server and database you want to use. === Step 2 - Edit idtest.egg-info/sqlobject.txt === {{{ #!python db_module=idtest.model, turbogears.identity.soprovider }}} === Step 3 - Create login.kid template === The login template and the controllers.py file are already created with up-to-date SVN quickstarts. This was instituted sometime around SVN 485. {{{ #!text/html Login to TurboGears



}}} === Step 4 - Create secured.kid template === {{{ #!text/html Welcome to Secured TurboGears


This page is secured.

}}} === Step 5 - edit controllers.py === Ad this code to the top of the file: {{{ #!python from turbogears import identity import cherrypy }}} Then add this inside the model class: {{{ #!python @turbogears.expose( html="idtest.templates.login" ) def login( self, *args, **kw ): if hasattr(cherrypy.request,"identity_errors"): msg= str(cherrypy.request.identity_errors) else: msg= "Please log in" cherrypy.response.status=403 return dict( message=msg, previous_url=cherrypy.request.path ) #to preserve the session you may want to return this instead: #return dict( message = msg, previous_url = turbogears.url( cherrypy.request.path, cherrypy.request.paramMap ) ) @turbogears.expose( html="idtest.templates.secured" ) @identity.require( in_group="admin" ) def secured( self ): return dict() }}} Note: You may need to revise the above code for the @identity.require decorator. In a [http://groups.google.com/group/turbogears/browse_thread/thread/8dc90943e2cce3ce/42de9e3ae86f7aaf?q=identity&rnum=1#42de9e3ae86f7aaf recent mailing list post], Jeff Watkins writes the following. {{{ n the past you decorated your methods as such: @turbogears.expose() @identity.require( group="admin", permission="foo,bar" ) The require decorator checked whether the visitor was a member of the admin group AND had the permission foo AND had the permission bar. Many people wanted something more flexible, and with revision 400, any of the following are valid require decorators: @identity.require( in_group( "admin" ) ) @identity.require( in_all_groups( "admin", "editor" ) ) @identity.require( in_any_group( "admin", "editor" ) ) @identity.require( has_permission( "edit" ) ) @identity.require( has_all_permissions( "edit", "delete", "update" ) ) @identity.require( has_any_permission( "edit", "delete", "update" ) ) But most importantly, you can use decorators like theses: @identity.require( Any( in_group( "admin" ), has_permission ( "edit" ) ) ) @identity.require( All( from_host( "" ), has_permission ( "edit" ) ) ) @identity.require( All( from_any_host( "", "" ), in_group( "editor" ) ) ) You can also use these same predicates in your own code: if in_group( "admin" ) and has_permission( "edit" ): pass else: pass I still haven't addressed the need for something like `is_owner`, because that seems *so* model specific. }}} However, you may need to use the in_group, in_all_groups, etc. functions in the {{{identity}}} namespace. For example: {{{ @identity.require( in_group( "admin" ) ) }}} changes to {{{ @identity.require( identity.in_group( "admin" ) ) }}} === Step 6 - Turn on Identity management === Edit dev.cfg. Under the "IDENTITY" heading (around line 68), uncomment and edit the following to turn on identity management. Edit the failure url as well. {{{ identity.on=True identity.failure_url="/login" }}} === Step 7 - Create the database === {{{ $ tg-admin sql create }}} === Step 8 - Create a user and group === Using Catwalk is probably the easiest way to create user/group/permissions. Use this method if you can't get Catwalk set up. {{{ $ 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=TG_User( userId="jeff", emailAddress="jeff@metrocat.org", displayName="Jeff Watkins", password="xxxxx" ) >>> g=TG_Group( groupId="admin", displayName="Administrators" ) >>> hub.commit() >>> }}} === Step 9 - Testing the login === Start the project: {{{ #!python $ ./idtest-start.py }}} and visit secured page http://localhost:8080/secured and login with the username and password you just created. It should fail with the message: {{{ Not member of group: admin }}} === Step 10 - Add the 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=TG_User.get(1) >>> g=TG_Group.get(1) >>> u.addTG_Group(g) >>> hub.commit() >>> }}} === Step 11 - Revisit secured page and login === Browse to http://localhost:8080/secured again and login, this time you should see the content of secured.kid ---- == Restricting Access to whole Subdirectory == (from mailing list) You should be able to restrict access to a subdirectory by subclassing SecureResource in your descendent objects. So you might have the following: {{{ #!python class Toxicologia(controller.Controller, identity.SecureResource): required_permissions= ["write"] required_groups= ["admin"] allowed_hosts= [""] identity_required= True }}} You can apply whatever decorators you want on the methods of the Toxicologia instance. So each method could have additional restrictions. And Toxicologia could have SecureObjects as well. However, access to exposed methods of Toxicologia and any SecureObjects would have to satisfy the authorisation requirements for Toxicologia. ---- == Specifying an 'or' type for group Access == There are two ways to handle this: '''1''' Derive your Controller from SecureResource (in addition to Controller) and check the permissions explicitly. For example: {{{ #!python 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 throws, er, 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 into each function that requires it. '''2''' Write your own decorator function. This is not for the faint at heart. But it gives you absolute flexibility. Take a look at the two decorators in turbogears/identity/conditions.py. They'll give you a head-start on what you'll have to do. ---- === Applying security settings, not from source code, but from configuration data === You should be able to specify security settings not only from source code but via some other means. The goal is to allow an administrator to set the security policy, not the programmer. ---- == FAQ's == === How do I retrieve the userId in my application code? === 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 I wanted to access the users’ group info, how would I do that? === There are two ways you can access the group information. '''1.''' Via the current identity object: {{{ #!python from turbogears import identity if 'admin' in identity.current.groups: pass }}} '''2.''' Via the user object on the current identity: {{{ #!python 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.'' ----