{"id":1020,"date":"2010-10-29T21:02:20","date_gmt":"2010-10-30T01:02:20","guid":{"rendered":"http:\/\/cd34.com\/blog\/?p=1020"},"modified":"2010-11-02T23:41:01","modified_gmt":"2010-11-03T03:41:01","slug":"repoze-whorepoze-what-with-pylons-step-by-step","status":"publish","type":"post","link":"https:\/\/cd34.com\/blog\/framework\/repoze-whorepoze-what-with-pylons-step-by-step\/","title":{"rendered":"Repoze.who\/Repoze.what with Pylons (step by step)"},"content":{"rendered":"<p>After working through quite a bit of the documentation on the pylons site and the repoze site, I didn&#8217;t really find a step by step guide to get repoze.who\/repoze.what working with Pylons.<\/p>\n<p>Some of the references used:<\/p>\n<p>* http:\/\/code.gustavonarea.net\/repoze.what-pylons\/Manual\/Protecting.html<br \/>\n* http:\/\/wiki.pylonshq.com\/display\/pylonscookbook\/Authorization+with+repoze.what<\/p>\n<p>Thanks to my nephew Qwait for overloading ActionProtector to intercept 403s and give an additional chance to authenticate.<\/p>\n<pre>\r\nwget http:\/\/pylonshq.com\/download\/1.0\/go-pylons.py\r\npython go-pylons.py --no-site-packages pylons\r\ncd pylons\r\nsource bin\/activate\r\neasy_install repoze.what-quickstart\r\neasy_install repoze.what-pylons\r\neasy_install mysql-python\r\npaster create -t pylons project\r\ncd project\r\n<\/pre>\n<p>Because of the association table and the cascade, you must use MySQL or Postgresql rather than SQLite.  You also might need to modify development.ini [server:main] host\/port.<\/p>\n<pre>\r\ncd project\r\n<\/pre>\n<p><strong>config\/middleware.py:<\/strong><\/p>\n<pre>\r\n    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)\r\n    from project.lib.auth import add_auth\r\n    app = add_auth(app, config)\r\n<\/pre>\n<p><strong>config\/routing.py:<\/strong><\/p>\n<pre>\r\n    map.connect('\/login', controller='login', action='login')\r\n    map.connect('\/login\/submit', controller='login', action='login_handler')\r\n    map.connect('\/login\/continue', controller='login', action='post_login')\r\n    map.connect('\/logout\/continue', controller='login', action='post_logout')\r\n    map.connect('\/logout', controller='login', action='logout_handler')\r\n<\/pre>\n<p><strong>lib\/auth.py:<\/strong> &#8211; modified to intercept 403 and provide chance to authenticate<\/p>\n<pre>\r\nfrom pylons import response, url\r\nfrom pylons.controllers.util import redirect\r\n\r\nfrom repoze.what.plugins.quickstart import setup_sql_auth\r\nfrom repoze.what.plugins import pylonshq\r\n\r\nimport project.lib.helpers as h\r\n\r\nfrom project.model.meta import Session\r\nfrom project.model.auth import AuthUser, AuthGroup, AuthPermission\r\n\r\ndef add_auth(app, config):\r\n   return setup_sql_auth(\r\n       app, AuthUser, AuthGroup, AuthPermission, Session,\r\n       login_handler = '\/login\/submit',\r\n       logout_handler = '\/logout',\r\n       post_login_url = '\/login\/continue',\r\n       post_logout_url = '\/logout\/continue',\r\n       cookie_secret = 'my_secret_word',\r\n       translations = {\r\n           'user_name' : 'username',\r\n           'groups' : 'auth_groups',\r\n           'group_name' : 'name',\r\n           'permissions' : 'auth_permissions',\r\n           'permission_name' : 'name'\r\n       }\r\n   )\r\n\r\ndef redirect_auth_denial(reason):\r\n    if response.status_int == 401:\r\n        message = 'You are not logged in.'\r\n        message_type = 'warning'\r\n    else:\r\n        message = 'You do not have the permissions to access this page.'\r\n        message_type = 'error'\r\n\r\n    h.flash(message, message_type)\r\n    redirect(url('\/login', came_from=url.current()))\r\n\r\nclass ActionProtector(pylonshq.ActionProtector):\r\n    default_denial_handler = staticmethod(redirect_auth_denial)\r\n<\/pre>\n<p><strong>model\/auth.py:<\/strong><\/p>\n<pre>\r\nfrom sqlalchemy import *\r\nfrom sqlalchemy.databases import mysql\r\nfrom sqlalchemy.orm import relation, backref, synonym\r\nfrom sqlalchemy.orm.exc import NoResultFound\r\n\r\nfrom project.model.meta import Base\r\n\r\nimport os\r\nfrom hashlib import sha1\r\nfrom datetime import datetime\r\n\r\ngroup_permission_table = Table('auth_group_permissions', Base.metadata,\r\n    Column('group_id', mysql.BIGINT(20, unsigned=True), ForeignKey('auth_groups.id', onupdate='CASCADE', ondelete='CASCADE')),\r\n    Column('permission_id', mysql.BIGINT(20, unsigned=True), ForeignKey('auth_permissions.id', onupdate='CASCADE', ondelete='CASCADE'))\r\n)\r\nuser_group_table = Table('auth_user_groups', Base.metadata,\r\n    Column('user_id', mysql.BIGINT(20, unsigned=True), ForeignKey('auth_users.id', onupdate='CASCADE', ondelete='CASCADE')),\r\n    Column('group_id', mysql.BIGINT(20, unsigned=True), ForeignKey('auth_groups.id', onupdate='CASCADE', ondelete='CASCADE'))\r\n)\r\n\r\nclass AuthGroup(Base):\r\n    __tablename__ = 'auth_groups'\r\n\r\n    id = Column(mysql.BIGINT(20, unsigned=True), primary_key=True, autoincrement=True)\r\n    name = Column(Unicode(80), unique=True, nullable=False)\r\n    created = Column(mysql.DATE())\r\n\r\n    users = relation('AuthUser', secondary=user_group_table, backref='auth_groups')\r\n\r\n    def __repr__(self):\r\n        return '&lt;group: name=%s>' % self.name\r\n\r\n    def __unicode__(self):\r\n        return self.name\r\n\r\nclass AuthUser(Base):\r\n    __tablename__ = 'auth_users'\r\n\r\n    id = Column(mysql.BIGINT(20, unsigned=True), primary_key=True, autoincrement=True)\r\n    username = Column(Unicode(80), nullable=False)\r\n    _password = Column('password', Unicode(80), nullable=False)\r\n \r\n    @property\r\n    def permissions(self):\r\n        perms = set()\r\n        for g in self.groups:\r\n            perms = perms | set(g.permissions)\r\n        return perms\r\n\r\n    def _set_password(self, password):\r\n        hashed_password = password\r\n\r\n        if isinstance(password, unicode):\r\n            password_8bit = password.encode('UTF-8')\r\n        else:\r\n            password_8bit = password\r\n\r\n        salt = sha1()\r\n        salt.update(os.urandom(60))\r\n        hash = sha1()\r\n        hash.update(password_8bit + salt.hexdigest())\r\n        hashed_password = salt.hexdigest() + hash.hexdigest()\r\n\r\n        if not isinstance(hashed_password, unicode):\r\n            hashed_password = hashed_password.decode('UTF-8')\r\n        self._password = hashed_password\r\n\r\n    def _get_password(self):\r\n        return self._password\r\n\r\n    password = synonym('_password', descriptor=property(_get_password, _set_password))\r\n\r\n    def validate_password(self, password):\r\n        hashed_pass = sha1()\r\n        hashed_pass.update(password + self.password[:40])\r\n        return self.password[40:] == hashed_pass.hexdigest()\r\n\r\n    def __repr__(self):\r\n        return '&lt;user: id=\"%s\" username=\"%s\" email=\"%s\">' % (self.id, self.username, self.email)\r\n\r\n    def __unicode__(self):\r\n        return self.username\r\n\r\nclass AuthPermission(Base):\r\n    __tablename__ = 'auth_permissions'\r\n\r\n    id = Column(mysql.BIGINT(20, unsigned=True), primary_key=True, autoincrement=True)\r\n    name = Column(Unicode(80), unique=True, nullable=False)\r\n    description = Column(mysql.TEXT())\r\n\r\n    groups = relation(AuthGroup, secondary=group_permission_table, backref='auth_permissions')\r\n\r\n    def __unicode__(self):\r\n        return self.permission_name\r\n<\/pre>\n<p><strong>controllers\/login.py:<\/strong><\/p>\n<pre>\r\nfrom pylons import request, response, session, tmpl_context, config, url\r\nfrom pylons.controllers.util import redirect\r\n\r\nfrom project.lib.base import BaseController, render\r\nfrom project.lib.helpers import flash\r\n\r\nclass LoginController(BaseController):\r\n    def login(self):\r\n        login_counter = request.environ['repoze.who.logins']\r\n        if login_counter > 0:\r\n            flash('Wrong credentials')\r\n        tmpl_context.login_counter = login_counter\r\n        tmpl_context.came_from = request.params.get('came_from') or url('\/')\r\n        return render('login.mako')\r\n\r\n    def login_handler(self):\r\n        pass\r\n\r\n    def post_login(self):\r\n        identity = request.environ.get('repoze.who.identity')\r\n        came_from = str(request.params.get('came_from', '')) or url('\/')\r\n        if not identity:\r\n            login_counter = request.environ['repoze.who.logins'] + 1\r\n            redirect(url('\/login', came_from=came_from, __logins=login_counter))\r\n        redirect(came_from)\r\n\r\n    def logout_handler(self):\r\n        pass\r\n     \r\n    def post_logout(self):\r\n        redirect('\/')\r\n<\/pre>\n<p><strong>templates\/login.mako:<\/strong><\/p>\n<pre>\r\n&lt;% messages = h.flash.pop_messages() %>\r\n% if messages:\r\n&lt;div class=\"flash\">\r\n    % for message in messages:\r\n      &lt;p class=\"${message.category}\">${message}\r\n    % endfor\r\n&lt;\/div>\r\n% endif\r\n\r\n  &lt;form action=\"${h.url('\/login\/submit', came_from=tmpl_context.came_from, __logins=tmpl_context.login_counter)}\" method=\"POST\">\r\n    &lt;label for=\"login\">Username:&lt;input type=\"text\" id=\"login\" name=\"login\" \/>&lt;br \/>\r\n    &lt;label for=\"password\">Password:&lt;input type=\"password\" id=\"password\" name=\"password\" \/>\r\n    &lt;input type=\"submit\" value=\"Login\" \/>\r\n  &lt;\/form>\r\n<\/pre>\n<p><strong>controllers\/root.py:<\/strong><\/p>\n<pre>\r\nfrom pylons import request, response, session, tmpl_context, config\r\n\r\n#from repoze.what.plugins.pylonshq import ActionProtector, ControllerProtector\r\nfrom project.lib.auth import ActionProtector\r\nfrom repoze.what.predicates import is_user, has_permission, in_group\r\n\r\nfrom project.lib.base import BaseController, render\r\n\r\nclass RootController(BaseController):\r\n    def index(self):\r\n        return render('index.mako')\r\n\r\n    @ActionProtector(is_user('test'))\r\n    def user(self):\r\n        return render('loggedin.mako')\r\n\r\n    @ActionProtector(is_user('nottest'))\r\n    def notuser(self):\r\n        return render('loggedin.mako')\r\n\r\n    @ActionProtector(in_group('admin'))\r\n    def admin(self):\r\n        return render('loggedin.mako')\r\n\r\n    @ActionProtector(has_permission('edit'))\r\n    def edit(self):\r\n        return render('loggedin.mako')\r\n<\/pre>\n<p><strong>lib\/helpers.py:<\/strong><\/p>\n<pre>\r\nfrom pylons import url\r\n\r\nfrom webhelpers.pylonslib import Flash as _Flash\r\nflash = _Flash()\r\n<\/pre>\n<p><strong>websetup.py:<\/strong> &#8211; after the Session, Base import:<\/p>\n<pre>\r\nfrom project.model.auth import *\r\n<\/pre>\n<p><strong>Setup\/Create the database, start paster<\/strong><\/p>\n<pre>\r\npaster setup-app development.ini\r\npaster serve --reload development.ini\r\n<\/pre>\n<p><strong>createuser.py:<\/strong><\/p>\n<pre>\r\n#!\/usr\/bin\/python2.6\r\n\r\nfrom sqlalchemy import create_engine\r\nengine = create_engine('mysql:\/\/user:pass@localhost\/dbname', echo=True)\r\nfrom sqlalchemy.orm import sessionmaker\r\nSession = sessionmaker(bind=engine)\r\nsession = Session()\r\n\r\nfrom project.model.auth import *\r\n\r\nu = AuthUser()\r\nu.username = u'test'\r\nu.password = u'test'\r\nsession.add(u)\r\ng = AuthGroup()\r\ng.name = u'admin'\r\ng.users.append(u)\r\nsession.add(g)\r\np = AuthPermission()\r\np.name = u'edit'\r\np.groups.append(g)\r\nsession.add(p)\r\nsession.commit()\r\n<\/pre>\n<div style=\"float:left;\">\n<div id=\"fb-root\"><\/div>\n<fb:like href=\"https:\/\/cd34.com\/blog\/framework\/repoze-whorepoze-what-with-pylons-step-by-step\/\" width=\"250\" send=\"false\" show_faces=\"false\" layout=\"button_count\" action=\"recommend\"><\/fb:like>\n<\/div><div style=\"clear:both;\"><\/div>","protected":false},"excerpt":{"rendered":"<p>After working through quite a bit of the documentation on the pylons site and the repoze site, I didn&#8217;t really find a step by step guide to get repoze.who\/repoze.what working with Pylons. Some of the references used: * http:\/\/code.gustavonarea.net\/repoze.what-pylons\/Manual\/Protecting.html * http:\/\/wiki.pylonshq.com\/display\/pylonscookbook\/Authorization+with+repoze.what Thanks to my nephew Qwait for overloading ActionProtector to intercept 403s and give an [&hellip;]<\/p>\n<div style=\"float:left;\">\n<div id=\"fb-root\"><\/div>\n<fb:like href=\"https:\/\/cd34.com\/blog\/framework\/repoze-whorepoze-what-with-pylons-step-by-step\/\" width=\"250\" send=\"false\" show_faces=\"false\" layout=\"button_count\" action=\"recommend\"><\/fb:like>\n<\/div><div style=\"clear:both;\"><\/div>","protected":false},"author":15,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[80,147],"class_list":["post-1020","post","type-post","status-publish","format-standard","hentry","category-framework","tag-pylons","tag-repoze"],"_links":{"self":[{"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/posts\/1020","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/comments?post=1020"}],"version-history":[{"count":9,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/posts\/1020\/revisions"}],"predecessor-version":[{"id":1029,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/posts\/1020\/revisions\/1029"}],"wp:attachment":[{"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/media?parent=1020"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/categories?post=1020"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/tags?post=1020"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}