View | Details | Raw Unified | Return to bug 55960
Collapse All | Expand All

(-)test/org/apache/catalina/authenticator/TestSSOnonLoginAndBasicAuthenticator.java (-141 / +428 lines)
Lines 22-35 Link Here
22
import java.util.List;
22
import java.util.List;
23
import java.util.Map;
23
import java.util.Map;
24
24
25
import javax.servlet.http.HttpServletResponse;
26
25
import static org.junit.Assert.assertEquals;
27
import static org.junit.Assert.assertEquals;
26
import static org.junit.Assert.assertNull;
28
import static org.junit.Assert.assertFalse;
29
import static org.junit.Assert.assertNotNull;
27
import static org.junit.Assert.assertTrue;
30
import static org.junit.Assert.assertTrue;
28
31
29
import org.junit.Test;
32
import org.junit.Test;
30
33
31
import org.apache.catalina.Context;
34
import org.apache.catalina.Context;
32
import org.apache.catalina.startup.TesterServlet;
35
import org.apache.catalina.Session;
36
import org.apache.catalina.session.ManagerBase;
37
import org.apache.catalina.startup.TesterServletEncodeUrl;
33
import org.apache.catalina.startup.Tomcat;
38
import org.apache.catalina.startup.Tomcat;
34
import org.apache.catalina.startup.TomcatBaseTest;
39
import org.apache.catalina.startup.TomcatBaseTest;
35
import org.apache.tomcat.util.buf.ByteChunk;
40
import org.apache.tomcat.util.buf.ByteChunk;
Lines 47-58 Link Here
47
 * simply cannot access protected resources. These tests exercise the
52
 * simply cannot access protected resources. These tests exercise the
48
 * the way successfully authenticating a different webapp under the
53
 * the way successfully authenticating a different webapp under the
49
 * BasicAuthenticator triggers the additional SSO logic for both webapps.
54
 * BasicAuthenticator triggers the additional SSO logic for both webapps.
55
 *
56
 * <p>
57
 * The two Authenticators are thoroughly exercised by two other unit test
58
 * classes: TestBasicAuthParser and TestNonLoginAndBasicAuthenticator.
59
 * This class mainly examines the way the Single SignOn Valve interacts with
60
 * two webapps when the second cannot be authenticated directly, but needs
61
 * to inherit its authentication via the other.
62
 *
63
 * <p>
64
 * When the server and client can both use cookies, the authentication
65
 * is preserved through the exchange of a JSSOSESSIONID cookie, which
66
 * is different to the individual and unique JSESSIONID cookies assigned
67
 * separately to the two webapp sessions.
68
 *
69
 * <p>
70
 * The other situation examined is where the server returns authentication
71
 * cookies, but the client is configured to ignore them. The Tomcat
72
 * documentation clearly states that SSO <i>requires</i> the client to
73
 * support cookies, so access to resources in other webapp containers
74
 * receives no SSO assistance.
50
 */
75
 */
51
public class TestSSOnonLoginAndBasicAuthenticator extends TomcatBaseTest {
76
public class TestSSOnonLoginAndBasicAuthenticator extends TomcatBaseTest {
52
77
78
    protected static final boolean USE_COOKIES = true;
79
    protected static final boolean NO_COOKIES = !USE_COOKIES;
80
53
    private static final String USER = "user";
81
    private static final String USER = "user";
54
    private static final String PWD = "pwd";
82
    private static final String PWD = "pwd";
55
    private static final String ROLE = "role";
83
    private static final String ROLE = "role";
84
    private static final String NICE_METHOD = "Basic";
56
85
57
    private static final String HTTP_PREFIX = "http://localhost:";
86
    private static final String HTTP_PREFIX = "http://localhost:";
58
    private static final String CONTEXT_PATH_NOLOGIN = "/nologin";
87
    private static final String CONTEXT_PATH_NOLOGIN = "/nologin";
Lines 60-275 Link Here
60
    private static final String URI_PROTECTED = "/protected";
89
    private static final String URI_PROTECTED = "/protected";
61
    private static final String URI_PUBLIC = "/anyoneCanAccess";
90
    private static final String URI_PUBLIC = "/anyoneCanAccess";
62
91
63
    private static final int SHORT_TIMEOUT_SECS = 4;
92
    // session expiry in web.xml is defined in minutes
64
    private static final long SHORT_TIMEOUT_DELAY_MSECS =
93
    private static final int SHORT_SESSION_TIMEOUT_MINS = 1;
65
                                    ((SHORT_TIMEOUT_SECS + 3) * 1000);
94
    private static final int LONG_SESSION_TIMEOUT_MINS = 2;
66
    private static final int LONG_TIMEOUT_SECS = 10;
67
    private static final long LONG_TIMEOUT_DELAY_MSECS =
68
                                    ((LONG_TIMEOUT_SECS + 5) * 1000);
69
95
70
    private static String CLIENT_AUTH_HEADER = "authorization";
96
    // we don't change the expiry scan interval - just the iteration count
71
    private static String SERVER_COOKIES = "Set-Cookie";
97
    private static final int MANAGER_SCAN_INTERVAL_SECS = 10;
72
    private static String BROWSER_COOKIES = "Cookie";
98
    private static final int MANAGER_EXPIRE_SESSIONS_FAST = 1;
73
99
100
    // now compute some delays - beware of the units!
101
    private static final int EXTRA_DELAY_SECS = 5;
102
    private static final long REASONABLE_MSECS_TO_EXPIRY =
103
            (((MANAGER_SCAN_INTERVAL_SECS * MANAGER_EXPIRE_SESSIONS_FAST)
104
                    + EXTRA_DELAY_SECS) * 1000);
105
106
    private static final String CLIENT_AUTH_HEADER = "authorization";
107
    private static final String SERVER_AUTH_HEADER = "WWW-Authenticate";
108
    private static final String SERVER_COOKIE_HEADER = "Set-Cookie";
109
    private static final String CLIENT_COOKIE_HEADER = "Cookie";
110
    private static final String ENCODE_SESSION_PARAM = "jsessionid";
111
    private static final String ENCODE_SSOSESSION_PARAM = "jssosessionid";
112
113
    private static final
114
            TestSSOnonLoginAndBasicAuthenticator.BasicCredentials
115
                    NO_CREDENTIALS = null;
116
    private static final
117
            TestSSOnonLoginAndBasicAuthenticator.BasicCredentials
118
                    GOOD_CREDENTIALS =
119
                new TestSSOnonLoginAndBasicAuthenticator.BasicCredentials(
120
                            NICE_METHOD, USER, PWD);
121
122
    private Tomcat tomcat;
123
    private Context basicContext;
124
    private Context nonloginContext;
74
    private List<String> cookies;
125
    private List<String> cookies;
126
    private String encodedURL;
75
127
76
    /*
128
    /*
77
     * Try to access an unprotected resource without an established
129
     * Run some sanity checks without an established SSO session
78
     * SSO session.
130
     * to make sure the test environment is correct.
79
     * This should be permitted.
80
     */
131
     */
81
    @Test
132
    @Test
82
    public void testAcceptPublicNonLogin() throws Exception {
133
    public void testEssentialEnvironment() throws Exception {
134
135
        // should be permitted to access an unprotected resource.
83
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PUBLIC,
136
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PUBLIC,
84
                        false, false, 200);
137
                       USE_COOKIES, HttpServletResponse.SC_OK);
138
139
        // should not be permitted to access a protected resource
140
        // with the two Authenticators used in the remaining tests.
141
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
142
                USE_COOKIES, HttpServletResponse.SC_FORBIDDEN);
143
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
144
                NO_CREDENTIALS, USE_COOKIES,
145
                HttpServletResponse.SC_UNAUTHORIZED);
85
    }
146
    }
86
147
87
    /*
88
     * Try to access a protected resource without an established
89
     * SSO session.
90
     * This should be rejected with SC_FORBIDDEN 403 status.
91
     */
92
    @Test
148
    @Test
93
    public void testRejectProtectedNonLogin() throws Exception {
149
    public void testEssentialEnvironmentWithoutCookies() throws Exception {
150
151
        // should be permitted to access an unprotected resource.
152
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PUBLIC,
153
                       NO_COOKIES, HttpServletResponse.SC_OK);
154
155
        // should not be permitted to access a protected resource
156
        // with the two Authenticators used in the remaining tests.
94
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
157
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
95
                        false, true, 403);
158
                NO_COOKIES, HttpServletResponse.SC_FORBIDDEN);
159
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
160
                NO_CREDENTIALS, NO_COOKIES,
161
                HttpServletResponse.SC_UNAUTHORIZED);
96
    }
162
    }
97
163
98
    /*
164
    /*
99
     * Logon to access a protected resource using BASIC authentication,
165
     * Logon to access a protected resource using BASIC authentication,
100
     * which will establish an SSO session.
166
     * which will establish an SSO session.
101
     * Wait until the SSO session times-out, then try to re-access
167
     * Wait until the SSO session times-out, then try to re-access
102
     * the resource.
168
     * the resource. This should be rejected with SC_FORBIDDEN 401 status.
103
     * This should be rejected with SC_FORBIDDEN 401 status, which
169
     *
104
     * will then be followed by successful re-authentication.
170
     * Note: this test will run for slightly more than 1 minute.
105
     */
171
     */
106
    @Test
172
    @Test
107
    public void testBasicLoginSessionTimeout() throws Exception {
173
    public void testBasicAccessAndSessionTimeout() throws Exception {
108
        doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
174
109
                true, 401, false, 200);
175
        setRapidSessionTimeoutDetection();
110
        // wait long enough for my session to expire
176
111
        Thread.sleep(SHORT_TIMEOUT_DELAY_MSECS);
177
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
112
        doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
178
                NO_CREDENTIALS, USE_COOKIES,
113
                true, 401, false, 200);
179
                HttpServletResponse.SC_UNAUTHORIZED);
180
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
181
                GOOD_CREDENTIALS, USE_COOKIES,
182
                HttpServletResponse.SC_OK);
183
184
        // verify the SSOID exists as a cookie
185
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
186
                GOOD_CREDENTIALS, USE_COOKIES,
187
                HttpServletResponse.SC_OK);
188
189
        // make the session time out and lose authentication
190
        doImminentSessionTimeout(basicContext);
191
192
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
193
                NO_CREDENTIALS, USE_COOKIES,
194
                HttpServletResponse.SC_UNAUTHORIZED);
114
    }
195
    }
115
196
197
116
    /*
198
    /*
117
     * Logon to access a protected resource using BASIC authentication,
199
     * Logon to access a protected resource using BASIC authentication,
118
     * which will establish an SSO session.
200
     * which will establish an SSO session.
119
     * Immediately try to access a protected resource in the NonLogin
201
     * Immediately try to access a protected resource in the NonLogin
120
     * webapp, but without sending the SSO session cookie.
202
     * webapp while providing the SSO session cookie received from the
121
     * This should be rejected with SC_FORBIDDEN 403 status.
203
     * first webapp. This should be successful with SC_OK 200 status.
122
     */
204
     */
123
    @Test
205
    @Test
124
    public void testBasicLoginRejectProtectedWithoutCookies() throws Exception {
206
    public void testBasicLoginThenAcceptWithCookies() throws Exception {
125
        doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
207
126
                true, 401, false, 200);
208
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
209
                NO_CREDENTIALS, NO_COOKIES,
210
                HttpServletResponse.SC_UNAUTHORIZED);
211
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
212
                GOOD_CREDENTIALS, USE_COOKIES, HttpServletResponse.SC_OK);
213
214
        // send the cookie which proves we have an authenticated SSO session
127
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
215
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
128
                        false, true, 403);
216
                       USE_COOKIES, HttpServletResponse.SC_OK);
129
    }
217
    }
130
218
131
    /*
219
    /*
132
     * Logon to access a protected resource using BASIC authentication,
220
     * Logon to access a protected resource using BASIC authentication,
133
     * which will establish an SSO session.
221
     * which will establish an SSO session.
134
     * Immediately try to access a protected resource in the NonLogin
222
     * Immediately try to access a protected resource in the NonLogin
135
     * webapp while sending the SSO session cookie provided by the
223
     * webapp, but without sending the SSO session cookie.
136
     * first webapp.
224
     * This should be rejected with SC_FORBIDDEN 403 status.
137
     * This should be successful with SC_OK 200 status.
138
     */
225
     */
139
    @Test
226
    @Test
140
    public void testBasicLoginAcceptProtectedWithCookies() throws Exception {
227
    public void testBasicLoginThenRejectWithoutCookie() throws Exception {
141
        doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
228
142
                true, 401, false, 200);
229
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
230
                NO_CREDENTIALS, USE_COOKIES,
231
                HttpServletResponse.SC_UNAUTHORIZED);
232
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
233
                GOOD_CREDENTIALS, USE_COOKIES,
234
                HttpServletResponse.SC_OK);
235
236
        // fail to send the authentication cookie to the other webapp.
143
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
237
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
144
                        true, false, 200);
238
                NO_COOKIES, HttpServletResponse.SC_FORBIDDEN);
145
    }
239
    }
146
240
147
    /*
241
    /*
148
     * Logon to access a protected resource using BASIC authentication,
242
     * Logon to access a protected resource using BASIC authentication,
149
     * which will establish an SSO session.
243
     * which will establish an SSO session.
244
     * Then try to access a protected resource in the NonLogin
245
     * webapp by sending the JSESSIONID from the redirect header.
246
     * The access request should be rejected because the Basic webapp's
247
     * sessionID is not valid for any other container.
248
     */
249
    @Test
250
    public void testBasicAccessThenAcceptAuthWithUri() throws Exception {
251
252
        setAlwaysUseSession();
253
254
        // first, fail to access the protected resource without credentials
255
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
256
                NO_CREDENTIALS, NO_COOKIES,
257
                HttpServletResponse.SC_UNAUTHORIZED);
258
259
        // now, access the protected resource with good credentials
260
        // to establish the session
261
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
262
                GOOD_CREDENTIALS, NO_COOKIES,
263
                HttpServletResponse.SC_OK);
264
265
        // next, access it again to harvest the session id url parameter
266
        String forwardParam = "?nextUrl=" + CONTEXT_PATH_LOGIN + URI_PROTECTED;
267
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED + forwardParam,
268
                GOOD_CREDENTIALS, NO_COOKIES,
269
                HttpServletResponse.SC_OK);
270
271
        // verify the sessionID was encoded in the absolute URL
272
        String firstEncodedURL = encodedURL;
273
        assertTrue(firstEncodedURL.contains(ENCODE_SESSION_PARAM));
274
275
        // access the protected resource with the encoded url (with session id)
276
        doTestBasic(firstEncodedURL + forwardParam,
277
                NO_CREDENTIALS, NO_COOKIES,
278
                HttpServletResponse.SC_OK);
279
280
        // verify the sessionID has not changed
281
        // verify the SSO sessionID was not encoded
282
        String secondEncodedURL = encodedURL;
283
        assertEquals(firstEncodedURL, secondEncodedURL);
284
        assertFalse(firstEncodedURL.contains(ENCODE_SSOSESSION_PARAM));
285
286
        // extract the first container's session ID
287
        int ix = secondEncodedURL.indexOf(ENCODE_SESSION_PARAM);
288
        String sessionId = secondEncodedURL.substring(ix);
289
290
        // expect to fail using that sessionID in a different container
291
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED + ";" + sessionId,
292
                NO_COOKIES, HttpServletResponse.SC_FORBIDDEN);
293
    }
294
295
    /*
296
     * Logon to access a protected resource using BASIC authentication,
297
     * which will establish an SSO session.
150
     * Immediately try to access a protected resource in the NonLogin
298
     * Immediately try to access a protected resource in the NonLogin
151
     * webapp while sending the SSO session cookie provided by the
299
     * webapp while providing the SSO session cookie received from the
152
     * first webapp.
300
     * first webapp. This should be successful with SC_OK 200 status.
153
     * This should be successful with SC_OK 200 status.
154
     *
301
     *
155
     * Then, wait long enough for the BASIC session to expire. (The SSO
302
     * Then, wait long enough for the BASIC session to expire. (The SSO
156
     * session should remain active because the NonLogin session has
303
     * session should remain active because the NonLogin session has
157
     * not yet expired).
304
     * not yet expired).
158
     *
159
     * Try to access the protected resource again, before the SSO session
305
     * Try to access the protected resource again, before the SSO session
160
     * has expired.
306
     * has expired. This should be successful with SC_OK 200 status.
161
     * This should be successful with SC_OK 200 status.
162
     *
307
     *
163
     * Finally, wait for the non-login session to expire and try again..
308
     * Finally, wait for the non-login session to expire and try again..
164
     * This should be rejected with SC_FORBIDDEN 403 status.
309
     * This should be rejected with SC_FORBIDDEN 403 status.
165
     *
310
     *
166
     * (see bugfix https://issues.apache.org/bugzilla/show_bug.cgi?id=52303)
311
     * (see bugfix https://issues.apache.org/bugzilla/show_bug.cgi?id=52303)
312
     *
313
     * Note: this test will run for slightly more than 3 minutes.
167
     */
314
     */
168
    @Test
315
    @Test
169
    public void testBasicExpiredAcceptProtectedWithCookies() throws Exception {
316
    public void testBasicExpiredAcceptProtectedWithCookies() throws Exception {
170
        doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
317
171
                true, 401, false, 200);
318
        setRapidSessionTimeoutDetection();
319
320
        // begin with a repeat of testBasicLoginAcceptProtectedWithCookies
321
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
322
                NO_CREDENTIALS, USE_COOKIES,
323
                HttpServletResponse.SC_UNAUTHORIZED);
324
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
325
                GOOD_CREDENTIALS, USE_COOKIES,
326
                HttpServletResponse.SC_OK);
172
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
327
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
173
                        true, false, 200);
328
                       USE_COOKIES, HttpServletResponse.SC_OK);
174
329
175
        // wait long enough for the BASIC session to expire,
330
        // wait long enough for the BASIC session to expire,
176
        // but not long enough for NonLogin session expiry
331
        // but not long enough for the NonLogin session expiry.
177
        Thread.sleep(SHORT_TIMEOUT_DELAY_MSECS);
332
        doImminentSessionTimeout(basicContext);
333
334
        // this successful NonLogin access should replenish the
335
        // the individual session expiry time and keep the SSO session alive
178
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
336
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
179
                        true, false, 200);
337
                       USE_COOKIES, HttpServletResponse.SC_OK);
180
338
181
        // wait long enough for my NonLogin session to expire
339
        // wait long enough for the NonLogin session to expire,
182
        // and tear down the SSO session at the same time.
340
        // which will also tear down the SSO session at the same time.
183
        Thread.sleep(LONG_TIMEOUT_DELAY_MSECS);
341
        doImminentSessionTimeout(nonloginContext);
184
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
342
185
                        false, true, 403);
343
        doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED, USE_COOKIES,
344
                HttpServletResponse.SC_FORBIDDEN);
345
        doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
346
                NO_CREDENTIALS, USE_COOKIES,
347
                HttpServletResponse.SC_UNAUTHORIZED);
348
186
    }
349
    }
187
350
188
351
189
    public void doTestNonLogin(String uri, boolean addCookies,
352
    public void doTestNonLogin(String uri, boolean useCookie,
190
            boolean expectedReject, int expectedRC)
353
            int expectedRC) throws Exception {
191
            throws Exception {
192
354
193
        Map<String,List<String>> reqHeaders = new HashMap<>();
355
        Map<String,List<String>> reqHeaders = new HashMap<>();
194
        if (addCookies) {
195
            addCookies(reqHeaders);
196
        }
197
        Map<String,List<String>> respHeaders = new HashMap<>();
356
        Map<String,List<String>> respHeaders = new HashMap<>();
198
357
358
        if (useCookie && (cookies != null)) {
359
            reqHeaders.put(CLIENT_COOKIE_HEADER + ":", cookies);
360
        }
361
199
        ByteChunk bc = new ByteChunk();
362
        ByteChunk bc = new ByteChunk();
200
        int rc = getUrl(HTTP_PREFIX + getPort() + uri, bc, reqHeaders,
363
        int rc = getUrl(HTTP_PREFIX + getPort() + uri, bc, reqHeaders,
201
                respHeaders);
364
                respHeaders);
202
365
203
        if (expectedReject) {
366
        if (expectedRC != HttpServletResponse.SC_OK) {
204
            assertEquals(expectedRC, rc);
367
            assertEquals(expectedRC, rc);
205
            assertTrue(bc.getLength() > 0);
368
            assertTrue(bc.getLength() > 0);
206
        }
369
        }
207
        else {
370
        else {
208
            assertEquals(200, rc);
209
            assertEquals("OK", bc.toString());
371
            assertEquals("OK", bc.toString());
210
            saveCookies(respHeaders);
211
        }
372
        }
212
}
373
}
213
374
214
    public void doTestBasic(String user, String pwd, String uri,
375
    private void doTestBasic(String uri,
215
            boolean expectedReject1, int expectedRC1,
376
            TestSSOnonLoginAndBasicAuthenticator.BasicCredentials credentials,
216
            boolean expectedReject2, int expectedRC2) throws Exception {
377
            boolean useCookie, int expectedRC) throws Exception {
217
378
218
        // the first access attempt should be challenged
379
        Map<String,List<String>> reqHeaders = new HashMap<>();
219
        Map<String,List<String>> reqHeaders1 = new HashMap<>();
380
        Map<String,List<String>> respHeaders = new HashMap<>();
220
        Map<String,List<String>> respHeaders1 = new HashMap<>();
221
381
222
        ByteChunk bc = new ByteChunk();
382
        if (useCookie && (cookies != null)) {
223
        int rc = getUrl(HTTP_PREFIX + getPort() + uri, bc, reqHeaders1,
383
            reqHeaders.put(CLIENT_COOKIE_HEADER + ":", cookies);
224
                respHeaders1);
225
226
        if (expectedReject1) {
227
            assertEquals(expectedRC1, rc);
228
            assertTrue(bc.getLength() > 0);
229
        }
384
        }
230
        else {
385
        else {
231
            assertEquals(200, rc);
386
            if (credentials != null) {
232
            assertEquals("OK", bc.toString());
387
                List<String> auth = new ArrayList<>();
233
            return;
388
                auth.add(credentials.getCredentials());
389
                reqHeaders.put(CLIENT_AUTH_HEADER, auth);
390
            }
234
        }
391
        }
235
392
236
        // the second access attempt should be successful
393
        ByteChunk bc = new ByteChunk();
237
        String credentials = user + ":" + pwd;
394
        int rc = getUrl(HTTP_PREFIX + getPort() + uri, bc, reqHeaders,
395
                respHeaders);
238
396
239
        String base64auth = Base64.encodeBase64String(
397
        assertEquals("Unexpected Return Code", expectedRC, rc);
240
                credentials.getBytes(StandardCharsets.ISO_8859_1));
398
        if (expectedRC != HttpServletResponse.SC_OK) {
241
        String authLine = "Basic " + base64auth;
399
            assertTrue(bc.getLength() > 0);
242
400
            if (expectedRC == HttpServletResponse.SC_UNAUTHORIZED) {
243
        List<String> auth = new ArrayList<>();
401
                // The server should identify the acceptable method(s)
244
        auth.add(authLine);
402
                boolean methodFound = false;
245
        Map<String,List<String>> reqHeaders2 = new HashMap<>();
403
                List<String> authHeaders = respHeaders.get(SERVER_AUTH_HEADER);
246
        reqHeaders2.put(CLIENT_AUTH_HEADER, auth);
404
                for (String authHeader : authHeaders) {
247
405
                    if (authHeader.indexOf(NICE_METHOD) > -1) {
248
        Map<String,List<String>> respHeaders2 = new HashMap<>();
406
                        methodFound = true;
249
407
                        break;
250
        bc.recycle();
408
                    }
251
        rc = getUrl(HTTP_PREFIX + getPort() + uri, bc, reqHeaders2,
409
                }
252
                respHeaders2);
410
                assertTrue(methodFound);
253
411
            }
254
        if (expectedReject2) {
255
            assertEquals(expectedRC2, rc);
256
            assertNull(bc.toString());
257
        }
412
        }
258
        else {
413
        else {
259
            assertEquals(200, rc);
414
            String thePage = bc.toString();
260
            assertEquals("OK", bc.toString());
415
            assertNotNull(thePage);
261
            saveCookies(respHeaders2);
416
            assertTrue(thePage.startsWith("OK"));
417
            if (useCookie) {
418
                List<String> newCookies = respHeaders.get(SERVER_COOKIE_HEADER);
419
                if (newCookies != null) {
420
                    // harvest cookies whenever the server sends some new ones
421
                    cookies = newCookies;
422
                }
423
            }
424
            else {
425
                encodedURL = "";
426
                final String start = "<a href=\"";
427
                final String end = "\">";
428
                int iStart = thePage.indexOf(start);
429
                int iEnd = 0;
430
                if (iStart > -1) {
431
                    iStart += start.length();
432
                    iEnd = thePage.indexOf(end, iStart);
433
                    if (iEnd > -1) {
434
                        encodedURL = thePage.substring(iStart, iEnd);
435
                    }
436
                }
437
            }
262
        }
438
        }
263
    }
439
    }
264
440
265
441
442
443
444
    /*
445
     * setup two webapps for every test
446
     *
447
     * note: the super class tearDown method will stop tomcat
448
     */
266
    @Override
449
    @Override
267
    public void setUp() throws Exception {
450
    public void setUp() throws Exception {
268
451
269
        super.setUp();
452
        super.setUp();
270
453
271
        // create a tomcat server using the default in-memory Realm
454
        // create a tomcat server using the default in-memory Realm
272
        Tomcat tomcat = getTomcatInstance();
455
        tomcat = getTomcatInstance();
273
456
274
        // associate the SingeSignOn Valve before the Contexts
457
        // associate the SingeSignOn Valve before the Contexts
275
        SingleSignOn sso = new SingleSignOn();
458
        SingleSignOn sso = new SingleSignOn();
Lines 280-350 Link Here
280
        tomcat.addRole(USER, ROLE);
463
        tomcat.addRole(USER, ROLE);
281
464
282
        // setup both NonLogin and Login webapps
465
        // setup both NonLogin and Login webapps
283
        setUpNonLogin(tomcat);
466
        setUpNonLogin();
284
        setUpLogin(tomcat);
467
        setUpLogin();
285
468
286
        tomcat.start();
469
        tomcat.start();
287
    }
470
    }
288
471
289
    private void setUpNonLogin(Tomcat tomcat) throws Exception {
472
    @Override
473
    public void tearDown() throws Exception {
290
474
475
        tomcat.stop();
476
    }
477
478
    private void setUpNonLogin() throws Exception {
479
291
        // Must have a real docBase for webapps - just use temp
480
        // Must have a real docBase for webapps - just use temp
292
        Context ctxt = tomcat.addContext(CONTEXT_PATH_NOLOGIN,
481
        nonloginContext = tomcat.addContext(CONTEXT_PATH_NOLOGIN,
293
                System.getProperty("java.io.tmpdir"));
482
                System.getProperty("java.io.tmpdir"));
294
        ctxt.setSessionTimeout(LONG_TIMEOUT_SECS);
483
        nonloginContext.setSessionTimeout(LONG_SESSION_TIMEOUT_MINS);
295
484
296
        // Add protected servlet
485
        // Add protected servlet to the context
297
        Tomcat.addServlet(ctxt, "TesterServlet1", new TesterServlet());
486
        Tomcat.addServlet(nonloginContext, "TesterServlet1",
298
        ctxt.addServletMapping(URI_PROTECTED, "TesterServlet1");
487
                new TesterServletEncodeUrl());
488
        nonloginContext.addServletMapping(URI_PROTECTED, "TesterServlet1");
299
489
300
        SecurityCollection collection1 = new SecurityCollection();
490
        SecurityCollection collection1 = new SecurityCollection();
301
        collection1.addPattern(URI_PROTECTED);
491
        collection1.addPattern(URI_PROTECTED);
302
        SecurityConstraint sc1 = new SecurityConstraint();
492
        SecurityConstraint sc1 = new SecurityConstraint();
303
        sc1.addAuthRole(ROLE);
493
        sc1.addAuthRole(ROLE);
304
        sc1.addCollection(collection1);
494
        sc1.addCollection(collection1);
305
        ctxt.addConstraint(sc1);
495
        nonloginContext.addConstraint(sc1);
306
496
307
        // Add unprotected servlet
497
        // Add unprotected servlet to the context
308
        Tomcat.addServlet(ctxt, "TesterServlet2", new TesterServlet());
498
        Tomcat.addServlet(nonloginContext, "TesterServlet2",
309
        ctxt.addServletMapping(URI_PUBLIC, "TesterServlet2");
499
                new TesterServletEncodeUrl());
500
        nonloginContext.addServletMapping(URI_PUBLIC, "TesterServlet2");
310
501
311
        SecurityCollection collection2 = new SecurityCollection();
502
        SecurityCollection collection2 = new SecurityCollection();
312
        collection2.addPattern(URI_PUBLIC);
503
        collection2.addPattern(URI_PUBLIC);
313
        SecurityConstraint sc2 = new SecurityConstraint();
504
        SecurityConstraint sc2 = new SecurityConstraint();
314
        // do not add a role - which signals access permitted without one
505
        // do not add a role - which signals access permitted without one
315
        sc2.addCollection(collection2);
506
        sc2.addCollection(collection2);
316
        ctxt.addConstraint(sc2);
507
        nonloginContext.addConstraint(sc2);
317
508
318
        // Configure the authenticator and inherit the Realm from Engine
509
        // Configure the authenticator and inherit the Realm from Engine
319
        LoginConfig lc = new LoginConfig();
510
        LoginConfig lc = new LoginConfig();
320
        lc.setAuthMethod("NONE");
511
        lc.setAuthMethod("NONE");
321
        ctxt.setLoginConfig(lc);
512
        nonloginContext.setLoginConfig(lc);
322
        ctxt.getPipeline().addValve(new NonLoginAuthenticator());
513
        AuthenticatorBase nonloginAuthenticator = new NonLoginAuthenticator();
514
        nonloginContext.getPipeline().addValve(nonloginAuthenticator);
323
    }
515
    }
324
516
325
    private void setUpLogin(Tomcat tomcat) throws Exception {
517
    private void setUpLogin() throws Exception {
326
518
327
        // Must have a real docBase for webapps - just use temp
519
        // Must have a real docBase for webapps - just use temp
328
        Context ctxt = tomcat.addContext(CONTEXT_PATH_LOGIN,
520
        basicContext = tomcat.addContext(CONTEXT_PATH_LOGIN,
329
                System.getProperty("java.io.tmpdir"));
521
                System.getProperty("java.io.tmpdir"));
330
        ctxt.setSessionTimeout(SHORT_TIMEOUT_SECS);
522
        basicContext.setSessionTimeout(SHORT_SESSION_TIMEOUT_MINS);
331
523
332
        // Add protected servlet
524
        // Add protected servlet to the context
333
        Tomcat.addServlet(ctxt, "TesterServlet3", new TesterServlet());
525
        Tomcat.addServlet(basicContext, "TesterServlet3",
334
        ctxt.addServletMapping(URI_PROTECTED, "TesterServlet3");
526
                new TesterServletEncodeUrl());
335
527
        basicContext.addServletMapping(URI_PROTECTED, "TesterServlet3");
336
        SecurityCollection collection = new SecurityCollection();
528
        SecurityCollection collection = new SecurityCollection();
337
        collection.addPattern(URI_PROTECTED);
529
        collection.addPattern(URI_PROTECTED);
338
        SecurityConstraint sc = new SecurityConstraint();
530
        SecurityConstraint sc = new SecurityConstraint();
339
        sc.addAuthRole(ROLE);
531
        sc.addAuthRole(ROLE);
340
        sc.addCollection(collection);
532
        sc.addCollection(collection);
341
        ctxt.addConstraint(sc);
533
        basicContext.addConstraint(sc);
342
534
343
        // Configure the appropriate authenticator
535
        // Add unprotected servlet to the context
536
        Tomcat.addServlet(basicContext, "TesterServlet4",
537
                new TesterServletEncodeUrl());
538
        basicContext.addServletMapping(URI_PUBLIC, "TesterServlet4");
539
        SecurityCollection collection2 = new SecurityCollection();
540
        collection2.addPattern(URI_PUBLIC);
541
        SecurityConstraint sc2 = new SecurityConstraint();
542
        // do not add a role - which signals access permitted without one
543
        sc2.addCollection(collection2);
544
        basicContext.addConstraint(sc2);
545
546
        // Configure the authenticator and inherit the Realm from Engine
344
        LoginConfig lc = new LoginConfig();
547
        LoginConfig lc = new LoginConfig();
345
        lc.setAuthMethod("BASIC");
548
        lc.setAuthMethod("BASIC");
346
        ctxt.setLoginConfig(lc);
549
        basicContext.setLoginConfig(lc);
347
        ctxt.getPipeline().addValve(new BasicAuthenticator());
550
        AuthenticatorBase basicAuthenticator = new BasicAuthenticator();
551
        basicContext.getPipeline().addValve(basicAuthenticator);
348
    }
552
    }
349
553
350
    /*
554
    /*
Lines 353-359 Link Here
353
    protected void saveCookies(Map<String,List<String>> respHeaders) {
557
    protected void saveCookies(Map<String,List<String>> respHeaders) {
354
558
355
        // we only save the Cookie values, not header prefix
559
        // we only save the Cookie values, not header prefix
356
        cookies = respHeaders.get(SERVER_COOKIES);
560
        cookies = respHeaders.get(SERVER_COOKIE_HEADER);
357
    }
561
    }
358
562
359
    /*
563
    /*
Lines 362-368 Link Here
362
    protected void addCookies(Map<String,List<String>> reqHeaders) {
566
    protected void addCookies(Map<String,List<String>> reqHeaders) {
363
567
364
        if ((cookies != null) && (cookies.size() > 0)) {
568
        if ((cookies != null) && (cookies.size() > 0)) {
365
            reqHeaders.put(BROWSER_COOKIES + ":", cookies);
569
            reqHeaders.put(CLIENT_COOKIE_HEADER + ":", cookies);
366
        }
570
        }
367
    }
571
    }
368
}
572
573
    /*
574
     * Force non-default behaviour for both Authenticators.
575
     * The session id will not be regenerated after authentication,
576
     * which is less secure but needed for browsers that will not
577
     * handle cookies.
578
     */
579
    private void setAlwaysUseSession() {
580
581
        ((AuthenticatorBase) basicContext.getAuthenticator())
582
                .setAlwaysUseSession(true);
583
        ((AuthenticatorBase) nonloginContext.getAuthenticator())
584
                .setAlwaysUseSession(true);
585
    }
586
587
    /*
588
     * Force faster timeout for an active Container than can
589
     * be defined in web.xml. By getting to the active Session we
590
     * can choose seconds instead of minutes.
591
     * Note: shamelessly cloned from ManagerBase - beware of synch issues
592
     *       on the underlying sessions.
593
     */
594
    private void doImminentSessionTimeout(Context activeContext) {
595
596
        ManagerBase manager = (ManagerBase) activeContext.getManager();
597
        Session[] sessions = manager.findSessions();
598
        for (int i = 0; i < sessions.length; i++) {
599
            if (sessions[i]!=null && sessions[i].isValid()) {
600
                sessions[i].setMaxInactiveInterval(EXTRA_DELAY_SECS);
601
                // leave it to be expired by the manager
602
            }
603
        }
604
        try {
605
            Thread.sleep(REASONABLE_MSECS_TO_EXPIRY);
606
        }
607
        catch (InterruptedException ie) {/* ignored */};
608
609
        // paranoid verification that active sessions have now gone
610
        sessions = manager.findSessions();
611
        assertTrue(sessions.length == 0);
612
    }
613
614
    /*
615
     * Force rapid timeout scanning for both webapps
616
     * The StandardManager default service cycle time is 10 seconds,
617
     * with a session expiry scan every 6 cycles.
618
     */
619
    private void setRapidSessionTimeoutDetection() {
620
621
        ((ManagerBase) basicContext.getManager())
622
                .setProcessExpiresFrequency(MANAGER_EXPIRE_SESSIONS_FAST);
623
        ((ManagerBase) nonloginContext.getManager())
624
                .setProcessExpiresFrequency(MANAGER_EXPIRE_SESSIONS_FAST);
625
    }
626
627
    /*
628
     * Encapsulate the logic to generate an HTTP header
629
     * for BASIC Authentication.
630
     * Note: only used internally, so no need to validate arguments.
631
     */
632
    private static final class BasicCredentials {
633
634
        private final String method;
635
        private final String username;
636
        private final String password;
637
        private final String credentials;
638
639
        private BasicCredentials(String aMethod,
640
                String aUsername, String aPassword) {
641
            method = aMethod;
642
            username = aUsername;
643
            password = aPassword;
644
            String userCredentials = username + ":" + password;
645
            byte[] credentialsBytes =
646
                    userCredentials.getBytes(StandardCharsets.ISO_8859_1);
647
            String base64auth = Base64.encodeBase64String(credentialsBytes);
648
            credentials= method + " " + base64auth;
649
        }
650
651
        private String getCredentials() {
652
            return credentials;
653
        }
654
    }
655
}
(-)test/org/apache/catalina/startup/TesterServletEncodeUrl.java (-1 / +21 lines)
Lines 24-33 Link Here
24
import javax.servlet.http.HttpServletRequest;
24
import javax.servlet.http.HttpServletRequest;
25
import javax.servlet.http.HttpServletResponse;
25
import javax.servlet.http.HttpServletResponse;
26
26
27
public class TesterServlet extends HttpServlet {
27
/**
28
 * A test servlet that will always encode the url in case the client requires
29
 * session persistence but is not configured to support cookies.
30
 */
31
public class TesterServletEncodeUrl extends HttpServlet {
28
32
29
    private static final long serialVersionUID = 1L;
33
    private static final long serialVersionUID = 1L;
30
34
35
    /**
36
     * Almost minimal processing for a servlet.
37
     *
38
     * @param nextUrl The url the caller would like to go to next. If
39
     *                supplied, put an encoded url into the returned
40
     *                html page as a hyperlink.
41
     */
31
    @Override
42
    @Override
32
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
43
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
33
            throws ServletException, IOException {
44
            throws ServletException, IOException {
Lines 35-39 Link Here
35
        resp.setContentType("text/plain");
46
        resp.setContentType("text/plain");
36
        PrintWriter out = resp.getWriter();
47
        PrintWriter out = resp.getWriter();
37
        out.print("OK");
48
        out.print("OK");
49
50
        String param = req.getParameter("nextUrl");
51
        if (param!=null) {
52
            // append an encoded url to carry the sessionids
53
            String targetUrl = resp.encodeURL(param);
54
            out.print(". You want to go <a href=\"");
55
            out.print(targetUrl);
56
            out.print("\">here next</a>.");
57
        }
38
    }
58
    }
39
}
59
}

Return to bug 55960