1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 #include "test-utils.h"
7 static GMainLoop *loop;
10 /* Explanation of what you should see */
11 const char *explanation;
13 /* URL to test against */
16 /* Provided passwords, 1 character each. ('1', '2', and '3'
17 * mean the correct passwords for "realm1", "realm2", and
18 * "realm3" respectively. '4' means "use the wrong password".)
19 * The first password (if present) will be used by
20 * authenticate(), and the second (if present) will be used by
25 /* Whether to pass user and password in the URL or not.
29 /* Expected passwords, 1 character each. (As with the provided
30 * passwords, with the addition that '0' means "no
31 * Authorization header expected".) Used to verify that soup
32 * used the password it was supposed to at each step.
36 /* What the final status code should be. */
40 /* Will either point to main_tests or relogin_tests
42 static SoupAuthTest *current_tests;
44 static SoupAuthTest main_tests[] = {
45 { "No auth available, should fail",
46 "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
48 { "Should fail with no auth, fail again with bad password, and give up",
49 "Basic/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
51 { "Auth provided this time, so should succeed",
52 "Basic/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
54 { "Now should automatically reuse previous auth",
55 "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
57 { "Subdir should also automatically reuse auth",
58 "Basic/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
60 { "Subdir should retry last auth, but will fail this time",
61 "Basic/realm1/realm2/", "", FALSE, "1", SOUP_STATUS_UNAUTHORIZED },
63 { "Now should use provided auth",
64 "Basic/realm1/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
66 { "Reusing last auth. Should succeed on first try",
67 "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
69 { "Reuse will fail, but 2nd try will succeed because it's a known realm",
70 "Basic/realm1/realm2/realm1/", "", FALSE, "21", SOUP_STATUS_OK },
72 { "Should succeed on first try. (Known realm with cached password)",
73 "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
75 { "Fail once, then use typoed password, then use right password",
76 "Basic/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
79 { "No auth available, should fail",
80 "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
82 { "Should fail with no auth, fail again with bad password, and give up",
83 "Digest/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
85 { "Known realm, auth provided, so should succeed",
86 "Digest/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
88 { "Now should automatically reuse previous auth",
89 "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
91 { "Subdir should also automatically reuse auth",
92 "Digest/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
94 { "Password provided, should succeed",
95 "Digest/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
97 { "Should already know correct domain and use provided auth on first try",
98 "Digest/realm1/realm2/", "2", FALSE, "2", SOUP_STATUS_OK },
100 { "Reusing last auth. Should succeed on first try",
101 "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
103 { "Should succeed on first try because of earlier domain directive",
104 "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
106 { "Fail once, then use typoed password, then use right password",
107 "Digest/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
110 { "Make sure we haven't forgotten anything",
111 "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
113 { "Make sure we haven't forgotten anything",
114 "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
116 { "Make sure we haven't forgotten anything",
117 "Basic/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
119 { "Make sure we haven't forgotten anything",
120 "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
122 { "Make sure we haven't forgotten anything",
123 "Basic/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
126 { "Make sure we haven't forgotten anything",
127 "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
129 { "Make sure we haven't forgotten anything",
130 "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
132 { "Make sure we haven't forgotten anything",
133 "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
135 { "Make sure we haven't forgotten anything",
136 "Digest/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
138 { "Make sure we haven't forgotten anything",
139 "Digest/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
141 { "Now the server will reject the formerly-good password",
142 "Basic/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
144 { "Make sure we've forgotten it",
145 "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
147 { "Likewise, reject the formerly-good Digest password",
148 "Digest/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
150 { "Make sure we've forgotten it",
151 "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
153 { "Fail with URI-embedded password, then use right password in the authenticate signal",
154 "Basic/realm3/", "43", TRUE, "43", SOUP_STATUS_OK }
157 static const char *auths[] = {
158 "no password", "password 1",
159 "password 2", "password 3",
160 "intentionally wrong password",
164 identify_auth (SoupMessage *msg)
169 header = soup_message_headers_get_one (msg->request_headers,
174 if (!g_ascii_strncasecmp (header, "Basic ", 6)) {
178 token = (char *)g_base64_decode (header + 6, &len);
179 num = token[len - 1] - '0';
184 user = strstr (header, "username=\"user");
186 num = user[14] - '0';
191 g_assert (num >= 0 && num <= 4);
197 handler (SoupMessage *msg, gpointer data)
199 char *expected = data;
202 auth = identify_auth (msg);
204 debug_printf (1, " %d %s (using %s)\n",
205 msg->status_code, msg->reason_phrase,
209 exp = *expected - '0';
211 debug_printf (1, " expected %s!\n", auths[exp]);
214 memmove (expected, expected + 1, strlen (expected));
216 debug_printf (1, " expected to be finished\n");
222 authenticate (SoupSession *session, SoupMessage *msg,
223 SoupAuth *auth, gboolean retrying, gpointer data)
226 char *username, *password;
229 if (!current_tests[*i].provided[0])
232 if (!current_tests[*i].provided[1])
234 num = current_tests[*i].provided[1];
236 num = current_tests[*i].provided[0];
238 username = g_strdup_printf ("user%c", num);
239 password = g_strdup_printf ("realm%c", num);
240 soup_auth_authenticate (auth, username, password);
246 bug271540_sent (SoupMessage *msg, gpointer data)
248 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
249 gboolean *authenticated = data;
250 int auth = identify_auth (msg);
252 if (!*authenticated && auth) {
253 debug_printf (1, " using auth on message %d before authenticating!!??\n", n);
255 } else if (*authenticated && !auth) {
256 debug_printf (1, " sent unauthenticated message %d after authenticating!\n", n);
262 bug271540_authenticate (SoupSession *session, SoupMessage *msg,
263 SoupAuth *auth, gboolean retrying, gpointer data)
265 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
266 gboolean *authenticated = data;
268 if (strcmp (soup_auth_get_scheme_name (auth), "Basic") != 0 ||
269 strcmp (soup_auth_get_realm (auth), "realm1") != 0)
272 if (!*authenticated) {
273 debug_printf (1, " authenticating message %d\n", n);
274 soup_auth_authenticate (auth, "user1", "realm1");
275 *authenticated = TRUE;
277 debug_printf (1, " asked to authenticate message %d after authenticating!\n", n);
283 bug271540_finished (SoupSession *session, SoupMessage *msg, gpointer data)
286 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
288 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
289 debug_printf (1, " got status '%d %s' on message %d!\n",
290 msg->status_code, msg->reason_phrase, n);
296 g_main_loop_quit (loop);
300 do_pipelined_auth_test (const char *base_uri)
302 SoupSession *session;
304 gboolean authenticated;
308 debug_printf (1, "Testing pipelined auth (bug 271540):\n");
309 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
311 authenticated = FALSE;
312 g_signal_connect (session, "authenticate",
313 G_CALLBACK (bug271540_authenticate), &authenticated);
315 uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
316 for (i = 0; i < 10; i++) {
317 msg = soup_message_new (SOUP_METHOD_GET, uri);
318 g_object_set_data (G_OBJECT (msg), "#", GINT_TO_POINTER (i + 1));
319 g_signal_connect (msg, "wrote_headers",
320 G_CALLBACK (bug271540_sent), &authenticated);
322 soup_session_queue_message (session, msg,
323 bug271540_finished, &i);
327 loop = g_main_loop_new (NULL, TRUE);
328 g_main_loop_run (loop);
329 g_main_loop_unref (loop);
331 soup_test_session_abort_unref (session);
334 /* We test two different things here:
336 * 1. If we get a 401 response with "WWW-Authenticate: Digest
337 * stale=true...", we should retry and succeed *without* the
338 * session asking for a password again.
340 * 2. If we get a successful response with "Authentication-Info:
341 * nextnonce=...", we should update the nonce automatically so as
342 * to avoid getting a stale nonce error on the next request.
344 * In our Apache config, /Digest/realm1 and /Digest/realm1/expire are
345 * set up to use the same auth info, but only the latter has an
346 * AuthDigestNonceLifetime (of 2 seconds). The way nonces work in
347 * Apache, a nonce received from /Digest/realm1 will still expire in
348 * /Digest/realm1/expire, but it won't issue a nextnonce for a request
349 * in /Digest/realm1. This lets us test both behaviors.
351 * The expected conversation is:
357 * WWW-Authenticate: Digest nonce=A
359 * [emit 'authenticate']
362 * Authorization: Digest nonce=A
365 * [No Authentication-Info]
367 * [sleep 2 seconds: nonce A is no longer valid, but we have no
368 * way of knowing that]
371 * GET /Digest/realm1/expire/
372 * Authorization: Digest nonce=A
375 * WWW-Authenticate: Digest stale=true nonce=B
377 * GET /Digest/realm1/expire/
378 * Authorization: Digest nonce=B
381 * Authentication-Info: nextnonce=C
386 * GET /Digest/realm1/expire/
387 * Authorization: Digest nonce=C
388 * [nonce=B would work here too]
391 * Authentication-Info: nextnonce=D
393 * [sleep 1 second; nonces B and C are no longer valid, but D is]
396 * GET /Digest/realm1/expire/
397 * Authorization: Digest nonce=D
400 * Authentication-Info: nextnonce=D
405 digest_nonce_authenticate (SoupSession *session, SoupMessage *msg,
406 SoupAuth *auth, gboolean retrying, gpointer data)
411 if (strcmp (soup_auth_get_scheme_name (auth), "Digest") != 0 ||
412 strcmp (soup_auth_get_realm (auth), "realm1") != 0)
415 soup_auth_authenticate (auth, "user1", "realm1");
419 digest_nonce_unauthorized (SoupMessage *msg, gpointer data)
421 gboolean *got_401 = data;
426 do_digest_nonce_test (SoupSession *session,
427 const char *nth, const char *uri,
428 gboolean expect_401, gboolean expect_signal)
433 msg = soup_message_new (SOUP_METHOD_GET, uri);
435 g_signal_connect (session, "authenticate",
436 G_CALLBACK (digest_nonce_authenticate),
439 soup_message_add_status_code_handler (msg, "got_headers",
440 SOUP_STATUS_UNAUTHORIZED,
441 G_CALLBACK (digest_nonce_unauthorized),
444 soup_session_send_message (session, msg);
445 if (got_401 != expect_401) {
446 debug_printf (1, " %s request %s a 401 Unauthorized!\n", nth,
447 got_401 ? "got" : "did not get");
450 if (msg->status_code != SOUP_STATUS_OK) {
451 debug_printf (1, " %s request got status %d %s!\n", nth,
452 msg->status_code, msg->reason_phrase);
456 debug_printf (1, " %s request succeeded\n", nth);
457 g_object_unref (msg);
461 do_digest_expiration_test (const char *base_uri)
463 SoupSession *session;
466 debug_printf (1, "\nTesting digest nonce expiration:\n");
468 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
470 uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
471 do_digest_nonce_test (session, "First", uri, TRUE, TRUE);
474 uri = g_strconcat (base_uri, "Digest/realm1/expire/", NULL);
475 do_digest_nonce_test (session, "Second", uri, TRUE, FALSE);
477 do_digest_nonce_test (session, "Third", uri, FALSE, FALSE);
479 do_digest_nonce_test (session, "Fourth", uri, FALSE, FALSE);
482 soup_test_session_abort_unref (session);
485 /* Async auth test. We queue three requests to /Basic/realm1, ensuring
486 * that they are sent in order. The first and third ones will be
487 * paused from the authentication callback. The second will be allowed
488 * to fail. Shortly after the third one requests auth, we'll provide
489 * the auth and unpause the two remaining messages, allowing them to
494 async_authenticate (SoupSession *session, SoupMessage *msg,
495 SoupAuth *auth, gboolean retrying, gpointer data)
497 SoupAuth **saved_auth = data;
498 int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
500 debug_printf (2, " async_authenticate msg%d\n", id);
502 /* The session will try to authenticate msg3 *before* sending
503 * it, because it already knows it's going to need the auth.
506 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
507 debug_printf (2, " (ignoring)\n");
511 soup_session_pause_message (session, msg);
513 *saved_auth = g_object_ref (auth);
514 g_main_loop_quit (loop);
518 async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
520 int *remaining = user_data;
521 int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
523 debug_printf (2, " async_finished msg%d\n", id);
527 g_main_loop_quit (loop);
531 async_authenticate_assert_once (SoupSession *session, SoupMessage *msg,
532 SoupAuth *auth, gboolean retrying, gpointer data)
534 gboolean *been_here = data;
536 debug_printf (2, " async_authenticate_assert_once\n");
539 debug_printf (1, " ERROR: async_authenticate_assert_once called twice\n");
546 async_authenticate_assert_once_and_stop (SoupSession *session, SoupMessage *msg,
547 SoupAuth *auth, gboolean retrying, gpointer data)
549 gboolean *been_here = data;
551 debug_printf (2, " async_authenticate_assert_once_and_stop\n");
554 debug_printf (1, " ERROR: async_authenticate_assert_once called twice\n");
559 soup_session_pause_message (session, msg);
560 g_main_loop_quit (loop);
564 do_async_auth_test (const char *base_uri)
566 SoupSession *session;
567 SoupMessage *msg1, *msg2, *msg3, msg2_bak;
570 SoupAuth *auth = NULL;
574 debug_printf (1, "\nTesting async auth:\n");
576 loop = g_main_loop_new (NULL, TRUE);
577 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
580 uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
582 msg1 = soup_message_new ("GET", uri);
583 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
584 auth_id = g_signal_connect (session, "authenticate",
585 G_CALLBACK (async_authenticate), &auth);
588 soup_session_queue_message (session, msg1, async_finished, &remaining);
589 g_main_loop_run (loop);
590 g_signal_handler_disconnect (session, auth_id);
592 /* async_authenticate will pause msg1 and quit loop */
594 msg2 = soup_message_new ("GET", uri);
595 g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2));
596 soup_session_send_message (session, msg2);
598 if (msg2->status_code == SOUP_STATUS_UNAUTHORIZED)
599 debug_printf (1, " msg2 failed as expected\n");
601 debug_printf (1, " msg2 got wrong status! (%u)\n",
606 /* msg2 should be done at this point; assuming everything is
607 * working correctly, the session won't look at it again; we
608 * ensure that if it does, it will crash the test program.
610 memcpy (&msg2_bak, msg2, sizeof (SoupMessage));
611 memset (msg2, 0, sizeof (SoupMessage));
613 msg3 = soup_message_new ("GET", uri);
614 g_object_set_data (G_OBJECT (msg3), "id", GINT_TO_POINTER (3));
615 auth_id = g_signal_connect (session, "authenticate",
616 G_CALLBACK (async_authenticate), NULL);
619 soup_session_queue_message (session, msg3, async_finished, &remaining);
620 g_main_loop_run (loop);
621 g_signal_handler_disconnect (session, auth_id);
623 /* async_authenticate will pause msg3 and quit loop */
625 /* Now do the auth, and restart */
627 soup_auth_authenticate (auth, "user1", "realm1");
628 g_object_unref (auth);
629 soup_session_unpause_message (session, msg1);
630 soup_session_unpause_message (session, msg3);
632 g_main_loop_run (loop);
634 /* async_finished will quit the loop */
636 debug_printf (1, " msg1 didn't get authenticate signal!\n");
640 if (msg1->status_code == SOUP_STATUS_OK)
641 debug_printf (1, " msg1 succeeded\n");
643 debug_printf (1, " msg1 FAILED! (%u %s)\n",
644 msg1->status_code, msg1->reason_phrase);
647 if (msg3->status_code == SOUP_STATUS_OK)
648 debug_printf (1, " msg3 succeeded\n");
650 debug_printf (1, " msg3 FAILED! (%u %s)\n",
651 msg3->status_code, msg3->reason_phrase);
655 soup_test_session_abort_unref (session);
657 g_object_unref (msg1);
658 g_object_unref (msg3);
659 memcpy (msg2, &msg2_bak, sizeof (SoupMessage));
660 g_object_unref (msg2);
662 /* Test that giving the wrong password doesn't cause multiple
663 * authenticate signals the second time.
665 debug_printf (1, "\nTesting async auth with wrong password (#522601):\n");
667 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
671 msg1 = soup_message_new ("GET", uri);
672 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
673 auth_id = g_signal_connect (session, "authenticate",
674 G_CALLBACK (async_authenticate), &auth);
677 soup_session_queue_message (session, msg1, async_finished, &remaining);
678 g_main_loop_run (loop);
679 g_signal_handler_disconnect (session, auth_id);
680 soup_auth_authenticate (auth, "user1", "wrong");
681 g_object_unref (auth);
682 soup_session_unpause_message (session, msg1);
685 auth_id = g_signal_connect (session, "authenticate",
686 G_CALLBACK (async_authenticate_assert_once),
688 g_main_loop_run (loop);
689 g_signal_handler_disconnect (session, auth_id);
692 debug_printf (1, " authenticate not emitted?\n");
696 soup_test_session_abort_unref (session);
697 g_object_unref (msg1);
699 /* Test that giving no password doesn't cause multiple
700 * authenticate signals the second time.
702 debug_printf (1, "\nTesting async auth with no password (#583462):\n");
704 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
707 /* Send a message that doesn't actually authenticate
709 msg1 = soup_message_new ("GET", uri);
710 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
711 auth_id = g_signal_connect (session, "authenticate",
712 G_CALLBACK (async_authenticate), NULL);
715 soup_session_queue_message (session, msg1, async_finished, &remaining);
716 g_main_loop_run (loop);
717 g_signal_handler_disconnect (session, auth_id);
718 soup_session_unpause_message (session, msg1);
719 g_main_loop_run (loop);
720 g_object_unref(msg1);
722 /* Now send a second message */
723 msg1 = soup_message_new ("GET", uri);
724 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (2));
727 auth_id = g_signal_connect (session, "authenticate",
728 G_CALLBACK (async_authenticate_assert_once_and_stop),
731 soup_session_queue_message (session, msg1, async_finished, &remaining);
732 g_main_loop_run (loop);
733 soup_session_unpause_message (session, msg1);
735 g_main_loop_run (loop);
736 g_signal_handler_disconnect (session, auth_id);
738 soup_test_session_abort_unref (session);
739 g_object_unref (msg1);
742 g_main_loop_unref (loop);
746 const char *password;
749 const char *response;
754 select_auth_authenticate (SoupSession *session, SoupMessage *msg,
755 SoupAuth *auth, gboolean retrying, gpointer data)
757 SelectAuthData *sad = data;
758 const char *header, *basic, *digest;
759 int round = retrying ? 1 : 0;
761 header = soup_message_headers_get_list (msg->response_headers,
763 basic = strstr (header, "Basic");
764 digest = strstr (header, "Digest");
765 if (basic && digest) {
767 sad->round[round].headers = "Basic, Digest";
769 sad->round[round].headers = "Digest, Basic";
771 sad->round[round].headers = "Basic";
773 sad->round[round].headers = "Digest";
775 sad->round[round].response = soup_auth_get_scheme_name (auth);
776 if (sad->password && !retrying)
777 soup_auth_authenticate (auth, "user", sad->password);
781 select_auth_test_one (SoupURI *uri,
782 gboolean disable_digest, const char *password,
783 const char *first_headers, const char *first_response,
784 const char *second_headers, const char *second_response,
789 SoupSession *session;
791 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
793 soup_session_remove_feature_by_type (session, SOUP_TYPE_AUTH_DIGEST);
795 g_signal_connect (session, "authenticate",
796 G_CALLBACK (select_auth_authenticate), &sad);
797 memset (&sad, 0, sizeof (sad));
798 sad.password = password;
800 msg = soup_message_new_from_uri ("GET", uri);
801 soup_session_send_message (session, msg);
803 if (strcmp (sad.round[0].headers, first_headers) != 0) {
804 debug_printf (1, " Header order wrong: expected %s, got %s\n",
805 first_headers, sad.round[0].headers);
808 if (strcmp (sad.round[0].response, first_response) != 0) {
809 debug_printf (1, " Selected auth type wrong: expected %s, got %s\n",
810 first_response, sad.round[0].response);
814 if (second_headers && !sad.round[1].headers) {
815 debug_printf (1, " Expected a second round!\n");
817 } else if (!second_headers && sad.round[1].headers) {
818 debug_printf (1, " Didn't expect a second round!\n");
820 } else if (second_headers && second_response) {
821 if (strcmp (sad.round[1].headers, second_headers) != 0) {
822 debug_printf (1, " Second round header order wrong: expected %s, got %s\n",
823 second_headers, sad.round[1].headers);
826 if (strcmp (sad.round[1].response, second_response) != 0) {
827 debug_printf (1, " Second round selected auth type wrong: expected %s, got %s\n",
828 second_response, sad.round[1].response);
833 if (msg->status_code != final_status) {
834 debug_printf (1, " Final status wrong: expected %u, got %u\n",
835 final_status, msg->status_code);
839 g_object_unref (msg);
840 soup_test_session_abort_unref (session);
844 server_callback (SoupServer *server, SoupMessage *msg,
845 const char *path, GHashTable *query,
846 SoupClientContext *context, gpointer data)
848 soup_message_set_response (msg, "text/plain",
851 soup_message_set_status (msg, SOUP_STATUS_OK);
855 server_basic_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
856 const char *username, const char *password, gpointer data)
858 if (strcmp (username, "user") != 0)
860 return strcmp (password, "good-basic") == 0;
864 server_digest_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
865 const char *username, gpointer data)
867 if (strcmp (username, "user") != 0)
869 return soup_auth_domain_digest_encode_password ("user",
875 do_select_auth_test (void)
878 SoupAuthDomain *basic_auth_domain, *digest_auth_domain;
881 debug_printf (1, "\nTesting selection among multiple auths:\n");
883 /* It doesn't seem to be possible to configure Apache to serve
884 * multiple auth types for a single URL. So we have to use
885 * SoupServer here. We know that SoupServer handles the server
886 * side of this scenario correctly, because we test it against
887 * curl in server-auth-test.
889 server = soup_test_server_new (FALSE);
890 soup_server_add_handler (server, NULL,
891 server_callback, NULL, NULL);
893 uri = soup_uri_new ("http://127.0.0.1/");
894 soup_uri_set_port (uri, soup_server_get_port (server));
896 basic_auth_domain = soup_auth_domain_basic_new (
897 SOUP_AUTH_DOMAIN_REALM, "auth-test",
898 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
899 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
901 soup_server_add_auth_domain (server, basic_auth_domain);
903 digest_auth_domain = soup_auth_domain_digest_new (
904 SOUP_AUTH_DOMAIN_REALM, "auth-test",
905 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
906 SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, server_digest_auth_callback,
908 soup_server_add_auth_domain (server, digest_auth_domain);
910 debug_printf (1, " Testing with no auth\n");
911 select_auth_test_one (uri, FALSE, NULL,
912 "Basic, Digest", "Digest",
914 SOUP_STATUS_UNAUTHORIZED);
916 debug_printf (1, " Testing with bad password\n");
917 select_auth_test_one (uri, FALSE, "bad",
918 "Basic, Digest", "Digest",
919 "Basic, Digest", "Digest",
920 SOUP_STATUS_UNAUTHORIZED);
922 debug_printf (1, " Testing with good password\n");
923 select_auth_test_one (uri, FALSE, "good",
924 "Basic, Digest", "Digest",
928 /* Test with Digest disabled in the client. */
929 debug_printf (1, " Testing without Digest with no auth\n");
930 select_auth_test_one (uri, TRUE, NULL,
931 "Basic, Digest", "Basic",
933 SOUP_STATUS_UNAUTHORIZED);
935 debug_printf (1, " Testing without Digest with bad password\n");
936 select_auth_test_one (uri, TRUE, "bad",
937 "Basic, Digest", "Basic",
938 "Basic, Digest", "Basic",
939 SOUP_STATUS_UNAUTHORIZED);
941 debug_printf (1, " Testing without Digest with good password\n");
942 select_auth_test_one (uri, TRUE, "good-basic",
943 "Basic, Digest", "Basic",
947 /* Now flip the order of the domains, verify that this flips
948 * the order of the headers, and make sure that digest auth
952 soup_server_remove_auth_domain (server, basic_auth_domain);
953 soup_server_remove_auth_domain (server, digest_auth_domain);
954 soup_server_add_auth_domain (server, digest_auth_domain);
955 soup_server_add_auth_domain (server, basic_auth_domain);
957 debug_printf (1, " Testing flipped with no auth\n");
958 select_auth_test_one (uri, FALSE, NULL,
959 "Digest, Basic", "Digest",
961 SOUP_STATUS_UNAUTHORIZED);
963 debug_printf (1, " Testing flipped with bad password\n");
964 select_auth_test_one (uri, FALSE, "bad",
965 "Digest, Basic", "Digest",
966 "Digest, Basic", "Digest",
967 SOUP_STATUS_UNAUTHORIZED);
969 debug_printf (1, " Testing flipped with good password\n");
970 select_auth_test_one (uri, FALSE, "good",
971 "Digest, Basic", "Digest",
975 g_object_unref (basic_auth_domain);
976 g_object_unref (digest_auth_domain);
978 soup_test_server_quit_unref (server);
982 sneakily_close_connection (SoupMessage *msg, gpointer user_data)
984 /* Sneakily close the connection after the response, by
985 * tricking soup-message-io into thinking that had been
986 * the plan all along.
988 soup_message_headers_append (msg->response_headers,
989 "Connection", "close");
993 auth_close_request_started (SoupServer *server, SoupMessage *msg,
994 SoupClientContext *client, gpointer user_data)
996 g_signal_connect (msg, "wrote-headers",
997 G_CALLBACK (sneakily_close_connection), NULL);
1001 SoupSession *session;
1007 auth_close_idle_authenticate (gpointer user_data)
1009 AuthCloseData *acd = user_data;
1011 soup_auth_authenticate (acd->auth, "user", "good-basic");
1012 soup_session_unpause_message (acd->session, acd->msg);
1014 g_object_unref (acd->auth);
1019 auth_close_authenticate (SoupSession *session, SoupMessage *msg,
1020 SoupAuth *auth, gboolean retrying, gpointer data)
1022 AuthCloseData *acd = data;
1024 soup_session_pause_message (session, msg);
1025 acd->auth = g_object_ref (auth);
1026 g_idle_add (auth_close_idle_authenticate, acd);
1030 do_auth_close_test (void)
1033 SoupAuthDomain *basic_auth_domain;
1037 debug_printf (1, "\nTesting auth when server times out connection:\n");
1039 server = soup_test_server_new (FALSE);
1040 soup_server_add_handler (server, NULL,
1041 server_callback, NULL, NULL);
1043 uri = soup_uri_new ("http://127.0.0.1/close");
1044 soup_uri_set_port (uri, soup_server_get_port (server));
1046 basic_auth_domain = soup_auth_domain_basic_new (
1047 SOUP_AUTH_DOMAIN_REALM, "auth-test",
1048 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
1049 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
1051 soup_server_add_auth_domain (server, basic_auth_domain);
1052 g_object_unref (basic_auth_domain);
1054 g_signal_connect (server, "request-started",
1055 G_CALLBACK (auth_close_request_started), NULL);
1057 acd.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1058 g_signal_connect (acd.session, "authenticate",
1059 G_CALLBACK (auth_close_authenticate), &acd);
1061 acd.msg = soup_message_new_from_uri ("GET", uri);
1062 soup_uri_free (uri);
1063 soup_session_send_message (acd.session, acd.msg);
1065 if (acd.msg->status_code != SOUP_STATUS_OK) {
1066 debug_printf (1, " Final status wrong: expected %u, got %u %s\n",
1067 SOUP_STATUS_OK, acd.msg->status_code,
1068 acd.msg->reason_phrase);
1072 g_object_unref (acd.msg);
1073 soup_test_session_abort_unref (acd.session);
1074 soup_test_server_quit_unref (server);
1078 infinite_cancel (gpointer session)
1080 soup_session_abort (session);
1085 infinite_authenticate (SoupSession *session, SoupMessage *msg,
1086 SoupAuth *auth, gboolean retrying, gpointer data)
1088 soup_auth_authenticate (auth, "user", "bad");
1092 do_infinite_auth_test (const char *base_uri)
1094 SoupSession *session;
1099 debug_printf (1, "\nTesting broken infinite-loop auth:\n");
1101 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1102 g_signal_connect (session, "authenticate",
1103 G_CALLBACK (infinite_authenticate), NULL);
1105 uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
1106 msg = soup_message_new ("GET", uri);
1109 timeout = g_timeout_add (500, infinite_cancel, session);
1110 expect_warning = TRUE;
1111 soup_session_send_message (session, msg);
1113 if (msg->status_code == SOUP_STATUS_CANCELLED) {
1114 debug_printf (1, " FAILED: Got stuck in loop");
1116 } else if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
1117 debug_printf (1, " Final status wrong: expected 401, got %u\n",
1122 g_source_remove (timeout);
1123 soup_test_session_abort_unref (session);
1124 g_object_unref (msg);
1128 disappear_request_read (SoupServer *server, SoupMessage *msg,
1129 SoupClientContext *context, gpointer user_data)
1131 /* Remove the WWW-Authenticate header if this was a failed attempt */
1132 if (soup_message_headers_get_one (msg->request_headers, "Authorization") &&
1133 msg->status_code == SOUP_STATUS_UNAUTHORIZED)
1134 soup_message_headers_remove (msg->response_headers, "WWW-Authenticate");
1138 disappear_authenticate (SoupSession *session, SoupMessage *msg,
1139 SoupAuth *auth, gboolean retrying, gpointer data)
1141 int *counter = data;
1145 soup_auth_authenticate (auth, "user", "bad");
1149 do_disappearing_auth_test (void)
1152 SoupAuthDomain *auth_domain;
1155 SoupSession *session;
1158 debug_printf (1, "\nTesting auth when server does not repeat challenge on failure:\n");
1160 server = soup_test_server_new (FALSE);
1161 soup_server_add_handler (server, NULL,
1162 server_callback, NULL, NULL);
1164 uri = soup_uri_new ("http://127.0.0.1/");
1165 soup_uri_set_port (uri, soup_server_get_port (server));
1167 auth_domain = soup_auth_domain_basic_new (
1168 SOUP_AUTH_DOMAIN_REALM, "auth-test",
1169 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
1170 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
1172 soup_server_add_auth_domain (server, auth_domain);
1173 g_signal_connect (server, "request-read",
1174 G_CALLBACK (disappear_request_read), NULL);
1176 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1179 g_signal_connect (session, "authenticate",
1180 G_CALLBACK (disappear_authenticate), &counter);
1182 msg = soup_message_new_from_uri ("GET", uri);
1183 soup_session_send_message (session, msg);
1186 debug_printf (1, " FAILED: Got stuck in loop");
1188 } else if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
1189 debug_printf (1, " Final status wrong: expected 401, got %u\n",
1194 g_object_unref (msg);
1195 soup_test_session_abort_unref (session);
1197 g_object_unref (auth_domain);
1198 soup_uri_free (uri);
1199 soup_test_server_quit_unref (server);
1202 static SoupAuthTest relogin_tests[] = {
1203 { "Auth provided via URL, should succeed",
1204 "Basic/realm12/", "1", TRUE, "01", SOUP_STATUS_OK },
1206 { "Now should automatically reuse previous auth",
1207 "Basic/realm12/", "", FALSE, "1", SOUP_STATUS_OK },
1209 { "Different auth provided via URL for the same realm, should succeed",
1210 "Basic/realm12/", "2", TRUE, "2", SOUP_STATUS_OK },
1212 { "Subdir should also automatically reuse auth",
1213 "Basic/realm12/subdir/", "", FALSE, "2", SOUP_STATUS_OK },
1215 { "Should fail with no auth",
1216 "Basic/realm12/", "4", TRUE, "4", SOUP_STATUS_UNAUTHORIZED },
1218 { "Make sure we've forgotten it",
1219 "Basic/realm12/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
1221 { "Should fail with no auth, fail again with bad password, and give up",
1222 "Basic/realm12/", "3", FALSE, "03", SOUP_STATUS_UNAUTHORIZED },
1226 do_batch_tests (const gchar *base_uri_str, gint ntests)
1228 SoupSession *session;
1230 char *expected, *uristr;
1234 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1235 g_signal_connect (session, "authenticate",
1236 G_CALLBACK (authenticate), &i);
1238 base_uri = soup_uri_new (base_uri_str);
1240 for (i = 0; i < ntests; i++) {
1241 SoupURI *soup_uri = soup_uri_new_with_base (base_uri, current_tests[i].url);
1243 debug_printf (1, "Test %d: %s\n", i + 1, current_tests[i].explanation);
1245 if (current_tests[i].url_auth) {
1246 gchar *username = g_strdup_printf ("user%c", current_tests[i].provided[0]);
1247 gchar *password = g_strdup_printf ("realm%c", current_tests[i].provided[0]);
1248 soup_uri_set_user (soup_uri, username);
1249 soup_uri_set_password (soup_uri, password);
1254 msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
1255 soup_uri_free (soup_uri);
1257 g_printerr ("auth-test: Could not parse URI\n");
1261 uristr = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1262 debug_printf (1, " GET %s\n", uristr);
1265 expected = g_strdup (current_tests[i].expected);
1266 soup_message_add_status_code_handler (
1267 msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
1268 G_CALLBACK (handler), expected);
1269 soup_message_add_status_code_handler (
1270 msg, "got_headers", SOUP_STATUS_OK,
1271 G_CALLBACK (handler), expected);
1272 soup_session_send_message (session, msg);
1273 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED &&
1274 msg->status_code != SOUP_STATUS_OK) {
1275 debug_printf (1, " %d %s !\n", msg->status_code,
1276 msg->reason_phrase);
1280 debug_printf (1, " expected %d more round(s)\n",
1281 (int)strlen (expected));
1286 if (msg->status_code != current_tests[i].final_status) {
1287 debug_printf (1, " expected %d\n",
1288 current_tests[i].final_status);
1291 debug_printf (1, "\n");
1293 g_object_unref (msg);
1295 soup_uri_free (base_uri);
1297 soup_test_session_abort_unref (session);
1301 main (int argc, char **argv)
1303 const char *base_uri;
1306 test_init (argc, argv, NULL);
1309 base_uri = "http://127.0.0.1:47524/";
1312 current_tests = main_tests;
1313 ntests = G_N_ELEMENTS (main_tests);
1314 do_batch_tests (base_uri, ntests);
1316 /* Re-login tests */
1317 current_tests = relogin_tests;
1318 ntests = G_N_ELEMENTS (relogin_tests);
1319 do_batch_tests (base_uri, ntests);
1321 /* Other regression tests */
1322 do_pipelined_auth_test (base_uri);
1323 do_digest_expiration_test (base_uri);
1324 do_async_auth_test (base_uri);
1325 do_select_auth_test ();
1326 do_auth_close_test ();
1327 do_infinite_auth_test (base_uri);
1328 do_disappearing_auth_test ();
1334 #else /* HAVE_APACHE */
1337 main (int argc, char **argv)
1339 return 77; /* SKIP */