1
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
3
<title>Twisted Documentation: HTTP Authentication</title>
4
<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
8
<h1 class="title">HTTP Authentication</h1>
9
<div class="toc"><ol/></div>
13
<p>Many of the previous examples have looked at how to serve content by using
14
existing resource classes or implementing new ones. In this example we'll use
15
Twisted Web's basic or digest HTTP authentication to control access to these
18
<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.guard.html" title="twisted.web.guard">guard</a></code>, the Twisted Web
19
module which provides most of the APIs that will be used in this example, helps
21
add <a href="http://en.wikipedia.org/wiki/Authentication" shape="rect">authentication</a>
22
and <a href="http://en.wikipedia.org/wiki/Authorization" shape="rect">authorization</a> to a
23
resource hierarchy. It does this by providing a resource which implements
24
<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code> to return
25
a <a href="dynamic-dispatch.html" shape="rect">dynamically selected resource</a>. The
26
selection is based on the authentication headers in the request. If those
27
headers indicate that the request is made on behalf of Alice, then Alice's
28
resource will be returned. If they indicate that it was made on behalf of Bob,
29
his will be returned. If the headers contain invalid credentials, an error
30
resource is returned. Whatever happens, once this resource is returned, URL
31
traversal continues as normal from that resource.</p>
33
<p>The resource that implements this is <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.guard.HTTPAuthSessionWrapper.html" title="twisted.web.guard.HTTPAuthSessionWrapper">HTTPAuthSessionWrapper</a></code>, though it is directly
34
responsible for very little of the process. It will extract headers from the
35
request and hand them off to a credentials factory to parse them according to
36
the appropriate standards (eg <a href="http://tools.ietf.org/html/rfc2617" shape="rect">HTTP
37
Authentication: Basic and Digest Access Authentication</a>) and then hand the
38
resulting credentials object off to a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code>, the core
39
of <a href="../../../core/howto/cred.html" shape="rect">Twisted Cred</a>, a system for
40
uniform handling of authentication and authorization. We won't discuss Twisted
41
Cred in much depth here. To make use of it with Twisted Web, the only thing you
42
really need to know is how to implement an <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code>.</p>
44
<p>You need to implement a realm because the realm is the object that actually
45
decides which resources are used for which users. This can be as complex or as
46
simple as it suitable for your application. For this example we'll keep it very
47
simple: each user will have a resource which is a static file listing of the
48
<code>public_html</code> directory in their UNIX home directory. First, we need
49
to import <code>implements</code> from <code>zope.interface</code> and <code>IRealm</code> from
50
<code>twisted.cred.portal</code>. Together these will let me mark this class as
51
a realm (this is mostly - but not entirely - a documentation thing). We'll also
52
need <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.static.File.html" title="twisted.web.static.File">File</a></code> for the actual
53
implementation later.</p>
55
<pre class="python"><p class="py-linenumber">1
62
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
64
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>
65
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
67
<span class="py-src-keyword">class</span> <span class="py-src-identifier">PublicHTMLRealm</span>(<span class="py-src-parameter">object</span>):
68
<span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
71
<p>A realm only needs to implement one method: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.IRealm.requestAvatar.html" title="twisted.cred.portal.IRealm.requestAvatar">requestAvatar</a></code>. This method is called
72
after any successful authentication attempt (ie, Alice supplied the right
73
password). Its job is to return the <i>avatar</i> for the user who succeeded in
74
authenticating. An <i>avatar</i> is just an object that represents a user. In
75
this case, it will be a <code>File</code>. In general, with <code>Guard</code>,
76
the avatar must be a resource of some sort.</p>
78
<pre class="python"><p class="py-linenumber">1
82
</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
83
<span class="py-src-keyword">if</span> <span class="py-src-variable">IResource</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
84
<span class="py-src-keyword">return</span> (<span class="py-src-variable">IResource</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">"/home/%s/public_html"</span> % (<span class="py-src-variable">avatarId</span>,)), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>)
85
<span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>()
88
<p>A few notes on this method:</p>
90
<li>The <code>avatarId</code> parameter is essentially the username. It's the
91
job of some other code to extract the username from the request headers and
92
make sure it gets passed here.</li>
93
<li>The <code>mind</code> is always <code>None</code> when writing a realm to
94
be used with <code>Guard</code>. You can ignore it until you want to write a
95
realm for something else.</li>
96
<li><code>Guard</code> is always
97
passed <code class="twisted.web.resource">IResource</code> as
98
the <code>interfaces</code> parameter. If <code>interfaces</code> only
99
contains interfaces your code doesn't understand,
100
raising <code>NotImplementedError</code> is the thing to do, as
101
above. You'll only need to worry about getting a different interface when
102
you write a realm for something other than <code>Guard</code>.</li>
103
<li>If you want to track when a user logs out, that's what the last element of
104
the returned tuple is for. It will be called when this avatar logs
105
out. <code>lambda: None</code> is the idiomatic no-op logout function.</li>
106
<li>Notice that the path handling code in this example is written very
107
poorly. This example may be vulnerable to certain unintentional information
108
disclosure attacks. This sort of problem is exactly the
109
reason <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.filepath.FilePath.html" title="twisted.python.filepath.FilePath">FilePath</a></code>
110
exists. However, that's an example for another day...</li>
113
<p>We're almost ready to set up the resource for this example. To create an
114
<code>HTTPAuthSessionWrapper</code>, though, we need two things. First, a
115
portal, which requires the realm above, plus at least one credentials
118
<pre class="python"><p class="py-linenumber">1
122
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Portal</span>
123
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePasswordDB</span>
125
<span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">PublicHTMLRealm</span>(), [<span class="py-src-variable">FilePasswordDB</span>(<span class="py-src-string">'httpd.password'</span>)])
128
<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.checkers.FilePasswordDB.html" title="twisted.cred.checkers.FilePasswordDB">FilePasswordDB</a></code> is the
129
credentials checker. It knows how to read <code>passwd(5)</code>-style (loosely)
130
files to check credentials against. It is responsible for the authentication
131
work after <code>HTTPAuthSessionWrapper</code> extracts the credentials from the
134
<p>Next we need either <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.guard.BasicCredentialFactory.html" title="twisted.web.guard.BasicCredentialFactory">BasicCredentialFactory</a></code> or
135
<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.guard.DigestCredentialFactory.html" title="twisted.web.guard.DigestCredentialFactory">DigestCredentialFactory</a></code>. The
136
former knows how to challenge HTTP clients to do basic authentication; the
137
latter, digest authentication. We'll use digest here:</p>
139
<pre class="python"><p class="py-linenumber">1
142
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DigestCredentialFactory</span>
144
<span class="py-src-variable">credentialFactory</span> = <span class="py-src-variable">DigestCredentialFactory</span>(<span class="py-src-string">"md5"</span>, <span class="py-src-string">"example.org"</span>)
147
<p>The two parameters to this constructor are the hash algorithm and the HTTP
148
authentication realm which will be used. The only other valid hash algorithm is
149
"sha" (but be careful, MD5 is more widely supported than SHA). The HTTP
150
authentication realm is mostly just a string that is presented to the user to
151
let them know why they're authenticating (you can read more about this in the
152
<a href="http://tools.ietf.org/html/rfc2617" shape="rect">RFC</a>).</p>
154
<p>With those things created, we can finally
155
instantiate <code>HTTPAuthSessionWrapper</code>:</p>
157
<pre class="python"><p class="py-linenumber">1
160
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">HTTPAuthSessionWrapper</span>
162
<span class="py-src-variable">resource</span> = <span class="py-src-variable">HTTPAuthSessionWrapper</span>(<span class="py-src-variable">portal</span>, [<span class="py-src-variable">credentialFactory</span>])
165
<p>There's just one last thing that needs to be done
166
here. When <a href="rpy-scripts.html" shape="rect">rpy scripts</a> were introduced, it was
167
mentioned that they are evaluated in an unusual context. This is the first
168
example that actually needs to take this into account. It so happens that
169
<code>DigestCredentialFactory</code> instances are stateful. Authentication will
170
only succeed if the same instance is used to both generate challenges and
171
examine the responses to those challenges. However, the normal mode of operation
172
for an rpy script is for it to be re-executed for every request. This leads to a
174
<code>DigestCredentialFactory</code> being created for every request, preventing
175
any authentication attempt from ever succeeding.</p>
177
<p>There are two ways to deal with this. First, and the better of the two ways,
178
we could move almost all of the code into a real Python module, including the
179
code that instantiates the <code>DigestCredentialFactory</code>. This would
180
ensure that the same instance was used for every request. Second, and the easier
181
of the two ways, we could add a call to <code>cache()</code> to the beginning of
184
<pre class="python"><p class="py-linenumber">1
185
</p><span class="py-src-variable">cache</span>()
188
<p><code>cache</code> is part of the globals of any rpy script, so you don't
189
need to import it (it's okay to be cringing at this
190
point). Calling <code>cache</code> makes Twisted re-use the result of the first
191
evaluation of the rpy script for subsequent requests too - just what we want in
194
<p>Here's the complete example (with imports re-arranged to the more
195
conventional style):</p>
197
<pre class="python"><p class="py-linenumber"> 1
219
</p><span class="py-src-variable">cache</span>()
221
<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
223
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>, <span class="py-src-variable">Portal</span>
224
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePasswordDB</span>
225
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
226
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IResource</span>
227
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">HTTPAuthSessionWrapper</span>, <span class="py-src-variable">DigestCredentialFactory</span>
229
<span class="py-src-keyword">class</span> <span class="py-src-identifier">PublicHTMLRealm</span>(<span class="py-src-parameter">object</span>):
230
<span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
232
<span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
233
<span class="py-src-keyword">if</span> <span class="py-src-variable">IResource</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
234
<span class="py-src-keyword">return</span> (<span class="py-src-variable">IResource</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">"/home/%s/public_html"</span> % (<span class="py-src-variable">avatarId</span>,)), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>)
235
<span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>()
237
<span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">PublicHTMLRealm</span>(), [<span class="py-src-variable">FilePasswordDB</span>(<span class="py-src-string">'httpd.password'</span>)])
239
<span class="py-src-variable">credentialFactory</span> = <span class="py-src-variable">DigestCredentialFactory</span>(<span class="py-src-string">"md5"</span>, <span class="py-src-string">"localhost:8080"</span>)
240
<span class="py-src-variable">resource</span> = <span class="py-src-variable">HTTPAuthSessionWrapper</span>(<span class="py-src-variable">portal</span>, [<span class="py-src-variable">credentialFactory</span>])
243
<p>And voila, a password-protected per-user Twisted Web server.</p>
247
<p><a href="../index.html">Index</a></p>
248
<span class="version">Version: 10.0.0</span>
b'\\ No newline at end of file'