Changeset 5618

Show
Ignore:
Timestamp:
10/25/08 14:22:47 (3 months ago)
Author:
Gustavo
Message:

Finished the docs for tgext.authorization's quickstart! Yeah!

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • docs/2.0/docs/main/Extensions/Authorization/Quickstart.rst

    r5574 r5618  
    66.. moduleauthor:: Agendaless Consulting and Contributors 
    77 
    8 :Status: Draft 
    9  
     8:Status: Official 
     9 
     10This document describes the :mod:`tgext.authorization`'s quickstart, from a 
     11basic introduction to all the available functionality it provides. 
     12 
     13.. contents:: Table of Contents 
     14    :depth: 2 
    1015 
    1116Overview 
    1217-------- 
    13 @TODO 
     18 
     19TG2 applications may take advantage of a rather simple, and usual,  
     20authentication and authorization setup, in which the users' data, the groups 
     21and the permissions used in the application are all stored in a SQLAlchemy 
     22managed database. 
     23 
     24It requires some code in your application, which is automatically added for 
     25you if enabled auth while creating the project. If you want to use it on an 
     26existing project, you should check :ref:`implementing`. 
    1427 
    1528 
    1629How it works 
    1730------------ 
    18 @TODO 
     31 
     32While :mod:`tgext.authorization` is meant to deal with authorization only, 
     33this module defines a :mod:`repoze.who` authenticator (which deals with your 
     34users' login using your users' table) and a function that TG2 uses to setup 
     35:mod:`repoze.who` with :mod:`tgext.authorization` support. 
     36 
     37Only TurboGears is supposed to deal with :mod:`tgext.authorization.quickstart` 
     38directly, except with a function you may need to customize some things in  
     39:mod:`repoze.who`: 
     40 
     41.. function:: setup_sql_auth(app, config, user_class, group_class, permission_class, session[, form_plugin=None, form_identifies=True, identifiers=None, authenticators=[], challengers=[], mdproviders=[], translations={}]) 
     42     
     43    Setup :mod:`repoze.who` and :mod:`tgext.authorization` with SQL 
     44    authentication and authorization only. 
     45     
     46    :param app: Your TG2 application. 
     47    :param config: You TG2 application's configuration (the one defined in 
     48        ``{yourproject}.config.app_cfg``). 
     49    :param user_class: The SQLAlchemy class for the users. 
     50    :param group_class: The SQLAlchemy class for the groups. 
     51    :param permission_class: The SQLAlchemy class for the permissions. 
     52    :param session: The SQLAlchemy session. 
     53    :param form_plugin: The main :mod:`repoze.who` challenger plugin; this is  
     54        usually a login form. 
     55    :param form_identifies: Whether the ``form_plugin`` may and should act as 
     56        an :mod:`repoze.who` identifier. 
     57    :param identifiers: Secondary :mod:`repoze.who` identifier plugins, if any. 
     58    :param authenticators: The :mod:`repoze.who` authenticators to be used. 
     59    :param challengers: Secondary :mod:`repoze.who` challenger plugins, if any. 
     60    :param mdproviders: Secondary :mod:`repoze.who` metadata plugins, if any. 
     61    :param translations: The TG2 application's base_config.sa_auth.translations 
     62    :return: The TG2 application with authentication and authorization. 
     63     
     64    .. note:: 
     65     
     66        Only use this function if you need to add secondary :mod:`repoze.who` 
     67        identifier, authenticator, challenger and/or metadata provider plugins 
     68        because the other settings may be easily set in 
     69        ``{yourproject}.config.app_cfg``. 
    1970 
    2071 
    2172Customizing the model structure assumed by the quickstart 
    2273--------------------------------------------------------- 
    23 @TODO 
    24  
    25  
    26 Implementing it in an existing project 
    27 -------------------------------------- 
    28 @TODO 
     74 
     75Your auth-related model doesn't `have to` be like the default one, where the 
     76class for your users, groups and permissions are, respectively, ``User``, 
     77``Group`` and ``Permission``, and your users' user name is available in 
     78``User.user_name``. What if you prefer ``Member`` and ``Team`` instead of 
     79``User`` and ``Group``, respectively? Or what if you prefer ``Group.members`` 
     80instead of ``Group.users``? You're in the right place! 
     81 
     82Changing class names 
     83~~~~~~~~~~~~~~~~~~~~ 
     84 
     85Changing the name of an auth-related class (``User``, ``Group`` or ``Permission``) 
     86is a rather simple task. Just rename it in your model, and then make sure to 
     87update ``{yourproject}.config.app_cfg`` accordingly. 
     88 
     89For example, if you renamed ``User`` to ``Member``, ``{yourproject}.config.app_cfg`` 
     90should look like this:: 
     91 
     92    # ... 
     93    from yourproject import model 
     94    # ... 
     95    base_config.sa_auth.user_class = model.Member 
     96    # ... 
     97 
     98Changing attribute names 
     99~~~~~~~~~~~~~~~~~~~~~~~~ 
     100 
     101You can also change the name of the attributes assumed by 
     102:mod:`tgext.authorization` in your auth-related classes, such as renaming 
     103``User.groups`` by ``User.memberships``. 
     104 
     105Changing such values is what :mod:`tgext.authorization` calls "translating". 
     106You may set the translations for the attributes of the models 
     107:mod:`tgext.authorization` deals with in ``{yourproject}.config.app_cfg``. For 
     108example, if you want to replace ``Group.users`` by ``Group.members``, you may 
     109set the following translation in that file:: 
     110 
     111    base_config.sa_auth.translations.users = 'members' 
     112 
     113These are the translations you may set in ``base_config.sa_auth.translations``: 
     114    * ``user_name``: The translation for the attribute in ``User.user_name``. 
     115    * ``users``: The translation for the attribute in ``Group.users``. 
     116    * ``group_name``: The translation for the attribute in ``Group.group_name``. 
     117    * ``groups``: The translation for the attribute in ``User.groups`` and 
     118      ``Permission.groups``. 
     119    * ``permission_name``: The translation for the attribute in 
     120      ``Permission.permission_name``. 
     121    * ``permissions``: The translation for the attribute in ``User.permissions`` 
     122      and ``Group.permissions``. 
     123    * ``validate_password``: The translation for the method in 
     124      ``User.validate_password``. 
     125 
     126 
     127.. _implementing: 
     128 
     129Enabling the quickstart in an existing project 
     130---------------------------------------------- 
     131 
     132To enable authentication and authorization via :mod:`tgext.authorization`'s 
     133quickstart, you should follow the instructions described in this section: 
     134 
     135    #. Go to ``{yourproject}.config.app_cfg`` and define the following settings: 
     136        * ``base_config.auth_backend``: The name of the  
     137          authentication/authorization backend. Set it to "sqlalchemy". 
     138        * ``base_config.sa_auth.dbsession``: Your model's SQLAlchemy session. 
     139        * ``base_config.sa_auth.user_class``: Your user class. 
     140        * ``base_config.sa_auth.group_class``: Your group class. 
     141        * ``base_config.sa_auth.permission_class``: Your permission class. 
     142        
     143       It may look like this:: 
     144            
     145           # ... 
     146           from yourproject import model 
     147           # ... 
     148           base_config.auth_backend = 'sqlalchemy' 
     149           base_config.sa_auth.dbsession = model.DBSession 
     150           base_config.sa_auth.user_class = model.User 
     151           base_config.sa_auth.group_class = model.Group 
     152           base_config.sa_auth.permission_class = model.Permission 
     153           # ... 
     154 
     155    #. Now define your auth-related data model in, say,  
     156       ``{yourproject}.model.auth``, with at least the definitions below (you 
     157       may add more columns if you want):: 
     158         
     159        import md5 
     160        import sha 
     161        from datetime import datetime 
     162         
     163        from tg import config 
     164        from sqlalchemy import Table, ForeignKey, Column 
     165        from sqlalchemy.types import String, Unicode, UnicodeText, Integer, DateTime, \ 
     166                                     Boolean, Float 
     167        from sqlalchemy.orm import relation, backref, synonym 
     168         
     169        from yourproject.model import DeclarativeBase, metadata, DBSession 
     170         
     171         
     172        # This is the association table for the many-to-many relationship between 
     173        # groups and permissions. 
     174        group_permission_table = Table('tg_group_permission', metadata, 
     175            Column('group_id', Integer, ForeignKey('tg_group.group_id', 
     176                onupdate="CASCADE", ondelete="CASCADE")), 
     177            Column('permission_id', Integer, ForeignKey('tg_permission.permission_id', 
     178                onupdate="CASCADE", ondelete="CASCADE")) 
     179        ) 
     180         
     181        # This is the association table for the many-to-many relationship between 
     182        # groups and members - this is, the memberships. 
     183        user_group_table = Table('tg_user_group', metadata, 
     184            Column('user_id', Integer, ForeignKey('tg_user.user_id', 
     185                onupdate="CASCADE", ondelete="CASCADE")), 
     186            Column('group_id', Integer, ForeignKey('tg_group.group_id', 
     187                onupdate="CASCADE", ondelete="CASCADE")) 
     188        ) 
     189         
     190        # auth model 
     191         
     192        class Group(DeclarativeBase): 
     193            """An ultra-simple group definition. 
     194            """ 
     195            __tablename__ = 'tg_group' 
     196         
     197            group_id = Column(Integer, autoincrement=True, primary_key=True) 
     198         
     199            group_name = Column(Unicode(16), unique=True) 
     200         
     201            display_name = Column(Unicode(255)) 
     202         
     203            created = Column(DateTime, default=datetime.now) 
     204         
     205            users = relation('User', secondary=user_group_table, backref='groups') 
     206         
     207            def __repr__(self): 
     208                return '<Group: name=%s>' % self.group_name 
     209         
     210         
     211        class User(DeclarativeBase): 
     212            """Reasonably basic User definition. Probably would want additional 
     213            attributes. 
     214            """ 
     215            __tablename__ = 'tg_user' 
     216         
     217            user_id = Column(Integer, autoincrement=True, primary_key=True) 
     218         
     219            user_name = Column(Unicode(16), unique=True) 
     220         
     221            email_address = Column(Unicode(255), unique=True) 
     222         
     223            display_name = Column(Unicode(255)) 
     224         
     225            _password = Column('password', Unicode(40)) 
     226         
     227            created = Column(DateTime, default=datetime.now) 
     228         
     229            def __repr__(self): 
     230                return '<User: email="%s", display name="%s">' % ( 
     231                        self.email_address, self.display_name) 
     232         
     233            @property 
     234            def permissions(self): 
     235                perms = set() 
     236                for g in self.groups: 
     237                    perms = perms | set(g.permissions) 
     238                return perms 
     239         
     240            def _set_password(self, password): 
     241                """encrypts password on the fly using the encryption 
     242                algo defined in the configuration 
     243                """ 
     244                algorithm = self.get_encryption_method() 
     245                self._password = self.__encrypt_password(algorithm, password) 
     246         
     247            def _get_password(self): 
     248                """returns password 
     249                """ 
     250                return self._password 
     251         
     252            password = synonym('password', descriptor=property(_get_password, 
     253                                                               _set_password)) 
     254         
     255            def __encrypt_password(self, algorithm, password): 
     256                """Hash the given password with the specified algorithm. Valid values 
     257                for algorithm are 'md5' and 'sha1'. All other algorithm values will 
     258                be essentially a no-op.""" 
     259                hashed_password = password 
     260         
     261                if isinstance(password, unicode): 
     262                    password_8bit = password.encode('UTF-8') 
     263         
     264                else: 
     265                    password_8bit = password 
     266         
     267                if "md5" == algorithm: 
     268                    hashed_password = md5.new(password_8bit).hexdigest() 
     269         
     270                elif "sha1" == algorithm: 
     271                    hashed_password = sha.new(password_8bit).hexdigest() 
     272         
     273                # TODO: re-add the possibility to provide own hasing algo 
     274                # here... just get the real config... 
     275         
     276                #elif "custom" == algorithm: 
     277                #    custom_encryption_path = turbogears.config.get( 
     278                #        "auth.custom_encryption", None ) 
     279                # 
     280                #    if custom_encryption_path: 
     281                #        custom_encryption = turbogears.util.load_class( 
     282                #            custom_encryption_path) 
     283         
     284                #    if custom_encryption: 
     285                #        hashed_password = custom_encryption(password_8bit) 
     286         
     287                # make sure the hased password is an UTF-8 object at the end of the 
     288                # process because SQLAlchemy _wants_ a unicode object for Unicode columns 
     289                if not isinstance(hashed_password, unicode): 
     290                    hashed_password = hashed_password.decode('UTF-8') 
     291         
     292                return hashed_password 
     293         
     294            def get_encryption_method(self): 
     295                """returns the encryption method from the config 
     296                If None is set, or auth is disabled this will return None 
     297                """ 
     298                auth_system = config.get('sa_auth', None) 
     299                if auth_system is None: 
     300                    # if auth is not activated in the config we should warn 
     301                    # the admin through the logs... and return None 
     302                    return None 
     303         
     304                return auth_system.get('password_encryption_method', None) 
     305         
     306            def validate_password(self, password): 
     307                """Check the password against existing credentials. 
     308                this method _MUST_ return a boolean. 
     309         
     310                @param password: the password that was provided by the user to 
     311                try and authenticate. This is the clear text version that we will 
     312                need to match against the (possibly) encrypted one in the database. 
     313                @type password: unicode object 
     314                """ 
     315                algorithm = self.get_encryption_method() 
     316                return self.password == self.__encrypt_password(algorithm, password) 
     317         
     318         
     319        class Permission(DeclarativeBase): 
     320            """A relationship that determines what each Group can do""" 
     321            __tablename__ = 'tg_permission' 
     322         
     323            permission_id = Column(Integer, autoincrement=True, primary_key=True) 
     324         
     325            permission_name = Column(Unicode(16), unique=True) 
     326         
     327            description = Column(Unicode(255)) 
     328         
     329            groups = relation(Group, secondary=group_permission_table, 
     330                              backref='permissions') 
     331 
     332       Finally, make sure these classes are imported at the end of your 
     333       ``{yourproject}/model/__init__.py``:: 
     334        
     335           from auth import User, Group, Permission 
     336 
     337    #. Finally, you may want to create some default users, groups and permissions 
     338       to try authorization in your application. In ``{yourproject}.websetup`` 
     339       you may add a code like this in your ``setup_config()`` function:: 
     340        
     341            # ... 
     342             
     343            model.metadata.create_all(bind=config['pylons.app_globals'].sa_engine) 
     344             
     345            u = model.User() 
     346            u.user_name = u'manager' 
     347            u.display_name = u'Example manager' 
     348            u.email_address = u'manager@somedomain.com' 
     349            u.password = u'managepass' 
     350         
     351            model.DBSession.save(u) 
     352         
     353            g = model.Group() 
     354            g.group_name = u'managers' 
     355            g.display_name = u'Managers Group' 
     356         
     357            g.users.append(u) 
     358         
     359            model.DBSession.save(g) 
     360         
     361            p = model.Permission() 
     362            p.permission_name = u'manage' 
     363            p.description = u'This permission give an administrative right to the bearer' 
     364            p.groups.append(g) 
     365         
     366            model.DBSession.save(p) 
     367            model.DBSession.flush() 
     368         
     369            u1 = model.User() 
     370            u1.user_name = u'editor' 
     371            u1.display_name = u'Exemple editor' 
     372            u1.email_address = u'editor@somedomain.com' 
     373            u1.password = u'editpass' 
     374         
     375            model.DBSession.save(u1) 
     376            model.DBSession.flush() 
     377             
     378            transaction.commit() 
     379            print "Successfully setup" 
     380 
     381       And then populate your test database with these rows. To do so, first 
     382       delete the file ``devdata.db`` from your project's root directory, and 
     383       finally run the command below from the same directory:: 
     384        
     385           paster setup-app development.ini 
     386 
     387 
     388Disabling the quickstart 
     389------------------------ 
     390 
     391If you need more flexibility than that provided by the quickstart, you may 
     392disable it by removing (or commenting) the following line from  
     393``{yourproject}.config.app_cfg``:: 
     394 
     395    base_config.auth_backend = 'sqlalchemy' 
     396 
     397Then you may also want to delete those settings like ``base_config.sa_auth.*``, 
     398because they'll be ignored.