challenger

OAuth 2.0-based authentication service that validates user can receive messages at a certain address
Log | Files | Refs | Submodules | README | LICENSE

commit 733e4c43afdf1cb2d46b1c05f0b43aef10bd03cb
parent dd72e4d0b0d108c7d90e1f22f1d2e2ef0c0271fe
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Thu, 17 Apr 2025 11:30:48 +0200

fix #9349

Diffstat:
Msrc/challenger/challenger-httpd.c | 3++-
Msrc/challenger/challenger-httpd_challenge.c | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/challenger/challenger-httpd_setup.c | 7+++++++
Msrc/challenger/challenger.conf | 6++++--
Msrc/challengerdb/Makefile.am | 1+
Msrc/challengerdb/challenger-0001.sql | 2--
Asrc/challengerdb/pg_address_get.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/challengerdb/pg_address_get.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Msrc/challengerdb/plugin_challengerdb_postgres.c | 3+++
Msrc/include/challenger_database_plugin.h | 17+++++++++++++++++
10 files changed, 245 insertions(+), 9 deletions(-)

diff --git a/src/challenger/challenger-httpd.c b/src/challenger/challenger-httpd.c @@ -445,9 +445,10 @@ handle_mhd_completion_callback (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Finished handling request with status %d (HTTP status %u)\n", (int) toe, - ci->http_status); + (NULL != ci) ? ci->http_status : 0); if (NULL != hc->cc) hc->cc (hc->ctx); + GNUNET_free (hc->full_url); GNUNET_free (hc); *con_cls = NULL; } diff --git a/src/challenger/challenger-httpd_challenge.c b/src/challenger/challenger-httpd_challenge.c @@ -34,6 +34,11 @@ #define MAX_RETRIES 3 /** + * Set to 1 to dump addresses into the log. + */ +#define DEBUG 0 + +/** * Context for a /challenge operation. */ struct ChallengeContext @@ -100,6 +105,11 @@ struct ChallengeContext char *state; /** + * Buffer used by #TALER_MHD_parse_post_json(). Or NULL. + */ + void *jbuffer; + + /** * When did we transmit last? */ struct GNUNET_TIME_Absolute last_tx_time; @@ -164,6 +174,11 @@ struct ChallengeContext * Did we do the DB interaction? */ bool db_finished; + + /** + * Is the upload in JSON? + */ + bool is_json; }; @@ -222,6 +237,7 @@ cleanup_ctx (void *cls) GNUNET_OS_process_wait (bc->child)); bc->child = NULL; } + TALER_MHD_parse_post_cleanup_callback (bc->jbuffer); json_decref (bc->address); GNUNET_free (bc->data); GNUNET_free (bc->state); @@ -321,10 +337,12 @@ send_tan (struct ChallengeContext *bc) address = json_dumps (bc->address, JSON_COMPACT); +#if DEBUG GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running auth command `%s' on address `%s'\n", CH_auth_command, address); +#endif bc->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, p, NULL, @@ -579,7 +597,6 @@ CH_handler_challenge (struct CH_HandlerContext *hc, hc->cc = &cleanup_ctx; hc->ctx = bc; bc->pst = GNUNET_OS_PROCESS_UNKNOWN; - bc->address = json_object (); bc->tan = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, 100000000); @@ -600,6 +617,21 @@ CH_handler_challenge (struct CH_HandlerContext *hc, TALER_EC_GENERIC_PARAMETER_MISSING, hc->path); } + { + const char *ct; + + ct = MHD_lookup_connection_value (hc->connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONTENT_TYPE); + bc->is_json = ( (NULL != ct) && + (0 == strcasecmp (ct, + "application/json")) ); + if (! bc->is_json) + bc->address = json_object (); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Processing /challenge upload with %s encoding...\n", + ct); + } TALER_MHD_check_content_length (hc->connection, 1024); GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -633,18 +665,38 @@ CH_handler_challenge (struct CH_HandlerContext *hc, es); } /* handle upload */ - if (0 != *upload_data_size) + if (bc->is_json) + { + if (NULL == bc->address) + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_post_json (hc->connection, + &bc->jbuffer, + upload_data, + upload_data_size, + &bc->address); + if (GNUNET_SYSERR != res) + return MHD_YES; + GNUNET_break (0); + return MHD_NO; + } + else + { + GNUNET_break (0 == *upload_data_size); + } + } + else if (0 != *upload_data_size) { enum MHD_Result res; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Processing /challenge upload...\n"); res = MHD_post_process (bc->pp, upload_data, *upload_data_size); *upload_data_size = 0; if (MHD_YES == res) return MHD_YES; + GNUNET_break (0); return MHD_NO; } if (NULL != bc->last_key) @@ -657,6 +709,7 @@ CH_handler_challenge (struct CH_HandlerContext *hc, bc->data_len = 0; GNUNET_free (bc->last_key); } +#if DEBUG { char *address; @@ -667,6 +720,7 @@ CH_handler_challenge (struct CH_HandlerContext *hc, address); free (address); } +#endif { const char *bad_field; @@ -686,8 +740,59 @@ CH_handler_challenge (struct CH_HandlerContext *hc, for (unsigned int r = 0; r < MAX_RETRIES; r++) { enum GNUNET_DB_QueryStatus qs; + json_t *old_address; + const json_t *ro; GNUNET_assert (NULL == bc->client_redirect_uri); + qs = CH_db->address_get ( + CH_db->cls, + &bc->nonce, + &old_address); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + return reply_error (bc, + "internal-error", + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "validation-get"); + case GNUNET_DB_STATUS_SOFT_ERROR: + if (r < MAX_RETRIES - 1) + continue; + GNUNET_break (0); + return reply_error (bc, + "internal-error", + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "validation-get"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break_op (0); + return reply_error (bc, + "validation-unknown", + MHD_HTTP_NOT_FOUND, + TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN, + NULL); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + ro = json_object_get (old_address, + "read_only"); + if ( (NULL != ro) && + (json_boolean_value (ro)) && + (1 != json_equal (old_address, + bc->address)) ) + { + GNUNET_break_op (0); + json_decref (old_address); + return reply_error (bc, + "address-read-only", + MHD_HTTP_FORBIDDEN, + TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_READ_ONLY, + NULL); + } + json_decref (old_address); + qs = CH_db->challenge_set_address_and_pin ( CH_db->cls, &bc->nonce, diff --git a/src/challenger/challenger-httpd_setup.c b/src/challenger/challenger-httpd_setup.c @@ -29,6 +29,11 @@ */ #define MAX_RETRIES 3 +/** + * Set to 1 to dump addresses into the log. + */ +#define DEBUG 0 + struct SetupContext { @@ -122,9 +127,11 @@ CH_handler_setup (struct CH_HandlerContext *hc, } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Address data uploaded in /setup\n"); +#if DEBUG json_dumpf (sc->root, stderr, JSON_INDENT (2)); +#endif } { diff --git a/src/challenger/challenger.conf b/src/challenger/challenger.conf @@ -32,6 +32,7 @@ VALIDATION_EXPIRATION = 365d # Base URL of our service. Must end with '/'. #BASE_URL = https://challenger.DOMAIN/ +BASE_URL = http://localhost:9967/ # Name of a file with the message to send with the challenge. MESSAGE_TEMPLATE_FILE = ${DATADIR}templates/default-challenge-message.txt @@ -39,11 +40,12 @@ MESSAGE_TEMPLATE_FILE = ${DATADIR}templates/default-challenge-message.txt # Which external command should be used to transmit challenges? # Example commands are challenger-send-{sms,email,post}.sh # AUTH_COMMAND = +AUTH_COMMAND = /usr/bin/true - -# What address type are we validating? (phone, email, address, etc.) +# What address type are we validating? (phone, email, postal, etc.) # A template of the form 'enter-$ADDRESS_TYPE-form' must # exist and the field names must be supported by the # AUTH_COMMAND. # # ADDRESS_TYPE = +ADDRESS_TYPE = postal diff --git a/src/challengerdb/Makefile.am b/src/challengerdb/Makefile.am @@ -71,6 +71,7 @@ libchallengerdb_la_LDFLAGS = \ -no-undefined libchallenger_plugin_db_postgres_la_SOURCES = \ + pg_address_get.h pg_address_get.c \ pg_client_add.h pg_client_add.c \ pg_client_modify.h pg_client_modify.c \ pg_client_delete.h pg_client_delete.c \ diff --git a/src/challengerdb/challenger-0001.sql b/src/challengerdb/challenger-0001.sql @@ -61,8 +61,6 @@ CREATE TABLE IF NOT EXISTS validations ,client_redirect_uri VARCHAR ); - - COMMENT ON TABLE validations IS 'Active validations where we send a challenge to an address of a user'; COMMENT ON COLUMN validations.client_serial_id diff --git a/src/challengerdb/pg_address_get.c b/src/challengerdb/pg_address_get.c @@ -0,0 +1,57 @@ +/* + This file is part of Challenger + Copyright (C) 2025 Taler Systems SA + + Challenger is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Challenger is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + Challenger; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file challengerdb/pg_address_get.c + * @brief Implementation of the address_get function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_address_get.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +CH_PG_address_get (void *cls, + const struct CHALLENGER_ValidationNonceP *nonce, + json_t **address) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (nonce), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("address", + address), + NULL), + GNUNET_PQ_result_spec_end + }; + + *address = NULL; + PREPARE (pg, + "address_get", + "SELECT " + " address" + " FROM validations" + " WHERE nonce=$1"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "address_get", + params, + rs); +} diff --git a/src/challengerdb/pg_address_get.h b/src/challengerdb/pg_address_get.h @@ -0,0 +1,45 @@ +/* + This file is part of Challenger + Copyright (C) 2025 Taler Systems SA + + Challenger is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Challenger is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + Challenger; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file challengerdb/pg_address_get.h + * @brief implementation of the address_get function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_ADDRESS_GET_H +#define PG_ADDRESS_GET_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "challenger_database_plugin.h" + + +/** + * Return address details. + * + * @param cls + * @param nonce unique nonce to use to identify the validation + * @param[out] address set to client-provided address (or to NULL) + * @return transaction status: + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the nonce was found + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we do not know the nonce + * #GNUNET_DB_STATUS_HARD_ERROR on failure + */ +enum GNUNET_DB_QueryStatus +CH_PG_address_get (void *cls, + const struct CHALLENGER_ValidationNonceP *nonce, + json_t **address); + +#endif diff --git a/src/challengerdb/plugin_challengerdb_postgres.c b/src/challengerdb/plugin_challengerdb_postgres.c @@ -26,6 +26,7 @@ #include "challenger_database_plugin.h" #include "challenger_database_lib.h" #include "pg_helper.h" +#include "pg_address_get.h" #include "pg_client_add.h" #include "pg_client_modify.h" #include "pg_client_delete.h" @@ -388,6 +389,8 @@ libchallenger_plugin_db_postgres_init (void *cls) = &postgres_commit_transaction; plugin->rollback = &postgres_rollback; + plugin->address_get + = &CH_PG_address_get; plugin->client_add = &CH_PG_client_add; plugin->client_modify diff --git a/src/include/challenger_database_plugin.h b/src/include/challenger_database_plugin.h @@ -393,6 +393,23 @@ struct CHALLENGER_DatabasePlugin /** + * Return address known for a particular nonce. + * + * @param cls + * @param nonce unique nonce to use to identify the validation + * @param[out] address set to client-provided address, can be set to NULL! + * @return transaction status: + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the nonce was found + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we do not know the nonce + * #GNUNET_DB_STATUS_HARD_ERROR on failure + */ + enum GNUNET_DB_QueryStatus + (*address_get)(void *cls, + const struct CHALLENGER_ValidationNonceP *nonce, + json_t **address); + + + /** * Return validation details including PKCE parameters. Used by `/solve`, `/auth`, and * `/info` endpoints to authorize and return validated user address to the client. *