Author: brane Date: Mon Jun 16 18:38:31 2025 New Revision: 1926486 URL: http://svn.apache.org/viewvc?rev=1926486&view=rev Log: On the user-defined-authn branch: Implement all user-defined authentication callbacks to the point that a simple test succeeds.
* serf.h (serf_authn_get_realm_func_t): New callback type for user-defined schemes. (serf_authn_handle_func_t, serf_authn_setup_request_func_t, serf_authn_validate_response_func_t): Update argument lists. (serf_authn_register_scheme): Add new argument get_realm to the prototype. * auth/auth.h (serf__authn_scheme_t::user_get_realm_func): New struct member. * auth/auth.c (serf_authn_register_scheme): Implement the new get_realm argument. * auth/auth_user_defined.c (authn_baton_wrapper): Renamed from callback_authn_baton. (authn_baton_wrapper::pipelining_reset): New struct member. (callback_fn_t, validate_handler): Helper for callback argument validation. (serf__authn_user__init_conn): Defer to validate_handler and remember in the baton if we have to reset the connection's pipelining state. (serf__authn_user__handle, serf__authn_user__setup_request, serf__authn_user__validate_response): Implement these callbacks. * test/test_auth.c (test_authn_register_one, test_authn_register_two, test_authn_register_twice, test_authn_registered_pool_cleanup): Update scheme registration. (user_authn_baton::get_realm_count): New struct member. (user_authn_get_realm): New test callback. (user_authn_handle, user_authn_setup_request, user_authn_validate_response): Implement these callbacks. (user_authn_credentials): Renamed from user_authn_credentials_callback. Extract the realm name and log the result. (user_authentication): Add two more test mode arguments: one to control request pipelining and one to set the scheme flags. Improve the state checks and verify that credentials are cached between requests. (test_user_authentication, test_user_authentication_keepalive_off): Update forwarded arguments. (test_user_authentication_tweaked, test_user_authentication_pipelining_off): New test cases. (test_auth): Register all four user_authentication test cases. Modified: serf/branches/user-defined-authn/auth/auth.c serf/branches/user-defined-authn/auth/auth.h serf/branches/user-defined-authn/auth/auth_user_defined.c serf/branches/user-defined-authn/serf.h serf/branches/user-defined-authn/test/test_auth.c Modified: serf/branches/user-defined-authn/auth/auth.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/auth/auth.c?rev=1926486&r1=1926485&r2=1926486&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth.c (original) +++ serf/branches/user-defined-authn/auth/auth.c Mon Jun 16 18:38:31 2025 @@ -662,6 +662,7 @@ static apr_status_t cleanup_user_scheme( apr_status_t serf_authn_register_scheme( serf_context_t *ctx, const char *name, void *baton, int flags, serf_authn_init_conn_func_t init_conn, + serf_authn_get_realm_func_t get_realm, serf_authn_handle_func_t handle, serf_authn_setup_request_func_t setup_request, serf_authn_validate_response_func_t validate_response, @@ -702,6 +703,7 @@ apr_status_t serf_authn_register_scheme( authn_scheme->user_flags = flags; authn_scheme->user_baton = baton; authn_scheme->user_init_conn_func = init_conn; + authn_scheme->user_get_realm_func = get_realm; authn_scheme->user_handle_func = handle; authn_scheme->user_setup_request_func = setup_request; authn_scheme->user_validate_response_func = validate_response; Modified: serf/branches/user-defined-authn/auth/auth.h URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/auth/auth.h?rev=1926486&r1=1926485&r2=1926486&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth.h (original) +++ serf/branches/user-defined-authn/auth/auth.h Mon Jun 16 18:38:31 2025 @@ -128,6 +128,7 @@ struct serf__authn_scheme_t { /* Authentication callbacks. */ serf_authn_init_conn_func_t user_init_conn_func; + serf_authn_get_realm_func_t user_get_realm_func; serf_authn_handle_func_t user_handle_func; serf_authn_setup_request_func_t user_setup_request_func; serf_authn_validate_response_func_t user_validate_response_func; Modified: serf/branches/user-defined-authn/auth/auth_user_defined.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/auth/auth_user_defined.c?rev=1926486&r1=1926485&r2=1926486&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth_user_defined.c (original) +++ serf/branches/user-defined-authn/auth/auth_user_defined.c Mon Jun 16 18:38:31 2025 @@ -48,46 +48,66 @@ static serf__authn_info_t *get_authn_inf /* Used for serf__authn_info_t::baton */ -struct callback_authn_baton { +struct authn_baton_wrapper { /* The connection's pipelining state before we changed it. */ int pipelining; + /* Was pipelining reset to its previous value?. */ + bool pipelining_reset; + /* The user-defined scheme's per-connection baton. */ void *user_authn_baton; }; -apr_status_t -serf__authn_user__init_conn(const serf__authn_scheme_t *scheme, - int code, - serf_connection_t *conn, - apr_pool_t *pool) +typedef apr_status_t (*callback_fn_t)(); +static apr_status_t validate_handler(serf_config_t *config, + const serf__authn_scheme_t *scheme, + int peer_id, + const char *callback_name, + callback_fn_t callback_func, + serf__authn_info_t *authn_info) { - serf__authn_info_t *const authn_info = get_authn_info(code, conn); - struct callback_authn_baton *authn_baton; - apr_status_t status = APR_SUCCESS; - - serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config, - "User-defined scheme %s: callback: init-conn\n", - scheme->name); - if (!validate_user_authn(scheme)) { - serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config, + serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, config, "Not a user-defined scheme: %s\n", scheme->name); return APR_EINVAL; } if (!scheme->user_init_conn_func) { - serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config, - "User-defined scheme %s: missing callback: init-conn\n", - scheme->name); + serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, config, + "User-defined scheme %s: missing callback: %s\n", + scheme->name, callback_name); return APR_ENOTIMPL; } if (!authn_info) { - serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config, - "User-defined scheme %s init-conn: invalid code: %d\n", - scheme->name, code); + serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, config, + "User-defined scheme %s %s: invalid peer: %d\n", + scheme->name, callback_name, peer_id); return APR_ERANGE; } + return APR_SUCCESS; +} + + +apr_status_t +serf__authn_user__init_conn(const serf__authn_scheme_t *scheme, + int code, + serf_connection_t *conn, + apr_pool_t *pool) +{ + serf__authn_info_t *const authn_info = get_authn_info(code, conn); + struct authn_baton_wrapper *authn_baton; + apr_status_t status; + + status = validate_handler(conn->config, scheme, code, "init-conn", + scheme->user_init_conn_func, + authn_info); + if (status) + return status; + + serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config, + "User-defined scheme %s: callback: init-conn\n", + scheme->name); authn_baton = authn_info->baton; if (authn_baton == NULL) { @@ -115,6 +135,9 @@ serf__authn_user__init_conn(const serf__ "User-defined scheme %s: pipelining off for %s\n", scheme->name, conn->host_url); authn_baton->pipelining = serf__connection_set_pipelining(conn, 0); + authn_baton->pipelining_reset = false; + } else { + authn_baton->pipelining_reset = true; } return status; } @@ -129,23 +152,85 @@ serf__authn_user__handle(const serf__aut const char *auth_attr, apr_pool_t *pool) { - serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, request->conn->config, + serf_connection_t *const conn = request->conn; + serf__authn_info_t *const authn_info = get_authn_info(code, conn); + serf_context_t *const ctx = conn->ctx; + struct authn_baton_wrapper *authn_baton; + char *username, *password; + apr_pool_t *scratch_pool; + apr_status_t status; + + status = validate_handler(conn->config, scheme, code, "handle-auth", + scheme->user_handle_func, + authn_info); + if (status) + return status; + + serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, ctx->config, "User-defined scheme %s: callback: handle-auth\n", scheme->name); - if (!validate_user_authn(scheme)) { - serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, request->conn->config, - "Not a user-defined scheme: %s\n", scheme->name); - return APR_EINVAL; - } - if (!scheme->user_handle_func) { - serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, request->conn->config, - "User-defined scheme %s: missing callback: handle-auth\n", - scheme->name); - return APR_ENOTIMPL; + /* The credentials callback must be set if the scheme requires it. */ + if (scheme->user_flags & SERF_AUTHN_FLAG_CREDS) { + bool failed = false; + + if (!ctx->cred_cb) { + serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config, + "User-defined scheme %s handle-auth:" + " missing credentials callback\n", + scheme->name); + failed = true; + } + + if (!scheme->user_get_realm_func) { + serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, ctx->config, + "User-defined scheme %s handle-auth:" + " missing callback: get-realm\n", + scheme->name); + failed = true; + } + + if (failed) + return SERF_ERROR_AUTHN_FAILED; } - return APR_ENOTIMPL; + authn_baton = authn_info->baton; + apr_pool_create(&scratch_pool, pool); + + status = APR_SUCCESS; + if (scheme->user_flags & SERF_AUTHN_FLAG_CREDS) { + const char *realm_name; + status = scheme->user_get_realm_func(scheme->user_baton, + authn_baton->user_authn_baton, + auth_hdr, auth_attr, + scratch_pool, scratch_pool, + &realm_name); + if (!status) { + const char *const realm = serf__construct_realm( + SERF__PEER_FROM_CODE(code), conn, realm_name, scratch_pool); + status = serf__provide_credentials(ctx, + &username, &password, + request, + code, scheme->name, + realm, scratch_pool); + } + if (status) + goto cleanup; + } else { + username = password = NULL; + } + + status = scheme->user_handle_func(scheme->user_baton, + authn_baton->user_authn_baton, code, + auth_hdr, auth_attr, + SERF__HEADER_FROM_CODE(code), + username, password, + request, response, + pool, scratch_pool); + + cleanup: + apr_pool_destroy(scratch_pool); + return status; } @@ -159,23 +244,38 @@ serf__authn_user__setup_request(const se const char *uri, serf_bucket_t *hdrs_bkt) { - serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config, - "User-defined scheme %s: callback: setup-request\n", - scheme->name); + const int peer_id = SERF__CODE_FROM_PEER(peer); + serf__authn_info_t *const authn_info = get_authn_info(peer_id, conn); + struct authn_baton_wrapper *authn_baton; + apr_pool_t *scratch_pool; + apr_status_t status; + + status = validate_handler(conn->config, scheme, peer_id, "setup-request", + scheme->user_setup_request_func, + authn_info); + if (status) + return status; - if (!validate_user_authn(scheme)) { - serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config, - "Not a user-defined scheme: %s\n", scheme->name); - return APR_EINVAL; - } - if (!scheme->user_setup_request_func) { + if (!authn_info->baton) { serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config, - "User-defined scheme %s: missing callback: setup-request\n", + "User-defined scheme %s setup-request: no authn data\n", scheme->name); - return APR_ENOTIMPL; + return APR_ERANGE; } - return APR_ENOTIMPL; + serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config, + "User-defined scheme %s: callback: setup-request\n", + scheme->name); + + authn_baton = authn_info->baton; + apr_pool_create(&scratch_pool, conn->pool); + status = scheme->user_setup_request_func(scheme->user_baton, + authn_baton->user_authn_baton, + code, conn, request, + method, uri, hdrs_bkt, + scratch_pool); + apr_pool_destroy(scratch_pool); + return status; } @@ -188,22 +288,52 @@ serf__authn_user__validate_response(cons serf_bucket_t *response, apr_pool_t *pool) { + const int peer_id = SERF__CODE_FROM_PEER(peer); + serf__authn_info_t *const authn_info = get_authn_info(peer_id, conn); + int reset_pipelining = 0; + struct authn_baton_wrapper *authn_baton; + apr_pool_t *scratch_pool; + apr_status_t status; + + status = validate_handler(conn->config, scheme, peer_id, + "validate-response", + scheme->user_validate_response_func, + authn_info); + if (status) + return status; + + if (!authn_info->baton) { + serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config, + "User-defined scheme %s validate-response: no authn data\n", + scheme->name); + return APR_ERANGE; + } + serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config, "User-defined scheme %s: callback: validate-response\n", scheme->name); - if (!validate_user_authn(scheme)) { - serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config, - "Not a user-defined scheme: %s\n", scheme->name); - return APR_EINVAL; - } - if (!scheme->user_validate_response_func) { - serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, conn->config, - "User-defined scheme %s:" - " missing callback: validate-response\n", - scheme->name); - return APR_ENOTIMPL; + authn_baton = authn_info->baton; + apr_pool_create(&scratch_pool, conn->pool); + status = scheme->user_validate_response_func(scheme->user_baton, + authn_baton->user_authn_baton, + code, conn, request, response, + scratch_pool, + &reset_pipelining); + + /* Reset pipelining if the scheme requires it. */ + if (status == APR_SUCCESS && reset_pipelining + && !authn_baton->pipelining_reset + && !(scheme->user_flags & SERF_AUTHN_FLAG_PIPE)) { + serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, conn->config, + "User-defined scheme %s: pipelining reset to %s for %s\n", + scheme->name, + authn_baton->pipelining ? "on" : "off", + conn->host_url); + serf__connection_set_pipelining(conn, authn_baton->pipelining); + authn_baton->pipelining_reset = true; } - return APR_ENOTIMPL; + apr_pool_destroy(scratch_pool); + return status; } Modified: serf/branches/user-defined-authn/serf.h URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/serf.h?rev=1926486&r1=1926485&r2=1926486&view=diff ============================================================================== --- serf/branches/user-defined-authn/serf.h (original) +++ serf/branches/user-defined-authn/serf.h Mon Jun 16 18:38:31 2025 @@ -980,19 +980,33 @@ typedef apr_status_t /** TODO: */ typedef apr_status_t +(*serf_authn_get_realm_func_t)(void *baton, + void *authn_baton, + const char *authn_header, + const char *authn_attributes, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool, + const char **realm_name); + +/** TODO: */ +typedef apr_status_t (*serf_authn_handle_func_t)(void *baton, + void *authn_baton, int code, + const char *authn_header, + const char *authn_attributes, + const char *response_header, + const char *username, + const char *password, serf_request_t *request, serf_bucket_t *response, - const char *auth_hdr, - const char *auth_attr, apr_pool_t *result_pool, apr_pool_t *scratch_pool); /** TODO: */ typedef apr_status_t (*serf_authn_setup_request_func_t)(void *baton, - int peer, + void *authn_baton, int code, serf_connection_t *conn, serf_request_t *request, @@ -1004,12 +1018,13 @@ typedef apr_status_t /** TODO: */ typedef apr_status_t (*serf_authn_validate_response_func_t)(void *baton, - int peer, + void *authn_baton, int code, serf_connection_t *conn, serf_request_t *request, serf_bucket_t *response, - apr_pool_t *scratch_pool); + apr_pool_t *scratch_pool, + int *reset_pipelining); /** * Register an autehtication scheme. @@ -1044,6 +1059,7 @@ typedef apr_status_t apr_status_t serf_authn_register_scheme( serf_context_t *ctx, const char *name, void *baton, int flags, serf_authn_init_conn_func_t init_conn, + serf_authn_get_realm_func_t get_realm, serf_authn_handle_func_t handle, serf_authn_setup_request_func_t setup_request, serf_authn_validate_response_func_t validate_response, Modified: serf/branches/user-defined-authn/test/test_auth.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/test/test_auth.c?rev=1926486&r1=1926485&r2=1926486&view=diff ============================================================================== --- serf/branches/user-defined-authn/test/test_auth.c (original) +++ serf/branches/user-defined-authn/test/test_auth.c Mon Jun 16 18:38:31 2025 @@ -533,7 +533,7 @@ static void test_authn_register_one(CuTe /* Register an authentication scheme */ status = serf_authn_register_scheme(tb->context, "Fizzle", baton, SERF_AUTHN_FLAG_NONE, - NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, tb->pool, &type); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertTrue(tc, type != SERF_AUTHN_NONE); @@ -558,14 +558,14 @@ static void test_authn_register_two(CuTe /* Register the schemes */ status = serf_authn_register_scheme(tb->context, "Tweedledee", baton1, SERF_AUTHN_FLAG_NONE, - NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, tb->pool, &type1); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertTrue(tc, type1 != SERF_AUTHN_NONE); status = serf_authn_register_scheme(tb->context, "Tweedledum", baton2, SERF_AUTHN_FLAG_NONE, - NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, tb->pool, &type2); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertTrue(tc, type2 != SERF_AUTHN_NONE); @@ -593,14 +593,14 @@ static void test_authn_register_twice(Cu /* Register an authentication scheme */ status = serf_authn_register_scheme(tb->context, "Tweens", baton, SERF_AUTHN_FLAG_NONE, - NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, tb->pool, &type); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertTrue(tc, type != SERF_AUTHN_NONE); status = serf_authn_register_scheme(tb->context, "Tweens", baton, SERF_AUTHN_FLAG_NONE, - NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, tb->pool, &epyt); CuAssertIntEquals(tc, APR_EEXIST, status); CuAssertTrue(tc, epyt == SERF_AUTHN_NONE); @@ -643,7 +643,7 @@ static void test_authn_registered_pool_c /* Register an authentication scheme */ status = serf_authn_register_scheme(tb->context, "Killed", baton, SERF_AUTHN_FLAG_NONE, - NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, scheme_pool, &type); CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertTrue(tc, type != SERF_AUTHN_NONE); @@ -663,6 +663,7 @@ struct user_authn_baton { 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; @@ -704,21 +705,67 @@ static apr_status_t user_authn_init_conn return APR_SUCCESS; } +static apr_status_t user_authn_get_realm(void *baton, + void *authn_baton, + const char *authn_header, + const char *authn_attributes, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool, + const char **realm_name) +{ + const char *end; + apr_size_t length; + USER_AUTHN_COUNT(baton, get_realm); + + test__log(TEST_VERBOSE, __FILE__, + "user_authn_get_realm, header %s, attrs %s\n", + authn_header, authn_attributes); + + if (strncasecmp(authn_header, "TweedleDee", 10)) { + *realm_name = "Wonderland"; + return APR_SUCCESS; + } + + if (strncasecmp(authn_attributes, "scope=", 6)) + return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE; + + authn_attributes += 6; + if ((end = strchr(authn_attributes, ' '))) { + length = end - authn_attributes; + } else { + length = strlen(authn_attributes); + } + *realm_name = apr_pstrndup(result_pool, authn_attributes, length); + return APR_SUCCESS; +} + static apr_status_t user_authn_handle(void *baton, + void *authn_baton, int code, + const char *authn_header, + const char *authn_attributes, + const char *response_header, + const char *username, + const char *password, serf_request_t *request, serf_bucket_t *response, - const char *auth_hdr, - const char *auth_attr, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { + user_authn_baton_t *const ab = authn_baton; USER_AUTHN_COUNT(baton, handle); + + if (username != NULL) + return SERF_ERROR_AUTHN_INITALIZATION_FAILED; + + ab->header = apr_pstrdup(result_pool, response_header); + ab->value = apr_pstrcat(result_pool, b->name, " ", password, NULL); + return APR_SUCCESS; } static apr_status_t user_authn_setup_request(void *baton, - int peer, + void *authn_baton, int code, serf_connection_t *conn, serf_request_t *request, @@ -727,33 +774,48 @@ static apr_status_t user_authn_setup_req serf_bucket_t *headers, apr_pool_t *scratch_pool) { + user_authn_baton_t *const ab = authn_baton; USER_AUTHN_COUNT(baton, setup_request); - return APR_SUCCESS; + + + if (ab && ab->header && 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); + return APR_SUCCESS; + } + + return SERF_ERROR_AUTHN_FAILED; } static apr_status_t user_authn_validate_response(void *baton, - int peer, + void *authn_baton, int code, serf_connection_t *conn, serf_request_t *request, serf_bucket_t *response, - apr_pool_t *scratch_pool) + apr_pool_t *scratch_pool, + int *reset_pipelining) { USER_AUTHN_COUNT(baton, validate_response); + *reset_pipelining = 1; return APR_SUCCESS; } #undef USER_AUTHN_COUNT static apr_status_t -user_authn_credentials_callback(char **username, - char **password, - serf_request_t *request, void *baton, - int code, const char *authn_type, - const char *scope, - apr_pool_t *pool) +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; @@ -761,32 +823,42 @@ user_authn_credentials_callback(char **u return REPORT_TEST_SUITE_ERROR(); if (strncmp(user_authn_prefix, authn_type, strlen(user_authn_prefix)) != 0) return REPORT_TEST_SUITE_ERROR(); - if (strcmp("Alice", scope) != 0) + + realm_name = realm + strlen(realm) - strlen("Alice"); + if (strcmp("Alice", realm_name) != 0) 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, *password); return APR_SUCCESS; } -static void user_authentication(CuTest *tc, int close_conn) +static void user_authentication(CuTest *tc, + int close_conn, + int use_pipelining, + const char *tweak) { test_baton_t *tb = tc->testBaton; - handler_baton_t handler_ctx[2]; + 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("TweedleDee", tb->pool); user_authn_t *const tdum = user_authn_make_baton("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(tb->context, tdee->name, tdee, - SERF_AUTHN_FLAG_CREDS - | SERF_AUTHN_FLAG_PIPE, + status = serf_authn_register_scheme(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, @@ -794,10 +866,9 @@ static void user_authentication(CuTest * CuAssertIntEquals(tc, APR_SUCCESS, status); CuAssertTrue(tc, typedee != SERF_AUTHN_NONE); - status = serf_authn_register_scheme(tb->context, tdum->name, tdum, - SERF_AUTHN_FLAG_CREDS - | SERF_AUTHN_FLAG_PIPE, + status = serf_authn_register_scheme(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, @@ -816,14 +887,19 @@ static void user_authentication(CuTest * CuAssertIntEquals(tc, APR_SUCCESS, status); serf_config_authn_types(tb->context, typedee | typedum); - serf_config_credentials_callback(tb->context, user_authn_credentials_callback); + serf_config_credentials_callback(tb->context, user_authn_credentials); + + /* Adjust the authentication header. */ + if (tweak) + hdr_value = apr_pstrcat(tb->pool, hdr_value, + " tweak=", tweak, 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", "tweeDlEdee scope=Alice"), + WithHeader("www-Authenticate", hdr_value), OnConditionThat(close_conn, WithConnectionCloseHeader)) GETRequest(URLEqualTo("/"), HeaderEqualTo("Authorization", "TweedleDee TweedleDee")) @@ -832,17 +908,51 @@ static void user_authentication(CuTest * AllRequestsReceivedInOrder EndGiven - create_new_request(tb, &handler_ctx[0], "GET", "/", 1); + 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); + &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 - CuAssertIntEquals(tc, 4, tdee->all_count); + /* 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); @@ -854,14 +964,35 @@ static void user_authentication(CuTest * static void test_user_authentication(CuTest *tc) { - user_authentication(tc, 0 /* don't close connection */); + user_authentication(tc, + 0 /* don't close connection */, + 1 /* allow pipelining during authn */, + 0 /* no authn header tweaks */); +} + +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); + user_authentication(tc, + 1 /* close connection */, + 1 /* allow pipelining during authn */, + 0 /* no authn header tweaks */); } +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 authn header tweaks */); +} /*****************************************************************************/ @@ -885,8 +1016,10 @@ CuSuite *test_auth(void) 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_keepalive_off); */ + 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; }