guardindepth.xhtml   [plain text]


<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Guard In Depth</title></head><body>
<h1>Guard In Depth</h1>

<div class="note">
<p>
This HOWTO documents Woven Guard, part of the Woven framework. The Woven framework should not be used for new projects. The newer <a href="http://www.divmod.org/Home/Projects/Nevow/">Nevow</a> framework, available as part of the <a href="http://www.divmod.org/Quotient/">Quotient</a> project, is a simpler framework with consistent semantics and better testing and is strongly recommended over Woven.
</p>
</div>

<p>Woven guard is founded on the <a href="cred.xhtml">twisted.cred</a>
framework, and it is important to be somewhat familiar with cred to work with
guard. As always, when using this framework, start by writing a
<code>IRealm</code>. The guard module expects the realm to respond to the
<code>resource.IResource</code> interace.</p>

<p>Here is an example of a simple realm which generates different
resources for logged-in and non-logged-in users:</p>

<pre class="python">
from twisted.cred import portal, checkers
from twisted.web import resource, static

def noLogout():
    pass

class Realm:
    __implements__ = portal.IRealm
    def requestAvatar(self, avatarId, mind, *interfaces):
        if resource.IResource not in interfaces:
            raise NotImplementedError
        if avatarId is checkers.ANONYMOUS:
            avatar = static.Data("Who are you?", 'text/html')
        else:
            avatar = static.Data("The answer is 42", 'text/html')
        return (resource.IResource, avatar, noLogout)
</pre>

<p>This realm makes sure that only non-anonymous users will know
the answer to life, the universe and everything.</p>

<p>Usually, one or more custom resource classes will be written
when using guard, but it is not necessary. For example, a guard
intended to protect a certain directory can use a
<code>static.File</code> resource. Note that the <code>IRealm</code>
should be prepared to get the <code>ANONYMOUS</code> avatar ID,
and handle it correctly. From whatever resource the anonymous
avatar gets, a link should point to <code>guard.INIT_PERSPECTIVE</code>
relative to the guard root. This is the only way users can
log in. From whatever resources the
non-anonymous avatars get it is strongly recommended to link to
<code>guard.DESTROY_PERSPECTIVE</code>. This is the only way
users can log out.</p>

<pre class="python">
from twisted.cred import portal, checkers
from twisted.web import resource, static
from twisted.web.woven import guard

login='&lt;a href="%s">login&lt;/a>' % guard.PERSPECTIVE_INIT
logout='&lt;a href="%s">logout&lt;/a>' % guard.PERSPECTIVE_DESTROY

def noLogout():
    pass

class Realm:
    __implements__ = portal.IRealm
    def requestAvatar(self, avatarId, mind, *interfaces):
        if resource.IResource not in interfaces:
            raise NotImplementedError
        if avatarId is checkers.ANONYMOUS:
            avatar = static.Data("Who are you?&lt;br>"+login, 'text/html')
        else:
            avatar = static.Data("The answer is 42, %s&lt;br>%s" % (avatarId,
                                 logout), 'text/html')
        return (resource.IResource, avatar, noLogout)
</pre>

<p>Once the realm is written, it is possible to generate a resource
which will wrap it with appropriate code to manage users, sessions
and authentication. But, as always, nothing deals with the realm
directly -- all the rest of the code deals with a <code>Portal</code>
which wraps the realm.</p>

<div class="note">
You will almost always want to put <code>checkers.AllowAnonymousAccess</code>
in the checkers registered for the <code>Portal</code>, otherwise it
will be impossible to log in.
</div>

<p>The canonical thing is to use
<code>resource=guard.SessionWrapper(guard.UsernamePasswordWrapper(portal,
                 callback=callback)</code>.
The <code>callback</code> is used to redirect the request to
the appropriate place after a successful login. Usually, you
will want to redirect to the parent:</p>

<pre class="python">
def parentRedirect(_):
    return util.ParentRedirect()
</pre>

<p>When a client first reaches a guarded resource, it is redirected
to <code>session-init/</code>. From there, it will be redirected
to a URL containing a long magic hex string, where a cookie will
be set, and then to the original URL with
<code>?__session_just_started__=1</code> tucked at the end. The
addition is to guarantee that the client will not think it is in
a redirection loop (wget, for example, has that problem).</p>

<p>Note that in resources which are children of the guarded resources,
<code>request.getSession</code> automatically returns the Woven session.
Since it is a <code>Componentized</code>, it is possible to use
<code>getComponent</code> and <code>setComponent</code> to keep
state related to a user in the session.</p>

<p>For simple cases, the approach described here leads to quite a bit
of boiler-plate code (about 30 lines or so). If a web application has
simple authentication needs, it is possible to use <code>simpleguard</code>,
which allows you to skip implementing a realm yourself.</p>

<p>The important function in <code>simpleguard</code> is
<code>resource=guardResource(resource, checkers, nonauthenticated=None)</code>.
<code>checkers</code> should be a list of <code>ICredentialChecker</code>s.
It is not necessary to put <code>AllowAnonymousAccess</code> here --
it will be added automatically. This allow you to differentiate in resources
only based on authenticated/anonymous users, without finer distinction.
However, in the given resources, and their children, it is possible
to use <code>request.getComponent(simpleguard.Authenticated)</code>. This
will return <code>None</code> if the request is anonymous, or an instance
with a <code>.name</code> attribute, which is the avatar ID. This can
allow the application to personalize parts of it.</p>

<p>The way the login page and error-on-login page look can be customized
when creating the guarded resource. Here is an example:</p>

<a href="listings/guard/login.py" class="py-listing">listings/guard/login.py</a>

<p>The trick here is that, of course, we define the <em>original</em> login
page: while <code>guard.PERSPECTIVE_INIT</code> gives a default form, it
is quite all right to use a different one. In this example, we just use
the home page of non-authenticated users to show the login form. There
is one other case where the default form will be shown: if the attempt
to login did not succeed. However, this is only what happens by default:
we can override this by supplying an <code>errback</code>. This will
be called with a model describing the form, and all we have to do is
to use woven to display it.</p>

<p>Note that this example uses <code>simpleguard</code>, but equivalent
code can be developed using <code>guard</code> -- the errback should
be passed to the <code>UsernamePasswordWrapper</code> constructor
in that case.</p>

</body></html>