Skip to content

Commit

Permalink
Finish the Pyramid for Pylons Users guide.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeorr committed Jun 4, 2012
1 parent 48f7568 commit 3d5936f
Show file tree
Hide file tree
Showing 7 changed files with 397 additions and 36 deletions.
4 changes: 4 additions & 0 deletions links.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@

.. _Deform: http://docs.pylonsproject.org/projects/deform/en/latest/
.. _pyramid_simpleform: http://packages.python.org/pyramid_simpleform/

.. _Kotti: http://kotti.readthedocs.org/en/latest/index.html
.. _Ptah: http://ptahproject.readthedocs.org/en/latest/index.html
.. _Khufu: http://pypi.python.org/pypi?%3Aaction=search&term=khufu&submit=search
160 changes: 160 additions & 0 deletions pylons/auth.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
Authentication and Authorization
++++++++++++++++++++++++++++++++

*This chapter is contributed by Eric Rasmussen.*

Pyramid has built-in authentication and authorization capibalities that make it
easy to restrict handler actions. Here is an overview of the steps you'll
generally need to take:

1) Create a root factory in your model that associates allow/deny directives
with groups and permissions
2) Create users and groups in your model
3) Create a callback function to retrieve a list of groups a user is subscribed to based on their user ID
4) Make a "forbidden view" that will be invoked when a Forbidden exception is
raised.
5) Create a login action that will check the username/password and remember the
user if successful
6) Restrict access to handler actions by passing in a
permission='somepermission' argument to ``@view_config``.
7) Wire it all together in your config

You can get started by adding an import statement and custom root factory to
your model::

from pyramid.security import Allow, Everyone

class RootFactory(object):
__acl__ = [ (Allow, Everyone, "everybody"),
(Allow, "basic", "entry"),
(Allow, "secured", ("entry", "topsecret"))
]
def __init__(self, request):
pass

The custom root factory generates objects that will be used as the context of
requests sent to your web application. The first attribute of the root factory
is the ACL, or access control list. It's a list of tuples that contain a
directive to handle the request (such as Allow or Deny), the group that is
granted or denied access to the resource, and a permission (or optionally a
tuple of permissions) to be associated with that group.

The example access control list above indicates that we will allow everyone to
view pages with the 'everybody' permission, members of the basic group to view
pages restricted with the 'entry' permission, and members of the secured group
to view pages restricted with either the 'entry' or 'topsecret' permissions.
The special principal 'Everyone' is a built-in feature that allows any person
visiting your site (known as a principal) access to a given resource.

For a user to login, you can create a handler that validates the login and
password (or any additional criteria) submitted through a form. You'll
typically want to add the following imports::

from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget
Once you validate a user's login and password against the model, you can set
the headers to "remember" the user's ID, and then you can redirect the user to
the home page or url they were trying to access::

# retrieve the userid from the model on valid login
headers = remember(self.request, userid)
return HTTPFound(location=someurl, headers=headers)

Note that in the call to the remember function, we're passing in the user ID we
retrieved from the database and stored in the variable 'userid' (an arbitrary
name used here as an example). However, you could just as easily pass in a
username or other unique identifier. Whatever you decide to "remember" is what
will be passed to the groupfinder callback function that returns a list of
groups a user belongs to. If you import ``authenticated_userid``, which is a
useful way to retrieve user information in a handler action, it will return the
information you set the headers to "remember".

To log a user out, you "forget" them, and use HTTPFound to redirect to another
url::

headers = forget(self.request)
return HTTPFound(location=someurl, headers=headers)

Before you restrict a handler action with a permission, you will need a
callback function to return a list of groups that a user ID belongs to. Here is
one way to implement it in your model, in this case assuming you have a Groups
object with a groupname attribute and a Users object with a mygroups relation
to Groups::

def groupfinder(userid, request):
user = Users.by_id(userid)
return [g.groupname for g in user.mygroups]

As an example, you could now import and use the @action decorator to restrict
by permission, and authenticated_userid to retrieve the user's ID from the
request::

from pyramid_handlers import action
from pyramid.security import authenticated_userid
from models import Users

class MainHandler(object):
def __init__(self, request):
self.request = request
@action(renderer="welcome.html", permission="entry")
def index(self):
userid = authenticated_userid(self.request)
user = Users.by_id(userid)
username = user.username
return {"currentuser": username}

This gives us a very simple way to restrict handler actions and also obtain
information about the user. This example assumes we have a Users class with a
convenience class method called by_id to return the user object. You can then
access any of the object's attributes defined in your model (such as username,
email address, etc.), and pass those to a template as dictionary key/values in
your return statement.

If you would like a specific handler action to be called when a forbidden
exception is raised, you need to add a forbidden view. This was covered
earlier, but for completelness::

@view_config(renderer='myapp:templates/forbidden.html',
context='pyramid.exceptions.Forbidden')
@action(renderer='forbidden.html')
def forbidden(request):
...

The last step is to configure __init__.py to use your auth policy. Make sure to
add these imports::

from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from .models import groupfinder

In your main function you'll want to define your auth policies so you can
include them in the call to Configurator::

authn_policy = AuthTktAuthenticationPolicy('secretstring',
callback=groupfinder)
authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings,
root_factory='myapp.models.RootFactory',
authentication_policy=authn_policy,
authorization_policy=authz_policy)
config.scan()

The capabilities for authentication and authorization in Pyramid are very easy
to get started with compared to using Pylons and repoze.what. The advantage is
easier to maintain code and built-in methods to handle common tasks like
remembering or forgetting users, setting permissions, and easily modifying the
groupfinder callback to work with your model. For cases where it's manageable
to set permissions in advance in your root factory and restrict individual
handler actions, this is by far the simplest way to get up and running while
still offering robust user and group management capabilities through your
model.

However, if your application requires the ability to create/edit/delete
permissions (not just access through group membership), or you require the use
of advanced predicates, you can either build your own auth system (see the
Pyramid docs for details) or integrate an existing system like repoze.what.

You can also use "repoze.who" with Pyramid's authorization system if you want to
use Who's authenticators and configuration.
12 changes: 12 additions & 0 deletions pylons/deployment.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Deployment
++++++++++

Deployment is the same for Pyramid as for Pylons. Specify the desired WSGI
server in the "[server:main]" and run "pserve" with it. The default server in
Pyramid is Waitress, compared to PasteHTTPServer in Pylons.

Waitress' advantage is that it runs on Python 3. Its disadvantage is that it
doesn't seek and destroy stuck threads like PasteHTTPServer does. If you're
like me, that's enough reason not to use Waitress in production. You can switch
to PasteHTTPServer or CherryPy server if you wish, or use a method like
mod_wsgi that doesn't require a Python HTTP server.
8 changes: 4 additions & 4 deletions pylons/index.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
Pyramid for Pylons Users
++++++++++++++++++++++++

:Updated: 2012-04-30
:Updated: 2012-06-12
:Versions: Pyramid 1.3
:Author: Mike Orr
:Contributors:

*XXX This guide is under construction....*

This guide discusses how Pyramid 1.3 differs from Pylons 1, and a few ways to
make it more like Pylons. The guide may also be helpful to readers coming from
Django or another Rails-like framework. The author has been a Pylons developer
Expand Down Expand Up @@ -36,8 +34,10 @@ skim through the rest of the manual to see which sections cover which topics.
exceptions
static
sessions
deployment
auth
other
migrate
unfinished


.. include:: ../links.rst
41 changes: 31 additions & 10 deletions pylons/migrate.rst
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
Migrating an Existing Pylons Application
++++++++++++++++++++++++++++++++++++++++

If you're upgrading a large Pylons application to Pyramid, you can do it one
route at a time and have it fall back to the Pylons application for URLs which
don't exist. There are a few ways to set this up. One is to use Paste Cascade
-- the same way Pylons serves static files. In the INI file, make the main
application ``paste.cascade.Cascade``, delegating to the Pyramid application
first and falling back to the Pylons app.
There are two general ways to port a Pylons application to Pyramid. One is to
start from scratch, expressing the application's behavior in Pyramid. Many
aspects such as the models, templates, and static files can be used unchanged
or mostly unchanged. Other aspects like such as the controllers and globals
will have to be rewritten. The route map can be ported to the new syntax, or
you can take the opportunity to restructure your routes.

Another way is to wrap the Pylons application in a Pyramid view. See
`pyramid.wsgiapp.wsgiapp2`_ and `Porting an Existing WSGI Application to
Pyramid`_.
The other way is to port one URL at a time, and let Pyramid serve the ported
URLs and Pylons serve the unported URLs. There are several ways to do this:

* Run both the Pyramid and Python applications in Apache, and use mod_rewrite
to send different URLs to different applications.
* Set up ``paste.cascade`` in the INI file, so that it will first try one
application and then the other if the URL returns "Not Found". (This is how
Pylons serves static files.)
* Wrap the Pylons application in a Pyramid view. See pyramid.wsgiapp.wsgiapp2_.

Also see the `Porting Applications to Pyramid`_ section in the Cookbook.

*Caution:* running a Pyramid and a Pylons application simultaneously may bring up
some tricky issues such as coordiating database connections, sessions, data
files, etc. These are beyond the scope of this Guide.

You'll also have to choose whether to write the Pyramid application in Python 2
or 3. Pyramid 1.3 runs on Python 3, along with Mako and SQLAlchemy, and the
Waitress and CherryPy HTTP servers (but not PasteHTTPServer). But not all
optional libraries have been ported yet, and your application may depend on
libraries which haven't been.


.. include:: ../links.rst


.. _pyramid.wsgiapp.wsgiapp2: http://docs.pylonsproject.org/projects/pyramid/en/latest/api/wsgi.html#pyramid.wsgi.wsgiapp2
.. _Porting an Existing WSGI Application to Pyramid: http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/porting/wsgi.html
.. _Porting Applications to Pyramid: ../porting/index.html
Loading

0 comments on commit 3d5936f

Please sign in to comment.