summaryrefslogtreecommitdiff
path: root/tests/test_tools/simpletest/docs/en/authentication_documentation.html
blob: c90d61e53120ddf3e3d2378c91b11b13779821bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>SimpleTest documentation for testing log-in and authentication</title>
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
</head>
<body>
<div class="menu_back">
<div class="menu">
<h2>
<a href="index.html">SimpleTest</a>
</h2>
<ul>
<li>
<a href="overview.html">Overview</a>
</li>
<li>
<a href="unit_test_documentation.html">Unit tester</a>
</li>
<li>
<a href="group_test_documentation.html">Group tests</a>
</li>
<li>
<a href="mock_objects_documentation.html">Mock objects</a>
</li>
<li>
<a href="partial_mocks_documentation.html">Partial mocks</a>
</li>
<li>
<a href="reporter_documentation.html">Reporting</a>
</li>
<li>
<a href="expectation_documentation.html">Expectations</a>
</li>
<li>
<a href="web_tester_documentation.html">Web tester</a>
</li>
<li>
<a href="form_testing_documentation.html">Testing forms</a>
</li>
<li>
<span class="chosen">Authentication</span>
</li>
<li>
<a href="browser_documentation.html">Scriptable browser</a>
</li>
</ul>
</div>
</div>
<h1>Authentication documentation</h1>
<div class="content">
        
            <p>
                One of the trickiest, and yet most important, areas
                of testing web sites is the security.
                Testing these schemes is one of the core goals of
                the SimpleTest web tester.
            </p>
        
        <p>
<a class="target" name="basic">
<h2>Basic HTTP authentication</h2>
</a>
</p>
            <p>
                If you fetch a page protected by basic authentication then
                rather than receiving content, you will instead get a 401
                header.
                We can illustrate this with this test...
<pre>
class AuthenticationTest extends WebTestCase {<strong>
    function test401Header() {
        $this-&gt;get('http://www.lastcraft.com/protected/');
        $this-&gt;showHeaders();
    }</strong>
}
</pre>
                This allows us to see the challenge header...
                <div class="demo">
                    <h1>File test</h1>
<pre style="background-color: lightgray; color: black">
HTTP/1.1 401 Authorization Required
Date: Sat, 18 Sep 2004 19:25:18 GMT
Server: Apache/1.3.29 (Unix) PHP/4.3.4
WWW-Authenticate: Basic realm="SimpleTest basic authentication"
Connection: close
Content-Type: text/html; charset=iso-8859-1
</pre>
                    <div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
                    <strong>0</strong> passes, <strong>0</strong> fails and <strong>0</strong> exceptions.</div>
                </div>
                We are trying to get away from visual inspection though, and so SimpleTest
                allows to make automated assertions against the challenge.
                Here is a thorough test of our header...
<pre>
class AuthenticationTest extends WebTestCase {
    function test401Header() {
        $this-&gt;get('http://www.lastcraft.com/protected/');<strong>
        $this-&gt;assertAuthentication('Basic');
        $this-&gt;assertResponse(401);
        $this-&gt;assertRealm('SimpleTest basic authentication');</strong>
    }
}
</pre>
                Any one of these tests would normally do on it's own depending
                on the amount of detail you want to see.
            </p>
            <p>
                One theme that runs through SimpleTest is the ability to use
                <span class="new_code">SimpleExpectation</span> objects wherever a simple
                match is not enough.
                If you want only an approximate match to the realm for
                example, you can do this...
<pre>
class AuthenticationTest extends WebTestCase {
    function test401Header() {
        $this-&gt;get('http://www.lastcraft.com/protected/');
        $this-&gt;assertRealm(<strong>new PatternExpectation('/simpletest/i')</strong>);
    }
}
</pre>
                Most of the time we are not interested in testing the
                authentication itself, but want to get past it to test
                the pages underneath.
                As soon as the challenge has been issued we can reply with
                an authentication response...
<pre>
class AuthenticationTest extends WebTestCase {
    function testCanAuthenticate() {
        $this-&gt;get('http://www.lastcraft.com/protected/');<strong>
        $this-&gt;authenticate('Me', 'Secret');</strong>
        $this-&gt;assertTitle(...);
    }
}
</pre>
                The username and password will now be sent with every
                subsequent request to that directory and subdirectories.
                You will have to authenticate again if you step outside
                the authenticated directory, but SimpleTest is smart enough
                to merge subdirectories into a common realm.
            </p>
            <p>
                You can shortcut this step further by encoding the log in
                details straight into the URL...
<pre>
class AuthenticationTest extends WebTestCase {
    function testCanReadAuthenticatedPages() {
        $this-&gt;get('http://<strong>Me:Secret@</strong>www.lastcraft.com/protected/');
        $this-&gt;assertTitle(...);
    }
}
</pre>
                If your username or password has special characters, then you
                will have to URL encode them or the request will not be parsed
                correctly.
                Also this header will not be sent on subsequent requests if
                you request a page with a fully qualified URL.
                If you navigate with relative URLs though, the authentication
                information will be preserved.
            </p>
            <p>
                Only basic authentication is currently supported and this is
                only really secure in tandem with HTTPS connections.
                This is usually enough to protect test server from prying eyes,
                however.
                Digest authentication and NTLM authentication may be added
                in the future.
            </p>
        
        <p>
<a class="target" name="cookies">
<h2>Cookies</h2>
</a>
</p>
            <p>
                Basic authentication doesn't give enough control over the
                user interface for web developers.
                More likely this functionality will be coded directly into
                the web architecture using cookies and complicated timeouts.
            </p>
            <p>
                Starting with a simple log-in form...
<pre>
&lt;form&gt;
    Username:
    &lt;input type="text" name="u" value="" /&gt;&lt;br /&gt;
    Password:
    &lt;input type="password" name="p" value="" /&gt;&lt;br /&gt;
    &lt;input type="submit" value="Log in" /&gt;
&lt;/form&gt;
</pre>
                Which looks like...
            </p>
            <p>
                <form class="demo">
                    Username:
                    <input type="text" name="u" value="">
<br>
                    Password:
                    <input type="password" name="p" value="">
<br>
                    <input type="submit" value="Log in">
                </form>
            </p>
            <p>
                Let's suppose that in fetching this page a cookie has been
                set with a session ID.
                We are not going to fill the form in yet, just test that
                we are tracking the user.
                Here is the test...
<pre>
class LogInTest extends WebTestCase {
    function testSessionCookieSetBeforeForm() {
        $this-&gt;get('http://www.my-site.com/login.php');<strong>
        $this-&gt;assertCookie('SID');</strong>
    }
}
</pre>
                All we are doing is confirming that the cookie is set.
                As the value is likely to be rather cryptic it's not
                really worth testing this with...
<pre>
class LogInTest extends WebTestCase {
    function testSessionCookieIsCorrectPattern() {
        $this-&gt;get('http://www.my-site.com/login.php');
        $this-&gt;assertCookie('SID', <strong>new PatternExpectation('/[a-f0-9]{32}/i')</strong>);
    }
}
</pre>
                The rest of the test would be the same as any other form,
                but we might want to confirm that we still have the same
                cookie after log-in as before we entered.
                We wouldn't want to lose track of this after all.
                Here is a possible test for this...
<pre>
class LogInTest extends WebTestCase {
    ...
    function testSessionCookieSameAfterLogIn() {
        $this-&gt;get('http://www.my-site.com/login.php');<strong>
        $session = $this-&gt;getCookie('SID');
        $this-&gt;setField('u', 'Me');
        $this-&gt;setField('p', 'Secret');
        $this-&gt;click('Log in');
        $this-&gt;assertText('Welcome Me');
        $this-&gt;assertCookie('SID', $session);</strong>
    }
}
</pre>
                This confirms that the session identifier is maintained
                afer log-in.
            </p>
            <p>
                We could even attempt to spoof our own system by setting
                arbitrary cookies to gain access...
<pre>
class LogInTest extends WebTestCase {
    ...
    function testSessionCookieSameAfterLogIn() {
        $this-&gt;get('http://www.my-site.com/login.php');<strong>
        $this-&gt;setCookie('SID', 'Some other session');
        $this-&gt;get('http://www.my-site.com/restricted.php');</strong>
        $this-&gt;assertText('Access denied');
    }
}
</pre>
                Is your site protected from this attack?
            </p>
        
        <p>
<a class="target" name="session">
<h2>Browser sessions</h2>
</a>
</p>
            <p>
                If you are testing an authentication system a critical piece
                of behaviour is what happens when a user logs back in.
                We would like to simulate closing and reopening a browser...
<pre>
class LogInTest extends WebTestCase {
    ...
    function testLoseAuthenticationAfterBrowserClose() {
        $this-&gt;get('http://www.my-site.com/login.php');
        $this-&gt;setField('u', 'Me');
        $this-&gt;setField('p', 'Secret');
        $this-&gt;click('Log in');
        $this-&gt;assertText('Welcome Me');<strong>
        
        $this-&gt;restart();
        $this-&gt;get('http://www.my-site.com/restricted.php');
        $this-&gt;assertText('Access denied');</strong>
    }
}
</pre>
                The <span class="new_code">WebTestCase::restart()</span> method will
                preserve cookies that have unexpired timeouts, but throw away
                those that are temporary or expired.
                You can optionally specify the time and date that the restart
                happened.
            </p>
            <p>
                Expiring cookies can be a problem.
                After all, if you have a cookie that expires after an hour,
                you don't want to stall the test for an hour while the
                cookie passes it's timeout.
            </p>
            <p>
                To push the cookies over the hour limit you can age them
                before you restart the session...
<pre>
class LogInTest extends WebTestCase {
    ...
    function testLoseAuthenticationAfterOneHour() {
        $this-&gt;get('http://www.my-site.com/login.php');
        $this-&gt;setField('u', 'Me');
        $this-&gt;setField('p', 'Secret');
        $this-&gt;click('Log in');
        $this-&gt;assertText('Welcome Me');
        <strong>
        $this-&gt;ageCookies(3600);</strong>
        $this-&gt;restart();
        $this-&gt;get('http://www.my-site.com/restricted.php');
        $this-&gt;assertText('Access denied');
    }
}
</pre>
                After the restart it will appear that cookies are an
                hour older and any that pass their expiry will have
                disappeared.
            </p>
        
    </div>
<div class="copyright">
            Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004
        </div>
</body>
</html>