| /* ==================================================================== |
| * 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 void digest_check_parameters(CuTest *tc, |
| apr_status_t expected_status, |
| const char *alg, |
| const char *qop) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[2]; |
| const char *hdr; |
| apr_status_t status; |
| |
| /* fprintf(stderr, "alg=%s qop=%s\n", alg, qop); */ |
| |
| /* 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_DIGEST); |
| serf_config_credentials_callback(tb->context, digest_authn_callback); |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| /* Construct the response header. */ |
| alg = alg ? apr_psprintf(tb->pool, ",algorithm=\"%s\"", alg) : ""; |
| qop = qop ? apr_psprintf(tb->pool, ",qop=\"%s\"", qop) : ""; |
| hdr = apr_psprintf(tb->pool, "Digest realm=\"Test Suite\"," |
| "nonce=\"ABCDEF1234567890\",opaque=\"myopaque\"" |
| "%s%s", alg, qop); |
| /* fprintf(stderr, "hdr=%s\n", hdr); */ |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), HeaderNotSet("Authorization")) |
| Respond(WithCode(401), WithChunkedBody("1"), |
| WithHeader("www-Authenticate", hdr)) |
| GETRequest(URLEqualTo("/"), HeaderSet("Authorization")) |
| Respond(WithCode(200), WithChunkedBody("")) |
| Expect |
| AllRequestsReceivedInOrder |
| EndGiven |
| |
| status = run_client_and_mock_servers_loops(tb, 1, handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, expected_status, status); |
| } |
| |
| static void test_digest_valid_params(CuTest *tc) |
| { |
| digest_check_parameters(tc, APR_SUCCESS, NULL, NULL); |
| digest_check_parameters(tc, APR_SUCCESS, "MD5", NULL); |
| digest_check_parameters(tc, APR_SUCCESS, NULL, "auth"); |
| digest_check_parameters(tc, APR_SUCCESS, "MD5", "auth"); |
| digest_check_parameters(tc, APR_SUCCESS, "MD5", "auth-int auth"); |
| digest_check_parameters(tc, APR_SUCCESS, "MD5", "crumple auth auth-int"); |
| } |
| |
| static void test_digest_invalid_params(CuTest *tc) |
| { |
| static const apr_status_t expected = SERF_ERROR_AUTHN_NOT_SUPPORTED; |
| digest_check_parameters(tc, expected, "", NULL); |
| digest_check_parameters(tc, expected, "MD5-sess", NULL); |
| digest_check_parameters(tc, expected, "SHA-256", NULL); |
| digest_check_parameters(tc, expected, "SHA-256-sess", NULL); |
| digest_check_parameters(tc, expected, "SHA-512-256", NULL); |
| digest_check_parameters(tc, expected, "SHA-512-256-sess", NULL); |
| digest_check_parameters(tc, expected, NULL, ""); |
| digest_check_parameters(tc, expected, NULL, "auth-int"); |
| digest_check_parameters(tc, expected, NULL, "crumple"); |
| digest_check_parameters(tc, expected, NULL, "crumple auth-int"); |
| } |
| |
| 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); |
| } |
| |
| |
| static void test_authn_register_one(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| void *const baton = (void *)0xdeadbeef; |
| apr_status_t status; |
| int type; |
| |
| status = setup_test_context(tb, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* Register an authentication scheme */ |
| status = serf_authn_register_scheme(&type, tb->context, |
| "Fizzle", baton, |
| SERF_AUTHN_FLAG_NONE, |
| NULL, NULL, NULL, NULL, NULL, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertTrue(tc, type != SERF_AUTHN_NONE); |
| |
| /* Unregister the scheme */ |
| status = serf_authn_unregister_scheme(tb->context, |
| type, "fiZzlE", tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| } |
| |
| static void test_authn_register_two(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| void *const baton1 = (void *)0xdeadbeef; |
| void *const baton2 = (void *)0xbadf00d; |
| apr_status_t status; |
| int type1, type2; |
| |
| status = setup_test_context(tb, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* Register the schemes */ |
| status = serf_authn_register_scheme(&type1, tb->context, |
| "Tweedledee", baton1, |
| SERF_AUTHN_FLAG_NONE, |
| NULL, NULL, NULL, NULL, NULL, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertTrue(tc, type1 != SERF_AUTHN_NONE); |
| |
| status = serf_authn_register_scheme(&type2, tb->context, |
| "Tweedledum", baton2, |
| SERF_AUTHN_FLAG_NONE, |
| NULL, NULL, NULL, NULL, NULL, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertTrue(tc, type2 != SERF_AUTHN_NONE); |
| CuAssertTrue(tc, type2 != type1); |
| |
| /* Unregister the schemes */ |
| status =serf_authn_unregister_scheme(tb->context, |
| type1, "tweedleDee", tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| status =serf_authn_unregister_scheme(tb->context, |
| type2, "tweedleDum", tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| } |
| |
| static void test_authn_register_twice(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| void *const baton = (void *)0xdeadbeef; |
| apr_status_t status; |
| int type, epyt; |
| |
| status = setup_test_context(tb, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* Register an authentication scheme */ |
| status = serf_authn_register_scheme(&type, tb->context, |
| "Tweens", baton, |
| SERF_AUTHN_FLAG_NONE, |
| NULL, NULL, NULL, NULL, NULL, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertTrue(tc, type != SERF_AUTHN_NONE); |
| |
| status = serf_authn_register_scheme(&epyt, tb->context, |
| "Tweens", baton, |
| SERF_AUTHN_FLAG_NONE, |
| NULL, NULL, NULL, NULL, NULL, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_EEXIST, status); |
| CuAssertTrue(tc, epyt == SERF_AUTHN_NONE); |
| |
| /* Unregister the scheme */ |
| status =serf_authn_unregister_scheme(tb->context, |
| type, "Tweens", tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| } |
| |
| static void test_authn_unregister_unknown(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| apr_status_t status; |
| |
| status = setup_test_context(tb, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* Unregister the scheme */ |
| status = serf_authn_unregister_scheme(tb->context, |
| 1 << 15, "Neverland", tb->pool); |
| CuAssertIntEquals(tc, APR_ENOENT, status); |
| } |
| |
| static void test_authn_registered_pool_cleanup(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| void *const baton = (void *)0xdeadbeef; |
| apr_pool_t *scheme_pool; |
| apr_status_t status; |
| int type; |
| |
| status = setup_test_context(tb, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* Create a pool for the new scheme. */ |
| apr_pool_create(&scheme_pool, tb->pool); |
| CuAssertTrue(tc, scheme_pool != NULL); |
| |
| /* Register an authentication scheme */ |
| status = serf_authn_register_scheme(&type, tb->context, |
| "Killed", baton, |
| SERF_AUTHN_FLAG_NONE, |
| NULL, NULL, NULL, NULL, NULL, |
| scheme_pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertTrue(tc, type != SERF_AUTHN_NONE); |
| |
| /* Destroy the pool. Its cleanup function should unregister the scheme. */ |
| apr_pool_destroy(scheme_pool); |
| |
| |
| /* Try to unregister the scheme; this should fail. */ |
| status = serf_authn_unregister_scheme(tb->context, |
| type, "Killed", tb->pool); |
| CuAssertIntEquals(tc, APR_ENOENT, status); |
| } |
| |
| typedef struct user_authn_baton user_authn_t; |
| struct user_authn_baton { |
| CuTest *tc; |
| const char *name; |
| int all_count; |
| int init_conn_count; |
| int get_realm_count; |
| int handle_count; |
| int setup_request_count; |
| int validate_response_count; |
| }; |
| |
| #define USER_AUTHN_COUNT(baton, callback) \ |
| user_authn_t *const b = (baton); \ |
| do { \ |
| ++b->callback##_count; \ |
| ++b->all_count; \ |
| } while(0) |
| |
| static user_authn_t *user_authn_make_baton(CuTest *tc, const char* name, |
| apr_pool_t *pool) |
| { |
| user_authn_t *baton = apr_pcalloc(pool, sizeof(*baton)); |
| baton->tc = tc; |
| baton->name = apr_pstrdup(pool, name); |
| return baton; |
| } |
| |
| typedef struct user_authn_cache user_authn_baton_t; |
| struct user_authn_cache { |
| const char *header; |
| const char *value; |
| }; |
| |
| static const char *const user_authn_prefix = "Tweedle"; |
| |
| static apr_status_t user_authn_init_conn(void **authn_baton, |
| void *baton, int code, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| USER_AUTHN_COUNT(baton, init_conn); |
| |
| if (strncmp(user_authn_prefix, b->name, strlen(user_authn_prefix)) != 0) |
| return SERF_ERROR_AUTHN_INITALIZATION_FAILED; |
| |
| *authn_baton = apr_pcalloc(result_pool, sizeof(user_authn_baton_t)); |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t user_authn_get_realm(const char **realm_name, |
| void *baton, |
| void *authn_baton, |
| const char *authn_header, |
| apr_hash_t *authn_parameters, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *name; |
| USER_AUTHN_COUNT(baton, get_realm); |
| |
| test__log(TEST_VERBOSE, __FILE__, |
| "user_authn_get_realm, header %s\n", authn_header); |
| |
| if (strncasecmp(authn_header, "TweedleDee", 10)) { |
| *realm_name = "Wonderland"; |
| return APR_SUCCESS; |
| } |
| |
| name = apr_hash_get(authn_parameters, "scope", 5); |
| if (!name) |
| return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE; |
| |
| *realm_name = apr_pstrdup(result_pool, name); |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t user_authn_handle(int *reset_pipelining, |
| void *baton, |
| void *authn_baton, |
| int code, |
| const char *authn_header, |
| apr_hash_t *authn_parameters, |
| const char *response_header, |
| const char *username, |
| const char *password, |
| serf_request_t *request, |
| serf_bucket_t *response, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| user_authn_baton_t *const ab = authn_baton; |
| USER_AUTHN_COUNT(baton, handle); |
| |
| CuAssertTrue(b->tc, username == NULL); |
| ab->header = apr_pstrdup(result_pool, response_header); |
| ab->value = apr_pstrcat(result_pool, b->name, " ", password, NULL); |
| |
| *reset_pipelining = 1; |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t user_authn_setup_request(int *reset_pipelining, |
| void *baton, |
| void *authn_baton, |
| serf_connection_t *conn, |
| serf_request_t *request, |
| const char *method, |
| const char *uri, |
| serf_bucket_t *headers, |
| apr_pool_t *scratch_pool) |
| { |
| user_authn_baton_t *const ab = authn_baton; |
| USER_AUTHN_COUNT(baton, setup_request); |
| |
| CuAssertPtrNotNull(b->tc, authn_baton); |
| CuAssertPtrNotNull(b->tc, ab->header); |
| CuAssertPtrNotNull(b->tc, ab->value); |
| |
| test__log(TEST_VERBOSE, __FILE__, |
| "user_authn_setup_request, header %s: %s\n", |
| ab->header, ab->value); |
| |
| serf_bucket_headers_setn(headers, ab->header, ab->value); |
| |
| *reset_pipelining = 1; |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t user_authn_validate_response(int *reset_pipelining, |
| void *baton, |
| void *authn_baton, |
| int code, |
| serf_connection_t *conn, |
| apr_hash_t *authn_params, |
| serf_request_t *request, |
| serf_bucket_t *response, |
| apr_pool_t *scratch_pool) |
| { |
| const char *knight; |
| USER_AUTHN_COUNT(baton, validate_response); |
| |
| CuAssertPtrNotNull(b->tc, authn_params); |
| knight = apr_hash_get(authn_params, "white", 5); |
| CuAssertPtrNotNull(b->tc, knight); |
| test__log(TEST_VERBOSE, __FILE__, |
| "user_authn_validate_response, info white=%s\n", |
| knight); |
| CuAssertStrEquals(b->tc, "Knight", knight); |
| |
| *reset_pipelining = 1; |
| return APR_SUCCESS; |
| } |
| #undef USER_AUTHN_COUNT |
| |
| static apr_status_t |
| user_authn_credentials(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 *realm_name; |
| |
| tb->result_flags |= TEST_RESULT_AUTHNCB_CALLED; |
| |
| if (code != SERF_AUTHN_CODE_HOST) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strncmp(user_authn_prefix, authn_type, strlen(user_authn_prefix)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| realm_name = strrchr(realm, ' '); |
| if (!realm_name |
| || (strcmp(" Alice", realm_name) |
| && strcmp(" Cheshire", realm_name))) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| *username = NULL; |
| *password = apr_pstrdup(pool, authn_type); |
| test__log(TEST_VERBOSE, __FILE__, |
| "user credentials, realm %s, password %s\n", |
| realm_name + 1, *password); |
| |
| return APR_SUCCESS; |
| } |
| |
| static void user_authentication(CuTest *tc, |
| int close_conn, |
| int use_pipelining, |
| const char *scope) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx; |
| int num_requests_sent; |
| apr_status_t status; |
| int typedee, typedum; |
| const char *hdr_value = "tweeDlEdee scope=Alice"; |
| user_authn_t *const tdee = user_authn_make_baton(tc, "TweedleDee", tb->pool); |
| user_authn_t *const tdum = user_authn_make_baton(tc, "TweedleDum", tb->pool); |
| const int flags = (SERF_AUTHN_FLAG_CREDS |
| | (use_pipelining ? SERF_AUTHN_FLAG_PIPE : 0)); |
| |
| status = setup_test_context(tb, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| status = serf_authn_register_scheme(&typedee, tb->context, |
| tdee->name, tdee, flags, |
| user_authn_init_conn, |
| user_authn_get_realm, |
| user_authn_handle, |
| user_authn_setup_request, |
| user_authn_validate_response, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertTrue(tc, typedee != SERF_AUTHN_NONE); |
| |
| status = serf_authn_register_scheme(&typedum, tb->context, |
| tdum->name, tdum, flags, |
| user_authn_init_conn, |
| user_authn_get_realm, |
| user_authn_handle, |
| user_authn_setup_request, |
| user_authn_validate_response, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertTrue(tc, typedum != SERF_AUTHN_NONE); |
| CuAssertTrue(tc, typedum != typedee); |
| |
| /* Test that a request is retried and authentication |
| headers are set correctly. */ |
| num_requests_sent = 1; |
| |
| /* 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, typedee | typedum); |
| serf_config_credentials_callback(tb->context, user_authn_credentials); |
| |
| /* Adjust the authentication header. */ |
| if (scope) |
| hdr_value = apr_pstrcat(tb->pool, hdr_value, |
| ", scope=", scope, NULL); |
| |
| /* 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(SERF_AUTHN_CODE_HOST),WithChunkedBody("1"), |
| WithHeader("www-Authenticate", hdr_value), |
| OnConditionThat(close_conn, WithConnectionCloseHeader)) |
| GETRequest(URLEqualTo("/"), |
| HeaderEqualTo("Authorization", "TweedleDee TweedleDee")) |
| Respond(WithCode(200), |
| WithHeader("Authentication-Info", "White=Knight"), |
| WithChunkedBody("")) |
| Expect |
| AllRequestsReceivedInOrder |
| EndGiven |
| |
| tb->result_flags = 0; |
| create_new_request(tb, &handler_ctx, "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); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); |
| CuAssertIntEquals(tc, 0, tdum->all_count); |
| CuAssertIntEquals(tc, 1, tdee->init_conn_count); |
| CuAssertIntEquals(tc, 1, tdee->get_realm_count); |
| CuAssertIntEquals(tc, 1, tdee->handle_count); |
| CuAssertIntEquals(tc, 1, tdee->setup_request_count); |
| CuAssertIntEquals(tc, 1, tdee->validate_response_count); |
| CuAssertIntEquals(tc, 5, tdee->all_count); |
| 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", "TweedleDee TweedleDee")) |
| Respond(WithCode(200), WithChunkedBody("")) |
| Expect |
| AllRequestsReceivedInOrder |
| EndGiven |
| |
| tb->result_flags = 0; |
| create_new_request(tb, &handler_ctx, "GET", "/", 2); |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| &handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| CuAssertIntEquals(tc, 1 + num_requests_sent, tb->handled_requests->nelts); |
| CuAssertTrue(tc, !(tb->result_flags & TEST_RESULT_AUTHNCB_CALLED)); |
| CuAssertIntEquals(tc, 0, tdum->all_count); |
| CuAssertIntEquals(tc, 1, tdee->init_conn_count); |
| CuAssertIntEquals(tc, 1, tdee->get_realm_count); |
| CuAssertIntEquals(tc, 1, tdee->handle_count); |
| CuAssertIntEquals(tc, 2, tdee->setup_request_count); |
| CuAssertIntEquals(tc, 2, tdee->validate_response_count); |
| CuAssertIntEquals(tc, 7, tdee->all_count); |
| Verify(tb->mh) |
| CuAssertTrue(tc, VerifyAllExpectationsOk); |
| EndVerify |
| |
| status = serf_authn_unregister_scheme(tb->context, |
| typedum, tdum->name, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| status = serf_authn_unregister_scheme(tb->context, |
| typedee, tdee->name, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| } |
| |
| static void test_user_authentication(CuTest *tc) |
| { |
| user_authentication(tc, |
| 0 /* don't close connection */, |
| 1 /* allow pipelining during authn */, |
| 0 /* no custom scope */); |
| } |
| |
| static void test_user_authentication_tweaked(CuTest *tc) |
| { |
| user_authentication(tc, |
| 0 /* don't close connection */, |
| 1 /* allow pipelining during authn */, |
| "Cheshire" /* tweak autn header */); |
| } |
| |
| static void test_user_authentication_keepalive_off(CuTest *tc) |
| { |
| user_authentication(tc, |
| 1 /* close connection */, |
| 1 /* allow pipelining during authn */, |
| 0 /* no custom scope */); |
| } |
| |
| static void test_user_authentication_pipelining_off(CuTest *tc) |
| { |
| user_authentication(tc, |
| 0 /* don't close connection */, |
| 0 /* don't allow pipelining during authn */, |
| 0 /* no custom scope */); |
| } |
| |
| |
| /*****************************************************************************/ |
| 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_digest_valid_params); |
| SUITE_ADD_TEST(suite, test_digest_invalid_params); |
| SUITE_ADD_TEST(suite, test_basic_switch_realms); |
| SUITE_ADD_TEST(suite, test_digest_switch_realms); |
| SUITE_ADD_TEST(suite, test_auth_on_HEAD); |
| SUITE_ADD_TEST(suite, test_authn_register_one); |
| SUITE_ADD_TEST(suite, test_authn_register_two); |
| SUITE_ADD_TEST(suite, test_authn_register_twice); |
| SUITE_ADD_TEST(suite, test_authn_unregister_unknown); |
| SUITE_ADD_TEST(suite, test_authn_registered_pool_cleanup); |
| SUITE_ADD_TEST(suite, test_user_authentication); |
| SUITE_ADD_TEST(suite, test_user_authentication_tweaked); |
| SUITE_ADD_TEST(suite, test_user_authentication_keepalive_off); |
| SUITE_ADD_TEST(suite, test_user_authentication_pipelining_off); |
| |
| return suite; |
| } |