{"id":1044,"date":"2010-11-11T13:26:13","date_gmt":"2010-11-11T17:26:13","guid":{"rendered":"http:\/\/cd34.com\/blog\/?p=1044"},"modified":"2010-11-11T14:58:08","modified_gmt":"2010-11-11T18:58:08","slug":"converting-pylons-1-0-repoze-whorepoze-what-to-pyramid-1-0a1-authentication-backed-with-mysql","status":"publish","type":"post","link":"https:\/\/cd34.com\/blog\/framework\/converting-pylons-1-0-repoze-whorepoze-what-to-pyramid-1-0a1-authentication-backed-with-mysql\/","title":{"rendered":"Converting Pylons 1.0 Repoze.who\/Repoze.what to Pyramid 1.0a1 Authentication backed with MySQL"},"content":{"rendered":"<p>For one of our projects we used Pylons 1.0 with Repoze.who\/Repoze.what, though, we only used groups without permissions.  In our application, a member could access one of three controllers based on their membership in a group.  There are a number of methods that can be used to set up authentication.  One method is to create a thin table with the Primary Key, Username and Password and use an association table to add any additional information. This has the ability to be flexible when you need to add a number of fields that shouldn&#8217;t be contained within the AuthUser table.  Another method is to use the AuthUser table to hold the related information.  This example uses the latter method.<\/p>\n<p>The first changes made are to __init__.py to add the policies and Forbidden view.  This view allows us to prompt a user for a username\/password when they visit a page that is protected by the ACLs in Pyramid.<\/p>\n<p>auth.py contains our authentication model with the Permission models removed.  We put the login\/logout views, forbidden view along with the groupfinder and the RootFactory in login.py to consolidate authentication into an auth.py and login.py file for installations in other applications.  Last but not least is a simple template to present the user with a login page.<\/p>\n<p>For our RootFactory, we&#8217;ve defined three groups, client, manager and admin.  Within our __init__.py (or our views if we use add_handler), we can restrict permissions using Pyramid&#8217;s authentication.<\/p>\n<p>In our __init__.py, we can use the permission= to specify membership in a group.<\/p>\n<pre>\r\nconfig.add_route('admin2', '\/admin\/', view='project.admin.index', permission='admin', view_renderer='admin_index.jinja2')\r\n<\/pre>\n<p>If we have used add_handler:<\/p>\n<pre>\r\nconfig.add_route('admin2', '\/admin\/', view='project.admin.index', view_renderer='admin_index.jinja2')\r\n<\/pre>\n<p>We can use the permissions ACL in the @action decorator:<\/p>\n<pre>\r\n@action(renderer='admin_index.jinja2', permission='admin')\r\n<\/pre>\n<p>To access the userid in your views:<\/p>\n<pre>\r\nfrom pyramid.security import authenticated_userid\r\n\r\nuserid = authenticated_userid(request)\r\n<\/pre>\n<p>At this point, we&#8217;ve migrated a Repoze.who\/Repoze.what authentication scheme that only used Group membership as its criteria and we have an SQL backed authentication system under Pyramid.<\/p>\n<p>Most of the guidance for this came from:<\/p>\n<p>* http:\/\/docs.pylonshq.com\/pyramid\/dev\/tutorials\/wiki2\/authorization.html<\/p>\n<p><strong>Modifications to __init__.py:<\/strong><\/p>\n<p>Added to the import section:<\/p>\n<pre>\r\nfrom pyramid.authentication import AuthTktAuthenticationPolicy\r\nfrom pyramid.authorization import ACLAuthorizationPolicy\r\n\r\nfrom project.login import forbidden_view, groupfinder\r\nfrom pyramid.exceptions import Forbidden\r\n<\/pre>\n<p><strong>Added to the Configurator:<\/strong><\/p>\n<pre>\r\n    authn_policy = AuthTktAuthenticationPolicy(\r\n        'sosecret', callback=groupfinder)\r\n    authz_policy = ACLAuthorizationPolicy()\r\n    config = Configurator(settings=settings,\r\n                          root_factory='project.login.RootFactory',\r\n                          authentication_policy=authn_policy,\r\n                          authorization_policy=authz_policy)\r\n<\/pre>\n<p>Added within the begin()\/end() block of the Configurator<\/p>\n<pre>\r\n    config.add_view(forbidden_view, context=Forbidden)\r\n    config.add_route('login', '\/login',\r\n                     view='project.login.login',\r\n                     view_renderer='project:templates\/login.pt')\r\n    config.add_route('logout', '\/logout',\r\n                     view='project.login.logout',)\r\n<\/pre>\n<p><strong>auth.py:<\/strong><\/p>\n<pre>\r\nimport transaction\r\n\r\nfrom sqlalchemy import create_engine\r\nfrom sqlalchemy import Column\r\nfrom sqlalchemy import Integer\r\nfrom sqlalchemy import Unicode\r\n\r\nfrom sqlalchemy.exc import IntegrityError\r\nfrom sqlalchemy.ext.declarative import declarative_base\r\n\r\nfrom sqlalchemy.orm import scoped_session\r\nfrom sqlalchemy.orm import sessionmaker\r\n\r\nfrom zope.sqlalchemy import ZopeTransactionExtension\r\n\r\nDBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))\r\nBase = declarative_base()\r\n\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\nimport os\r\nfrom hashlib import sha1\r\nfrom datetime import datetime\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 '<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    email = Column(Unicode(80), nullable=False)\r\n    contact = Column(Unicode(80), nullable=False)\r\n    company = Column(Unicode(80), nullable=False)\r\n\r\n    groups = relation('AuthGroup', secondary=user_group_table, backref='auth_users')\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 '<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<\/user:><\/group:><\/pre>\n<p><strong>login.py:<\/strong><\/p>\n<pre>\r\nfrom pyramid.httpexceptions import HTTPFound\r\nfrom pyramid.security import remember\r\nfrom pyramid.security import forget\r\nfrom pyramid.security import Allow\r\nfrom pyramid.security import Everyone\r\nfrom pyramid.url import route_url\r\nfrom pyramid.renderers import render_to_response\r\n\r\nfrom project.auth import AuthUser\r\nfrom project.models import DBSession\r\n\r\ndef login(request):\r\n    dbsession = DBSession()\r\n    login_url = route_url('login', request)\r\n    referrer = request.url\r\n    if referrer == login_url:\r\n        referrer = '\/' # never use the login form itself as came_from\r\n    came_from = request.params.get('came_from', referrer)\r\n    message = ''\r\n    login = ''\r\n    password = ''\r\n    if 'form.submitted' in request.params:\r\n        login = request.params['login']\r\n        password = request.params['password']\r\n        auth = dbsession.query(AuthUser).filter(AuthUser.username==login).first()\r\n        if auth.validate_password(password):\r\n            headers = remember(request, login)\r\n            \"\"\"\r\n                  We use the Primary Key as our identifier once someone has authenticated rather than the\r\n                  username.  You can change what is returned as the userid by altering what is passed to\r\n                  remember.\r\n            \"\"\"\r\n            #headers = remember(request, auth.id)\r\n            return HTTPFound(location = came_from,\r\n                             headers = headers)\r\n        message = 'Failed login'\r\n\r\n    return dict(\r\n        message = message,\r\n        url = request.application_url + '\/login',\r\n        came_from = came_from,\r\n        login = login,\r\n        password = password,\r\n        )\r\n    \r\ndef logout(request):\r\n    headers = forget(request)\r\n    return HTTPFound(location = route_url('root', request),\r\n                     headers = headers)\r\n    \r\ndef forbidden_view(request):\r\n    login_url = route_url('login', request)\r\n    referrer = request.url\r\n    if referrer == login_url:\r\n        referrer = '\/' # never use the login form itself as came_from\r\n    came_from = request.params.get('came_from', referrer)\r\n    return render_to_response('templates\/login.pt', dict(\r\n               message = '',\r\n               url = request.application_url + '\/login',\r\n               came_from = came_from,\r\n               login = '',\r\n               password = '',\r\n           ), request=request)\r\n\r\ndef groupfinder(userid, request):\r\n    dbsession = DBSession()\r\n    auth = dbsession.query(AuthUser).filter(AuthUser.id==userid).first()\r\n    if auth:\r\n        return [('group:%s' % group.name) for group in auth.groups]\r\n\r\nclass RootFactory(object):\r\n    __acl__ = [ (Allow, 'group:client', 'client'),\r\n                (Allow, 'group:manager', 'manager'),\r\n                (Allow, 'group:admin', 'admin') ]\r\n    def __init__(self, request):\r\n        self.__dict__.update(request.matchdict)\r\n<\/pre>\n<p><strong>templates\/login.pt:<\/strong><\/p>\n<pre>\r\n&lt;!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD XHTML 1.0 Transitional\/\/EN\"\r\n  \"http:\/\/www.w3.org\/TR\/xhtml1\/DTD\/xhtml1-transitional.dtd\">\r\n&lt;html\r\n    xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"\r\n    xmlns:tal=\"http:\/\/xml.zope.org\/namespaces\/tal\">\r\n\r\n&lt;head>\r\n  &lt;meta content=\"text\/html; charset=utf-8\" http-equiv=\"Content-Type\"\/>\r\n  &lt;title>Authentication Test&lt;\/title>\r\n  &lt;link rel=\"stylesheet\" type=\"text\/css\"\r\n        href=\"${request.application_url}\/static\/style.css\" \/>\r\n&lt;\/head>\r\n\r\n&lt;body>\r\n\r\n&lt;h1>Log In&lt;\/h1>\r\n\r\n&lt;div tal:replace=\"message\"\/>\r\n\r\n&lt;div class=\"main_content\">\r\n  &lt;form action=\"${url}\" method=\"post\">\r\n    &lt;input type=\"hidden\" name=\"came_from\" value=\"${came_from}\"\/>\r\n    &lt;input type=\"text\" name=\"login\" value=\"${login}\"\/>\r\n    &lt;br\/>\r\n    &lt;input type=\"password\" name=\"password\" value=\"${password}\"\/>\r\n    &lt;br\/>\r\n    &lt;input type=\"submit\" name=\"form.submitted\" value=\"Log In\"\/>\r\n  &lt;\/form>\r\n&lt;\/div>  \r\n\r\n&lt;\/body>\r\n&lt;\/html>\r\n<\/pre>\n<div style=\"float:left;\">\n<div id=\"fb-root\"><\/div>\n<fb:like href=\"https:\/\/cd34.com\/blog\/framework\/converting-pylons-1-0-repoze-whorepoze-what-to-pyramid-1-0a1-authentication-backed-with-mysql\/\" 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>For one of our projects we used Pylons 1.0 with Repoze.who\/Repoze.what, though, we only used groups without permissions. In our application, a member could access one of three controllers based on their membership in a group. There are a number of methods that can be used to set up authentication. One method is to create [&hellip;]<\/p>\n<div style=\"float:left;\">\n<div id=\"fb-root\"><\/div>\n<fb:like href=\"https:\/\/cd34.com\/blog\/framework\/converting-pylons-1-0-repoze-whorepoze-what-to-pyramid-1-0a1-authentication-backed-with-mysql\/\" 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":[152,80,148,151,150],"class_list":["post-1044","post","type-post","status-publish","format-standard","hentry","category-framework","tag-authentication","tag-pylons","tag-pyramid","tag-repoze-what","tag-repoze-who"],"_links":{"self":[{"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/posts\/1044","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=1044"}],"version-history":[{"count":10,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/posts\/1044\/revisions"}],"predecessor-version":[{"id":1053,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/posts\/1044\/revisions\/1053"}],"wp:attachment":[{"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/media?parent=1044"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/categories?post=1044"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cd34.com\/blog\/wp-json\/wp\/v2\/tags?post=1044"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}