| /* ==================================================================== |
| * 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 <apr_strings.h> |
| |
| #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 REPORT_TEST_SUITE_ERROR(); |
| } |
| |
| /* Tests that authn fails if all authn schemes are disabled. */ |
| static void test_authentication_disabled(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| apr_status_t status; |
| |
| /* Set up a test context with a server */ |
| setup_test_mock_server(tb); |
| status = setup_test_client_context(tb, NULL, tb->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); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1")) |
| Respond(WithCode(401), WithChunkedBody("1"), |
| WithHeader("WWW-Authenticate", "Basic realm=\"Test Suite\"")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| status = run_client_and_mock_servers_loops(tb, 1, |
| handler_ctx, tb->pool); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllRequestsReceived); |
| EndVerify |
| CuAssertIntEquals(tc, SERF_ERROR_AUTHN_NOT_SUPPORTED, status); |
| 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 = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| apr_status_t status; |
| |
| |
| /* Set up a test context with a server */ |
| setup_test_mock_server(tb); |
| status = setup_test_client_context(tb, NULL, tb->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); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1")) |
| Respond(WithCode(401), WithChunkedBody("1"), |
| WithHeader("WWW-Authenticate", |
| "NotExistent realm=\"Test Suite\"")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| status = run_client_and_mock_servers_loops(tb, 1, |
| handler_ctx, tb->pool); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllRequestsReceived); |
| EndVerify |
| CuAssertIntEquals(tc, SERF_ERROR_AUTHN_NOT_SUPPORTED, status); |
| 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 REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Basic", authn_type) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp(apr_psprintf(pool, "<http://localhost:%d> Test Suite", |
| tb->serv_port), realm) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| *username = "serf"; |
| *password = "serftest"; |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Test template, used for KeepAlive Off and KeepAlive On test */ |
| static void basic_authentication(CuTest *tc, int close_conn) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[2]; |
| int num_requests_sent; |
| apr_status_t status; |
| |
| |
| /* Set up a test context with a server */ |
| setup_test_mock_server(tb); |
| status = setup_test_client_context(tb, NULL, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| serf_config_authn_types(tb->context, SERF_AUTHN_BASIC); |
| serf_config_credentials_callback(tb->context, basic_authn_callback); |
| |
| /* Test that a request is retried and authentication headers are set |
| correctly. */ |
| num_requests_sent = 1; |
| |
| /* c2VyZjpzZXJmdGVzdA== is base64 encoded serf:serftest . */ |
| /* Use non-standard case WWW-Authenticate header and scheme name to test |
| for case insensitive comparisons. */ |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), HeaderNotSet("Authorization")) |
| Respond(WithCode(401),WithChunkedBody("1"), |
| WithHeader("www-Authenticate", "bAsIc realm=\"Test Suite\""), |
| OnConditionThat(close_conn, WithConnectionCloseHeader)) |
| GETRequest(URLEqualTo("/"), |
| HeaderEqualTo("Authorization", "Basic c2VyZjpzZXJmdGVzdA==")) |
| Respond(WithCode(200),WithChunkedBody("")) |
| Expect |
| AllRequestsReceivedInOrder |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllExpectationsOk); |
| EndVerify |
| |
| /* Test that credentials were cached by asserting that the authn callback |
| wasn't called again. */ |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), |
| HeaderEqualTo("Authorization", "Basic c2VyZjpzZXJmdGVzdA==")) |
| Respond(WithCode(200), WithChunkedBody("")) |
| Expect |
| AllRequestsReceivedInOrder |
| EndGiven |
| tb->result_flags = 0; |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 2); |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertTrue(tc, !(tb->result_flags & TEST_RESULT_AUTHNCB_CALLED)); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllExpectationsOk); |
| EndVerify |
| } |
| |
| static void test_basic_authentication(CuTest *tc) |
| { |
| basic_authentication(tc, 0 /* don't close connection */); |
| } |
| |
| static void test_basic_authentication_keepalive_off(CuTest *tc) |
| { |
| basic_authentication(tc, 1); |
| } |
| |
| 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 REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Digest", authn_type) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp(apr_psprintf(pool, "<http://localhost:%d> Test Suite", |
| tb->serv_port), realm) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| *username = "serf"; |
| *password = "serftest"; |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Test template, used for KeepAlive Off and KeepAlive On test */ |
| static void digest_authentication(CuTest *tc, int close_conn) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[2]; |
| int num_requests_sent; |
| apr_status_t status; |
| |
| /* Set up a test context with a server */ |
| setup_test_mock_server(tb); |
| status = setup_test_client_context(tb, NULL, tb->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; |
| |
| |
| /* Expected string relies on strict order of attributes of Digest, which are |
| not guaranteed. |
| 6ff0d4cc201513ce970d5c6b25e1043b is encoded as: |
| md5hex(md5hex("serf:Test Suite:serftest") & ":" & |
| md5hex("ABCDEF1234567890") & ":" & |
| md5hex("GET:/test/index.html")) |
| */ |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/test/index.html"), HeaderNotSet("Authorization")) |
| Respond(WithCode(401), WithChunkedBody("1"), |
| WithHeader("www-Authenticate", "Digest realm=\"Test Suite\"," |
| "nonce=\"ABCDEF1234567890\",opaque=\"myopaque\"," |
| "algorithm=\"MD5\",qop-options=\"auth\""), |
| OnConditionThat(close_conn, WithConnectionCloseHeader)) |
| GETRequest(URLEqualTo("/test/index.html"), |
| HeaderEqualTo("Authorization", "Digest realm=\"Test Suite\", " |
| "username=\"serf\", nonce=\"ABCDEF1234567890\", " |
| "uri=\"/test/index.html\", " |
| "response=\"6ff0d4cc201513ce970d5c6b25e1043b\", " |
| "opaque=\"myopaque\", algorithm=\"MD5\"")) |
| Respond(WithCode(200),WithChunkedBody("")) |
| Expect |
| AllRequestsReceivedInOrder |
| EndGiven |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertIntEquals(tc, num_requests_sent, tb->handled_requests->nelts); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllExpectationsOk); |
| EndVerify |
| |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); |
| } |
| |
| static void test_digest_authentication(CuTest *tc) |
| { |
| digest_authentication(tc, 0 /* don't close connection */); |
| } |
| |
| 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, 1); |
| } |
| |
| 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 REPORT_TEST_SUITE_ERROR(); |
| if (strcmp(exp_realm, realm) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| if (strcmp(apr_psprintf(pool, "<http://localhost:%d> Test Suite", |
| tb->serv_port), realm) == 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 = tc->testBaton; |
| handler_baton_t handler_ctx[2]; |
| int num_requests_sent; |
| apr_status_t status; |
| |
| const char *exp_authz_test_suite = apr_psprintf(tb->pool, "%s %s", scheme, |
| authz_attr_test_suite); |
| const char *exp_authz_wrong_realm = apr_psprintf(tb->pool, "%s %s", scheme, |
| authz_attr_wrong_realm); |
| const char *exp_authz_new_realm = apr_psprintf(tb->pool, "%s %s", scheme, |
| authz_attr_new_realm); |
| |
| /* Set up a test context with a server */ |
| setup_test_mock_server(tb); |
| status = setup_test_client_context(tb, NULL, tb->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); |
| |
| /* Test that a request is retried and authentication headers are set |
| correctly. */ |
| tb->user_baton = apr_psprintf(tb->pool, "<http://localhost:%d> Test Suite", |
| mhServerPortNr(tb->mh)); |
| num_requests_sent = 1; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderNotSet("Authorization")) |
| Respond(WithCode(401), WithChunkedBody("1"), |
| WithHeader("WWW-Authenticate", |
| apr_psprintf(tb->pool, "%s realm=\"Test Suite\"%s", |
| scheme, authn_attr))) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Authorization", exp_authz_test_suite)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertIntEquals(tc, num_requests_sent, tb->handled_requests->nelts); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllRequestsReceivedInOrder); |
| EndVerify |
| 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; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("2"), |
| HeaderEqualTo("Authorization", exp_authz_test_suite)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 2); |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllRequestsReceivedInOrder); |
| EndVerify |
| 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 = apr_psprintf(tb->pool, "<http://localhost:%d> New Realm", |
| mhServerPortNr(tb->mh)); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/newrealm/index.html"), ChunkedBodyEqualTo("3"), |
| HeaderEqualTo("Authorization", exp_authz_wrong_realm)) |
| Respond(WithCode(401), WithChunkedBody("1"), |
| WithHeader("WWW-Authenticate", |
| apr_psprintf(tb->pool, "%s realm=\"New Realm\"%s", |
| scheme, authn_attr))) |
| GETRequest(URLEqualTo("/newrealm/index.html"), ChunkedBodyEqualTo("3"), |
| HeaderEqualTo("Authorization", exp_authz_new_realm)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/newrealm/index.html", 3); |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllRequestsReceivedInOrder); |
| EndVerify |
| 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 = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| int num_requests_sent; |
| apr_status_t status; |
| |
| /* Set up a test context with a server */ |
| setup_test_mock_server(tb); |
| status = setup_test_client_context(tb, NULL, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| serf_config_authn_types(tb->context, SERF_AUTHN_BASIC); |
| serf_config_credentials_callback(tb->context, basic_authn_callback); |
| |
| /* Test that a request is retried and authentication headers are set |
| correctly. */ |
| num_requests_sent = 1; |
| |
| Given(tb->mh) |
| HEADRequest(URLEqualTo("/"), BodyEqualTo(""), |
| HeaderNotSet("Authorization")) |
| Respond(WithCode(401), WithBody(""), |
| WithHeader("WWW-Authenticate", "Basic Realm=\"Test Suite\"")) |
| HEADRequest(URLEqualTo("/"), BodyEqualTo(""), |
| HeaderEqualTo("Authorization", "Basic c2VyZjpzZXJmdGVzdA==")) |
| Respond(WithCode(200), WithBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "HEAD", "/", -1); |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertIntEquals(tc, num_requests_sent, tb->handled_requests->nelts); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllRequestsReceivedInOrder); |
| EndVerify |
| |
| 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; |
| } |