/* ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ #include #include "serf.h" #include "test_serf.h" static apr_status_t authn_callback_expect_not_called(char **username, char **password, serf_request_t *request, void *baton, int code, const char *authn_type, const char *realm, apr_pool_t *pool) { handler_baton_t *handler_ctx = baton; test_baton_t *tb = handler_ctx->tb; tb->result_flags |= TEST_RESULT_AUTHNCB_CALLED; /* Should not have been called. */ return SERF_ERROR_ISSUE_IN_TESTSUITE; } /* Tests that authn fails if all authn schemes are disabled. */ static void test_authentication_disabled(CuTest *tc) { test_baton_t *tb; handler_baton_t handler_ctx[2]; test_server_message_t message_list[] = { {CHUNKED_REQUEST(1, "1")} }; test_server_action_t action_list[] = { {SERVER_RESPOND, "HTTP/1.1 401 Unauthorized" CRLF "Transfer-Encoding: chunked" CRLF "WWW-Authenticate: Basic realm=""Test Suite""" CRLF CRLF "1" CRLF CRLF "0" CRLF CRLF}, }; apr_status_t status; apr_pool_t *test_pool = tc->testBaton; /* Set up a test context with a server */ status = test_http_server_setup(&tb, message_list, 1, action_list, 1, 0, NULL, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); serf_config_authn_types(tb->context, SERF_AUTHN_NONE); serf_config_credentials_callback(tb->context, authn_callback_expect_not_called); create_new_request(tb, &handler_ctx[0], "GET", "/", 1); status = test_helper_run_requests_no_check(tc, tb, 1, handler_ctx, test_pool); CuAssertIntEquals(tc, SERF_ERROR_AUTHN_NOT_SUPPORTED, status); CuAssertIntEquals(tc, 1, tb->sent_requests->nelts); CuAssertIntEquals(tc, 1, tb->accepted_requests->nelts); CuAssertIntEquals(tc, 0, tb->handled_requests->nelts); CuAssertTrue(tc, !(tb->result_flags & TEST_RESULT_AUTHNCB_CALLED)); } /* Tests that authn fails if encountered an unsupported scheme. */ static void test_unsupported_authentication(CuTest *tc) { test_baton_t *tb; handler_baton_t handler_ctx[2]; test_server_message_t message_list[] = { {CHUNKED_REQUEST(1, "1")} }; test_server_action_t action_list[] = { {SERVER_RESPOND, "HTTP/1.1 401 Unauthorized" CRLF "Transfer-Encoding: chunked" CRLF "WWW-Authenticate: NotExistent realm=""Test Suite""" CRLF CRLF "1" CRLF CRLF "0" CRLF CRLF}, }; apr_status_t status; apr_pool_t *test_pool = tc->testBaton; /* Set up a test context with a server */ status = test_http_server_setup(&tb, message_list, 1, action_list, 1, 0, NULL, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); serf_config_authn_types(tb->context, SERF_AUTHN_ALL); serf_config_credentials_callback(tb->context, authn_callback_expect_not_called); create_new_request(tb, &handler_ctx[0], "GET", "/", 1); status = test_helper_run_requests_no_check(tc, tb, 1, handler_ctx, test_pool); CuAssertIntEquals(tc, SERF_ERROR_AUTHN_NOT_SUPPORTED, status); CuAssertIntEquals(tc, 1, tb->sent_requests->nelts); CuAssertIntEquals(tc, 1, tb->accepted_requests->nelts); CuAssertIntEquals(tc, 0, tb->handled_requests->nelts); CuAssertTrue(tc, !(tb->result_flags & TEST_RESULT_AUTHNCB_CALLED)); } static apr_status_t basic_authn_callback(char **username, char **password, serf_request_t *request, void *baton, int code, const char *authn_type, const char *realm, apr_pool_t *pool) { handler_baton_t *handler_ctx = baton; test_baton_t *tb = handler_ctx->tb; tb->result_flags |= TEST_RESULT_AUTHNCB_CALLED; if (code != 401) return SERF_ERROR_ISSUE_IN_TESTSUITE; if (strcmp("Basic", authn_type) != 0) return SERF_ERROR_ISSUE_IN_TESTSUITE; if (strcmp(" Test Suite", realm) != 0) return SERF_ERROR_ISSUE_IN_TESTSUITE; *username = "serf"; *password = "serftest"; return APR_SUCCESS; } /* Test template, used for KeepAlive Off and KeepAlive On test */ static void basic_authentication(CuTest *tc, const char *resp_hdrs) { test_baton_t *tb; handler_baton_t handler_ctx[2]; int num_requests_sent, num_requests_recvd; test_server_message_t message_list[3]; test_server_action_t action_list[3]; apr_status_t status; apr_pool_t *test_pool = tc->testBaton; /* Expected string relies on strict order of headers, which is not guaranteed. c2VyZjpzZXJmdGVzdA== is base64 encoded serf:serftest . */ message_list[0].text = CHUNKED_REQUEST(1, "1"); message_list[1].text = apr_psprintf(test_pool, "GET / HTTP/1.1" CRLF "Host: localhost:12345" CRLF "Authorization: Basic c2VyZjpzZXJmdGVzdA==" CRLF "Transfer-Encoding: chunked" CRLF CRLF "1" CRLF "1" CRLF "0" CRLF CRLF); message_list[2].text = apr_psprintf(test_pool, "GET / HTTP/1.1" CRLF "Host: localhost:12345" CRLF "Authorization: Basic c2VyZjpzZXJmdGVzdA==" CRLF "Transfer-Encoding: chunked" CRLF CRLF "1" CRLF "2" CRLF "0" CRLF CRLF); action_list[0].kind = SERVER_RESPOND; /* Use non-standard case WWW-Authenticate header and scheme name to test for case insensitive comparisons. */ action_list[0].text = apr_psprintf(test_pool, "HTTP/1.1 401 Unauthorized" CRLF "Transfer-Encoding: chunked" CRLF "www-Authenticate: bAsIc realm=""Test Suite""" CRLF "%s" CRLF "1" CRLF CRLF "0" CRLF CRLF, resp_hdrs); action_list[1].kind = SERVER_RESPOND; action_list[1].text = CHUNKED_EMPTY_RESPONSE; action_list[2].kind = SERVER_RESPOND; action_list[2].text = CHUNKED_EMPTY_RESPONSE; /* Set up a test context with a server */ status = test_http_server_setup(&tb, message_list, 3, action_list, 3, 0, NULL, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); serf_config_authn_types(tb->context, SERF_AUTHN_BASIC); serf_config_credentials_callback(tb->context, basic_authn_callback); create_new_request(tb, &handler_ctx[0], "GET", "/", 1); /* Test that a request is retried and authentication headers are set correctly. */ num_requests_sent = 1; num_requests_recvd = 2; status = test_helper_run_requests_no_check(tc, tb, num_requests_sent, handler_ctx, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertIntEquals(tc, num_requests_recvd, tb->sent_requests->nelts); CuAssertIntEquals(tc, num_requests_recvd, tb->accepted_requests->nelts); CuAssertIntEquals(tc, num_requests_sent, tb->handled_requests->nelts); CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); /* Test that credentials were cached by asserting that the authn callback wasn't called again. */ tb->result_flags = 0; create_new_request(tb, &handler_ctx[0], "GET", "/", 2); status = test_helper_run_requests_no_check(tc, tb, num_requests_sent, handler_ctx, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertTrue(tc, !(tb->result_flags & TEST_RESULT_AUTHNCB_CALLED)); } static void test_basic_authentication(CuTest *tc) { basic_authentication(tc, ""); } static void test_basic_authentication_keepalive_off(CuTest *tc) { basic_authentication(tc, "Connection: close" CRLF); } static apr_status_t digest_authn_callback(char **username, char **password, serf_request_t *request, void *baton, int code, const char *authn_type, const char *realm, apr_pool_t *pool) { handler_baton_t *handler_ctx = baton; test_baton_t *tb = handler_ctx->tb; tb->result_flags |= TEST_RESULT_AUTHNCB_CALLED; if (code != 401) return SERF_ERROR_ISSUE_IN_TESTSUITE; if (strcmp("Digest", authn_type) != 0) return SERF_ERROR_ISSUE_IN_TESTSUITE; if (strcmp(" Test Suite", realm) != 0) return SERF_ERROR_ISSUE_IN_TESTSUITE; *username = "serf"; *password = "serftest"; return APR_SUCCESS; } /* Test template, used for KeepAlive Off and KeepAlive On test */ static void digest_authentication(CuTest *tc, const char *resp_hdrs) { test_baton_t *tb; handler_baton_t handler_ctx[2]; int num_requests_sent, num_requests_recvd; test_server_message_t message_list[2]; test_server_action_t action_list[2]; apr_pool_t *test_pool = tc->testBaton; apr_status_t status; /* Expected string relies on strict order of headers and attributes of Digest, both are not guaranteed. 6ff0d4cc201513ce970d5c6b25e1043b is encoded as: md5hex(md5hex("serf:Test Suite:serftest") & ":" & md5hex("ABCDEF1234567890") & ":" & md5hex("GET:/test/index.html")) */ message_list[0].text = CHUNKED_REQUEST_URI("/test/index.html", 1, "1"); message_list[1].text = apr_psprintf(test_pool, "GET /test/index.html HTTP/1.1" CRLF "Host: localhost:12345" CRLF "Authorization: Digest realm=\"Test Suite\", username=\"serf\", " "nonce=\"ABCDEF1234567890\", uri=\"/test/index.html\", " "response=\"6ff0d4cc201513ce970d5c6b25e1043b\", opaque=\"myopaque\", " "algorithm=\"MD5\"" CRLF "Transfer-Encoding: chunked" CRLF CRLF "1" CRLF "1" CRLF "0" CRLF CRLF); action_list[0].kind = SERVER_RESPOND; action_list[0].text = apr_psprintf(test_pool, "HTTP/1.1 401 Unauthorized" CRLF "Transfer-Encoding: chunked" CRLF "WWW-Authenticate: Digest realm=\"Test Suite\"," "nonce=\"ABCDEF1234567890\",opaque=\"myopaque\"," "algorithm=\"MD5\",qop-options=\"auth\"" CRLF "%s" CRLF "1" CRLF CRLF "0" CRLF CRLF, resp_hdrs); /* If the resp_hdrs includes "Connection: close", serf will automatically reset the connection from the client side, no need to use SERVER_KILL_CONNECTION. */ action_list[1].kind = SERVER_RESPOND; action_list[1].text = CHUNKED_EMPTY_RESPONSE; /* Set up a test context with a server */ status = test_http_server_setup(&tb, message_list, 2, action_list, 2, 0, NULL, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); /* Add both Basic and Digest here, should use Digest only. */ serf_config_authn_types(tb->context, SERF_AUTHN_BASIC | SERF_AUTHN_DIGEST); serf_config_credentials_callback(tb->context, digest_authn_callback); create_new_request(tb, &handler_ctx[0], "GET", "/test/index.html", 1); /* Test that a request is retried and authentication headers are set correctly. */ num_requests_sent = 1; num_requests_recvd = 2; status = test_helper_run_requests_no_check(tc, tb, num_requests_sent, handler_ctx, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertIntEquals(tc, num_requests_recvd, tb->sent_requests->nelts); CuAssertIntEquals(tc, num_requests_recvd, tb->accepted_requests->nelts); CuAssertIntEquals(tc, num_requests_sent, tb->handled_requests->nelts); CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); } static void test_digest_authentication(CuTest *tc) { digest_authentication(tc, ""); } static void test_digest_authentication_keepalive_off(CuTest *tc) { /* Add the Connection: close header to the response with the Digest headers. This to test that the Digest headers will be added to the retry of the request on the new connection. */ digest_authentication(tc, "Connection: close" CRLF); } static apr_status_t switched_realm_authn_callback(char **username, char **password, serf_request_t *request, void *baton, int code, const char *authn_type, const char *realm, apr_pool_t *pool) { handler_baton_t *handler_ctx = baton; test_baton_t *tb = handler_ctx->tb; const char *exp_realm = tb->user_baton; tb->result_flags |= TEST_RESULT_AUTHNCB_CALLED; if (code != 401) return SERF_ERROR_ISSUE_IN_TESTSUITE; if (strcmp(exp_realm, realm) != 0) return SERF_ERROR_ISSUE_IN_TESTSUITE; if (strcmp(realm, " Test Suite") == 0) { *username = "serf"; *password = "serftest"; } else { *username = "serf_newrealm"; *password = "serftest"; } return APR_SUCCESS; } /* Test template, used for both Basic and Digest switch realms test */ static void authentication_switch_realms(CuTest *tc, const char *scheme, const char *authn_attr, const char *authz_attr_test_suite, const char *authz_attr_wrong_realm, const char *authz_attr_new_realm) { test_baton_t *tb; handler_baton_t handler_ctx[2]; int num_requests_sent, num_requests_recvd; test_server_message_t message_list[5]; test_server_action_t action_list[5]; apr_pool_t *test_pool = tc->testBaton; apr_status_t status; message_list[0].text = CHUNKED_REQUEST(1, "1"); message_list[1].text = apr_psprintf(test_pool, "GET / HTTP/1.1" CRLF "Host: localhost:12345" CRLF "Authorization: %s %s" CRLF "Transfer-Encoding: chunked" CRLF CRLF "1" CRLF "1" CRLF "0" CRLF CRLF, scheme, authz_attr_test_suite); message_list[2].text = apr_psprintf(test_pool, "GET / HTTP/1.1" CRLF "Host: localhost:12345" CRLF "Authorization: %s %s" CRLF "Transfer-Encoding: chunked" CRLF CRLF "1" CRLF "2" CRLF "0" CRLF CRLF, scheme, authz_attr_test_suite); /* The client doesn't know that /newrealm/ is in another realm, so it reuses the credentials cached on the connection. */ message_list[3].text = apr_psprintf(test_pool, "GET /newrealm/index.html HTTP/1.1" CRLF "Host: localhost:12345" CRLF "Authorization: %s %s" CRLF "Transfer-Encoding: chunked" CRLF CRLF "1" CRLF "3" CRLF "0" CRLF CRLF, scheme, authz_attr_wrong_realm); message_list[4].text = apr_psprintf(test_pool, "GET /newrealm/index.html HTTP/1.1" CRLF "Host: localhost:12345" CRLF "Authorization: %s %s" CRLF "Transfer-Encoding: chunked" CRLF CRLF "1" CRLF "3" CRLF "0" CRLF CRLF, scheme, authz_attr_new_realm); action_list[0].kind = SERVER_RESPOND; action_list[0].text = apr_psprintf(test_pool, "HTTP/1.1 401 Unauthorized" CRLF "Transfer-Encoding: chunked" CRLF "WWW-Authenticate: %s realm=""Test Suite""%s" CRLF CRLF "1" CRLF CRLF "0" CRLF CRLF, scheme, authn_attr); action_list[1].kind = SERVER_RESPOND; action_list[1].text = CHUNKED_EMPTY_RESPONSE; action_list[2].kind = SERVER_RESPOND; action_list[2].text = CHUNKED_EMPTY_RESPONSE; action_list[3].kind = SERVER_RESPOND; action_list[3].text = apr_psprintf(test_pool, "HTTP/1.1 401 Unauthorized" CRLF "Transfer-Encoding: chunked" CRLF "WWW-Authenticate: %s realm=""New Realm""%s" CRLF CRLF "1" CRLF CRLF "0" CRLF CRLF, scheme, authn_attr); action_list[4].kind = SERVER_RESPOND; action_list[4].text = CHUNKED_EMPTY_RESPONSE; /* Set up a test context with a server */ status = test_http_server_setup(&tb, message_list, 5, action_list, 5, 0, NULL, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); serf_config_authn_types(tb->context, SERF_AUTHN_BASIC | SERF_AUTHN_DIGEST); serf_config_credentials_callback(tb->context, switched_realm_authn_callback); create_new_request(tb, &handler_ctx[0], "GET", "/", 1); /* Test that a request is retried and authentication headers are set correctly. */ num_requests_sent = 1; num_requests_recvd = 2; tb->user_baton = " Test Suite"; status = test_helper_run_requests_no_check(tc, tb, num_requests_sent, handler_ctx, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertIntEquals(tc, num_requests_recvd, tb->sent_requests->nelts); CuAssertIntEquals(tc, num_requests_recvd, tb->accepted_requests->nelts); CuAssertIntEquals(tc, num_requests_sent, tb->handled_requests->nelts); CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); /* Test that credentials were cached by asserting that the authn callback wasn't called again. */ tb->result_flags = 0; create_new_request(tb, &handler_ctx[0], "GET", "/", 2); status = test_helper_run_requests_no_check(tc, tb, num_requests_sent, handler_ctx, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertTrue(tc, !(tb->result_flags & TEST_RESULT_AUTHNCB_CALLED)); /* Switch realms. Test that serf asks the application for new credentials. */ tb->result_flags = 0; tb->user_baton = " New Realm"; create_new_request(tb, &handler_ctx[0], "GET", "/newrealm/index.html", 3); status = test_helper_run_requests_no_check(tc, tb, num_requests_sent, handler_ctx, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); } static void test_basic_switch_realms(CuTest *tc) { authentication_switch_realms(tc, "Basic", "", "c2VyZjpzZXJmdGVzdA==", "c2VyZjpzZXJmdGVzdA==", "c2VyZl9uZXdyZWFsbTpzZXJmdGVzdA=="); } static void test_digest_switch_realms(CuTest *tc) { authentication_switch_realms(tc, "Digest", ",nonce=\"ABCDEF1234567890\"," "opaque=\"myopaque\", algorithm=\"MD5\",qop-options=\"auth\"", /* response hdr attribute for Test Suite realm, uri / */ "realm=\"Test Suite\", username=\"serf\", nonce=\"ABCDEF1234567890\", " "uri=\"/\", response=\"3511a71fec5c02ab1c9212711a8baa58\", " "opaque=\"myopaque\", algorithm=\"MD5\"", /* response hdr attribute for Test Suite realm, uri /newrealm/index.html */ "realm=\"Test Suite\", username=\"serf\", nonce=\"ABCDEF1234567890\", " "uri=\"/newrealm/index.html\", response=\"c6b673cf44ad16ef379930856b607344\", " "opaque=\"myopaque\", algorithm=\"MD5\"", /* response hdr attribute for New Realm realm, uri /newrealm/index.html */ "realm=\"New Realm\", username=\"serf_newrealm\", nonce=\"ABCDEF1234567890\", " "uri=\"/newrealm/index.html\", response=\"f93f07d1412e53c421f66741a89198cb\", " "opaque=\"myopaque\", algorithm=\"MD5\""); } static void test_auth_on_HEAD(CuTest *tc) { test_baton_t *tb; handler_baton_t handler_ctx[2]; int num_requests_sent, num_requests_recvd; apr_status_t status; apr_pool_t *test_pool = tc->testBaton; test_server_message_t message_list[] = { { "HEAD / HTTP/1.1" CRLF "Host: localhost:12345" CRLF CRLF }, { "HEAD / HTTP/1.1" CRLF "Host: localhost:12345" CRLF "Authorization: Basic c2VyZjpzZXJmdGVzdA==" CRLF CRLF }, }; test_server_action_t action_list[] = { { SERVER_RESPOND, "HTTP/1.1 401 Unauthorized" CRLF "WWW-Authenticate: Basic Realm=""Test Suite""" CRLF CRLF }, { SERVER_RESPOND, "HTTP/1.1 200 Ok" CRLF "Content-Type: text/html" CRLF CRLF }, }; /* Set up a test context with a server */ status = test_http_server_setup(&tb, message_list, 2, action_list, 2, 0, NULL, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); serf_config_authn_types(tb->context, SERF_AUTHN_BASIC); serf_config_credentials_callback(tb->context, basic_authn_callback); create_new_request(tb, &handler_ctx[0], "HEAD", "/", -1); /* Test that a request is retried and authentication headers are set correctly. */ num_requests_sent = 1; num_requests_recvd = 2; status = test_helper_run_requests_no_check(tc, tb, num_requests_sent, handler_ctx, test_pool); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertIntEquals(tc, num_requests_recvd, tb->sent_requests->nelts); CuAssertIntEquals(tc, num_requests_recvd, tb->accepted_requests->nelts); CuAssertIntEquals(tc, num_requests_sent, tb->handled_requests->nelts); CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); } /*****************************************************************************/ CuSuite *test_auth(void) { CuSuite *suite = CuSuiteNew(); CuSuiteSetSetupTeardownCallbacks(suite, test_setup, test_teardown); SUITE_ADD_TEST(suite, test_authentication_disabled); SUITE_ADD_TEST(suite, test_unsupported_authentication); SUITE_ADD_TEST(suite, test_basic_authentication); SUITE_ADD_TEST(suite, test_basic_authentication_keepalive_off); SUITE_ADD_TEST(suite, test_digest_authentication); SUITE_ADD_TEST(suite, test_digest_authentication_keepalive_off); SUITE_ADD_TEST(suite, test_basic_switch_realms); SUITE_ADD_TEST(suite, test_digest_switch_realms); SUITE_ADD_TEST(suite, test_auth_on_HEAD); return suite; }