merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 8211f83ce4e41ec6b3be4dbc58c2fb1ae25ede38
parent ff08d11d8b86c0124555414689a7e8f845efe103
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 21 Jun 2025 21:12:38 +0200

-clean up logic, this should now fully fix #9556

Diffstat:
Msrc/backend/taler-merchant-httpd.c | 139++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/backend/taler-merchant-httpd.h | 4++--
Msrc/backend/taler-merchant-httpd_helper.c | 7+++++--
Msrc/backend/taler-merchant-httpd_private-post-instances.c | 14+++++++-------
4 files changed, 88 insertions(+), 76 deletions(-)

diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -469,32 +469,22 @@ TMH_get_scope_by_name (const char *name) enum GNUNET_GenericReturnValue -TMH_check_auth (const char *token, +TMH_check_auth (const char *password, struct TALER_MerchantAuthenticationSaltP *salt, struct TALER_MerchantAuthenticationHashP *hash) { struct TALER_MerchantAuthenticationHashP val; - char *dec; - size_t dec_len; if (GNUNET_is_zero (hash)) return GNUNET_OK; - if (NULL == token) + if (NULL == password) return GNUNET_SYSERR; - // FIXME: why do we urldecode here? This seems wrong - // if we got a password from 'Basic' auth header. - // In deprecated secret-token:password case, this - // was applicable -> move logic there! - dec_len = GNUNET_STRINGS_urldecode (token, - strlen (token), - &dec); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Checking against token with salt %s\n", TALER_B2S (salt)); TALER_merchant_instance_auth_hash_with_salt (&val, salt, - dec); - GNUNET_free (dec); + password); return (0 == GNUNET_memcmp (&val, hash)) @@ -504,16 +494,18 @@ TMH_check_auth (const char *token, /** - * Check if @a token grants access to @a instance. + * Check if @a userpass grants access to @a instance. * - * @param token FIXME: clarify + * @param userpass base64 encoded "$USERNAME:$PASSWORD" value + * from HTTP Basic "Authentication" header * @param instances the access controlled instance */ static enum GNUNET_GenericReturnValue -check_auth_instance (const char *token, +check_auth_instance (const char *userpass, struct TMH_MerchantInstance *instance) { char *tmp; + char *colon; const char *instance_name; const char *password; const char *target_instance = "admin"; @@ -522,31 +514,26 @@ check_auth_instance (const char *token, /* implicitly a zeroed out hash means no authentication */ if (GNUNET_is_zero (&instance->auth.auth_hash)) return GNUNET_OK; - if (NULL == token) + if (NULL == userpass) return GNUNET_SYSERR; if (0 == - GNUNET_STRINGS_base64_decode (token, - strlen (token), + GNUNET_STRINGS_base64_decode (userpass, + strlen (userpass), (void**) &tmp)) { return GNUNET_SYSERR; } - instance_name = strtok (tmp, - ":"); - if (NULL == instance_name) - { - GNUNET_free (tmp); - return GNUNET_SYSERR; - } - // FIXME: why would the password end at the first ':'? - password = strtok (NULL, - ":"); - if (NULL == password) + colon = strchr (tmp, + ':'); + if (NULL == colon) { GNUNET_free (tmp); return GNUNET_SYSERR; } - /* FIXME: how can instance->settings.id be NULL? */ + *colon = '\0'; + instance_name = tmp; + password = colon + 1; + /* instance->settings.id can be NULL if there is no instance yet */ if (NULL != instance->settings.id) target_instance = instance->settings.id; if (0 != strcmp (instance_name, @@ -563,7 +550,6 @@ check_auth_instance (const char *token, } -// FIXME: comment!? void TMH_compute_auth (const char *token, struct TALER_MerchantAuthenticationSaltP *salt, @@ -893,13 +879,24 @@ spa_redirect (const struct TMH_RequestHandler *rh, /** * Extract the token from authorization header value @a auth. + * The @a auth value can be a bearer token or a Basic + * authentication header. In both cases, this function + * updates @a auth to point to the actual credential, + * skipping spaces. * - * @param auth pointer to authorization header value, + * NOTE: We probably want to replace this function with MHD2 + * API calls in the future that are more robust. + * + * @param[in,out] auth pointer to authorization header value, * will be updated to point to the start of the token * or set to NULL if header value is invalid + * @param[out] is_basic_auth will be set to true if the + * authorization header uses basic authentication, + * otherwise to false */ static void -extract_auth (const char **auth) +extract_auth (const char **auth, + bool *is_basic_auth) { const char *bearer = "Bearer "; const char *basic = "Basic "; @@ -907,6 +904,7 @@ extract_auth (const char **auth) size_t offset = 0; bool is_bearer = false; + *is_basic_auth = false; if (0 == strncmp (tok, bearer, strlen (bearer))) @@ -919,6 +917,7 @@ extract_auth (const char **auth) strlen (basic))) { offset = strlen (basic); + *is_basic_auth = true; } else { @@ -2201,12 +2200,8 @@ url_handler (void *cls, because some reverse proxy is already doing it, and then that reverse proxy may forward malformed auth headers to the backend. */ - // FIXME: why do we check here *and* in extract_auth - // for "Basic "? Bad style! - is_basic_auth = (0 == strncmp (auth, - "Basic ", - strlen ("Basic "))); - extract_auth (&auth); + extract_auth (&auth, + &is_basic_auth); if (NULL == auth) auth_malformed = true; hc->auth_token = auth; @@ -2268,39 +2263,48 @@ url_handler (void *cls, &hc->auth_scope); if (TALER_EC_NONE != ec) { - // FIXME: Deprecated, remove after 1.1 - if (0 == strncasecmp (auth, + char *dec; + size_t dec_len; + const char *token; + + /* NOTE: Deprecated, remove sometime after v1.1 */ + if (0 != strncasecmp (auth, RFC_8959_PREFIX, strlen (RFC_8959_PREFIX))) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Trying deprecated secret-token:password API authN\n") - ; - if (GNUNET_OK != - TMH_check_auth (auth + strlen (RFC_8959_PREFIX), + GNUNET_break_op (0); + return TALER_MHD_reply_with_ec (connection, + ec, + NULL); + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Trying deprecated secret-token:password API authN\n"); + token = auth + strlen (RFC_8959_PREFIX); + dec_len = GNUNET_STRINGS_urldecode (token, + strlen (token), + &dec); + if ( (0 == dec_len) || + (GNUNET_OK != + TMH_check_auth (dec, &hc->instance->auth.auth_salt, - &hc->instance->auth.auth_hash)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Login failed\n"); - hc->auth_scope = TMH_AS_NONE; - } - else - { - hc->auth_scope = TMH_AS_ADMIN; - } + &hc->instance->auth.auth_hash)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Login failed\n"); + hc->auth_scope = TMH_AS_NONE; } else { - return TALER_MHD_reply_with_ec (connection, - ec, - NULL); + hc->auth_scope = TMH_AS_ADMIN; } + GNUNET_free (dec); } } } else + { hc->auth_scope = TMH_AS_NONE; + } } /* We grant access if: - scope is 'all' @@ -2312,11 +2316,16 @@ url_handler (void *cls, { if (auth_malformed && (TMH_AS_NONE == hc->auth_scope) ) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_UNAUTHORIZED, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "'" RFC_8959_PREFIX - "' prefix or 'Bearer' missing in 'Authorization' header"); + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_UNAUTHORIZED, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "'" RFC_8959_PREFIX + "' prefix or 'Bearer' missing in 'Authorization' header"); + } + GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_UNAUTHORIZED, TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED, diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h @@ -834,12 +834,12 @@ TMH_check_auth (const char *token, * Compute a @a hash from @a token hashes for * merchant instance authentication. * - * @param token the token to check + * @param password the password to check * @param[out] salt set to a fresh random salt * @param[out] hash set to the hash of @a token under @a salt */ void -TMH_compute_auth (const char *token, +TMH_compute_auth (const char *password, struct TALER_MerchantAuthenticationSaltP *salt, struct TALER_MerchantAuthenticationHashP *hash); diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c @@ -616,10 +616,13 @@ TMH_check_auth_config (struct MHD_Connection *connection, "token")) { json_t *pw_value; - pw_value = json_object_get (jauth, "password"); + + pw_value = json_object_get (jauth, + "password"); if (NULL == pw_value) { - pw_value = json_object_get (jauth, "token"); + pw_value = json_object_get (jauth, + "token"); } if (NULL == pw_value) { diff --git a/src/backend/taler-merchant-httpd_private-post-instances.c b/src/backend/taler-merchant-httpd_private-post-instances.c @@ -51,7 +51,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh, { struct TALER_MERCHANTDB_InstanceSettings is = { 0 }; struct TALER_MERCHANTDB_InstanceAuthSettings ias; - const char *auth_token = NULL; + const char *auth_password = NULL; const char *uts = "business"; struct TMH_WireMethod *wm_head = NULL; struct TMH_WireMethod *wm_tail = NULL; @@ -108,7 +108,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh, ret = TMH_check_auth_config (connection, jauth, - &auth_token); + &auth_password); if (GNUNET_OK != ret) { GNUNET_JSON_parse_free (spec); @@ -208,12 +208,12 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh, (NULL != is.logo && NULL != mi->settings.logo && 0 == strcmp (mi->settings.logo, is.logo))) && - ( ( (NULL != auth_token) && + ( ( (NULL != auth_password) && (GNUNET_OK == - TMH_check_auth (auth_token, + TMH_check_auth (auth_password, &mi->auth.auth_salt, &mi->auth.auth_hash)) ) || - ( (NULL == auth_token) && + ( (NULL == auth_password) && (GNUNET_YES == GNUNET_is_zero (&mi->auth.auth_hash))) ) && (1 == json_equal (mi->settings.address, @@ -244,7 +244,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh, } /* handle authentication token setup */ - if (NULL == auth_token) + if (NULL == auth_password) { memset (&ias.auth_salt, 0, @@ -256,7 +256,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh, else { /* Sets 'auth_salt' and 'auth_hash' */ - TMH_compute_auth (auth_token, + TMH_compute_auth (auth_password, &ias.auth_salt, &ias.auth_hash); }