commit 16d84e8a146dd3eacab92315ad2f654066e11066
parent d67d0a862d7a23e7d9a3f15d0920b1ba6450d7a6
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 6 Aug 2026 00:06:34 +0200
handle auth_hash and auth_salt are technically NULLable, so handle this accordingly
Diffstat:
3 files changed, 81 insertions(+), 9 deletions(-)
diff --git a/src/backenddb/get_instance_auth.c b/src/backenddb/get_instance_auth.c
@@ -34,13 +34,20 @@ TALER_MERCHANTDB_get_instance_auth (
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
+ bool no_hash;
+ bool no_salt;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("auth_hash",
- &ias->auth_hash),
- GNUNET_PQ_result_spec_auto_from_type ("auth_salt",
- &ias->auth_salt),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("auth_hash",
+ &ias->auth_hash),
+ &no_hash),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("auth_salt",
+ &ias->auth_salt),
+ &no_salt),
GNUNET_PQ_result_spec_end
};
+ enum GNUNET_DB_QueryStatus qs;
PREPARE (pg,
"get_instance_auth",
@@ -49,8 +56,18 @@ TALER_MERCHANTDB_get_instance_auth (
",auth_salt"
" FROM merchant.merchant_instances"
" WHERE merchant_id=$1");
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_instance_auth",
- params,
- rs);
+ memset (&ias->auth_hash,
+ 0,
+ sizeof (ias->auth_hash));
+ memset (&ias->auth_salt,
+ 0,
+ sizeof (ias->auth_salt));
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_instance_auth",
+ params,
+ rs);
+ /* 'auth_hash' and 'auth_salt' are nullable; the all-zero value we
+ left them at is how the rest of the code spells "no password
+ configured" (see TMH_check_auth()). */
+ return qs;
}
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
@@ -99,6 +99,7 @@
#include "merchant-database/iterate_webhooks_by_event.h"
#include "merchant-database/iterate_webhooks.h"
#include "merchant-database/get_exchange_wire_fee.h"
+#include "merchant-database/get_instance_auth.h"
#include "merchant-database/update_to_contract_terms_paid.h"
#include "merchant-database/update_to_contract_terms_wired.h"
#include "merchant-database/insert_refund_by_coin.h"
@@ -895,6 +896,57 @@ cleanup:
/**
+ * Tests that an instance whose 'auth_hash'/'auth_salt' are NULL is
+ * read back as "no password configured" instead of failing.
+ *
+ * Both columns are nullable, but the result spec did not allow NULL,
+ * so such a row made TALER_MERCHANTDB_get_instance_auth() return a
+ * hard error (HTTP 500 on every authenticated request of that
+ * instance). No code path in the tree writes NULL today -- the
+ * "no password" state is an all-zero hash -- so the NULL is set here
+ * directly via SQL.
+ *
+ * @param instance the instance to run the test against.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_get_instance_auth_null (const struct InstanceData *instance)
+{
+ struct TALER_MERCHANTDB_InstanceAuthSettings ias;
+ char sql[256];
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute (sql),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_snprintf (sql,
+ sizeof (sql),
+ "UPDATE merchant.merchant_instances"
+ " SET auth_hash=NULL"
+ " ,auth_salt=NULL"
+ " WHERE merchant_id='%s'",
+ instance->instance.id);
+ TEST_COND_RET_ON_FAIL (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (pg->conn,
+ es),
+ "Failed to NULL out the instance authentication\n");
+ memset (&ias,
+ 42,
+ sizeof (ias));
+ TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_get_instance_auth (
+ pg,
+ instance->instance.id,
+ &ias),
+ "Lookup of NULL instance authentication failed\n");
+ TEST_COND_RET_ON_FAIL (GNUNET_is_zero (&ias.auth_hash) &&
+ GNUNET_is_zero (&ias.auth_salt),
+ "NULL instance authentication was not zeroed out\n");
+ return 0;
+}
+
+
+/**
* Tests that storing a report always yields a fresh serial.
*
* 'merchant_reports' has no unique key other than the identity
@@ -1054,6 +1106,8 @@ run_test_instances (struct TestInstances_Closure *cls)
TEST_RET_ON_FAIL (test_reconnect_search_path (&cls->instances[0]));
/* Test that every stored report gets its own serial */
TEST_RET_ON_FAIL (test_insert_report_serial (&cls->instances[0]));
+ /* Test that a NULL authentication hash/salt is not an error */
+ TEST_RET_ON_FAIL (test_get_instance_auth_null (&cls->instances[0]));
/* Test instance private key deletion */
TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
diff --git a/src/include/merchant-database/get_instance_auth.h b/src/include/merchant-database/get_instance_auth.h
@@ -32,7 +32,8 @@ struct TALER_MERCHANTDB_PostgresContext;
*
* @param pg database context
* @param instance_id instance to query
- * @param[out] ias where to store the auth data
+ * @param[out] ias where to store the auth data; the hash and the salt
+ * are zeroed out if the instance has no password configured
*/
enum GNUNET_DB_QueryStatus
TALER_MERCHANTDB_get_instance_auth (struct TALER_MERCHANTDB_PostgresContext *pg,