• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 #include "test-utils.h"
4 
5 static const char *base_uri;
6 static GMainLoop *loop;
7 
8 typedef struct {
9 	/* Explanation of what you should see */
10 	const char *explanation;
11 
12 	/* URL to test against */
13 	const char *url;
14 
15 	/* Provided passwords, 1 character each. ('1', '2', and '3'
16 	 * mean the correct passwords for "realm1", "realm2", and
17 	 * "realm3" respectively. '4' means "use the wrong password".)
18 	 * The first password (if present) will be used by
19 	 * authenticate(), and the second (if present) will be used by
20 	 * reauthenticate().
21 	 */
22 	const char *provided;
23 
24 	/* Whether to pass user and password in the URL or not.
25 	 */
26 	gboolean url_auth;
27 
28 	/* Expected passwords, 1 character each. (As with the provided
29 	 * passwords, with the addition that '0' means "no
30 	 * Authorization header expected".) Used to verify that soup
31 	 * used the password it was supposed to at each step.
32 	 */
33 	const char *expected;
34 
35 	/* What the final status code should be. */
36 	guint final_status;
37 } SoupAuthTest;
38 
39 static SoupAuthTest main_tests[] = {
40 	{ "No auth available, should fail",
41 	  "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
42 
43 	{ "Should fail with no auth, fail again with bad password, and give up",
44 	  "Basic/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
45 
46 	{ "Auth provided this time, so should succeed",
47 	  "Basic/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
48 
49 	{ "Now should automatically reuse previous auth",
50 	  "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
51 
52 	{ "Subdir should also automatically reuse auth",
53 	  "Basic/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
54 
55 	{ "Subdir should retry last auth, but will fail this time",
56 	  "Basic/realm1/realm2/", "", FALSE, "1", SOUP_STATUS_UNAUTHORIZED },
57 
58 	{ "Now should use provided auth",
59 	  "Basic/realm1/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
60 
61 	{ "Reusing last auth. Should succeed on first try",
62 	  "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
63 
64 	{ "Reuse will fail, but 2nd try will succeed because it's a known realm",
65 	  "Basic/realm1/realm2/realm1/", "", FALSE, "21", SOUP_STATUS_OK },
66 
67 	{ "Should succeed on first try. (Known realm with cached password)",
68 	  "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
69 
70 	{ "Fail once, then use typoed password, then use right password",
71 	  "Basic/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
72 
73 
74 	{ "No auth available, should fail",
75 	  "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
76 
77 	{ "Should fail with no auth, fail again with bad password, and give up",
78 	  "Digest/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
79 
80 	{ "Known realm, auth provided, so should succeed",
81 	  "Digest/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
82 
83 	{ "Now should automatically reuse previous auth",
84 	  "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
85 
86 	{ "Subdir should also automatically reuse auth",
87 	  "Digest/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
88 
89 	{ "Password provided, should succeed",
90 	  "Digest/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
91 
92 	{ "Should already know correct domain and use provided auth on first try",
93 	  "Digest/realm1/realm2/", "2", FALSE, "2", SOUP_STATUS_OK },
94 
95 	{ "Reusing last auth. Should succeed on first try",
96 	  "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
97 
98 	{ "Should succeed on first try because of earlier domain directive",
99 	  "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
100 
101 	{ "Fail once, then use typoed password, then use right password",
102 	  "Digest/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
103 
104 
105 	{ "Make sure we haven't forgotten anything",
106 	  "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
107 
108 	{ "Make sure we haven't forgotten anything",
109 	  "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
110 
111 	{ "Make sure we haven't forgotten anything",
112 	  "Basic/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
113 
114 	{ "Make sure we haven't forgotten anything",
115 	  "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
116 
117 	{ "Make sure we haven't forgotten anything",
118 	  "Basic/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
119 
120 
121 	{ "Make sure we haven't forgotten anything",
122 	  "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
123 
124 	{ "Make sure we haven't forgotten anything",
125 	  "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
126 
127 	{ "Make sure we haven't forgotten anything",
128 	  "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
129 
130 	{ "Make sure we haven't forgotten anything",
131 	  "Digest/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
132 
133 	{ "Make sure we haven't forgotten anything",
134 	  "Digest/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
135 
136 	{ "Now the server will reject the formerly-good password",
137 	  "Basic/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
138 
139 	{ "Make sure we've forgotten it",
140 	  "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
141 
142 	{ "Likewise, reject the formerly-good Digest password",
143 	  "Digest/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
144 
145 	{ "Make sure we've forgotten it",
146 	  "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
147 
148 	{ "Fail with URI-embedded password, then use right password in the authenticate signal",
149 	  "Basic/realm3/", "43", TRUE, "43", SOUP_STATUS_OK },
150 
151 	{ NULL }
152 };
153 
154 static const char *auths[] = {
155 	"no password", "password 1",
156 	"password 2", "password 3",
157 	"intentionally wrong password",
158 };
159 
160 static int
identify_auth(SoupMessage * msg)161 identify_auth (SoupMessage *msg)
162 {
163 	const char *header;
164 	int num;
165 
166 	header = soup_message_headers_get_one (msg->request_headers,
167 					       "Authorization");
168 	if (!header)
169 		return 0;
170 
171 	if (!g_ascii_strncasecmp (header, "Basic ", 6)) {
172 		char *token;
173 		gsize len;
174 
175 		token = (char *)g_base64_decode (header + 6, &len);
176 		num = token[len - 1] - '0';
177 		g_free (token);
178 	} else {
179 		const char *user;
180 
181 		user = strstr (header, "username=\"user");
182 		if (user)
183 			num = user[14] - '0';
184 		else
185 			num = 0;
186 	}
187 
188 	g_assert (num >= 0 && num <= 4);
189 
190 	return num;
191 }
192 
193 static void
handler(SoupMessage * msg,gpointer data)194 handler (SoupMessage *msg, gpointer data)
195 {
196 	char *expected = data;
197 	int auth, exp;
198 
199 	auth = identify_auth (msg);
200 
201 	debug_printf (1, "  %d %s (using %s)\n",
202 		      msg->status_code, msg->reason_phrase,
203 		      auths[auth]);
204 
205 	if (*expected) {
206 		exp = *expected - '0';
207 		soup_test_assert (auth == exp,
208 				  "expected %s", auths[exp]);
209 		memmove (expected, expected + 1, strlen (expected));
210 	} else {
211 		soup_test_assert (*expected,
212 				  "expected to be finished");
213 	}
214 }
215 
216 static void
authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)217 authenticate (SoupSession *session, SoupMessage *msg,
218 	      SoupAuth *auth, gboolean retrying, gpointer data)
219 {
220 	SoupAuthTest *test = data;
221 	char *username, *password;
222 	char num;
223 
224 	if (!test->provided[0])
225 		return;
226 	if (retrying) {
227 		if (!test->provided[1])
228 			return;
229 		num = test->provided[1];
230 	} else
231 		num = test->provided[0];
232 
233 	username = g_strdup_printf ("user%c", num);
234 	password = g_strdup_printf ("realm%c", num);
235 	soup_auth_authenticate (auth, username, password);
236 	g_free (username);
237 	g_free (password);
238 }
239 
240 static void
bug271540_sent(SoupMessage * msg,gpointer data)241 bug271540_sent (SoupMessage *msg, gpointer data)
242 {
243 	int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
244 	gboolean *authenticated = data;
245 	int auth = identify_auth (msg);
246 
247 	soup_test_assert (*authenticated || !auth,
248 			  "using auth on message %d before authenticating", n);
249 	soup_test_assert (!*authenticated || auth,
250 			  "sent unauthenticated message %d after authenticating", n);
251 }
252 
253 static void
bug271540_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)254 bug271540_authenticate (SoupSession *session, SoupMessage *msg,
255 			SoupAuth *auth, gboolean retrying, gpointer data)
256 {
257 	int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
258 	gboolean *authenticated = data;
259 
260 	if (strcmp (soup_auth_get_scheme_name (auth), "Basic") != 0 ||
261 	    strcmp (soup_auth_get_realm (auth), "realm1") != 0)
262 		return;
263 
264 	if (!*authenticated) {
265 		debug_printf (1, "    authenticating message %d\n", n);
266 		soup_auth_authenticate (auth, "user1", "realm1");
267 		*authenticated = TRUE;
268 	} else {
269 		soup_test_assert (!*authenticated,
270 				  "asked to authenticate message %d after authenticating", n);
271 	}
272 }
273 
274 static void
bug271540_finished(SoupSession * session,SoupMessage * msg,gpointer data)275 bug271540_finished (SoupSession *session, SoupMessage *msg, gpointer data)
276 {
277 	int *left = data;
278 
279 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
280 
281 	(*left)--;
282 	if (!*left)
283 		g_main_loop_quit (loop);
284 }
285 
286 static void
do_pipelined_auth_test(void)287 do_pipelined_auth_test (void)
288 {
289 	SoupSession *session;
290 	SoupMessage *msg;
291 	gboolean authenticated;
292 	char *uri;
293 	int i;
294 
295 	g_test_bug ("271540");
296 
297 	SOUP_TEST_SKIP_IF_NO_APACHE;
298 
299 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
300 
301 	authenticated = FALSE;
302 	g_signal_connect (session, "authenticate",
303 			  G_CALLBACK (bug271540_authenticate), &authenticated);
304 
305 	uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
306 	for (i = 0; i < 10; i++) {
307 		msg = soup_message_new (SOUP_METHOD_GET, uri);
308 		g_object_set_data (G_OBJECT (msg), "#", GINT_TO_POINTER (i + 1));
309 		g_signal_connect (msg, "wrote_headers",
310 				  G_CALLBACK (bug271540_sent), &authenticated);
311 
312 		soup_session_queue_message (session, msg,
313 					    bug271540_finished, &i);
314 	}
315 	g_free (uri);
316 
317 	loop = g_main_loop_new (NULL, TRUE);
318 	g_main_loop_run (loop);
319 	g_main_loop_unref (loop);
320 
321 	soup_test_session_abort_unref (session);
322 }
323 
324 /* We test two different things here:
325  *
326  *   1. If we get a 401 response with "WWW-Authenticate: Digest
327  *      stale=true...", we should retry and succeed *without* the
328  *      session asking for a password again.
329  *
330  *   2. If we get a successful response with "Authentication-Info:
331  *      nextnonce=...", we should update the nonce automatically so as
332  *      to avoid getting a stale nonce error on the next request.
333  *
334  * In our Apache config, /Digest/realm1 and /Digest/realm1/expire are
335  * set up to use the same auth info, but only the latter has an
336  * AuthDigestNonceLifetime (of 2 seconds). The way nonces work in
337  * Apache, a nonce received from /Digest/realm1 will still expire in
338  * /Digest/realm1/expire, but it won't issue a nextnonce for a request
339  * in /Digest/realm1. This lets us test both behaviors.
340  *
341  * The expected conversation is:
342  *
343  * First message
344  *   GET /Digest/realm1
345  *
346  *   401 Unauthorized
347  *   WWW-Authenticate: Digest nonce=A
348  *
349  *   [emit 'authenticate']
350  *
351  *   GET /Digest/realm1
352  *   Authorization: Digest nonce=A
353  *
354  *   200 OK
355  *   [No Authentication-Info]
356  *
357  * [sleep 2 seconds: nonce A is no longer valid, but we have no
358  * way of knowing that]
359  *
360  * Second message
361  *   GET /Digest/realm1/expire/
362  *   Authorization: Digest nonce=A
363  *
364  *   401 Unauthorized
365  *   WWW-Authenticate: Digest stale=true nonce=B
366  *
367  *   GET /Digest/realm1/expire/
368  *   Authorization: Digest nonce=B
369  *
370  *   200 OK
371  *   Authentication-Info: nextnonce=C
372  *
373  * [sleep 1 second]
374  *
375  * Third message
376  *   GET /Digest/realm1/expire/
377  *   Authorization: Digest nonce=C
378  *   [nonce=B would work here too]
379  *
380  *   200 OK
381  *   Authentication-Info: nextnonce=D
382  *
383  * [sleep 1 second; nonces B and C are no longer valid, but D is]
384  *
385  * Fourth message
386  *   GET /Digest/realm1/expire/
387  *   Authorization: Digest nonce=D
388  *
389  *   200 OK
390  *   Authentication-Info: nextnonce=D
391  *
392  */
393 
394 static void
digest_nonce_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)395 digest_nonce_authenticate (SoupSession *session, SoupMessage *msg,
396 			   SoupAuth *auth, gboolean retrying, gpointer data)
397 {
398 	if (retrying)
399 		return;
400 
401 	if (strcmp (soup_auth_get_scheme_name (auth), "Digest") != 0 ||
402 	    strcmp (soup_auth_get_realm (auth), "realm1") != 0)
403 		return;
404 
405 	soup_auth_authenticate (auth, "user1", "realm1");
406 }
407 
408 static void
digest_nonce_unauthorized(SoupMessage * msg,gpointer data)409 digest_nonce_unauthorized (SoupMessage *msg, gpointer data)
410 {
411 	gboolean *got_401 = data;
412 	*got_401 = TRUE;
413 }
414 
415 static void
do_digest_nonce_test(SoupSession * session,const char * nth,const char * uri,gboolean use_auth_cache,gboolean expect_401,gboolean expect_signal)416 do_digest_nonce_test (SoupSession *session,
417 		      const char *nth, const char *uri, gboolean use_auth_cache,
418 		      gboolean expect_401, gboolean expect_signal)
419 {
420 	SoupMessage *msg;
421 	gboolean got_401;
422 
423 	msg = soup_message_new (SOUP_METHOD_GET, uri);
424 	if (!use_auth_cache) {
425 		SoupMessageFlags flags = soup_message_get_flags (msg);
426 
427 		soup_message_set_flags (msg, flags | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
428 	}
429 	if (expect_signal) {
430 		g_signal_connect (session, "authenticate",
431 				  G_CALLBACK (digest_nonce_authenticate),
432 				  NULL);
433 	}
434 	soup_message_add_status_code_handler (msg, "got_headers",
435 					      SOUP_STATUS_UNAUTHORIZED,
436 					      G_CALLBACK (digest_nonce_unauthorized),
437 					      &got_401);
438 	got_401 = FALSE;
439 	soup_session_send_message (session, msg);
440 	soup_test_assert (got_401 == expect_401,
441 			  "%s request %s a 401 Unauthorized!\n", nth,
442 			  got_401 ? "got" : "did not get");
443 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
444 
445 	if (expect_signal) {
446 		g_signal_handlers_disconnect_by_func (session,
447 						      G_CALLBACK (digest_nonce_authenticate),
448 						      NULL);
449 	}
450 
451 	g_object_unref (msg);
452 }
453 
454 static void
do_digest_expiration_test(void)455 do_digest_expiration_test (void)
456 {
457 	SoupSession *session;
458 	char *uri;
459 
460 	SOUP_TEST_SKIP_IF_NO_APACHE;
461 
462 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
463 
464 	uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
465 	do_digest_nonce_test (session, "First", uri, TRUE, TRUE, TRUE);
466 	g_free (uri);
467 	g_usleep (2 * G_USEC_PER_SEC);
468 	uri = g_strconcat (base_uri, "Digest/realm1/expire/", NULL);
469 	do_digest_nonce_test (session, "Second", uri, TRUE, TRUE, FALSE);
470 	g_usleep (1 * G_USEC_PER_SEC);
471 	do_digest_nonce_test (session, "Third", uri, TRUE, FALSE, FALSE);
472 	g_usleep (1 * G_USEC_PER_SEC);
473 	do_digest_nonce_test (session, "Fourth", uri, TRUE, FALSE, FALSE);
474 	g_free (uri);
475 
476 	soup_test_session_abort_unref (session);
477 }
478 
479 /* Async auth test. We queue three requests to /Basic/realm1, ensuring
480  * that they are sent in order. The first and third ones will be
481  * paused from the authentication callback. The second will be allowed
482  * to fail. Shortly after the third one requests auth, we'll provide
483  * the auth and unpause the two remaining messages, allowing them to
484  * succeed.
485  */
486 
487 static void
async_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)488 async_authenticate (SoupSession *session, SoupMessage *msg,
489 		    SoupAuth *auth, gboolean retrying, gpointer data)
490 {
491 	SoupAuth **saved_auth = data;
492 	int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
493 
494 	debug_printf (2, "  async_authenticate msg%d\n", id);
495 
496 	/* The session will try to authenticate msg3 *before* sending
497 	 * it, because it already knows it's going to need the auth.
498 	 * Ignore that.
499 	 */
500 	if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
501 		debug_printf (2, "    (ignoring)\n");
502 		return;
503 	}
504 
505 	soup_session_pause_message (session, msg);
506 	if (saved_auth)
507 		*saved_auth = g_object_ref (auth);
508 	g_main_loop_quit (loop);
509 }
510 
511 static void
async_finished(SoupSession * session,SoupMessage * msg,gpointer user_data)512 async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
513 {
514 	int *remaining = user_data;
515 	int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
516 
517 	debug_printf (2, "  async_finished msg%d\n", id);
518 
519 	(*remaining)--;
520 	if (!*remaining)
521 		g_main_loop_quit (loop);
522 }
523 
524 static void
async_authenticate_assert_once(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)525 async_authenticate_assert_once (SoupSession *session, SoupMessage *msg,
526                                 SoupAuth *auth, gboolean retrying, gpointer data)
527 {
528 	gboolean *been_here = data;
529 
530 	debug_printf (2, "  async_authenticate_assert_once\n");
531 
532 	soup_test_assert (!*been_here,
533 			  "async_authenticate_assert_once called twice");
534 	*been_here = TRUE;
535 }
536 
537 static void
async_authenticate_assert_once_and_stop(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)538 async_authenticate_assert_once_and_stop (SoupSession *session, SoupMessage *msg,
539 					 SoupAuth *auth, gboolean retrying, gpointer data)
540 {
541 	gboolean *been_here = data;
542 
543 	debug_printf (2, "  async_authenticate_assert_once_and_stop\n");
544 
545 	soup_test_assert (!*been_here,
546 			  "async_authenticate_assert_once called twice");
547 	*been_here = TRUE;
548 
549 	soup_session_pause_message (session, msg);
550 	g_main_loop_quit (loop);
551 }
552 
553 static void
do_async_auth_good_password_test(void)554 do_async_auth_good_password_test (void)
555 {
556 	SoupSession *session;
557 	SoupMessage *msg1, *msg2, *msg3, msg2_bak;
558 	guint auth_id;
559 	char *uri;
560 	SoupAuth *auth = NULL;
561 	int remaining;
562 
563 	SOUP_TEST_SKIP_IF_NO_APACHE;
564 
565 	loop = g_main_loop_new (NULL, TRUE);
566 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
567 	uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
568 	remaining = 0;
569 
570 	msg1 = soup_message_new ("GET", uri);
571 	g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
572 	auth_id = g_signal_connect (session, "authenticate",
573 				    G_CALLBACK (async_authenticate), &auth);
574 	g_object_ref (msg1);
575 	remaining++;
576 	soup_session_queue_message (session, msg1, async_finished, &remaining);
577 	g_main_loop_run (loop);
578 	g_signal_handler_disconnect (session, auth_id);
579 
580 	/* async_authenticate will pause msg1 and quit loop */
581 
582 	msg2 = soup_message_new ("GET", uri);
583 	g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2));
584 	soup_session_send_message (session, msg2);
585 
586 	soup_test_assert_message_status (msg2, SOUP_STATUS_UNAUTHORIZED);
587 
588 	/* msg2 should be done at this point; assuming everything is
589 	 * working correctly, the session won't look at it again; we
590 	 * ensure that if it does, it will crash the test program.
591 	 */
592 	memcpy (&msg2_bak, msg2, sizeof (SoupMessage));
593 	memset (msg2, 0, sizeof (SoupMessage));
594 
595 	msg3 = soup_message_new ("GET", uri);
596 	g_object_set_data (G_OBJECT (msg3), "id", GINT_TO_POINTER (3));
597 	auth_id = g_signal_connect (session, "authenticate",
598 				    G_CALLBACK (async_authenticate), NULL);
599 	g_object_ref (msg3);
600 	remaining++;
601 	soup_session_queue_message (session, msg3, async_finished, &remaining);
602 	g_main_loop_run (loop);
603 	g_signal_handler_disconnect (session, auth_id);
604 
605 	/* async_authenticate will pause msg3 and quit loop */
606 
607 	/* Now do the auth, and restart */
608 	if (auth) {
609 		soup_auth_authenticate (auth, "user1", "realm1");
610 		g_object_unref (auth);
611 		soup_session_unpause_message (session, msg1);
612 		soup_session_unpause_message (session, msg3);
613 
614 		g_main_loop_run (loop);
615 
616 		/* async_finished will quit the loop */
617 	} else
618 		soup_test_assert (auth, "msg1 didn't get authenticate signal");
619 
620 	soup_test_assert_message_status (msg1, SOUP_STATUS_OK);
621 	soup_test_assert_message_status (msg3, SOUP_STATUS_OK);
622 
623 	soup_test_session_abort_unref (session);
624 
625 	g_object_unref (msg1);
626 	g_object_unref (msg3);
627 	memcpy (msg2, &msg2_bak, sizeof (SoupMessage));
628 	g_object_unref (msg2);
629 
630 	g_free (uri);
631 	g_main_loop_unref (loop);
632 }
633 
634 static void
do_async_auth_bad_password_test(void)635 do_async_auth_bad_password_test (void)
636 {
637 	SoupSession *session;
638 	SoupMessage *msg;
639 	guint auth_id;
640 	char *uri;
641 	SoupAuth *auth = NULL;
642 	int remaining;
643 	gboolean been_there;
644 
645 	/* Test that giving the wrong password doesn't cause multiple
646 	 * authenticate signals the second time.
647 	 */
648 	g_test_bug ("522601");
649 
650 	SOUP_TEST_SKIP_IF_NO_APACHE;
651 
652 	loop = g_main_loop_new (NULL, TRUE);
653 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
654 	uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
655 	remaining = 0;
656 	auth = NULL;
657 
658 	msg = soup_message_new ("GET", uri);
659 	g_object_set_data (G_OBJECT (msg), "id", GINT_TO_POINTER (1));
660 	auth_id = g_signal_connect (session, "authenticate",
661 				    G_CALLBACK (async_authenticate), &auth);
662 	g_object_ref (msg);
663 	remaining++;
664 	soup_session_queue_message (session, msg, async_finished, &remaining);
665 	g_main_loop_run (loop);
666 	g_signal_handler_disconnect (session, auth_id);
667 	soup_auth_authenticate (auth, "user1", "wrong");
668 	g_object_unref (auth);
669 	soup_session_unpause_message (session, msg);
670 
671 	been_there = FALSE;
672 	auth_id = g_signal_connect (session, "authenticate",
673 				    G_CALLBACK (async_authenticate_assert_once),
674 				    &been_there);
675 	g_main_loop_run (loop);
676 	g_signal_handler_disconnect (session, auth_id);
677 
678 	soup_test_assert (been_there,
679 			  "authenticate not emitted");
680 
681 	soup_test_session_abort_unref (session);
682 	g_object_unref (msg);
683 
684 	g_free (uri);
685 	g_main_loop_unref (loop);
686 }
687 
688 static void
do_async_auth_no_password_test(void)689 do_async_auth_no_password_test (void)
690 {
691 	SoupSession *session;
692 	SoupMessage *msg;
693 	guint auth_id;
694 	char *uri;
695 	int remaining;
696 	gboolean been_there;
697 
698 	/* Test that giving no password doesn't cause multiple
699 	 * authenticate signals the second time.
700 	 */
701 	g_test_bug ("583462");
702 
703 	SOUP_TEST_SKIP_IF_NO_APACHE;
704 
705 	loop = g_main_loop_new (NULL, TRUE);
706 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
707 	uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
708 	remaining = 0;
709 
710 	/* Send a message that doesn't actually authenticate
711 	 */
712 	msg = soup_message_new ("GET", uri);
713 	g_object_set_data (G_OBJECT (msg), "id", GINT_TO_POINTER (1));
714 	auth_id = g_signal_connect (session, "authenticate",
715 				    G_CALLBACK (async_authenticate), NULL);
716 	g_object_ref (msg);
717 	remaining++;
718 	soup_session_queue_message (session, msg, async_finished, &remaining);
719 	g_main_loop_run (loop);
720 	g_signal_handler_disconnect (session, auth_id);
721 	soup_session_unpause_message (session, msg);
722 	g_main_loop_run (loop);
723 	g_object_unref(msg);
724 
725 	/* Now send a second message */
726 	msg = soup_message_new ("GET", uri);
727 	g_object_set_data (G_OBJECT (msg), "id", GINT_TO_POINTER (2));
728 	g_object_ref (msg);
729 	been_there = FALSE;
730 	auth_id = g_signal_connect (session, "authenticate",
731 				    G_CALLBACK (async_authenticate_assert_once_and_stop),
732 				    &been_there);
733 	remaining++;
734 	soup_session_queue_message (session, msg, async_finished, &remaining);
735 	g_main_loop_run (loop);
736 	soup_session_unpause_message (session, msg);
737 
738 	g_main_loop_run (loop);
739 	g_signal_handler_disconnect (session, auth_id);
740 
741 	soup_test_session_abort_unref (session);
742 	g_object_unref (msg);
743 
744 	g_free (uri);
745 	g_main_loop_unref (loop);
746 }
747 
748 typedef struct {
749 	const char *password;
750 	struct {
751 		const char *headers;
752 		const char *response;
753 	} round[2];
754 } SelectAuthData;
755 
756 static void
select_auth_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)757 select_auth_authenticate (SoupSession *session, SoupMessage *msg,
758 			  SoupAuth *auth, gboolean retrying, gpointer data)
759 {
760 	SelectAuthData *sad = data;
761 	const char *header, *basic, *digest;
762 	int round = retrying ? 1 : 0;
763 
764 	header = soup_message_headers_get_list (msg->response_headers,
765 						"WWW-Authenticate");
766 	basic = strstr (header, "Basic");
767 	digest = strstr (header, "Digest");
768 	if (basic && digest) {
769 		if (basic < digest)
770 			sad->round[round].headers = "Basic, Digest";
771 		else
772 			sad->round[round].headers = "Digest, Basic";
773 	} else if (basic)
774 		sad->round[round].headers = "Basic";
775 	else if (digest)
776 		sad->round[round].headers = "Digest";
777 
778 	sad->round[round].response = soup_auth_get_scheme_name (auth);
779 	if (sad->password && !retrying)
780 		soup_auth_authenticate (auth, "user", sad->password);
781 }
782 
783 static void
select_auth_test_one(SoupURI * uri,gboolean disable_digest,const char * password,const char * first_headers,const char * first_response,const char * second_headers,const char * second_response,guint final_status)784 select_auth_test_one (SoupURI *uri,
785 		      gboolean disable_digest, const char *password,
786 		      const char *first_headers, const char *first_response,
787 		      const char *second_headers, const char *second_response,
788 		      guint final_status)
789 {
790 	SelectAuthData sad;
791 	SoupMessage *msg;
792 	SoupSession *session;
793 
794 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
795 	if (disable_digest)
796 		soup_session_remove_feature_by_type (session, SOUP_TYPE_AUTH_DIGEST);
797 
798 	g_signal_connect (session, "authenticate",
799 			  G_CALLBACK (select_auth_authenticate), &sad);
800 	memset (&sad, 0, sizeof (sad));
801 	sad.password = password;
802 
803 	msg = soup_message_new_from_uri ("GET", uri);
804 	soup_session_send_message (session, msg);
805 
806 	soup_test_assert (strcmp (sad.round[0].headers, first_headers) == 0,
807 			  "Header order wrong: expected %s, got %s",
808 			  first_headers, sad.round[0].headers);
809 	soup_test_assert (strcmp (sad.round[0].response, first_response) == 0,
810 			  "Selected auth type wrong: expected %s, got %s",
811 			  first_response, sad.round[0].response);
812 
813 	soup_test_assert (sad.round[1].headers || !second_headers,
814 			  "Expected a second round");
815 	soup_test_assert (!sad.round[1].headers || second_headers,
816 			  "Didn't expect a second round");
817 	if (second_headers && second_response) {
818 		soup_test_assert (strcmp (sad.round[1].headers, second_headers) == 0,
819 				  "Second round header order wrong: expected %s, got %s\n",
820 				  second_headers, sad.round[1].headers);
821 		soup_test_assert (strcmp (sad.round[1].response, second_response) == 0,
822 				  "Second round selected auth type wrong: expected %s, got %s\n",
823 				  second_response, sad.round[1].response);
824 	}
825 
826 	soup_test_assert_message_status (msg, final_status);
827 
828 	g_object_unref (msg);
829 	soup_test_session_abort_unref (session);
830 }
831 
832 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)833 server_callback (SoupServer *server, SoupMessage *msg,
834 		 const char *path, GHashTable *query,
835 		 SoupClientContext *context, gpointer data)
836 {
837 	soup_message_set_response (msg, "text/plain",
838 				   SOUP_MEMORY_STATIC,
839 				   "OK\r\n", 4);
840 	soup_message_set_status (msg, SOUP_STATUS_OK);
841 }
842 
843 static gboolean
server_basic_auth_callback(SoupAuthDomain * auth_domain,SoupMessage * msg,const char * username,const char * password,gpointer data)844 server_basic_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
845 			    const char *username, const char *password, gpointer data)
846 {
847 	if (strcmp (username, "user") != 0)
848 		return FALSE;
849 	return strcmp (password, "good-basic") == 0;
850 }
851 
852 static char *
server_digest_auth_callback(SoupAuthDomain * auth_domain,SoupMessage * msg,const char * username,gpointer data)853 server_digest_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
854 			     const char *username, gpointer data)
855 {
856 	if (strcmp (username, "user") != 0)
857 		return NULL;
858 	return soup_auth_domain_digest_encode_password ("user",
859 							"auth-test",
860 							"good");
861 }
862 
863 static void
do_select_auth_test(void)864 do_select_auth_test (void)
865 {
866 	SoupServer *server;
867 	SoupAuthDomain *basic_auth_domain, *digest_auth_domain;
868 	SoupURI *uri;
869 
870 	g_test_bug ("562339");
871 
872 	/* It doesn't seem to be possible to configure Apache to serve
873 	 * multiple auth types for a single URL. So we have to use
874 	 * SoupServer here. We know that SoupServer handles the server
875 	 * side of this scenario correctly, because we test it against
876 	 * curl in server-auth-test.
877 	 */
878 	server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
879 	soup_server_add_handler (server, NULL,
880 				 server_callback, NULL, NULL);
881 	uri = soup_test_server_get_uri (server, "http", NULL);
882 
883 	basic_auth_domain = soup_auth_domain_basic_new (
884 		SOUP_AUTH_DOMAIN_REALM, "auth-test",
885 		SOUP_AUTH_DOMAIN_ADD_PATH, "/",
886 		SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
887 		NULL);
888 	soup_server_add_auth_domain (server, basic_auth_domain);
889 
890 	digest_auth_domain = soup_auth_domain_digest_new (
891 		SOUP_AUTH_DOMAIN_REALM, "auth-test",
892 		SOUP_AUTH_DOMAIN_ADD_PATH, "/",
893 		SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, server_digest_auth_callback,
894 		NULL);
895 	soup_server_add_auth_domain (server, digest_auth_domain);
896 
897 	debug_printf (1, "  Testing with no auth\n");
898 	select_auth_test_one (uri, FALSE, NULL,
899 			      "Basic, Digest", "Digest",
900 			      NULL, NULL,
901 			      SOUP_STATUS_UNAUTHORIZED);
902 
903 	debug_printf (1, "  Testing with bad password\n");
904 	select_auth_test_one (uri, FALSE, "bad",
905 			      "Basic, Digest", "Digest",
906 			      "Basic, Digest", "Digest",
907 			      SOUP_STATUS_UNAUTHORIZED);
908 
909 	debug_printf (1, "  Testing with good password\n");
910 	select_auth_test_one (uri, FALSE, "good",
911 			      "Basic, Digest", "Digest",
912 			      NULL, NULL,
913 			      SOUP_STATUS_OK);
914 
915 	/* Test with Digest disabled in the client. */
916 	debug_printf (1, "  Testing without Digest with no auth\n");
917 	select_auth_test_one (uri, TRUE, NULL,
918 			      "Basic, Digest", "Basic",
919 			      NULL, NULL,
920 			      SOUP_STATUS_UNAUTHORIZED);
921 
922 	debug_printf (1, "  Testing without Digest with bad password\n");
923 	select_auth_test_one (uri, TRUE, "bad",
924 			      "Basic, Digest", "Basic",
925 			      "Basic, Digest", "Basic",
926 			      SOUP_STATUS_UNAUTHORIZED);
927 
928 	debug_printf (1, "  Testing without Digest with good password\n");
929 	select_auth_test_one (uri, TRUE, "good-basic",
930 			      "Basic, Digest", "Basic",
931 			      NULL, NULL,
932 			      SOUP_STATUS_OK);
933 
934 	/* Now flip the order of the domains, verify that this flips
935 	 * the order of the headers, and make sure that digest auth
936 	 * *still* gets used.
937 	 */
938 
939 	soup_server_remove_auth_domain (server, basic_auth_domain);
940 	soup_server_remove_auth_domain (server, digest_auth_domain);
941 	soup_server_add_auth_domain (server, digest_auth_domain);
942 	soup_server_add_auth_domain (server, basic_auth_domain);
943 
944 	debug_printf (1, "  Testing flipped with no auth\n");
945 	select_auth_test_one (uri, FALSE, NULL,
946 			      "Digest, Basic", "Digest",
947 			      NULL, NULL,
948 			      SOUP_STATUS_UNAUTHORIZED);
949 
950 	debug_printf (1, "  Testing flipped with bad password\n");
951 	select_auth_test_one (uri, FALSE, "bad",
952 			      "Digest, Basic", "Digest",
953 			      "Digest, Basic", "Digest",
954 			      SOUP_STATUS_UNAUTHORIZED);
955 
956 	debug_printf (1, "  Testing flipped with good password\n");
957 	select_auth_test_one (uri, FALSE, "good",
958 			      "Digest, Basic", "Digest",
959 			      NULL, NULL,
960 			      SOUP_STATUS_OK);
961 
962 	g_object_unref (basic_auth_domain);
963 	g_object_unref (digest_auth_domain);
964 	soup_uri_free (uri);
965 	soup_test_server_quit_unref (server);
966 }
967 
968 static void
sneakily_close_connection(SoupMessage * msg,gpointer user_data)969 sneakily_close_connection (SoupMessage *msg, gpointer user_data)
970 {
971 	/* Sneakily close the connection after the response, by
972 	 * tricking soup-message-io into thinking that had been
973 	 * the plan all along.
974 	 */
975 	soup_message_headers_append (msg->response_headers,
976 				     "Connection", "close");
977 }
978 
979 static void
auth_close_request_started(SoupServer * server,SoupMessage * msg,SoupClientContext * client,gpointer user_data)980 auth_close_request_started (SoupServer *server, SoupMessage *msg,
981 			    SoupClientContext *client, gpointer user_data)
982 {
983 	g_signal_connect (msg, "wrote-headers",
984 			  G_CALLBACK (sneakily_close_connection), NULL);
985 }
986 
987 typedef struct {
988 	SoupSession *session;
989 	SoupMessage *msg;
990 	SoupAuth *auth;
991 } AuthCloseData;
992 
993 static gboolean
auth_close_idle_authenticate(gpointer user_data)994 auth_close_idle_authenticate (gpointer user_data)
995 {
996 	AuthCloseData *acd = user_data;
997 
998 	soup_auth_authenticate (acd->auth, "user", "good-basic");
999 	soup_session_unpause_message (acd->session, acd->msg);
1000 
1001 	g_object_unref (acd->auth);
1002 	return FALSE;
1003 }
1004 
1005 static void
auth_close_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)1006 auth_close_authenticate (SoupSession *session, SoupMessage *msg,
1007 			 SoupAuth *auth, gboolean retrying, gpointer data)
1008 {
1009 	AuthCloseData *acd = data;
1010 
1011 	soup_session_pause_message (session, msg);
1012 	acd->auth = g_object_ref (auth);
1013 	g_idle_add (auth_close_idle_authenticate, acd);
1014 }
1015 
1016 static void
do_auth_close_test(void)1017 do_auth_close_test (void)
1018 {
1019 	SoupServer *server;
1020 	SoupAuthDomain *basic_auth_domain;
1021 	SoupURI *uri;
1022 	AuthCloseData acd;
1023 
1024 	server = soup_test_server_new (SOUP_TEST_SERVER_DEFAULT);
1025 	soup_server_add_handler (server, NULL,
1026 				 server_callback, NULL, NULL);
1027 
1028 	uri = soup_test_server_get_uri (server, "http", NULL);
1029 	soup_uri_set_path (uri, "/close");
1030 
1031 	basic_auth_domain = soup_auth_domain_basic_new (
1032 		SOUP_AUTH_DOMAIN_REALM, "auth-test",
1033 		SOUP_AUTH_DOMAIN_ADD_PATH, "/",
1034 		SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
1035 		NULL);
1036 	soup_server_add_auth_domain (server, basic_auth_domain);
1037 	g_object_unref (basic_auth_domain);
1038 
1039 	g_signal_connect (server, "request-started",
1040 			  G_CALLBACK (auth_close_request_started), NULL);
1041 
1042 	acd.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1043 	g_signal_connect (acd.session, "authenticate",
1044 			  G_CALLBACK (auth_close_authenticate), &acd);
1045 
1046 	acd.msg = soup_message_new_from_uri ("GET", uri);
1047 	soup_uri_free (uri);
1048 	soup_session_send_message (acd.session, acd.msg);
1049 
1050 	soup_test_assert_message_status (acd.msg, SOUP_STATUS_OK);
1051 
1052 	g_object_unref (acd.msg);
1053 	soup_test_session_abort_unref (acd.session);
1054 	soup_test_server_quit_unref (server);
1055 }
1056 
1057 static gboolean
infinite_cancel(gpointer session)1058 infinite_cancel (gpointer session)
1059 {
1060 	soup_session_abort (session);
1061 	return FALSE;
1062 }
1063 
1064 static void
infinite_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)1065 infinite_authenticate (SoupSession *session, SoupMessage *msg,
1066 		       SoupAuth *auth, gboolean retrying, gpointer data)
1067 {
1068 	soup_auth_authenticate (auth, "user", "bad");
1069 }
1070 
1071 static void
do_infinite_auth_test(void)1072 do_infinite_auth_test (void)
1073 {
1074 	SoupSession *session;
1075 	SoupMessage *msg;
1076 	char *uri;
1077 	int timeout;
1078 
1079 	SOUP_TEST_SKIP_IF_NO_APACHE;
1080 
1081 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1082 	g_signal_connect (session, "authenticate",
1083 			  G_CALLBACK (infinite_authenticate), NULL);
1084 
1085 	uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
1086 	msg = soup_message_new ("GET", uri);
1087 	g_free (uri);
1088 
1089 	timeout = g_timeout_add (500, infinite_cancel, session);
1090 	g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING,
1091 			       "*stuck in infinite loop*");
1092 	soup_session_send_message (session, msg);
1093 	g_test_assert_expected_messages ();
1094 
1095 	soup_test_assert (msg->status_code != SOUP_STATUS_CANCELLED,
1096 			  "Got stuck in loop");
1097 	soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
1098 
1099 	g_source_remove (timeout);
1100 	soup_test_session_abort_unref (session);
1101 	g_object_unref (msg);
1102 }
1103 
1104 static void
disappear_request_read(SoupServer * server,SoupMessage * msg,SoupClientContext * context,gpointer user_data)1105 disappear_request_read (SoupServer *server, SoupMessage *msg,
1106 			SoupClientContext *context, gpointer user_data)
1107 {
1108 	/* Remove the WWW-Authenticate header if this was a failed attempt */
1109 	if (soup_message_headers_get_one (msg->request_headers, "Authorization") &&
1110 	    msg->status_code == SOUP_STATUS_UNAUTHORIZED)
1111 		soup_message_headers_remove (msg->response_headers, "WWW-Authenticate");
1112 }
1113 
1114 static void
disappear_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)1115 disappear_authenticate (SoupSession *session, SoupMessage *msg,
1116 			SoupAuth *auth, gboolean retrying, gpointer data)
1117 {
1118 	int *counter = data;
1119 
1120 	(*counter)++;
1121 	if (!retrying)
1122 		soup_auth_authenticate (auth, "user", "bad");
1123 }
1124 
1125 static void
do_disappearing_auth_test(void)1126 do_disappearing_auth_test (void)
1127 {
1128 	SoupServer *server;
1129 	SoupAuthDomain *auth_domain;
1130 	SoupURI *uri;
1131 	SoupMessage *msg;
1132 	SoupSession *session;
1133 	int counter;
1134 
1135 	g_test_bug_base ("https://bugzilla.redhat.com/");
1136 	g_test_bug ("916224");
1137 
1138 	server = soup_test_server_new (FALSE);
1139 	soup_server_add_handler (server, NULL,
1140 				 server_callback, NULL, NULL);
1141 	uri = soup_test_server_get_uri (server, "http", NULL);
1142 
1143 	auth_domain = soup_auth_domain_basic_new (
1144 						  SOUP_AUTH_DOMAIN_REALM, "auth-test",
1145 						  SOUP_AUTH_DOMAIN_ADD_PATH, "/",
1146 						  SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
1147 						  NULL);
1148 	soup_server_add_auth_domain (server, auth_domain);
1149 	g_signal_connect (server, "request-read",
1150 			  G_CALLBACK (disappear_request_read), NULL);
1151 
1152 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1153 
1154 	counter = 0;
1155 	g_signal_connect (session, "authenticate",
1156 			  G_CALLBACK (disappear_authenticate), &counter);
1157 
1158 	msg = soup_message_new_from_uri ("GET", uri);
1159 	soup_session_send_message (session, msg);
1160 
1161 	soup_test_assert (counter <= 2,
1162 			  "Got stuck in loop");
1163 	soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
1164 
1165 	g_object_unref (msg);
1166 	soup_test_session_abort_unref (session);
1167 
1168 	g_object_unref (auth_domain);
1169 	soup_uri_free (uri);
1170 	soup_test_server_quit_unref (server);
1171 }
1172 
1173 static SoupAuthTest relogin_tests[] = {
1174 	{ "Auth provided via URL, should succeed",
1175 	  "Basic/realm12/", "1", TRUE, "01", SOUP_STATUS_OK },
1176 
1177 	{ "Now should automatically reuse previous auth",
1178 	  "Basic/realm12/", "", FALSE, "1", SOUP_STATUS_OK },
1179 
1180 	{ "Different auth provided via URL for the same realm, should succeed",
1181 	  "Basic/realm12/", "2", TRUE, "2", SOUP_STATUS_OK },
1182 
1183 	{ "Subdir should also automatically reuse auth",
1184 	  "Basic/realm12/subdir/", "", FALSE, "2", SOUP_STATUS_OK },
1185 
1186 	{ "Should fail with no auth",
1187 	  "Basic/realm12/", "4", TRUE, "4", SOUP_STATUS_UNAUTHORIZED },
1188 
1189 	{ "Make sure we've forgotten it",
1190 	  "Basic/realm12/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
1191 
1192 	{ "Should fail with no auth, fail again with bad password, and give up",
1193 	  "Basic/realm12/", "3", FALSE, "03", SOUP_STATUS_UNAUTHORIZED },
1194 
1195 	{ NULL }
1196 };
1197 
1198 /* https://bugzilla.gnome.org/show_bug.cgi?id=755617 */
1199 static SoupAuthTest basic_root_pspace_test[] = {
1200 	{ "Auth provided via URL, should succeed",
1201 	  "BasicRoot", "1", TRUE, "01", SOUP_STATUS_OK },
1202 
1203 	{ "Parent dir should automatically reuse auth",
1204 	  "/", "1", FALSE, "1", SOUP_STATUS_OK },
1205 
1206 	{ NULL }
1207 };
1208 
1209 static void
do_batch_tests(gconstpointer data)1210 do_batch_tests (gconstpointer data)
1211 {
1212 	const SoupAuthTest *current_tests = data;
1213 	SoupSession *session;
1214 	SoupMessage *msg;
1215 	char *expected, *uristr;
1216 	SoupURI *base;
1217 	guint signal;
1218 	int i;
1219 
1220 	SOUP_TEST_SKIP_IF_NO_APACHE;
1221 
1222 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1223 	base = soup_uri_new (base_uri);
1224 
1225 	for (i = 0; current_tests[i].url; i++) {
1226 		SoupURI *soup_uri = soup_uri_new_with_base (base, current_tests[i].url);
1227 
1228 		debug_printf (1, "Test %d: %s\n", i + 1, current_tests[i].explanation);
1229 
1230 		if (current_tests[i].url_auth) {
1231 			gchar *username = g_strdup_printf ("user%c", current_tests[i].provided[0]);
1232 			gchar *password = g_strdup_printf ("realm%c", current_tests[i].provided[0]);
1233 			soup_uri_set_user (soup_uri, username);
1234 			soup_uri_set_password (soup_uri, password);
1235 			g_free (username);
1236 			g_free (password);
1237 		}
1238 
1239 		msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
1240 		soup_uri_free (soup_uri);
1241 		if (!msg) {
1242 			g_printerr ("auth-test: Could not parse URI\n");
1243 			exit (1);
1244 		}
1245 
1246 		uristr = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1247 		debug_printf (1, "  GET %s\n", uristr);
1248 		g_free (uristr);
1249 
1250 		expected = g_strdup (current_tests[i].expected);
1251 		soup_message_add_status_code_handler (
1252 			msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
1253 			G_CALLBACK (handler), expected);
1254 		soup_message_add_status_code_handler (
1255 			msg, "got_headers", SOUP_STATUS_OK,
1256 			G_CALLBACK (handler), expected);
1257 
1258 		signal = g_signal_connect (session, "authenticate",
1259 					   G_CALLBACK (authenticate),
1260 					   (gpointer)&current_tests[i]);
1261 		soup_session_send_message (session, msg);
1262 		g_signal_handler_disconnect (session, signal);
1263 
1264 		soup_test_assert_message_status (msg, current_tests[i].final_status);
1265 		soup_test_assert (!*expected,
1266 				  "expected %d more round(s)\n",
1267 				  (int)strlen (expected));
1268 
1269 		g_free (expected);
1270 		debug_printf (1, "\n");
1271 
1272 		g_object_unref (msg);
1273 	}
1274 	soup_uri_free (base);
1275 
1276 	soup_test_session_abort_unref (session);
1277 }
1278 
1279 static void
do_clear_credentials_test(void)1280 do_clear_credentials_test (void)
1281 {
1282 	SoupSession *session;
1283 	SoupAuthManager *manager;
1284 	char *uri;
1285 
1286 	SOUP_TEST_SKIP_IF_NO_APACHE;
1287 
1288 	session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
1289 
1290 	uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
1291 	do_digest_nonce_test (session, "First", uri, TRUE, TRUE, TRUE);
1292 
1293 	manager = SOUP_AUTH_MANAGER (soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER));
1294 	soup_auth_manager_clear_cached_credentials (manager);
1295 
1296 	do_digest_nonce_test (session, "Second", uri, TRUE, TRUE, TRUE);
1297 	g_free (uri);
1298 
1299 	soup_test_session_abort_unref (session);
1300 }
1301 
1302 static void
do_message_do_not_use_auth_cache_test(void)1303 do_message_do_not_use_auth_cache_test (void)
1304 {
1305 	SoupSession *session;
1306 	SoupAuthManager *manager;
1307 	SoupMessage *msg;
1308 	SoupMessageFlags flags;
1309 	SoupURI *soup_uri;
1310 	char *uri;
1311 
1312 	SOUP_TEST_SKIP_IF_NO_APACHE;
1313 
1314 	session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
1315 
1316 	uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
1317 
1318 	/* First check that cached credentials are not used */
1319 	do_digest_nonce_test (session, "First", uri, TRUE, TRUE, TRUE);
1320 	do_digest_nonce_test (session, "Second", uri, TRUE, FALSE, FALSE);
1321 	do_digest_nonce_test (session, "Third", uri, FALSE, TRUE, TRUE);
1322 
1323 	/* Passing credentials in the URI should always authenticate
1324 	 * no matter whether the cache is used or not
1325 	 */
1326 	soup_uri = soup_uri_new (uri);
1327 	soup_uri_set_user (soup_uri, "user1");
1328 	soup_uri_set_password (soup_uri, "realm1");
1329 	msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
1330 	flags = soup_message_get_flags (msg);
1331 	soup_message_set_flags (msg, flags | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
1332 	soup_session_send_message (session, msg);
1333 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1334 	g_object_unref (msg);
1335 	soup_uri_free (soup_uri);
1336 
1337 	manager = SOUP_AUTH_MANAGER (soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER));
1338 
1339 	soup_auth_manager_clear_cached_credentials (manager);
1340 
1341 	/* Now check that credentials are not stored */
1342 	do_digest_nonce_test (session, "First", uri, FALSE, TRUE, TRUE);
1343 	do_digest_nonce_test (session, "Second", uri, TRUE, TRUE, TRUE);
1344 	do_digest_nonce_test (session, "Third", uri, TRUE, FALSE, FALSE);
1345 
1346 	/* Credentials were stored for uri, but if we set SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE flag,
1347 	 * and we don't have the authenticate signal, it should respond with 401
1348 	 */
1349 	msg = soup_message_new (SOUP_METHOD_GET, uri);
1350 	flags = soup_message_get_flags (msg);
1351 	soup_message_set_flags (msg, flags | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
1352 	soup_session_send_message (session, msg);
1353 	soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
1354 	g_object_unref (msg);
1355 	g_free (uri);
1356 
1357 	soup_test_session_abort_unref (session);
1358 }
1359 
1360 static void
async_no_auth_cache_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,SoupAuth ** auth_out)1361 async_no_auth_cache_authenticate (SoupSession *session, SoupMessage *msg,
1362 				  SoupAuth *auth, gboolean retrying, SoupAuth **auth_out)
1363 {
1364 	debug_printf (1, "  async_no_auth_cache_authenticate\n");
1365 
1366 	soup_session_pause_message (session, msg);
1367 	*auth_out = g_object_ref (auth);
1368 	g_main_loop_quit (loop);
1369 }
1370 
1371 static void
async_no_auth_cache_finished(SoupSession * session,SoupMessage * msg,gpointer user_data)1372 async_no_auth_cache_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
1373 {
1374 	debug_printf (1, "  async_no_auth_cache_finished\n");
1375 
1376 	g_main_loop_quit (loop);
1377 }
1378 
1379 static void
do_async_message_do_not_use_auth_cache_test(void)1380 do_async_message_do_not_use_auth_cache_test (void)
1381 {
1382 	SoupSession *session;
1383 	SoupMessage *msg;
1384 	char *uri;
1385 	SoupAuth *auth = NULL;
1386 	SoupMessageFlags flags;
1387 
1388 	SOUP_TEST_SKIP_IF_NO_APACHE;
1389 
1390 	loop = g_main_loop_new (NULL, TRUE);
1391 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1392 	uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
1393 
1394 	msg = soup_message_new ("GET", uri);
1395 	g_free (uri);
1396 	g_signal_connect (session, "authenticate",
1397 			  G_CALLBACK (async_no_auth_cache_authenticate), &auth);
1398 	flags = soup_message_get_flags (msg);
1399 	soup_message_set_flags (msg, flags | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
1400 	g_object_ref (msg);
1401 	soup_session_queue_message (session, msg, async_no_auth_cache_finished, NULL);
1402 	g_main_loop_run (loop);
1403 
1404 	soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
1405 
1406 	soup_test_assert (auth, "msg didn't get authenticate signal");
1407 	soup_auth_authenticate (auth, "user1", "realm1");
1408 	g_object_unref (auth);
1409 
1410 	soup_session_unpause_message (session, msg);
1411 	g_main_loop_run (loop);
1412 
1413 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1414 
1415 	soup_test_session_abort_unref (session);
1416 	g_object_unref (msg);
1417 	g_main_loop_unref (loop);
1418 }
1419 
1420 static void
has_authorization_header_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)1421 has_authorization_header_authenticate (SoupSession *session, SoupMessage *msg,
1422 				       SoupAuth *auth, gboolean retrying, gpointer data)
1423 {
1424 	SoupAuth **saved_auth = data;
1425 
1426 	soup_auth_authenticate (auth, "user1", "realm1");
1427 	*saved_auth = g_object_ref (auth);
1428 }
1429 
1430 static void
has_authorization_header_authenticate_assert(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)1431 has_authorization_header_authenticate_assert (SoupSession *session, SoupMessage *msg,
1432 					      SoupAuth *auth, gboolean retrying, gpointer data)
1433 {
1434 	soup_test_assert (FALSE, "authenticate emitted unexpectedly");
1435 }
1436 
1437 static void
do_message_has_authorization_header_test(void)1438 do_message_has_authorization_header_test (void)
1439 {
1440 	SoupSession *session;
1441 	SoupMessage *msg;
1442 	SoupAuthManager *manager;
1443 	SoupAuth *auth = NULL;
1444 	char *token;
1445 	guint auth_id;
1446 	char *uri;
1447 
1448 	g_test_bug ("775882");
1449 
1450 	SOUP_TEST_SKIP_IF_NO_APACHE;
1451 
1452 	session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
1453 	uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
1454 
1455 	msg = soup_message_new ("GET", uri);
1456 	auth_id = g_signal_connect (session, "authenticate",
1457 			  G_CALLBACK (has_authorization_header_authenticate), &auth);
1458 	soup_session_send_message (session, msg);
1459 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1460 	soup_test_assert (SOUP_IS_AUTH (auth), "Expected a SoupAuth");
1461 	token = soup_auth_get_authorization (auth, msg);
1462 	g_object_unref (auth);
1463 	g_object_unref (msg);
1464 	g_signal_handler_disconnect (session, auth_id);
1465 
1466 	manager = SOUP_AUTH_MANAGER (soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER));
1467 	soup_auth_manager_clear_cached_credentials (manager);
1468 
1469 	msg = soup_message_new ("GET", uri);
1470 	soup_message_headers_replace (msg->request_headers, "Authorization", token);
1471 	auth_id = g_signal_connect (session, "authenticate",
1472 				    G_CALLBACK (has_authorization_header_authenticate_assert),
1473 				    NULL);
1474 	soup_session_send_message (session, msg);
1475 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1476 	g_object_unref (msg);
1477 
1478 	/* Check that we can also provide our own Authorization header when not using credentials cache. */
1479 	soup_auth_manager_clear_cached_credentials (manager);
1480 	msg = soup_message_new ("GET", uri);
1481 	soup_message_headers_replace (msg->request_headers, "Authorization", token);
1482 	soup_message_set_flags (msg, soup_message_get_flags (msg) | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
1483 	soup_session_send_message (session, msg);
1484 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1485 	g_object_unref (msg);
1486 	g_free (token);
1487 	g_signal_handler_disconnect (session, auth_id);
1488 
1489 	g_free (uri);
1490 	soup_test_session_abort_unref (session);
1491 }
1492 
1493 int
main(int argc,char ** argv)1494 main (int argc, char **argv)
1495 {
1496 	int ret;
1497 
1498 	test_init (argc, argv, NULL);
1499 	apache_init ();
1500 
1501 	base_uri = "http://127.0.0.1:47524/";
1502 
1503 	g_test_add_data_func ("/auth/main-tests", main_tests, do_batch_tests);
1504 	g_test_add_data_func ("/auth/relogin-tests", relogin_tests, do_batch_tests);
1505 	g_test_add_data_func ("/auth/basic-root-pspec-test", basic_root_pspace_test, do_batch_tests);
1506 	g_test_add_func ("/auth/pipelined-auth", do_pipelined_auth_test);
1507 	g_test_add_func ("/auth/digest-expiration", do_digest_expiration_test);
1508 	g_test_add_func ("/auth/async-auth/good-password", do_async_auth_good_password_test);
1509 	g_test_add_func ("/auth/async-auth/bad-password", do_async_auth_bad_password_test);
1510 	g_test_add_func ("/auth/async-auth/no-password", do_async_auth_no_password_test);
1511 	g_test_add_func ("/auth/select-auth", do_select_auth_test);
1512 	g_test_add_func ("/auth/auth-close", do_auth_close_test);
1513 	g_test_add_func ("/auth/infinite-auth", do_infinite_auth_test);
1514 	g_test_add_func ("/auth/disappearing-auth", do_disappearing_auth_test);
1515 	g_test_add_func ("/auth/clear-credentials", do_clear_credentials_test);
1516 	g_test_add_func ("/auth/message-do-not-use-auth-cache", do_message_do_not_use_auth_cache_test);
1517 	g_test_add_func ("/auth/async-message-do-not-use-auth-cache", do_async_message_do_not_use_auth_cache_test);
1518 	g_test_add_func ("/auth/authorization-header-request", do_message_has_authorization_header_test);
1519 
1520 	ret = g_test_run ();
1521 
1522 	test_cleanup ();
1523 	return ret;
1524 }
1525