11 #include "libsoup/soup.h"
12 #include "libsoup/soup-auth.h"
13 #include "libsoup/soup-session.h"
15 #include "test-utils.h"
17 static GMainLoop *loop;
20 /* Explanation of what you should see */
21 const char *explanation;
23 /* URL to test against */
26 /* Provided passwords, 1 character each. ('1', '2', and '3'
27 * mean the correct passwords for "realm1", "realm2", and
28 * "realm3" respectively. '4' means "use the wrong password".)
29 * The first password (if present) will be used by
30 * authenticate(), and the second (if present) will be used by
35 /* Whether to pass user and password in the URL or not.
39 /* Expected passwords, 1 character each. (As with the provided
40 * passwords, with the addition that '0' means "no
41 * Authorization header expected".) Used to verify that soup
42 * used the password it was supposed to at each step.
46 /* What the final status code should be. */
50 /* Will either point to main_tests or relogin_tests
52 static SoupAuthTest *current_tests;
54 static SoupAuthTest main_tests[] = {
55 { "No auth available, should fail",
56 "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
58 { "Should fail with no auth, fail again with bad password, and give up",
59 "Basic/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
61 { "Auth provided this time, so should succeed",
62 "Basic/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
64 { "Now should automatically reuse previous auth",
65 "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
67 { "Subdir should also automatically reuse auth",
68 "Basic/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
70 { "Subdir should retry last auth, but will fail this time",
71 "Basic/realm1/realm2/", "", FALSE, "1", SOUP_STATUS_UNAUTHORIZED },
73 { "Now should use provided auth",
74 "Basic/realm1/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
76 { "Reusing last auth. Should succeed on first try",
77 "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
79 { "Reuse will fail, but 2nd try will succeed because it's a known realm",
80 "Basic/realm1/realm2/realm1/", "", FALSE, "21", SOUP_STATUS_OK },
82 { "Should succeed on first try. (Known realm with cached password)",
83 "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
85 { "Fail once, then use typoed password, then use right password",
86 "Basic/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
89 { "No auth available, should fail",
90 "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
92 { "Should fail with no auth, fail again with bad password, and give up",
93 "Digest/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
95 { "Known realm, auth provided, so should succeed",
96 "Digest/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
98 { "Now should automatically reuse previous auth",
99 "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
101 { "Subdir should also automatically reuse auth",
102 "Digest/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
104 { "Password provided, should succeed",
105 "Digest/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
107 { "Should already know correct domain and use provided auth on first try",
108 "Digest/realm1/realm2/", "2", FALSE, "2", SOUP_STATUS_OK },
110 { "Reusing last auth. Should succeed on first try",
111 "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
113 { "Should succeed on first try because of earlier domain directive",
114 "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
116 { "Fail once, then use typoed password, then use right password",
117 "Digest/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
120 { "Make sure we haven't forgotten anything",
121 "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
123 { "Make sure we haven't forgotten anything",
124 "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
126 { "Make sure we haven't forgotten anything",
127 "Basic/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
129 { "Make sure we haven't forgotten anything",
130 "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
132 { "Make sure we haven't forgotten anything",
133 "Basic/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
136 { "Make sure we haven't forgotten anything",
137 "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
139 { "Make sure we haven't forgotten anything",
140 "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
142 { "Make sure we haven't forgotten anything",
143 "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
145 { "Make sure we haven't forgotten anything",
146 "Digest/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
148 { "Make sure we haven't forgotten anything",
149 "Digest/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
151 { "Now the server will reject the formerly-good password",
152 "Basic/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
154 { "Make sure we've forgotten it",
155 "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
157 { "Likewise, reject the formerly-good Digest password",
158 "Digest/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
160 { "Make sure we've forgotten it",
161 "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED }
164 static const char *auths[] = {
165 "no password", "password 1",
166 "password 2", "password 3",
167 "intentionally wrong password",
171 identify_auth (SoupMessage *msg)
176 header = soup_message_headers_get_one (msg->request_headers,
181 if (!g_ascii_strncasecmp (header, "Basic ", 6)) {
185 token = (char *)g_base64_decode (header + 6, &len);
186 num = token[len - 1] - '0';
191 user = strstr (header, "username=\"user");
193 num = user[14] - '0';
198 g_assert (num >= 0 && num <= 4);
204 handler (SoupMessage *msg, gpointer data)
206 char *expected = data;
209 auth = identify_auth (msg);
211 debug_printf (1, " %d %s (using %s)\n",
212 msg->status_code, msg->reason_phrase,
216 exp = *expected - '0';
218 debug_printf (1, " expected %s!\n", auths[exp]);
221 memmove (expected, expected + 1, strlen (expected));
223 debug_printf (1, " expected to be finished\n");
229 authenticate (SoupSession *session, SoupMessage *msg,
230 SoupAuth *auth, gboolean retrying, gpointer data)
233 char *username, *password;
236 if (!current_tests[*i].provided[0])
239 if (!current_tests[*i].provided[1])
241 num = current_tests[*i].provided[1];
243 num = current_tests[*i].provided[0];
245 username = g_strdup_printf ("user%c", num);
246 password = g_strdup_printf ("realm%c", num);
247 soup_auth_authenticate (auth, username, password);
253 bug271540_sent (SoupMessage *msg, gpointer data)
255 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
256 gboolean *authenticated = data;
257 int auth = identify_auth (msg);
259 if (!*authenticated && auth) {
260 debug_printf (1, " using auth on message %d before authenticating!!??\n", n);
262 } else if (*authenticated && !auth) {
263 debug_printf (1, " sent unauthenticated message %d after authenticating!\n", n);
269 bug271540_authenticate (SoupSession *session, SoupMessage *msg,
270 SoupAuth *auth, gboolean retrying, gpointer data)
272 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
273 gboolean *authenticated = data;
275 if (strcmp (soup_auth_get_scheme_name (auth), "Basic") != 0 ||
276 strcmp (soup_auth_get_realm (auth), "realm1") != 0)
279 if (!*authenticated) {
280 debug_printf (1, " authenticating message %d\n", n);
281 soup_auth_authenticate (auth, "user1", "realm1");
282 *authenticated = TRUE;
284 debug_printf (1, " asked to authenticate message %d after authenticating!\n", n);
290 bug271540_finished (SoupSession *session, SoupMessage *msg, gpointer data)
293 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
295 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
296 debug_printf (1, " got status '%d %s' on message %d!\n",
297 msg->status_code, msg->reason_phrase, n);
303 g_main_loop_quit (loop);
307 do_pipelined_auth_test (const char *base_uri)
309 SoupSession *session;
311 gboolean authenticated;
315 debug_printf (1, "Testing pipelined auth (bug 271540):\n");
316 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
318 authenticated = FALSE;
319 g_signal_connect (session, "authenticate",
320 G_CALLBACK (bug271540_authenticate), &authenticated);
322 uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
323 for (i = 0; i < 10; i++) {
324 msg = soup_message_new (SOUP_METHOD_GET, uri);
325 g_object_set_data (G_OBJECT (msg), "#", GINT_TO_POINTER (i + 1));
326 g_signal_connect (msg, "wrote_headers",
327 G_CALLBACK (bug271540_sent), &authenticated);
329 soup_session_queue_message (session, msg,
330 bug271540_finished, &i);
334 loop = g_main_loop_new (NULL, TRUE);
335 g_main_loop_run (loop);
336 g_main_loop_unref (loop);
338 soup_test_session_abort_unref (session);
341 /* We test two different things here:
343 * 1. If we get a 401 response with "WWW-Authenticate: Digest
344 * stale=true...", we should retry and succeed *without* the
345 * session asking for a password again.
347 * 2. If we get a successful response with "Authentication-Info:
348 * nextnonce=...", we should update the nonce automatically so as
349 * to avoid getting a stale nonce error on the next request.
351 * In our Apache config, /Digest/realm1 and /Digest/realm1/expire are
352 * set up to use the same auth info, but only the latter has an
353 * AuthDigestNonceLifetime (of 2 seconds). The way nonces work in
354 * Apache, a nonce received from /Digest/realm1 will still expire in
355 * /Digest/realm1/expire, but it won't issue a nextnonce for a request
356 * in /Digest/realm1. This lets us test both behaviors.
358 * The expected conversation is:
364 * WWW-Authenticate: Digest nonce=A
366 * [emit 'authenticate']
369 * Authorization: Digest nonce=A
372 * [No Authentication-Info]
374 * [sleep 2 seconds: nonce A is no longer valid, but we have no
375 * way of knowing that]
378 * GET /Digest/realm1/expire/
379 * Authorization: Digest nonce=A
382 * WWW-Authenticate: Digest stale=true nonce=B
384 * GET /Digest/realm1/expire/
385 * Authorization: Digest nonce=B
388 * Authentication-Info: nextnonce=C
393 * GET /Digest/realm1/expire/
394 * Authorization: Digest nonce=C
395 * [nonce=B would work here too]
398 * Authentication-Info: nextnonce=D
400 * [sleep 1 second; nonces B and C are no longer valid, but D is]
403 * GET /Digest/realm1/expire/
404 * Authorization: Digest nonce=D
407 * Authentication-Info: nextnonce=D
412 digest_nonce_authenticate (SoupSession *session, SoupMessage *msg,
413 SoupAuth *auth, gboolean retrying, gpointer data)
418 if (strcmp (soup_auth_get_scheme_name (auth), "Digest") != 0 ||
419 strcmp (soup_auth_get_realm (auth), "realm1") != 0)
422 soup_auth_authenticate (auth, "user1", "realm1");
426 digest_nonce_unauthorized (SoupMessage *msg, gpointer data)
428 gboolean *got_401 = data;
433 do_digest_nonce_test (SoupSession *session,
434 const char *nth, const char *uri,
435 gboolean expect_401, gboolean expect_signal)
440 msg = soup_message_new (SOUP_METHOD_GET, uri);
442 g_signal_connect (session, "authenticate",
443 G_CALLBACK (digest_nonce_authenticate),
446 soup_message_add_status_code_handler (msg, "got_headers",
447 SOUP_STATUS_UNAUTHORIZED,
448 G_CALLBACK (digest_nonce_unauthorized),
451 soup_session_send_message (session, msg);
452 if (got_401 != expect_401) {
453 debug_printf (1, " %s request %s a 401 Unauthorized!\n", nth,
454 got_401 ? "got" : "did not get");
457 if (msg->status_code != SOUP_STATUS_OK) {
458 debug_printf (1, " %s request got status %d %s!\n", nth,
459 msg->status_code, msg->reason_phrase);
463 debug_printf (1, " %s request succeeded\n", nth);
464 g_object_unref (msg);
468 do_digest_expiration_test (const char *base_uri)
470 SoupSession *session;
473 debug_printf (1, "\nTesting digest nonce expiration:\n");
475 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
477 uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
478 do_digest_nonce_test (session, "First", uri, TRUE, TRUE);
481 uri = g_strconcat (base_uri, "Digest/realm1/expire/", NULL);
482 do_digest_nonce_test (session, "Second", uri, TRUE, FALSE);
484 do_digest_nonce_test (session, "Third", uri, FALSE, FALSE);
486 do_digest_nonce_test (session, "Fourth", uri, FALSE, FALSE);
489 soup_test_session_abort_unref (session);
492 /* Async auth test. We queue three requests to /Basic/realm1, ensuring
493 * that they are sent in order. The first and third ones will be
494 * paused from the authentication callback. The second will be allowed
495 * to fail. Shortly after the third one requests auth, we'll provide
496 * the auth and unpause the two remaining messages, allowing them to
501 async_authenticate (SoupSession *session, SoupMessage *msg,
502 SoupAuth *auth, gboolean retrying, gpointer data)
504 SoupAuth **saved_auth = data;
505 int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
507 debug_printf (2, " async_authenticate msg%d\n", id);
509 /* The session will try to authenticate msg3 *before* sending
510 * it, because it already knows it's going to need the auth.
513 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
514 debug_printf (2, " (ignoring)\n");
518 soup_session_pause_message (session, msg);
520 *saved_auth = g_object_ref (auth);
521 g_main_loop_quit (loop);
525 async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
527 int *remaining = user_data;
528 int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
530 debug_printf (2, " async_finished msg%d\n", id);
534 g_main_loop_quit (loop);
538 async_authenticate_assert_once (SoupSession *session, SoupMessage *msg,
539 SoupAuth *auth, gboolean retrying, gpointer data)
541 gboolean *been_here = data;
543 debug_printf (2, " async_authenticate_assert_once\n");
546 debug_printf (1, " ERROR: async_authenticate_assert_once called twice\n");
553 async_authenticate_assert_once_and_stop (SoupSession *session, SoupMessage *msg,
554 SoupAuth *auth, gboolean retrying, gpointer data)
556 gboolean *been_here = data;
558 debug_printf (2, " async_authenticate_assert_once_and_stop\n");
561 debug_printf (1, " ERROR: async_authenticate_assert_once called twice\n");
566 soup_session_pause_message (session, msg);
567 g_main_loop_quit (loop);
571 do_async_auth_test (const char *base_uri)
573 SoupSession *session;
574 SoupMessage *msg1, *msg2, *msg3, msg2_bak;
577 SoupAuth *auth = NULL;
581 debug_printf (1, "\nTesting async auth:\n");
583 loop = g_main_loop_new (NULL, TRUE);
584 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
587 uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
589 msg1 = soup_message_new ("GET", uri);
590 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
591 auth_id = g_signal_connect (session, "authenticate",
592 G_CALLBACK (async_authenticate), &auth);
595 soup_session_queue_message (session, msg1, async_finished, &remaining);
596 g_main_loop_run (loop);
597 g_signal_handler_disconnect (session, auth_id);
599 /* async_authenticate will pause msg1 and quit loop */
601 msg2 = soup_message_new ("GET", uri);
602 g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2));
603 soup_session_send_message (session, msg2);
605 if (msg2->status_code == SOUP_STATUS_UNAUTHORIZED)
606 debug_printf (1, " msg2 failed as expected\n");
608 debug_printf (1, " msg2 got wrong status! (%u)\n",
613 /* msg2 should be done at this point; assuming everything is
614 * working correctly, the session won't look at it again; we
615 * ensure that if it does, it will crash the test program.
617 memcpy (&msg2_bak, msg2, sizeof (SoupMessage));
618 memset (msg2, 0, sizeof (SoupMessage));
620 msg3 = soup_message_new ("GET", uri);
621 g_object_set_data (G_OBJECT (msg3), "id", GINT_TO_POINTER (3));
622 auth_id = g_signal_connect (session, "authenticate",
623 G_CALLBACK (async_authenticate), NULL);
626 soup_session_queue_message (session, msg3, async_finished, &remaining);
627 g_main_loop_run (loop);
628 g_signal_handler_disconnect (session, auth_id);
630 /* async_authenticate will pause msg3 and quit loop */
632 /* Now do the auth, and restart */
634 soup_auth_authenticate (auth, "user1", "realm1");
635 g_object_unref (auth);
636 soup_session_unpause_message (session, msg1);
637 soup_session_unpause_message (session, msg3);
639 g_main_loop_run (loop);
641 /* async_finished will quit the loop */
643 debug_printf (1, " msg1 didn't get authenticate signal!\n");
647 if (msg1->status_code == SOUP_STATUS_OK)
648 debug_printf (1, " msg1 succeeded\n");
650 debug_printf (1, " msg1 FAILED! (%u %s)\n",
651 msg1->status_code, msg1->reason_phrase);
654 if (msg3->status_code == SOUP_STATUS_OK)
655 debug_printf (1, " msg3 succeeded\n");
657 debug_printf (1, " msg3 FAILED! (%u %s)\n",
658 msg3->status_code, msg3->reason_phrase);
662 soup_test_session_abort_unref (session);
664 g_object_unref (msg1);
665 g_object_unref (msg3);
666 memcpy (msg2, &msg2_bak, sizeof (SoupMessage));
667 g_object_unref (msg2);
669 /* Test that giving the wrong password doesn't cause multiple
670 * authenticate signals the second time.
672 debug_printf (1, "\nTesting async auth with wrong password (#522601):\n");
674 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
678 msg1 = soup_message_new ("GET", uri);
679 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
680 auth_id = g_signal_connect (session, "authenticate",
681 G_CALLBACK (async_authenticate), &auth);
684 soup_session_queue_message (session, msg1, async_finished, &remaining);
685 g_main_loop_run (loop);
686 g_signal_handler_disconnect (session, auth_id);
687 soup_auth_authenticate (auth, "user1", "wrong");
688 g_object_unref (auth);
689 soup_session_unpause_message (session, msg1);
692 auth_id = g_signal_connect (session, "authenticate",
693 G_CALLBACK (async_authenticate_assert_once),
695 g_main_loop_run (loop);
696 g_signal_handler_disconnect (session, auth_id);
699 debug_printf (1, " authenticate not emitted?\n");
703 soup_test_session_abort_unref (session);
704 g_object_unref (msg1);
706 /* Test that giving no password doesn't cause multiple
707 * authenticate signals the second time.
709 debug_printf (1, "\nTesting async auth with no password (#583462):\n");
711 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
714 /* Send a message that doesn't actually authenticate
716 msg1 = soup_message_new ("GET", uri);
717 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
718 auth_id = g_signal_connect (session, "authenticate",
719 G_CALLBACK (async_authenticate), NULL);
722 soup_session_queue_message (session, msg1, async_finished, &remaining);
723 g_main_loop_run (loop);
724 g_signal_handler_disconnect (session, auth_id);
725 soup_session_unpause_message (session, msg1);
726 g_main_loop_run (loop);
727 g_object_unref(msg1);
729 /* Now send a second message */
730 msg1 = soup_message_new ("GET", uri);
731 g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (2));
734 auth_id = g_signal_connect (session, "authenticate",
735 G_CALLBACK (async_authenticate_assert_once_and_stop),
738 soup_session_queue_message (session, msg1, async_finished, &remaining);
739 g_main_loop_run (loop);
740 soup_session_unpause_message (session, msg1);
742 g_main_loop_run (loop);
743 g_signal_handler_disconnect (session, auth_id);
745 soup_test_session_abort_unref (session);
746 g_object_unref (msg1);
749 g_main_loop_unref (loop);
753 const char *password;
756 const char *response;
761 select_auth_authenticate (SoupSession *session, SoupMessage *msg,
762 SoupAuth *auth, gboolean retrying, gpointer data)
764 SelectAuthData *sad = data;
765 const char *header, *basic, *digest;
766 int round = retrying ? 1 : 0;
768 header = soup_message_headers_get_list (msg->response_headers,
770 basic = strstr (header, "Basic");
771 digest = strstr (header, "Digest");
772 if (basic && digest) {
774 sad->round[round].headers = "Basic, Digest";
776 sad->round[round].headers = "Digest, Basic";
778 sad->round[round].headers = "Basic";
780 sad->round[round].headers = "Digest";
782 sad->round[round].response = soup_auth_get_scheme_name (auth);
783 if (sad->password && !retrying)
784 soup_auth_authenticate (auth, "user", sad->password);
788 select_auth_test_one (SoupURI *uri,
789 gboolean disable_digest, const char *password,
790 const char *first_headers, const char *first_response,
791 const char *second_headers, const char *second_response,
796 SoupSession *session;
798 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
800 soup_session_remove_feature_by_type (session, SOUP_TYPE_AUTH_DIGEST);
802 g_signal_connect (session, "authenticate",
803 G_CALLBACK (select_auth_authenticate), &sad);
804 memset (&sad, 0, sizeof (sad));
805 sad.password = password;
807 msg = soup_message_new_from_uri ("GET", uri);
808 soup_session_send_message (session, msg);
810 if (strcmp (sad.round[0].headers, first_headers) != 0) {
811 debug_printf (1, " Header order wrong: expected %s, got %s\n",
812 first_headers, sad.round[0].headers);
815 if (strcmp (sad.round[0].response, first_response) != 0) {
816 debug_printf (1, " Selected auth type wrong: expected %s, got %s\n",
817 first_response, sad.round[0].response);
821 if (second_headers && !sad.round[1].headers) {
822 debug_printf (1, " Expected a second round!\n");
824 } else if (!second_headers && sad.round[1].headers) {
825 debug_printf (1, " Didn't expect a second round!\n");
827 } else if (second_headers) {
828 if (strcmp (sad.round[1].headers, second_headers) != 0) {
829 debug_printf (1, " Second round header order wrong: expected %s, got %s\n",
830 second_headers, sad.round[1].headers);
833 if (strcmp (sad.round[1].response, second_response) != 0) {
834 debug_printf (1, " Second round selected auth type wrong: expected %s, got %s\n",
835 second_response, sad.round[1].response);
840 if (msg->status_code != final_status) {
841 debug_printf (1, " Final status wrong: expected %u, got %u\n",
842 final_status, msg->status_code);
846 g_object_unref (msg);
847 soup_test_session_abort_unref (session);
851 server_callback (SoupServer *server, SoupMessage *msg,
852 const char *path, GHashTable *query,
853 SoupClientContext *context, gpointer data)
855 soup_message_set_response (msg, "text/plain",
858 soup_message_set_status (msg, SOUP_STATUS_OK);
862 server_basic_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
863 const char *username, const char *password, gpointer data)
865 if (strcmp (username, "user") != 0)
867 return strcmp (password, "good-basic") == 0;
871 server_digest_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
872 const char *username, gpointer data)
874 if (strcmp (username, "user") != 0)
876 return soup_auth_domain_digest_encode_password ("user",
882 do_select_auth_test (void)
885 SoupAuthDomain *basic_auth_domain, *digest_auth_domain;
888 debug_printf (1, "\nTesting selection among multiple auths:\n");
890 /* It doesn't seem to be possible to configure Apache to serve
891 * multiple auth types for a single URL. So we have to use
892 * SoupServer here. We know that SoupServer handles the server
893 * side of this scenario correctly, because we test it against
894 * curl in server-auth-test.
896 server = soup_test_server_new (FALSE);
897 soup_server_add_handler (server, NULL,
898 server_callback, NULL, NULL);
900 uri = soup_uri_new ("http://127.0.0.1/");
901 soup_uri_set_port (uri, soup_server_get_port (server));
903 basic_auth_domain = soup_auth_domain_basic_new (
904 SOUP_AUTH_DOMAIN_REALM, "auth-test",
905 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
906 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
908 soup_server_add_auth_domain (server, basic_auth_domain);
910 digest_auth_domain = soup_auth_domain_digest_new (
911 SOUP_AUTH_DOMAIN_REALM, "auth-test",
912 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
913 SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, server_digest_auth_callback,
915 soup_server_add_auth_domain (server, digest_auth_domain);
917 debug_printf (1, " Testing with no auth\n");
918 select_auth_test_one (uri, FALSE, NULL,
919 "Basic, Digest", "Digest",
921 SOUP_STATUS_UNAUTHORIZED);
923 debug_printf (1, " Testing with bad password\n");
924 select_auth_test_one (uri, FALSE, "bad",
925 "Basic, Digest", "Digest",
926 "Basic, Digest", "Digest",
927 SOUP_STATUS_UNAUTHORIZED);
929 debug_printf (1, " Testing with good password\n");
930 select_auth_test_one (uri, FALSE, "good",
931 "Basic, Digest", "Digest",
935 /* Test with Digest disabled in the client. */
936 debug_printf (1, " Testing without Digest with no auth\n");
937 select_auth_test_one (uri, TRUE, NULL,
938 "Basic, Digest", "Basic",
940 SOUP_STATUS_UNAUTHORIZED);
942 debug_printf (1, " Testing without Digest with bad password\n");
943 select_auth_test_one (uri, TRUE, "bad",
944 "Basic, Digest", "Basic",
945 "Basic, Digest", "Basic",
946 SOUP_STATUS_UNAUTHORIZED);
948 debug_printf (1, " Testing without Digest with good password\n");
949 select_auth_test_one (uri, TRUE, "good-basic",
950 "Basic, Digest", "Basic",
954 /* Now flip the order of the domains, verify that this flips
955 * the order of the headers, and make sure that digest auth
959 soup_server_remove_auth_domain (server, basic_auth_domain);
960 soup_server_remove_auth_domain (server, digest_auth_domain);
961 soup_server_add_auth_domain (server, digest_auth_domain);
962 soup_server_add_auth_domain (server, basic_auth_domain);
964 debug_printf (1, " Testing flipped with no auth\n");
965 select_auth_test_one (uri, FALSE, NULL,
966 "Digest, Basic", "Digest",
968 SOUP_STATUS_UNAUTHORIZED);
970 debug_printf (1, " Testing flipped with bad password\n");
971 select_auth_test_one (uri, FALSE, "bad",
972 "Digest, Basic", "Digest",
973 "Digest, Basic", "Digest",
974 SOUP_STATUS_UNAUTHORIZED);
976 debug_printf (1, " Testing flipped with good password\n");
977 select_auth_test_one (uri, FALSE, "good",
978 "Digest, Basic", "Digest",
982 g_object_unref (basic_auth_domain);
983 g_object_unref (digest_auth_domain);
985 soup_test_server_quit_unref (server);
989 sneakily_close_connection (SoupMessage *msg, gpointer user_data)
991 /* Sneakily close the connection after the response, by
992 * tricking soup-message-io into thinking that had been
993 * the plan all along.
995 soup_message_headers_append (msg->response_headers,
996 "Connection", "close");
1000 auth_close_request_started (SoupServer *server, SoupMessage *msg,
1001 SoupClientContext *client, gpointer user_data)
1003 g_signal_connect (msg, "wrote-headers",
1004 G_CALLBACK (sneakily_close_connection), NULL);
1008 SoupSession *session;
1014 auth_close_idle_authenticate (gpointer user_data)
1016 AuthCloseData *acd = user_data;
1018 soup_auth_authenticate (acd->auth, "user", "good-basic");
1019 soup_session_unpause_message (acd->session, acd->msg);
1021 g_object_unref (acd->auth);
1026 auth_close_authenticate (SoupSession *session, SoupMessage *msg,
1027 SoupAuth *auth, gboolean retrying, gpointer data)
1029 AuthCloseData *acd = data;
1031 soup_session_pause_message (session, msg);
1032 acd->auth = g_object_ref (auth);
1033 g_idle_add (auth_close_idle_authenticate, acd);
1037 do_auth_close_test (void)
1040 SoupAuthDomain *basic_auth_domain;
1044 debug_printf (1, "\nTesting auth when server times out connection:\n");
1046 server = soup_test_server_new (FALSE);
1047 soup_server_add_handler (server, NULL,
1048 server_callback, NULL, NULL);
1050 uri = soup_uri_new ("http://127.0.0.1/close");
1051 soup_uri_set_port (uri, soup_server_get_port (server));
1053 basic_auth_domain = soup_auth_domain_basic_new (
1054 SOUP_AUTH_DOMAIN_REALM, "auth-test",
1055 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
1056 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
1058 soup_server_add_auth_domain (server, basic_auth_domain);
1060 g_signal_connect (server, "request-started",
1061 G_CALLBACK (auth_close_request_started), NULL);
1063 acd.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1064 g_signal_connect (acd.session, "authenticate",
1065 G_CALLBACK (auth_close_authenticate), &acd);
1067 acd.msg = soup_message_new_from_uri ("GET", uri);
1068 soup_session_send_message (acd.session, acd.msg);
1070 if (acd.msg->status_code != SOUP_STATUS_OK) {
1071 debug_printf (1, " Final status wrong: expected %u, got %u %s\n",
1072 SOUP_STATUS_OK, acd.msg->status_code,
1073 acd.msg->reason_phrase);
1077 g_object_unref (acd.msg);
1078 soup_test_session_abort_unref (acd.session);
1079 soup_test_server_quit_unref (server);
1082 static SoupAuthTest relogin_tests[] = {
1083 { "Auth provided via URL, should succeed",
1084 "Basic/realm12/", "1", TRUE, "01", SOUP_STATUS_OK },
1086 { "Now should automatically reuse previous auth",
1087 "Basic/realm12/", "", FALSE, "1", SOUP_STATUS_OK },
1089 { "Different auth provided via URL for the same realm, should succeed",
1090 "Basic/realm12/", "2", TRUE, "2", SOUP_STATUS_OK },
1092 { "Subdir should also automatically reuse auth",
1093 "Basic/realm12/subdir/", "", FALSE, "2", SOUP_STATUS_OK },
1095 { "Should fail with no auth",
1096 "Basic/realm12/", "4", TRUE, "4", SOUP_STATUS_UNAUTHORIZED },
1098 { "Make sure we've forgotten it",
1099 "Basic/realm12/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
1101 { "Should fail with no auth, fail again with bad password, and give up",
1102 "Basic/realm12/", "3", FALSE, "03", SOUP_STATUS_UNAUTHORIZED },
1106 do_batch_tests (const gchar *base_uri_str, gint ntests)
1108 SoupSession *session;
1110 char *expected, *uristr;
1114 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1115 g_signal_connect (session, "authenticate",
1116 G_CALLBACK (authenticate), &i);
1118 base_uri = soup_uri_new (base_uri_str);
1120 for (i = 0; i < ntests; i++) {
1121 SoupURI *soup_uri = soup_uri_new_with_base (base_uri, current_tests[i].url);
1123 debug_printf (1, "Test %d: %s\n", i + 1, current_tests[i].explanation);
1125 if (current_tests[i].url_auth) {
1126 gchar *username = g_strdup_printf ("user%c", current_tests[i].provided[0]);
1127 gchar *password = g_strdup_printf ("realm%c", current_tests[i].provided[0]);
1128 soup_uri_set_user (soup_uri, username);
1129 soup_uri_set_password (soup_uri, password);
1134 msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
1135 soup_uri_free (soup_uri);
1137 fprintf (stderr, "auth-test: Could not parse URI\n");
1141 uristr = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1142 debug_printf (1, " GET %s\n", uristr);
1145 expected = g_strdup (current_tests[i].expected);
1146 soup_message_add_status_code_handler (
1147 msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
1148 G_CALLBACK (handler), expected);
1149 soup_message_add_status_code_handler (
1150 msg, "got_headers", SOUP_STATUS_OK,
1151 G_CALLBACK (handler), expected);
1152 soup_session_send_message (session, msg);
1153 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED &&
1154 msg->status_code != SOUP_STATUS_OK) {
1155 debug_printf (1, " %d %s !\n", msg->status_code,
1156 msg->reason_phrase);
1160 debug_printf (1, " expected %d more round(s)\n",
1161 (int)strlen (expected));
1166 if (msg->status_code != current_tests[i].final_status) {
1167 debug_printf (1, " expected %d\n",
1168 current_tests[i].final_status);
1171 debug_printf (1, "\n");
1173 g_object_unref (msg);
1175 soup_uri_free (base_uri);
1177 soup_test_session_abort_unref (session);
1181 main (int argc, char **argv)
1183 const char *base_uri;
1186 test_init (argc, argv, NULL);
1189 base_uri = "http://127.0.0.1:47524/";
1192 current_tests = main_tests;
1193 ntests = G_N_ELEMENTS (main_tests);
1194 do_batch_tests (base_uri, ntests);
1196 /* Re-login tests */
1197 current_tests = relogin_tests;
1198 ntests = G_N_ELEMENTS (relogin_tests);
1199 do_batch_tests (base_uri, ntests);
1201 /* Other regression tests */
1202 do_pipelined_auth_test (base_uri);
1203 do_digest_expiration_test (base_uri);
1204 do_async_auth_test (base_uri);
1205 do_select_auth_test ();
1206 do_auth_close_test ();