1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 #include "test-utils.h"
5 static GMainLoop *loop;
8 /* Explanation of what you should see */
9 const char *explanation;
11 /* URL to test against */
14 /* Provided passwords, 1 character each. ('1', '2', and '3'
15 * mean the correct passwords for "realm1", "realm2", and
16 * "realm3" respectively. '4' means "use the wrong password".)
17 * The first password (if present) will be used by
18 * authenticate(), and the second (if present) will be used by
23 /* Whether to pass user and password in the URL or not.
27 /* Expected passwords, 1 character each. (As with the provided
28 * passwords, with the addition that '0' means "no
29 * Authorization header expected".) Used to verify that soup
30 * used the password it was supposed to at each step.
34 /* What the final status code should be. */
38 /* Will either point to main_tests or relogin_tests
40 static SoupAuthTest *current_tests;
42 static SoupAuthTest main_tests[] = {
43 { "No auth available, should fail",
44 "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
46 { "Should fail with no auth, fail again with bad password, and give up",
47 "Basic/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
49 { "Auth provided this time, so should succeed",
50 "Basic/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
52 { "Now should automatically reuse previous auth",
53 "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
55 { "Subdir should also automatically reuse auth",
56 "Basic/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
58 { "Subdir should retry last auth, but will fail this time",
59 "Basic/realm1/realm2/", "", FALSE, "1", SOUP_STATUS_UNAUTHORIZED },
61 { "Now should use provided auth",
62 "Basic/realm1/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
64 { "Reusing last auth. Should succeed on first try",
65 "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
67 { "Reuse will fail, but 2nd try will succeed because it's a known realm",
68 "Basic/realm1/realm2/realm1/", "", FALSE, "21", SOUP_STATUS_OK },
70 { "Should succeed on first try. (Known realm with cached password)",
71 "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
73 { "Fail once, then use typoed password, then use right password",
74 "Basic/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
77 { "No auth available, should fail",
78 "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
80 { "Should fail with no auth, fail again with bad password, and give up",
81 "Digest/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
83 { "Known realm, auth provided, so should succeed",
84 "Digest/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
86 { "Now should automatically reuse previous auth",
87 "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
89 { "Subdir should also automatically reuse auth",
90 "Digest/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
92 { "Password provided, should succeed",
93 "Digest/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
95 { "Should already know correct domain and use provided auth on first try",
96 "Digest/realm1/realm2/", "2", FALSE, "2", SOUP_STATUS_OK },
98 { "Reusing last auth. Should succeed on first try",
99 "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
101 { "Should succeed on first try because of earlier domain directive",
102 "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
104 { "Fail once, then use typoed password, then use right password",
105 "Digest/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
108 { "Make sure we haven't forgotten anything",
109 "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
111 { "Make sure we haven't forgotten anything",
112 "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
114 { "Make sure we haven't forgotten anything",
115 "Basic/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
117 { "Make sure we haven't forgotten anything",
118 "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
120 { "Make sure we haven't forgotten anything",
121 "Basic/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
124 { "Make sure we haven't forgotten anything",
125 "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
127 { "Make sure we haven't forgotten anything",
128 "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
130 { "Make sure we haven't forgotten anything",
131 "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
133 { "Make sure we haven't forgotten anything",
134 "Digest/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
136 { "Make sure we haven't forgotten anything",
137 "Digest/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
139 { "Now the server will reject the formerly-good password",
140 "Basic/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
142 { "Make sure we've forgotten it",
143 "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
145 { "Likewise, reject the formerly-good Digest password",
146 "Digest/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
148 { "Make sure we've forgotten it",
149 "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
151 { "Fail with URI-embedded password, then use right password in the authenticate signal",
152 "Basic/realm3/", "43", TRUE, "43", SOUP_STATUS_OK }
155 static const char *auths[] = {
156 "no password", "password 1",
157 "password 2", "password 3",
158 "intentionally wrong password",
162 identify_auth (SoupMessage *msg)
167 header = soup_message_headers_get_one (msg->request_headers,
172 if (!g_ascii_strncasecmp (header, "Basic ", 6)) {
176 token = (char *)g_base64_decode (header + 6, &len);
177 num = token[len - 1] - '0';
182 user = strstr (header, "username=\"user");
184 num = user[14] - '0';
189 g_assert (num >= 0 && num <= 4);
195 handler (SoupMessage *msg, gpointer data)
197 char *expected = data;
200 auth = identify_auth (msg);
202 debug_printf (1, " %d %s (using %s)\n",
203 msg->status_code, msg->reason_phrase,
207 exp = *expected - '0';
209 debug_printf (1, " expected %s!\n", auths[exp]);
212 memmove (expected, expected + 1, strlen (expected));
214 debug_printf (1, " expected to be finished\n");
220 authenticate (SoupSession *session, SoupMessage *msg,
221 SoupAuth *auth, gboolean retrying, gpointer data)
224 char *username, *password;
227 if (!current_tests[*i].provided[0])
230 if (!current_tests[*i].provided[1])
232 num = current_tests[*i].provided[1];
234 num = current_tests[*i].provided[0];
236 username = g_strdup_printf ("user%c", num);
237 password = g_strdup_printf ("realm%c", num);
238 soup_auth_authenticate (auth, username, password);
244 bug271540_sent (SoupMessage *msg, gpointer data)
246 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
247 gboolean *authenticated = data;
248 int auth = identify_auth (msg);
250 if (!*authenticated && auth) {
251 debug_printf (1, " using auth on message %d before authenticating!!??\n", n);
253 } else if (*authenticated && !auth) {
254 debug_printf (1, " sent unauthenticated message %d after authenticating!\n", n);
260 bug271540_authenticate (SoupSession *session, SoupMessage *msg,
261 SoupAuth *auth, gboolean retrying, gpointer data)
263 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
264 gboolean *authenticated = data;
266 if (strcmp (soup_auth_get_scheme_name (auth), "Basic") != 0 ||
267 strcmp (soup_auth_get_realm (auth), "realm1") != 0)
270 if (!*authenticated) {
271 debug_printf (1, " authenticating message %d\n", n);
272 soup_auth_authenticate (auth, "user1", "realm1");
273 *authenticated = TRUE;
275 debug_printf (1, " asked to authenticate message %d after authenticating!\n", n);
281 bug271540_finished (SoupSession *session, SoupMessage *msg, gpointer data)
284 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
286 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
287 debug_printf (1, " got status '%d %s' on message %d!\n",
288 msg->status_code, msg->reason_phrase, n);
294 g_main_loop_quit (loop);
298 do_pipelined_auth_test (const char *base_uri)
300 SoupSession *session;
302 gboolean authenticated;
306 debug_printf (1, "Testing pipelined auth (bug 271540):\n");
307 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
309 authenticated = FALSE;
310 g_signal_connect (session, "authenticate",
311 G_CALLBACK (bug271540_authenticate), &authenticated);
313 uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
314 for (i = 0; i < 10; i++) {
315 msg = soup_message_new (SOUP_METHOD_GET, uri);
316 g_object_set_data (G_OBJECT (msg), "#", GINT_TO_POINTER (i + 1));
317 g_signal_connect (msg, "wrote_headers",
318 G_CALLBACK (bug271540_sent), &authenticated);
320 soup_session_queue_message (session, msg,
321 bug271540_finished, &i);
325 loop = g_main_loop_new (NULL, TRUE);
326 g_main_loop_run (loop);
327 g_main_loop_unref (loop);
329 soup_test_session_abort_unref (session);
332 /* We test two different things here:
334 * 1. If we get a 401 response with "WWW-Authenticate: Digest
335 * stale=true...", we should retry and succeed *without* the
336 * session asking for a password again.
338 * 2. If we get a successful response with "Authentication-Info:
339 * nextnonce=...", we should update the nonce automatically so as
340 * to avoid getting a stale nonce error on the next request.
342 * In our Apache config, /Digest/realm1 and /Digest/realm1/expire are
343 * set up to use the same auth info, but only the latter has an
344 * AuthDigestNonceLifetime (of 2 seconds). The way nonces work in
345 * Apache, a nonce received from /Digest/realm1 will still expire in
346 * /Digest/realm1/expire, but it won't issue a nextnonce for a request
347 * in /Digest/realm1. This lets us test both behaviors.
349 * The expected conversation is:
355 * WWW-Authenticate: Digest nonce=A
357 * [emit 'authenticate']
360 * Authorization: Digest nonce=A
363 * [No Authentication-Info]
365 * [sleep 2 seconds: nonce A is no longer valid, but we have no
366 * way of knowing that]
369 * GET /Digest/realm1/expire/
370 * Authorization: Digest nonce=A
373 * WWW-Authenticate: Digest stale=true nonce=B
375 * GET /Digest/realm1/expire/
376 * Authorization: Digest nonce=B
379 * Authentication-Info: nextnonce=C
384 * GET /Digest/realm1/expire/
385 * Authorization: Digest nonce=C
386 * [nonce=B would work here too]
389 * Authentication-Info: nextnonce=D
391 * [sleep 1 second; nonces B and C are no longer valid, but D is]
394 * GET /Digest/realm1/expire/
395 * Authorization: Digest nonce=D
398 * Authentication-Info: nextnonce=D
403 digest_nonce_authenticate (SoupSession *session, SoupMessage *msg,
404 SoupAuth *auth, gboolean retrying, gpointer data)
409 if (strcmp (soup_auth_get_scheme_name (auth), "Digest") != 0 ||
410 strcmp (soup_auth_get_realm (auth), "realm1") != 0)
413 soup_auth_authenticate (auth, "user1", "realm1");
417 digest_nonce_unauthorized (SoupMessage *msg, gpointer data)
419 gboolean *got_401 = data;
424 do_digest_nonce_test (SoupSession *session,
425 const char *nth, const char *uri,
426 gboolean expect_401, gboolean expect_signal)
431 msg = soup_message_new (SOUP_METHOD_GET, uri);
433 g_signal_connect (session, "authenticate",
434 G_CALLBACK (digest_nonce_authenticate),
437 soup_message_add_status_code_handler (msg, "got_headers",
438 SOUP_STATUS_UNAUTHORIZED,
439 G_CALLBACK (digest_nonce_unauthorized),
442 soup_session_send_message (session, msg);
443 if (got_401 != expect_401) {
444 debug_printf (1, " %s request %s a 401 Unauthorized!\n", nth,
445 got_401 ? "got" : "did not get");
448 if (msg->status_code != SOUP_STATUS_OK) {
449 debug_printf (1, " %s request got status %d %s!\n", nth,
450 msg->status_code, msg->reason_phrase);
454 debug_printf (1, " %s request succeeded\n", nth);
455 g_object_unref (msg);
459 do_digest_expiration_test (const char *base_uri)
461 SoupSession *session;
464 debug_printf (1, "\nTesting digest nonce expiration:\n");
466 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
468 uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
469 do_digest_nonce_test (session, "First", uri, TRUE, TRUE);
472 uri = g_strconcat (base_uri, "Digest/realm1/expire/", NULL);
473 do_digest_nonce_test (session, "Second", uri, TRUE, FALSE);
475 do_digest_nonce_test (session, "Third", uri, FALSE, FALSE);
477 do_digest_nonce_test (session, "Fourth", uri, FALSE, FALSE);
480 soup_test_session_abort_unref (session);
483 /* Async auth test. We queue three requests to /Basic/realm1, ensuring
484 * that they are sent in order. The first and third ones will be
485 * paused from the authentication callback. The second will be allowed
486 * to fail. Shortly after the third one requests auth, we'll provide
487 * the auth and unpause the two remaining messages, allowing them to
492 async_authenticate (SoupSession *session, SoupMessage *msg,
493 SoupAuth *auth, gboolean retrying, gpointer data)
495 SoupAuth **saved_auth = data;
496 int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
498 debug_printf (2, " async_authenticate msg%d\n", id);
500 /* The session will try to authenticate msg3 *before* sending
501 * it, because it already knows it's going to need the auth.
504 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
505 debug_printf (2, " (ignoring)\n");
509 soup_session_pause_message (session, msg);
511 *saved_auth = g_object_ref (auth);
512 g_main_loop_quit (loop);
516 async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
518 int *remaining = user_data;
519 int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
521 debug_printf (2, " async_finished msg%d\n", id);
525 g_main_loop_quit (loop);
529 async_authenticate_assert_once (SoupSession *session, SoupMessage *msg,
530 SoupAuth *auth, gboolean retrying, gpointer data)
532 gboolean *been_here = data;
534 debug_printf (2, " async_authenticate_assert_once\n");
537 debug_printf (1, " ERROR: async_authenticate_assert_once called twice\n");
544 async_authenticate_assert_once_and_stop (SoupSession *session, SoupMessage *msg,
545 SoupAuth *auth, gboolean retrying, gpointer data)
547 gboolean *been_here = data;
549 debug_printf (2, " async_authenticate_assert_once_and_stop\n");
552 debug_printf (1, " ERROR: async_authenticate_assert_once called twice\n");
557 soup_session_pause_message (session, msg);
558 g_main_loop_quit (loop);
562 do_async_auth_test (const char *base_uri)
564 SoupSession *session;
565 SoupMessage *msg1, *msg2, *msg3, msg2_bak;
568 SoupAuth *auth = NULL;
572 debug_printf (1, "\nTesting async auth:\n");
574 loop = g_main_loop_new (NULL, TRUE);
575 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
578 uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
580 msg1 = soup_message_new ("GET", uri);
581 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
582 auth_id = g_signal_connect (session, "authenticate",
583 G_CALLBACK (async_authenticate), &auth);
586 soup_session_queue_message (session, msg1, async_finished, &remaining);
587 g_main_loop_run (loop);
588 g_signal_handler_disconnect (session, auth_id);
590 /* async_authenticate will pause msg1 and quit loop */
592 msg2 = soup_message_new ("GET", uri);
593 g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2));
594 soup_session_send_message (session, msg2);
596 if (msg2->status_code == SOUP_STATUS_UNAUTHORIZED)
597 debug_printf (1, " msg2 failed as expected\n");
599 debug_printf (1, " msg2 got wrong status! (%u)\n",
604 /* msg2 should be done at this point; assuming everything is
605 * working correctly, the session won't look at it again; we
606 * ensure that if it does, it will crash the test program.
608 memcpy (&msg2_bak, msg2, sizeof (SoupMessage));
609 memset (msg2, 0, sizeof (SoupMessage));
611 msg3 = soup_message_new ("GET", uri);
612 g_object_set_data (G_OBJECT (msg3), "id", GINT_TO_POINTER (3));
613 auth_id = g_signal_connect (session, "authenticate",
614 G_CALLBACK (async_authenticate), NULL);
617 soup_session_queue_message (session, msg3, async_finished, &remaining);
618 g_main_loop_run (loop);
619 g_signal_handler_disconnect (session, auth_id);
621 /* async_authenticate will pause msg3 and quit loop */
623 /* Now do the auth, and restart */
625 soup_auth_authenticate (auth, "user1", "realm1");
626 g_object_unref (auth);
627 soup_session_unpause_message (session, msg1);
628 soup_session_unpause_message (session, msg3);
630 g_main_loop_run (loop);
632 /* async_finished will quit the loop */
634 debug_printf (1, " msg1 didn't get authenticate signal!\n");
638 if (msg1->status_code == SOUP_STATUS_OK)
639 debug_printf (1, " msg1 succeeded\n");
641 debug_printf (1, " msg1 FAILED! (%u %s)\n",
642 msg1->status_code, msg1->reason_phrase);
645 if (msg3->status_code == SOUP_STATUS_OK)
646 debug_printf (1, " msg3 succeeded\n");
648 debug_printf (1, " msg3 FAILED! (%u %s)\n",
649 msg3->status_code, msg3->reason_phrase);
653 soup_test_session_abort_unref (session);
655 g_object_unref (msg1);
656 g_object_unref (msg3);
657 memcpy (msg2, &msg2_bak, sizeof (SoupMessage));
658 g_object_unref (msg2);
660 /* Test that giving the wrong password doesn't cause multiple
661 * authenticate signals the second time.
663 debug_printf (1, "\nTesting async auth with wrong password (#522601):\n");
665 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
669 msg1 = soup_message_new ("GET", uri);
670 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
671 auth_id = g_signal_connect (session, "authenticate",
672 G_CALLBACK (async_authenticate), &auth);
675 soup_session_queue_message (session, msg1, async_finished, &remaining);
676 g_main_loop_run (loop);
677 g_signal_handler_disconnect (session, auth_id);
678 soup_auth_authenticate (auth, "user1", "wrong");
679 g_object_unref (auth);
680 soup_session_unpause_message (session, msg1);
683 auth_id = g_signal_connect (session, "authenticate",
684 G_CALLBACK (async_authenticate_assert_once),
686 g_main_loop_run (loop);
687 g_signal_handler_disconnect (session, auth_id);
690 debug_printf (1, " authenticate not emitted?\n");
694 soup_test_session_abort_unref (session);
695 g_object_unref (msg1);
697 /* Test that giving no password doesn't cause multiple
698 * authenticate signals the second time.
700 debug_printf (1, "\nTesting async auth with no password (#583462):\n");
702 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
705 /* Send a message that doesn't actually authenticate
707 msg1 = soup_message_new ("GET", uri);
708 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
709 auth_id = g_signal_connect (session, "authenticate",
710 G_CALLBACK (async_authenticate), NULL);
713 soup_session_queue_message (session, msg1, async_finished, &remaining);
714 g_main_loop_run (loop);
715 g_signal_handler_disconnect (session, auth_id);
716 soup_session_unpause_message (session, msg1);
717 g_main_loop_run (loop);
718 g_object_unref(msg1);
720 /* Now send a second message */
721 msg1 = soup_message_new ("GET", uri);
722 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (2));
725 auth_id = g_signal_connect (session, "authenticate",
726 G_CALLBACK (async_authenticate_assert_once_and_stop),
729 soup_session_queue_message (session, msg1, async_finished, &remaining);
730 g_main_loop_run (loop);
731 soup_session_unpause_message (session, msg1);
733 g_main_loop_run (loop);
734 g_signal_handler_disconnect (session, auth_id);
736 soup_test_session_abort_unref (session);
737 g_object_unref (msg1);
740 g_main_loop_unref (loop);
744 const char *password;
747 const char *response;
752 select_auth_authenticate (SoupSession *session, SoupMessage *msg,
753 SoupAuth *auth, gboolean retrying, gpointer data)
755 SelectAuthData *sad = data;
756 const char *header, *basic, *digest;
757 int round = retrying ? 1 : 0;
759 header = soup_message_headers_get_list (msg->response_headers,
761 basic = strstr (header, "Basic");
762 digest = strstr (header, "Digest");
763 if (basic && digest) {
765 sad->round[round].headers = "Basic, Digest";
767 sad->round[round].headers = "Digest, Basic";
769 sad->round[round].headers = "Basic";
771 sad->round[round].headers = "Digest";
773 sad->round[round].response = soup_auth_get_scheme_name (auth);
774 if (sad->password && !retrying)
775 soup_auth_authenticate (auth, "user", sad->password);
779 select_auth_test_one (SoupURI *uri,
780 gboolean disable_digest, const char *password,
781 const char *first_headers, const char *first_response,
782 const char *second_headers, const char *second_response,
787 SoupSession *session;
789 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
791 soup_session_remove_feature_by_type (session, SOUP_TYPE_AUTH_DIGEST);
793 g_signal_connect (session, "authenticate",
794 G_CALLBACK (select_auth_authenticate), &sad);
795 memset (&sad, 0, sizeof (sad));
796 sad.password = password;
798 msg = soup_message_new_from_uri ("GET", uri);
799 soup_session_send_message (session, msg);
801 if (strcmp (sad.round[0].headers, first_headers) != 0) {
802 debug_printf (1, " Header order wrong: expected %s, got %s\n",
803 first_headers, sad.round[0].headers);
806 if (strcmp (sad.round[0].response, first_response) != 0) {
807 debug_printf (1, " Selected auth type wrong: expected %s, got %s\n",
808 first_response, sad.round[0].response);
812 if (second_headers && !sad.round[1].headers) {
813 debug_printf (1, " Expected a second round!\n");
815 } else if (!second_headers && sad.round[1].headers) {
816 debug_printf (1, " Didn't expect a second round!\n");
818 } else if (second_headers) {
819 if (strcmp (sad.round[1].headers, second_headers) != 0) {
820 debug_printf (1, " Second round header order wrong: expected %s, got %s\n",
821 second_headers, sad.round[1].headers);
824 if (strcmp (sad.round[1].response, second_response) != 0) {
825 debug_printf (1, " Second round selected auth type wrong: expected %s, got %s\n",
826 second_response, sad.round[1].response);
831 if (msg->status_code != final_status) {
832 debug_printf (1, " Final status wrong: expected %u, got %u\n",
833 final_status, msg->status_code);
837 g_object_unref (msg);
838 soup_test_session_abort_unref (session);
842 server_callback (SoupServer *server, SoupMessage *msg,
843 const char *path, GHashTable *query,
844 SoupClientContext *context, gpointer data)
846 soup_message_set_response (msg, "text/plain",
849 soup_message_set_status (msg, SOUP_STATUS_OK);
853 server_basic_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
854 const char *username, const char *password, gpointer data)
856 if (strcmp (username, "user") != 0)
858 return strcmp (password, "good-basic") == 0;
862 server_digest_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
863 const char *username, gpointer data)
865 if (strcmp (username, "user") != 0)
867 return soup_auth_domain_digest_encode_password ("user",
873 do_select_auth_test (void)
876 SoupAuthDomain *basic_auth_domain, *digest_auth_domain;
879 debug_printf (1, "\nTesting selection among multiple auths:\n");
881 /* It doesn't seem to be possible to configure Apache to serve
882 * multiple auth types for a single URL. So we have to use
883 * SoupServer here. We know that SoupServer handles the server
884 * side of this scenario correctly, because we test it against
885 * curl in server-auth-test.
887 server = soup_test_server_new (FALSE);
888 soup_server_add_handler (server, NULL,
889 server_callback, NULL, NULL);
891 uri = soup_uri_new ("http://127.0.0.1/");
892 soup_uri_set_port (uri, soup_server_get_port (server));
894 basic_auth_domain = soup_auth_domain_basic_new (
895 SOUP_AUTH_DOMAIN_REALM, "auth-test",
896 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
897 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
899 soup_server_add_auth_domain (server, basic_auth_domain);
901 digest_auth_domain = soup_auth_domain_digest_new (
902 SOUP_AUTH_DOMAIN_REALM, "auth-test",
903 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
904 SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, server_digest_auth_callback,
906 soup_server_add_auth_domain (server, digest_auth_domain);
908 debug_printf (1, " Testing with no auth\n");
909 select_auth_test_one (uri, FALSE, NULL,
910 "Basic, Digest", "Digest",
912 SOUP_STATUS_UNAUTHORIZED);
914 debug_printf (1, " Testing with bad password\n");
915 select_auth_test_one (uri, FALSE, "bad",
916 "Basic, Digest", "Digest",
917 "Basic, Digest", "Digest",
918 SOUP_STATUS_UNAUTHORIZED);
920 debug_printf (1, " Testing with good password\n");
921 select_auth_test_one (uri, FALSE, "good",
922 "Basic, Digest", "Digest",
926 /* Test with Digest disabled in the client. */
927 debug_printf (1, " Testing without Digest with no auth\n");
928 select_auth_test_one (uri, TRUE, NULL,
929 "Basic, Digest", "Basic",
931 SOUP_STATUS_UNAUTHORIZED);
933 debug_printf (1, " Testing without Digest with bad password\n");
934 select_auth_test_one (uri, TRUE, "bad",
935 "Basic, Digest", "Basic",
936 "Basic, Digest", "Basic",
937 SOUP_STATUS_UNAUTHORIZED);
939 debug_printf (1, " Testing without Digest with good password\n");
940 select_auth_test_one (uri, TRUE, "good-basic",
941 "Basic, Digest", "Basic",
945 /* Now flip the order of the domains, verify that this flips
946 * the order of the headers, and make sure that digest auth
950 soup_server_remove_auth_domain (server, basic_auth_domain);
951 soup_server_remove_auth_domain (server, digest_auth_domain);
952 soup_server_add_auth_domain (server, digest_auth_domain);
953 soup_server_add_auth_domain (server, basic_auth_domain);
955 debug_printf (1, " Testing flipped with no auth\n");
956 select_auth_test_one (uri, FALSE, NULL,
957 "Digest, Basic", "Digest",
959 SOUP_STATUS_UNAUTHORIZED);
961 debug_printf (1, " Testing flipped with bad password\n");
962 select_auth_test_one (uri, FALSE, "bad",
963 "Digest, Basic", "Digest",
964 "Digest, Basic", "Digest",
965 SOUP_STATUS_UNAUTHORIZED);
967 debug_printf (1, " Testing flipped with good password\n");
968 select_auth_test_one (uri, FALSE, "good",
969 "Digest, Basic", "Digest",
973 g_object_unref (basic_auth_domain);
974 g_object_unref (digest_auth_domain);
976 soup_test_server_quit_unref (server);
980 sneakily_close_connection (SoupMessage *msg, gpointer user_data)
982 /* Sneakily close the connection after the response, by
983 * tricking soup-message-io into thinking that had been
984 * the plan all along.
986 soup_message_headers_append (msg->response_headers,
987 "Connection", "close");
991 auth_close_request_started (SoupServer *server, SoupMessage *msg,
992 SoupClientContext *client, gpointer user_data)
994 g_signal_connect (msg, "wrote-headers",
995 G_CALLBACK (sneakily_close_connection), NULL);
999 SoupSession *session;
1005 auth_close_idle_authenticate (gpointer user_data)
1007 AuthCloseData *acd = user_data;
1009 soup_auth_authenticate (acd->auth, "user", "good-basic");
1010 soup_session_unpause_message (acd->session, acd->msg);
1012 g_object_unref (acd->auth);
1017 auth_close_authenticate (SoupSession *session, SoupMessage *msg,
1018 SoupAuth *auth, gboolean retrying, gpointer data)
1020 AuthCloseData *acd = data;
1022 soup_session_pause_message (session, msg);
1023 acd->auth = g_object_ref (auth);
1024 g_idle_add (auth_close_idle_authenticate, acd);
1028 do_auth_close_test (void)
1031 SoupAuthDomain *basic_auth_domain;
1035 debug_printf (1, "\nTesting auth when server times out connection:\n");
1037 server = soup_test_server_new (FALSE);
1038 soup_server_add_handler (server, NULL,
1039 server_callback, NULL, NULL);
1041 uri = soup_uri_new ("http://127.0.0.1/close");
1042 soup_uri_set_port (uri, soup_server_get_port (server));
1044 basic_auth_domain = soup_auth_domain_basic_new (
1045 SOUP_AUTH_DOMAIN_REALM, "auth-test",
1046 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
1047 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
1049 soup_server_add_auth_domain (server, basic_auth_domain);
1050 g_object_unref (basic_auth_domain);
1052 g_signal_connect (server, "request-started",
1053 G_CALLBACK (auth_close_request_started), NULL);
1055 acd.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1056 g_signal_connect (acd.session, "authenticate",
1057 G_CALLBACK (auth_close_authenticate), &acd);
1059 acd.msg = soup_message_new_from_uri ("GET", uri);
1060 soup_uri_free (uri);
1061 soup_session_send_message (acd.session, acd.msg);
1063 if (acd.msg->status_code != SOUP_STATUS_OK) {
1064 debug_printf (1, " Final status wrong: expected %u, got %u %s\n",
1065 SOUP_STATUS_OK, acd.msg->status_code,
1066 acd.msg->reason_phrase);
1070 g_object_unref (acd.msg);
1071 soup_test_session_abort_unref (acd.session);
1072 soup_test_server_quit_unref (server);
1075 static SoupAuthTest relogin_tests[] = {
1076 { "Auth provided via URL, should succeed",
1077 "Basic/realm12/", "1", TRUE, "01", SOUP_STATUS_OK },
1079 { "Now should automatically reuse previous auth",
1080 "Basic/realm12/", "", FALSE, "1", SOUP_STATUS_OK },
1082 { "Different auth provided via URL for the same realm, should succeed",
1083 "Basic/realm12/", "2", TRUE, "2", SOUP_STATUS_OK },
1085 { "Subdir should also automatically reuse auth",
1086 "Basic/realm12/subdir/", "", FALSE, "2", SOUP_STATUS_OK },
1088 { "Should fail with no auth",
1089 "Basic/realm12/", "4", TRUE, "4", SOUP_STATUS_UNAUTHORIZED },
1091 { "Make sure we've forgotten it",
1092 "Basic/realm12/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
1094 { "Should fail with no auth, fail again with bad password, and give up",
1095 "Basic/realm12/", "3", FALSE, "03", SOUP_STATUS_UNAUTHORIZED },
1099 do_batch_tests (const gchar *base_uri_str, gint ntests)
1101 SoupSession *session;
1103 char *expected, *uristr;
1107 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1108 g_signal_connect (session, "authenticate",
1109 G_CALLBACK (authenticate), &i);
1111 base_uri = soup_uri_new (base_uri_str);
1113 for (i = 0; i < ntests; i++) {
1114 SoupURI *soup_uri = soup_uri_new_with_base (base_uri, current_tests[i].url);
1116 debug_printf (1, "Test %d: %s\n", i + 1, current_tests[i].explanation);
1118 if (current_tests[i].url_auth) {
1119 gchar *username = g_strdup_printf ("user%c", current_tests[i].provided[0]);
1120 gchar *password = g_strdup_printf ("realm%c", current_tests[i].provided[0]);
1121 soup_uri_set_user (soup_uri, username);
1122 soup_uri_set_password (soup_uri, password);
1127 msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
1128 soup_uri_free (soup_uri);
1130 g_printerr ("auth-test: Could not parse URI\n");
1134 uristr = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1135 debug_printf (1, " GET %s\n", uristr);
1138 expected = g_strdup (current_tests[i].expected);
1139 soup_message_add_status_code_handler (
1140 msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
1141 G_CALLBACK (handler), expected);
1142 soup_message_add_status_code_handler (
1143 msg, "got_headers", SOUP_STATUS_OK,
1144 G_CALLBACK (handler), expected);
1145 soup_session_send_message (session, msg);
1146 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED &&
1147 msg->status_code != SOUP_STATUS_OK) {
1148 debug_printf (1, " %d %s !\n", msg->status_code,
1149 msg->reason_phrase);
1153 debug_printf (1, " expected %d more round(s)\n",
1154 (int)strlen (expected));
1159 if (msg->status_code != current_tests[i].final_status) {
1160 debug_printf (1, " expected %d\n",
1161 current_tests[i].final_status);
1164 debug_printf (1, "\n");
1166 g_object_unref (msg);
1168 soup_uri_free (base_uri);
1170 soup_test_session_abort_unref (session);
1174 main (int argc, char **argv)
1176 const char *base_uri;
1179 test_init (argc, argv, NULL);
1182 base_uri = "http://127.0.0.1:47524/";
1185 current_tests = main_tests;
1186 ntests = G_N_ELEMENTS (main_tests);
1187 do_batch_tests (base_uri, ntests);
1189 /* Re-login tests */
1190 current_tests = relogin_tests;
1191 ntests = G_N_ELEMENTS (relogin_tests);
1192 do_batch_tests (base_uri, ntests);
1194 /* Other regression tests */
1195 do_pipelined_auth_test (base_uri);
1196 do_digest_expiration_test (base_uri);
1197 do_async_auth_test (base_uri);
1198 do_select_auth_test ();
1199 do_auth_close_test ();