diff options
67 files changed, 2652 insertions, 1172 deletions
diff --git a/configure.ac b/configure.ac index a35da3345..2df8674eb 100644 --- a/configure.ac +++ b/configure.ac @@ -532,6 +532,7 @@ AC_CONFIG_FILES([Makefile src/exchangedb/Makefile src/exchange-tools/Makefile src/extensions/Makefile + src/extensions/age_restriction/Makefile src/lib/Makefile src/kyclogic/Makefile src/testing/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 9fa748d7f..e10ecf8d8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -32,5 +32,6 @@ SUBDIRS = \ auditor \ lib \ exchange-tools \ + extensions/age_restriction \ testing \ benchmark diff --git a/src/auditor/taler-auditor-httpd_deposit-confirmation.c b/src/auditor/taler-auditor-httpd_deposit-confirmation.c index f4d89b7ca..c7bb4f509 100644 --- a/src/auditor/taler-auditor-httpd_deposit-confirmation.c +++ b/src/auditor/taler-auditor-httpd_deposit-confirmation.c @@ -227,7 +227,7 @@ verify_and_execute_deposit_confirmation ( TALER_exchange_online_deposit_confirmation_verify ( &dc->h_contract_terms, &dc->h_wire, - NULL /* h_extensions! */, + &dc->h_policy, dc->exchange_timestamp, dc->wire_deadline, dc->refund_deadline, @@ -276,8 +276,8 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &dc.h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("h_extensions", - &dc.h_extensions), + GNUNET_JSON_spec_fixed_auto ("h_policy", + &dc.h_policy), GNUNET_JSON_spec_fixed_auto ("h_wire", &dc.h_wire), GNUNET_JSON_spec_timestamp ("exchange_timestamp", diff --git a/src/auditor/taler-auditor-sync.c b/src/auditor/taler-auditor-sync.c index 3c4c7e4c7..a7ccf800a 100644 --- a/src/auditor/taler-auditor-sync.c +++ b/src/auditor/taler-auditor-sync.c @@ -117,7 +117,8 @@ static struct Table tables[] = { { .rt = TALER_EXCHANGEDB_RT_RECOUP}, { .rt = TALER_EXCHANGEDB_RT_RECOUP_REFRESH }, { .rt = TALER_EXCHANGEDB_RT_EXTENSIONS}, - { .rt = TALER_EXCHANGEDB_RT_EXTENSION_DETAILS }, + { .rt = TALER_EXCHANGEDB_RT_POLICY_DETAILS }, + { .rt = TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS }, { .rt = TALER_EXCHANGEDB_RT_PURSE_REQUESTS}, { .rt = TALER_EXCHANGEDB_RT_PURSE_DECISION}, { .rt = TALER_EXCHANGEDB_RT_PURSE_MERGES}, diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c index da0ec99ec..7637e463d 100644 --- a/src/auditor/taler-helper-auditor-coins.c +++ b/src/auditor/taler-helper-auditor-coins.c @@ -1566,7 +1566,7 @@ deposit_cb (void *cls, &h_wire, &deposit->h_contract_terms, &deposit->coin.h_age_commitment, - NULL /* FIXME-Oec: #7270: h_extensions! */, + &deposit->h_policy, &h_denom_pub, deposit->timestamp, &deposit->merchant_pub, diff --git a/src/auditor/test-auditor.sh b/src/auditor/test-auditor.sh index 34a3980d9..0390c206b 100755 --- a/src/auditor/test-auditor.sh +++ b/src/auditor/test-auditor.sh @@ -87,7 +87,7 @@ function stop_libeufin() # Cleanup exchange and libeufin between runs. function cleanup() { - if test ! -z ${EPID:-} + if test ! -z "${EPID:-}" then echo -n "Stopping exchange $EPID..." kill -TERM $EPID @@ -102,7 +102,7 @@ function cleanup() function exit_cleanup() { echo "Running exit-cleanup" - if test ! -z ${POSTGRES_PATH:-} + if test ! -z "${POSTGRES_PATH:-}" then echo "Stopping Postgres at ${POSTGRES_PATH}" ${POSTGRES_PATH}/pg_ctl -D $TMPDIR -l /dev/null stop &> /dev/null || true @@ -2047,10 +2047,10 @@ taler-wallet-cli -h >/dev/null </dev/null 2>/dev/null || exit_skip "taler-wallet echo -n "Testing for Postgres" # Available directly in path? INITDB_BIN=$(command -v initdb) || true -if [[ ! -z $INITDB_BIN ]]; then +if [[ ! -z "$INITDB_BIN" ]]; then echo " FOUND (in path) at" $INITDB_BIN else - HAVE_INITDB=`find /usr -name "initdb" 2> /dev/null | grep postgres` || exit_skip " MISSING" + HAVE_INITDB=`find /usr -name "initdb" | head -1 2> /dev/null | grep postgres` || exit_skip " MISSING" echo " FOUND at" `dirname $HAVE_INITDB` INITDB_BIN=`echo $HAVE_INITDB | grep bin/initdb | grep postgres | sort -n | tail -n1` fi diff --git a/src/auditor/test-revocation.sh b/src/auditor/test-revocation.sh index 22d1c86ab..2751b5c20 100755 --- a/src/auditor/test-revocation.sh +++ b/src/auditor/test-revocation.sh @@ -81,7 +81,7 @@ function stop_libeufin() # Cleanup to run whenever we exit function cleanup() { - if test ! -z ${EPID:-} + if test ! -z "${EPID:-}" then echo -n "Stopping exchange $EPID..." kill -TERM $EPID @@ -96,7 +96,7 @@ function cleanup() function exit_cleanup() { echo "Running exit-cleanup" - if test ! -z ${POSTGRES_PATH:-} + if test -z "${POSTGRES_PATH:-}" then echo "Stopping Postgres at ${POSTGRES_PATH}" ${POSTGRES_PATH}/pg_ctl -D $TMPDIR -l /dev/null stop &> /dev/null || true @@ -640,10 +640,10 @@ taler-wallet-cli -h >/dev/null </dev/null 2>/dev/null || exit_skip "taler-wallet echo -n "Testing for Postgres" # Available directly in path? INITDB_BIN=$(command -v initdb) || true -if [[ ! -z $INITDB_BIN ]]; then +if [[ ! -z "$INITDB_BIN" ]]; then echo " FOUND (in path) at" $INITDB_BIN else - HAVE_INITDB=`find /usr -name "initdb" 2> /dev/null | grep postgres` || exit_skip " MISSING" + HAVE_INITDB=`find /usr -name "initdb" | head -1 2> /dev/null | grep postgres` || exit_skip " MISSING" echo " FOUND at" `dirname $HAVE_INITDB` INITDB_BIN=`echo $HAVE_INITDB | grep bin/initdb | grep postgres | sort -n | tail -n1` fi diff --git a/src/auditor/test-sync.sh b/src/auditor/test-sync.sh index 174e03c6e..92fe949f7 100755 --- a/src/auditor/test-sync.sh +++ b/src/auditor/test-sync.sh @@ -32,7 +32,7 @@ function exit_fail() { # Cleanup to run whenever we exit function cleanup() { - if test ! -z ${POSTGRES_PATH:-} + if test ! -z "${POSTGRES_PATH:-}" then ${POSTGRES_PATH}/pg_ctl -D $TMPDIR stop &> /dev/null || true fi @@ -111,10 +111,10 @@ taler-wallet-cli -h >/dev/null </dev/null 2>/dev/null || exit_skip "taler-wallet echo -n "Testing for Postgres" # Available directly in path? INITDB_BIN=$(command -v initdb) || true -if [[ ! -z $INITDB_BIN ]]; then +if [[ ! -z "$INITDB_BIN" ]]; then echo " FOUND (in path) at" $INITDB_BIN else - HAVE_INITDB=`find /usr -name "initdb" 2> /dev/null | grep postgres` || exit_skip " MISSING" + HAVE_INITDB=`find /usr -name "initdb" | head -1 2> /dev/null | grep postgres` || exit_skip " MISSING" echo " FOUND at" `dirname $HAVE_INITDB` INITDB_BIN=`echo $HAVE_INITDB | grep bin/initdb | grep postgres | sort -n | tail -n1` fi diff --git a/src/auditordb/auditor-0001.sql b/src/auditordb/auditor-0001.sql index a19655d42..1fdbe78c4 100644 --- a/src/auditordb/auditor-0001.sql +++ b/src/auditordb/auditor-0001.sql @@ -315,7 +315,7 @@ CREATE TABLE IF NOT EXISTS deposit_confirmations (master_pub BYTEA NOT NULL CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE ,serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64) - ,h_extensions BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64) + ,h_policy BYTEA NOT NULL CHECK (LENGTH(h_policy)=64) ,h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64) ,exchange_timestamp INT8 NOT NULL ,refund_deadline INT8 NOT NULL diff --git a/src/auditordb/pg_get_deposit_confirmations.c b/src/auditordb/pg_get_deposit_confirmations.c index 6c7fe73c4..3f0bd1e2f 100644 --- a/src/auditordb/pg_get_deposit_confirmations.c +++ b/src/auditordb/pg_get_deposit_confirmations.c @@ -87,8 +87,8 @@ deposit_confirmation_cb (void *cls, &serial_id), GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", &dc.h_contract_terms), - GNUNET_PQ_result_spec_auto_from_type ("h_extensions", - &dc.h_extensions), + GNUNET_PQ_result_spec_auto_from_type ("h_policy", + &dc.h_policy), GNUNET_PQ_result_spec_auto_from_type ("h_wire", &dc.h_wire), GNUNET_PQ_result_spec_timestamp ("exchange_timestamp", @@ -158,7 +158,7 @@ TAH_PG_get_deposit_confirmations ( "SELECT" " serial_id" ",h_contract_terms" - ",h_extensions" + ",h_policy" ",h_wire" ",exchange_timestamp" ",wire_deadline" diff --git a/src/auditordb/pg_insert_deposit_confirmation.c b/src/auditordb/pg_insert_deposit_confirmation.c index cc52ba4e5..675f8ed0d 100644 --- a/src/auditordb/pg_insert_deposit_confirmation.c +++ b/src/auditordb/pg_insert_deposit_confirmation.c @@ -35,7 +35,7 @@ TAH_PG_insert_deposit_confirmation ( struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (&dc->master_public_key), GNUNET_PQ_query_param_auto_from_type (&dc->h_contract_terms), - GNUNET_PQ_query_param_auto_from_type (&dc->h_extensions), + GNUNET_PQ_query_param_auto_from_type (&dc->h_policy), GNUNET_PQ_query_param_auto_from_type (&dc->h_wire), GNUNET_PQ_query_param_timestamp (&dc->exchange_timestamp), GNUNET_PQ_query_param_timestamp (&dc->wire_deadline), @@ -54,7 +54,7 @@ TAH_PG_insert_deposit_confirmation ( "INSERT INTO deposit_confirmations " "(master_pub" ",h_contract_terms" - ",h_extensions" + ",h_policy" ",h_wire" ",exchange_timestamp" ",wire_deadline" diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index 26ddf738d..a8a25bd3c 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -138,6 +138,12 @@ static struct GNUNET_CURL_RescheduleContext *rc; static const struct GNUNET_CONFIGURATION_Handle *kcfg; /** + * Age restriction configuration + */ +static bool ar_enabled = false; +static struct TALER_AgeRestrictionConfig ar_config = {0}; + +/** * Return value from main(). */ static int global_ret; @@ -164,11 +170,6 @@ static char *currency; static char *CFG_exchange_url; /** - * If age restriction is enabled, the age mask to be used - */ -static struct TALER_AgeMask age_mask = {0}; - -/** * A subcommand supported by this program. */ struct SubCommand @@ -2159,14 +2160,14 @@ upload_extensions (const char *exchange_url, /* 2. Verify the signature */ { - struct TALER_ExtensionConfigHashP h_config; + struct TALER_ExtensionManifestsHashP h_manifests; if (GNUNET_OK != - TALER_JSON_extensions_config_hash (extensions, &h_config)) + TALER_JSON_extensions_manifests_hash (extensions, &h_manifests)) { GNUNET_JSON_parse_free (spec); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "couldn't hash extensions\n"); + "couldn't hash extensions' manifests\n"); global_ret = EXIT_FAILURE; test_shutdown (); return; @@ -2176,8 +2177,8 @@ upload_extensions (const char *exchange_url, load_offline_key (GNUNET_NO)) return; - if (GNUNET_OK != TALER_exchange_offline_extension_config_hash_verify ( - &h_config, + if (GNUNET_OK != TALER_exchange_offline_extension_manifests_hash_verify ( + &h_manifests, &master_pub, &sig)) { @@ -3858,7 +3859,7 @@ load_age_mask (const char*section_name) static const struct TALER_AgeMask null_mask = {0}; enum GNUNET_GenericReturnValue ret; - if (age_mask.bits == 0) + if (! ar_enabled) return null_mask; if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value ( @@ -3870,14 +3871,14 @@ load_age_mask (const char*section_name) ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg, section_name, "AGE_RESTRICTED"); - if (GNUNET_YES == ret) - return age_mask; - if (GNUNET_SYSERR == ret) GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section_name, "AGE_RESTRICTED", "Value must be YES or NO\n"); + if (GNUNET_YES == ret) + return ar_config.mask; + return null_mask; } @@ -4218,18 +4219,24 @@ do_setup (char *const *args) static void do_extensions_show (char *const *args) { - const struct TALER_Extension *it; + const struct TALER_Extensions *it; json_t *exts = json_object (); json_t *obj; GNUNET_assert (NULL != exts); for (it = TALER_extensions_get_head (); - NULL != it; + NULL != it && NULL != it->extension; it = it->next) - GNUNET_assert (0 == - json_object_set_new (exts, - it->name, - it->config_to_json (it))); + { + const struct TALER_Extension *extension = it->extension; + int ret; + + ret = json_object_set_new (exts, + extension->name, + extension->manifest (extension)); + GNUNET_assert (-1 != ret); + } + obj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_object_steal ("extensions", exts)); @@ -4238,7 +4245,7 @@ do_extensions_show (char *const *args) json_dumps (obj, JSON_INDENT (2))); json_decref (obj); - next (args + 1); + next (args); } @@ -4249,35 +4256,38 @@ static void do_extensions_sign (char *const *args) { json_t *extensions = json_object (); - struct TALER_ExtensionConfigHashP h_config; + struct TALER_ExtensionManifestsHashP h_manifests; struct TALER_MasterSignatureP sig; - const struct TALER_Extension *it; + const struct TALER_Extensions *it; + bool found = false; json_t *obj; GNUNET_assert (NULL != extensions); - if (GNUNET_OK != - TALER_extensions_load_taler_config (kcfg)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "error while loading taler config for extensions\n"); - json_decref (extensions); - return; - } for (it = TALER_extensions_get_head (); - NULL != it; + NULL != it && NULL != it->extension; it = it->next) + { + const struct TALER_Extension *ext = it->extension; + GNUNET_assert (ext); + + found = true; + GNUNET_assert (0 == json_object_set_new (extensions, - it->name, - it->config_to_json (it))); + ext->name, + ext->manifest (ext))); + } + + if (! found) + return; if (GNUNET_OK != - TALER_JSON_extensions_config_hash (extensions, - &h_config)) + TALER_JSON_extensions_manifests_hash (extensions, + &h_manifests)) { json_decref (extensions); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "error while hashing config for extensions\n"); + "error while hashing manifest for extensions\n"); return; } @@ -4288,18 +4298,19 @@ do_extensions_sign (char *const *args) return; } - TALER_exchange_offline_extension_config_hash_sign (&h_config, - &master_priv, - &sig); + TALER_exchange_offline_extension_manifests_hash_sign (&h_manifests, + &master_priv, + &sig); obj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_object_steal ("extensions", extensions), GNUNET_JSON_pack_data_auto ( "extensions_sig", &sig)); + output_operation (OP_EXTENSIONS, obj); - next (args + 1); + next (args); } @@ -4500,25 +4511,31 @@ run (void *cls, (void) cls; (void) cfgfile; kcfg = cfg; - if (GNUNET_OK != - TALER_config_get_currency (kcfg, - ¤cy)) + + /* load extensions */ + GNUNET_assert (GNUNET_OK == + TALER_extensions_init (kcfg)); + + /* setup age restriction, if applicable */ { - global_ret = EXIT_NOTCONFIGURED; - return; + const struct TALER_AgeRestrictionConfig *arc; + + if (NULL != + (arc = TALER_extensions_get_age_restriction_config ())) + { + ar_config = *arc; + ar_enabled = true; + } } - /* load age mask, if age restriction is enabled */ - GNUNET_assert (GNUNET_OK == - TALER_extension_age_restriction_register ()); - if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg)) + if (GNUNET_OK != + TALER_config_get_currency (kcfg, + ¤cy)) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "error while loading taler config for extensions\n"); + global_ret = EXIT_NOTCONFIGURED; return; } - age_mask = TALER_extensions_age_restriction_ageMask (); ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &rc); diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 34f93879a..691e1ef7d 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -102,6 +102,14 @@ static int allow_address_reuse; const struct GNUNET_CONFIGURATION_Handle *TEH_cfg; /** + * Configuration of age restriction + * + * Set after loading the library, enabled in database event handler. + */ +bool TEH_age_restriction_enabled = false; +struct TALER_AgeRestrictionConfig TEH_age_restriction_config = {0}; + +/** * Handle to the HTTP server. */ static struct MHD_Daemon *mhd; @@ -143,11 +151,6 @@ char *TEH_currency; char *TEH_base_url; /** - * Age restriction flags and mask - */ -bool TEH_age_restriction_enabled = true; - -/** * Default timeout in seconds for HTTP requests. */ static unsigned int connection_timeout = 30; @@ -174,6 +177,7 @@ bool TEH_suicide; * TALER_SIGNATURE_MASTER_EXTENSION. */ struct TALER_MasterSignatureP TEH_extensions_sig; +bool TEH_extensions_signed = false; /** * Value to return from main() diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h index 0fda5ed8d..4d3fb4901 100644 --- a/src/exchange/taler-exchange-httpd.h +++ b/src/exchange/taler-exchange-httpd.h @@ -197,11 +197,6 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin; */ extern char *TEH_currency; -/* - * Age restriction extension state - */ -extern bool TEH_age_restriction_enabled; - /** * Our (externally visible) base URL. */ @@ -221,6 +216,7 @@ extern struct GNUNET_CURL_Context *TEH_curl_ctx; * Signature of the offline master key of all enabled extensions' configuration */ extern struct TALER_MasterSignatureP TEH_extensions_sig; +extern bool TEH_extensions_signed; /** * @brief Struct describing an URL and the handler for it. @@ -366,4 +362,8 @@ struct TEH_RequestHandler }; +/* Age restriction configuration */ +extern bool TEH_age_restriction_enabled; +extern struct TALER_AgeRestrictionConfig TEH_age_restriction_config; + #endif diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c index c2a9cbd54..4d4197ab6 100644 --- a/src/exchange/taler-exchange-httpd_batch-deposit.c +++ b/src/exchange/taler-exchange-httpd_batch-deposit.c @@ -87,15 +87,27 @@ struct BatchDepositContext const char *payto_uri; /** - * Additional details for extensions relevant for this + * Additional details for policy extension relevant for this * deposit operation, possibly NULL! */ - json_t *extension_details; + json_t *policy_json; /** - * Hash over @e extension_details. + * Will be true if policy_json were provided */ - struct TALER_ExtensionContractHashP h_extensions; + bool has_policy; + + /** + * If @e policy_json was present, the corresponding policy extension + * calculates these details. These will be persisted in the policy_details + * table. + */ + struct TALER_PolicyDetails policy_details; + + /** + * Hash over @e policy_details. + */ + struct TALER_ExtensionPolicyHashP h_policy; /** * Time when this request was generated. Used, for example, to @@ -173,7 +185,7 @@ again: &TEH_keys_exchange_sign_, &bdc->h_contract_terms, &bdc->h_wire, - &bdc->h_extensions, + bdc->has_policy ? &bdc->h_policy: NULL, bdc->exchange_timestamp, bdc->wire_deadline, bdc->refund_deadline, @@ -242,7 +254,7 @@ batch_deposit_transaction (void *cls, MHD_RESULT *mhd_ret) { struct BatchDepositContext *dc = cls; - enum GNUNET_DB_QueryStatus qs; + enum GNUNET_DB_QueryStatus qs = GNUNET_SYSERR; bool balance_ok; bool in_conflict; @@ -469,18 +481,19 @@ parse_coin (struct MHD_Connection *connection, TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != - TALER_wallet_deposit_verify (&deposit->amount_with_fee, - &deposit->deposit_fee, - &dc->h_wire, - &dc->h_contract_terms, - &deposit->coin.h_age_commitment, - &dc->h_extensions, - &deposit->coin.denom_pub_hash, - dc->timestamp, - &dc->merchant_pub, - dc->refund_deadline, - &deposit->coin.coin_pub, - &deposit->csig)) + TALER_wallet_deposit_verify ( + &deposit->amount_with_fee, + &deposit->deposit_fee, + &dc->h_wire, + &dc->h_contract_terms, + &deposit->coin.h_age_commitment, + dc->has_policy ? &dc->h_policy : NULL, + &deposit->coin.denom_pub_hash, + dc->timestamp, + &dc->merchant_pub, + dc->refund_deadline, + &deposit->coin.coin_pub, + &deposit->csig)) { TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n"); GNUNET_JSON_parse_free (spec); @@ -496,11 +509,6 @@ parse_coin (struct MHD_Connection *connection, deposit->h_contract_terms = dc->h_contract_terms; deposit->wire_salt = dc->wire_salt; deposit->receiver_wire_account = (char *) dc->payto_uri; - /* FIXME-OEC: #7270 should NOT insert the extension details N times, - but rather insert them ONCE and then per-coin only use - the resulting extension UUID/serial; so the data structure - here should be changed once we look at extensions in earnest. */ - deposit->extension_details = dc->extension_details; deposit->timestamp = dc->timestamp; deposit->refund_deadline = dc->refund_deadline; deposit->wire_deadline = dc->wire_deadline; @@ -517,7 +525,7 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, struct BatchDepositContext dc; json_t *coins; bool no_refund_deadline = true; - bool no_extensions = true; + bool no_policy_json = true; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("merchant_payto_uri", &dc.payto_uri), @@ -530,9 +538,9 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, GNUNET_JSON_spec_json ("coins", &coins), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("extension_details", - &dc.extension_details), - &no_extensions), + GNUNET_JSON_spec_json ("policy", + &dc.policy_json), + &no_policy_json), GNUNET_JSON_spec_timestamp ("timestamp", &dc.timestamp), GNUNET_JSON_spec_mark_optional ( @@ -563,6 +571,8 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, return MHD_YES; /* failure */ } + dc.has_policy = ! no_policy_json; + /* validate merchant's wire details (as far as we can) */ { char *emsg; @@ -607,11 +617,26 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, TALER_merchant_wire_signature_hash (dc.payto_uri, &dc.wire_salt, &dc.h_wire); - /* FIXME-OEC: #7270 hash actual extension JSON object here */ - // if (! no_extensions) - memset (&dc.h_extensions, - 0, - sizeof (dc.h_extensions)); + + /* handle policy, if present */ + if (dc.has_policy) + { + const char *error_hint = NULL; + + if (GNUNET_OK != + TALER_extensions_create_policy_details ( + dc.policy_json, + &dc.policy_details, + &error_hint)) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED, + error_hint); + + TALER_deposit_policy_hash (dc.policy_json, + &dc.h_policy); + } + dc.num_coins = json_array_size (coins); if (0 == dc.num_coins) { @@ -635,12 +660,32 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, struct TALER_EXCHANGEDB_Deposit); for (unsigned int i = 0; i<dc.num_coins; i++) { - if (GNUNET_OK != - (res = parse_coin (connection, - json_array_get (coins, - i), - &dc, - &dc.deposits[i]))) + do { + res = parse_coin (connection, + json_array_get (coins, i), + &dc, + &dc.deposits[i]); + if (GNUNET_OK != res) + break; + + /* If applicable, accumulate all contributions into the policy_details */ + if (dc.has_policy) + { + /* FIXME: how do deposit-fee and policy-fee interact? */ + struct TALER_Amount amount_without_fee; + + res = TALER_amount_subtract (&amount_without_fee, + &dc.deposits[i].amount_with_fee, + &dc.deposits[i].deposit_fee + ); + res = TALER_amount_add ( + &dc.policy_details.accumulated_total, + &dc.policy_details.accumulated_total, + &amount_without_fee); + } + } while(0); + + if (GNUNET_OK != res) { for (unsigned int j = 0; j<i; j++) TALER_denom_sig_free (&dc.deposits[j].coin.denom_sig); diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index 0484ab071..455888a89 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -21,6 +21,7 @@ * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff + * @author Özgür Kesim */ #include "platform.h" #include <gnunet/gnunet_util_lib.h> @@ -47,7 +48,7 @@ * @param connection connection to the client * @param coin_pub public key of the coin * @param h_wire hash of wire details - * @param h_extensions hash of applicable extensions + * @param h_policy hash of applicable policy extension * @param h_contract_terms hash of contract details * @param exchange_timestamp exchange's timestamp * @param refund_deadline until when this deposit be refunded @@ -61,7 +62,7 @@ reply_deposit_success ( struct MHD_Connection *connection, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, const struct TALER_PrivateContractHashP *h_contract_terms, struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp refund_deadline, @@ -78,7 +79,7 @@ reply_deposit_success ( &TEH_keys_exchange_sign_, h_contract_terms, h_wire, - h_extensions, + h_policy, exchange_timestamp, wire_deadline, refund_deadline, @@ -131,6 +132,29 @@ struct DepositContext */ uint64_t known_coin_id; + /* + * True if @e policy_json was provided + */ + bool has_policy; + + /** + * If @e has_policy is true, the corresponding policy extension calculates + * these details. These will be persisted in the policy_details table. + */ + struct TALER_PolicyDetails policy_details; + + /** + * Hash over the policy data for this deposit (remains unknown to the + * Exchange). Needed for the verification of the deposit's signature + */ + struct TALER_ExtensionPolicyHashP h_policy; + + /** + * When has_policy is true, and deposit->policy_details are + * persisted, this contains the id of the record in the policy_details table. + */ + uint64_t policy_details_serial_id; + }; @@ -163,14 +187,35 @@ deposit_transaction (void *cls, mhd_ret); if (qs < 0) return qs; - qs = TEH_plugin->do_deposit (TEH_plugin->cls, - dc->deposit, - dc->known_coin_id, - &dc->h_payto, - false, /* FIXME-OEC: extension blocked #7270 */ - &dc->exchange_timestamp, - &balance_ok, - &in_conflict); + + + /* If the deposit has a policy associated to it, persist it. This will + * insert or update the record. */ + if (dc->has_policy) + { + qs = TEH_plugin->persist_policy_details ( + TEH_plugin->cls, + &dc->policy_details, + &dc->policy_details_serial_id, + &dc->policy_details.accumulated_total, + &dc->policy_details.fulfillment_state); + + if (qs < 0) + return qs; + } + + + qs = TEH_plugin->do_deposit ( + TEH_plugin->cls, + dc->deposit, + dc->known_coin_id, + &dc->h_payto, + (dc->has_policy) + ? &dc->policy_details_serial_id + : NULL, + &dc->exchange_timestamp, + &balance_ok, + &in_conflict); if (qs < 0) { if (GNUNET_DB_STATUS_SOFT_ERROR == qs) @@ -216,6 +261,8 @@ TEH_handler_deposit (struct MHD_Connection *connection, struct DepositContext dc; struct TALER_EXCHANGEDB_Deposit deposit; const char *payto_uri; + struct TALER_ExtensionPolicyHashP *ph_policy = NULL; + bool no_policy_json; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("merchant_payto_uri", &payto_uri), @@ -240,12 +287,18 @@ TEH_handler_deposit (struct MHD_Connection *connection, &deposit.csig), GNUNET_JSON_spec_timestamp ("timestamp", &deposit.timestamp), + /* TODO: refund_deadline and merchant_pub will move into the + * extension policy_merchant_refunds */ GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_timestamp ("refund_deadline", &deposit.refund_deadline), NULL), GNUNET_JSON_spec_timestamp ("wire_transfer_deadline", &deposit.wire_deadline), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("policy", + &dc.policy_details.policy_json), + &no_policy_json), GNUNET_JSON_spec_end () }; struct TALER_MerchantWireHashP h_wire; @@ -271,6 +324,9 @@ TEH_handler_deposit (struct MHD_Connection *connection, return MHD_YES; /* failure */ } } + + dc.has_policy = ! no_policy_json; + /* validate merchant's wire details (as far as we can) */ { char *emsg; @@ -419,6 +475,26 @@ TEH_handler_deposit (struct MHD_Connection *connection, NULL); } + /* Check policy input and create policy details */ + if (dc.has_policy) + { + const char *error_hint = NULL; + + if (GNUNET_OK != + TALER_extensions_create_policy_details ( + dc.policy_details.policy_json, + &dc.policy_details, + &error_hint)) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED, + error_hint); + + TALER_deposit_policy_hash (dc.policy_details.policy_json, + &dc.h_policy); + ph_policy = &dc.h_policy; + } + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != TALER_wallet_deposit_verify (&deposit.amount_with_fee, @@ -426,7 +502,7 @@ TEH_handler_deposit (struct MHD_Connection *connection, &h_wire, &deposit.h_contract_terms, &deposit.coin.h_age_commitment, - NULL /* FIXME: h_extensions! */, + ph_policy, &deposit.coin.denom_pub_hash, deposit.timestamp, &deposit.merchant_pub, @@ -481,7 +557,7 @@ TEH_handler_deposit (struct MHD_Connection *connection, res = reply_deposit_success (connection, &deposit.coin.coin_pub, &h_wire, - NULL /* FIXME: h_extensions! */, + ph_policy, &deposit.h_contract_terms, dc.exchange_timestamp, deposit.refund_deadline, diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c index d6c26f6f4..30d1c5ac9 100644 --- a/src/exchange/taler-exchange-httpd_extensions.c +++ b/src/exchange/taler-exchange-httpd_extensions.c @@ -14,7 +14,7 @@ */ /** * @file taler-exchange-httpd_extensions.c - * @brief Handle extensions (age-restriction, peer2peer) + * @brief Handle extensions (age-restriction, policy extensions) * @author Özgür Kesim */ #include "platform.h" @@ -77,65 +77,84 @@ extension_update_event_cb (void *cls, return; } - // Get the config from the database as string + // Get the manifest from the database as string { - char *config_str = NULL; + char *manifest_str = NULL; enum GNUNET_DB_QueryStatus qs; json_error_t err; - json_t *config; + json_t *manifest_js; enum GNUNET_GenericReturnValue ret; - qs = TEH_plugin->get_extension_config (TEH_plugin->cls, - extension->name, - &config_str); + qs = TEH_plugin->get_extension_manifest (TEH_plugin->cls, + extension->name, + &manifest_str); if (qs < 0) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Couldn't get extension config\n"); + "Couldn't get extension manifest\n"); GNUNET_break (0); return; } // No config found -> disable extension - if (NULL == config_str) + if (NULL == manifest_str) { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No manifest found for extension %s, disabling it\n", + extension->name); extension->disable ((struct TALER_Extension *) extension); return; } // Parse the string as JSON - config = json_loads (config_str, JSON_DECODE_ANY, &err); - if (NULL == config) + manifest_js = json_loads (manifest_str, JSON_DECODE_ANY, &err); + if (NULL == manifest_js) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse config for extension `%s' as JSON: %s (%s)\n", + "Failed to parse manifest for extension `%s' as JSON: %s (%s)\n", extension->name, err.text, err.source); GNUNET_break (0); + free (manifest_js); return; } // Call the parser for the extension - ret = extension->load_json_config ( + ret = extension->load_config ( (struct TALER_Extension *) extension, - config); + json_object_get (manifest_js, "config")); if (GNUNET_OK != ret) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Couldn't parse configuration for extension %s from the database", - extension->name); + "Couldn't parse configuration for extension %s from the manifest in the database: %s\n", + extension->name, + manifest_str); GNUNET_break (0); } + + free (manifest_str); + json_decref (manifest_js); } /* Special case age restriction: Update global flag and mask */ if (TALER_Extension_AgeRestriction == type) { - TEH_age_restriction_enabled = - TALER_extensions_age_restriction_is_enabled (); + const struct TALER_AgeRestrictionConfig *conf = + TALER_extensions_get_age_restriction_config (); + TEH_age_restriction_enabled = false; + if (NULL != conf) + { + TEH_age_restriction_enabled = true; + TEH_age_restriction_config = *conf; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "[age restriction] DB event has changed the config to %s with mask: %s\n", + TEH_age_restriction_enabled ? "enabled": "DISABLED", + TALER_age_mask_to_string (&conf->mask)); + } } @@ -143,14 +162,30 @@ extension_update_event_cb (void *cls, enum GNUNET_GenericReturnValue TEH_extensions_init () { - GNUNET_assert (GNUNET_OK == - TALER_extension_age_restriction_register ()); - /* Set the event handler for updates */ struct GNUNET_DB_EventHeaderP ev = { .size = htons (sizeof (ev)), .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED), }; + + /* Load the shared libraries first */ + if (GNUNET_OK != + TALER_extensions_init (TEH_cfg)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "failed to load extensions"); + return GNUNET_SYSERR; + } + + /* Check for age restriction */ + { + const struct TALER_AgeRestrictionConfig *arc; + + if (NULL != + (arc = TALER_extensions_get_age_restriction_config ())) + TEH_age_restriction_config = *arc; + } + extensions_eh = TEH_plugin->event_listen (TEH_plugin->cls, GNUNET_TIME_UNIT_FOREVER_REL, &ev, @@ -162,17 +197,24 @@ TEH_extensions_init () return GNUNET_SYSERR; } - /* FIXME #7270: shall we load the extensions from the config right away? - * We do have to for now, as otherwise denominations with age restriction - * will not have the age mask set right upon initial generation. - */ - TALER_extensions_load_taler_config (TEH_cfg); - /* Trigger the initial load of configuration from the db */ - for (const struct TALER_Extension *it = TALER_extensions_get_head (); - NULL != it->next; + for (const struct TALER_Extensions *it = TALER_extensions_get_head (); + NULL != it && NULL != it->extension; it = it->next) - extension_update_event_cb (NULL, &it->type, sizeof(it->type)); + { + const struct TALER_Extension *ext = it->extension; + uint32_t typ = htonl (ext->type); + char *manifest = json_dumps (ext->manifest (ext), JSON_COMPACT); + + TEH_plugin->set_extension_manifest (TEH_plugin->cls, + ext->name, + manifest); + + extension_update_event_cb (NULL, + &typ, + sizeof(typ)); + free (manifest); + } return GNUNET_OK; } @@ -190,4 +232,168 @@ TEH_extensions_done () } +/* + * @brief Execute database transactions for /extensions/policy_* POST requests. + * + * @param cls a `struct TALER_PolicyFulfillmentOutcome` + * @param connection MHD request context + * @param[out] mhd_ret set to MHD status on error + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +policy_fulfillment_transaction ( + void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) +{ + struct TALER_PolicyFulfillmentTransactionData *fulfillment = cls; + + return TEH_plugin->add_policy_fulfillment_proof (TEH_plugin->cls, + fulfillment); +} + + +MHD_RESULT +TEH_extensions_post_handler ( + struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[]) +{ + const struct TALER_Extension *ext = NULL; + json_t *output; + struct TALER_PolicyDetails *policy_details = NULL; + size_t policy_details_count = 0; + + + if (NULL == args[0]) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, + "/extensions/$EXTENSION"); + } + + ext = TALER_extensions_get_by_name (args[0]); + if (NULL == ext) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, + "/extensions/$EXTENSION unknown"); + } + + if (NULL == ext->policy_post_handler) + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_IMPLEMENTED, + TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, + "POST /extensions/$EXTENSION not supported"); + + /* Extract hash_codes and retrieve related policy_details from the DB */ + { + enum GNUNET_GenericReturnValue ret; + enum GNUNET_DB_QueryStatus qs; + const char *error_msg; + struct GNUNET_HashCode *hcs; + size_t len; + json_t*val; + size_t idx; + json_t *jhash_codes = json_object_get (root, + "policy_hash_codes"); + if (! json_is_array (jhash_codes)) + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, + "policy_hash_codes are missing"); + + len = json_array_size (jhash_codes); + hcs = GNUNET_new_array (len, + struct GNUNET_HashCode); + policy_details = GNUNET_new_array (len, + struct TALER_PolicyDetails); + + json_array_foreach (jhash_codes, idx, val) + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto (NULL, &hcs[idx]), + GNUNET_JSON_spec_end () + }; + + ret = GNUNET_JSON_parse (val, + spec, + &error_msg, + NULL); + if (GNUNET_OK != ret) + break; + + qs = TEH_plugin->get_policy_details (TEH_plugin->cls, + &hcs[idx], + &policy_details[idx]); + if (qs < 0) + { + error_msg = "a policy_hash_code couldn't be found"; + break; + } + } + + GNUNET_free (hcs); + if (GNUNET_OK != ret) + { + GNUNET_free (policy_details); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, + error_msg); + } + } + + + { + enum GNUNET_GenericReturnValue ret; + + ret = ext->policy_post_handler (root, + &args[1], + policy_details, + policy_details_count, + &output); + + if (GNUNET_OK != ret) + { + TALER_MHD_reply_json_steal ( + rc->connection, + output, + MHD_HTTP_BAD_REQUEST); + } + + /* execute fulfillment transaction */ + { + MHD_RESULT mhd_ret; + struct TALER_PolicyFulfillmentTransactionData fulfillment = { + .proof = root, + .timestamp = GNUNET_TIME_timestamp_get (), + .details = policy_details, + .details_count = policy_details_count + }; + + if (GNUNET_OK != + TEH_DB_run_transaction (rc->connection, + "execute policy fulfillment", + TEH_MT_REQUEST_POLICY_FULFILLMENT, + &mhd_ret, + &policy_fulfillment_transaction, + &fulfillment)) + { + json_decref (output); + return mhd_ret; + } + } + } + + return TALER_MHD_reply_json_steal (rc->connection, + output, + MHD_HTTP_OK); +} + + /* end of taler-exchange-httpd_extensions.c */ diff --git a/src/exchange/taler-exchange-httpd_extensions.h b/src/exchange/taler-exchange-httpd_extensions.h index 4659b653e..e435f8f03 100644 --- a/src/exchange/taler-exchange-httpd_extensions.h +++ b/src/exchange/taler-exchange-httpd_extensions.h @@ -40,4 +40,19 @@ TEH_extensions_init (void); void TEH_extensions_done (void); + +/** + * Handle POST "/extensions/..." requests. + * + * @param rc request context + * @param root uploaded JSON data + * @param args array of additional options + * @return MHD result code + */ +MHD_RESULT +TEH_extensions_post_handler ( + struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[]); + #endif diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 9b7f28bd0..30c336539 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -17,6 +17,7 @@ * @file taler-exchange-httpd_keys.c * @brief management of our various keys * @author Christian Grothoff + * @author Özgür Kesim */ #include "platform.h" #include "taler_json_lib.h" @@ -815,10 +816,7 @@ static struct TALER_AgeMask load_age_mask (const char*section_name) { static const struct TALER_AgeMask null_mask = {0}; - struct TALER_AgeMask age_mask = TALER_extensions_age_restriction_ageMask (); - - if (age_mask.bits == 0) - return null_mask; + enum GNUNET_GenericReturnValue ret; if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value ( TEH_cfg, @@ -826,22 +824,29 @@ load_age_mask (const char*section_name) "AGE_RESTRICTED"))) return null_mask; + if (GNUNET_SYSERR == + (ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg, + section_name, + "AGE_RESTRICTED"))) { - enum GNUNET_GenericReturnValue ret; + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section_name, + "AGE_RESTRICTED", + "Value must be YES or NO\n"); + return null_mask; + } - if (GNUNET_SYSERR == - (ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg, - section_name, - "AGE_RESTRICTED"))) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section_name, - "AGE_RESTRICTED", - "Value must be YES or NO\n"); - return null_mask; - } + if (GNUNET_OK == ret) + { + if (! TEH_age_restriction_enabled) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "age restriction set in section %s, yet, age restriction is not enabled\n", + section_name); + return TEH_age_restriction_config.mask; } - return age_mask; + + + return null_mask; } @@ -1898,41 +1903,29 @@ create_krd (struct TEH_KeyStateHandle *ksh, bool has_extensions = false; /* Fill in the configurations of the enabled extensions */ - for (const struct TALER_Extension *extension = TALER_extensions_get_head (); - NULL != extension; - extension = extension->next) + for (const struct TALER_Extensions *iter = TALER_extensions_get_head (); + NULL != iter && NULL != iter->extension; + iter = iter->next) { - json_t *ext; - json_t *config_json; + const struct TALER_Extension *extension = iter->extension; + json_t *manifest; int r; - /* skip if not configured == disabled */ - if (NULL == extension->config || - NULL == extension->config_json) + /* skip if not enabled */ + if (! extension->enabled) continue; /* flag our findings so far */ has_extensions = true; - GNUNET_assert (NULL != extension->config_json); - config_json = json_copy (extension->config_json); - GNUNET_assert (NULL != config_json); - - ext = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_bool ("critical", - extension->critical), - GNUNET_JSON_pack_string ("version", - extension->version), - GNUNET_JSON_pack_object_steal ("config", - config_json) - ); - GNUNET_assert (NULL != ext); + manifest = extension->manifest (extension); + GNUNET_assert (manifest); r = json_object_set_new ( extensions, extension->name, - ext); + manifest); GNUNET_assert (0 == r); } @@ -1948,12 +1941,16 @@ create_krd (struct TEH_KeyStateHandle *ksh, extensions); GNUNET_assert (0 == r); - sig = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("extensions_sig", - &TEH_extensions_sig)); + /* Add the signature of the extensions, if it is not zero */ + if (TEH_extensions_signed) + { + sig = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("extensions_sig", + &TEH_extensions_sig)); - r = json_object_update (keys, sig); - GNUNET_assert (0 == r); + r = json_object_update (keys, sig); + GNUNET_assert (0 == r); + } } else { diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c index a663b1b06..989b88fb6 100644 --- a/src/exchange/taler-exchange-httpd_management_extensions.c +++ b/src/exchange/taler-exchange-httpd_management_extensions.c @@ -38,7 +38,7 @@ struct Extension { enum TALER_Extension_Type type; - json_t *config; + json_t *manifest; }; /** @@ -52,7 +52,7 @@ struct SetExtensionsContext }; /** - * Function implementing database transaction to set the configuration of + * Function implementing database transaction to set the manifests of * extensions. It runs the transaction logic. * - IF it returns a non-error code, the transaction logic MUST NOT queue a * MHD response. @@ -74,13 +74,13 @@ set_extensions (void *cls, { struct SetExtensionsContext *sec = cls; - /* save the configurations of all extensions */ + /* save the manifests of all extensions */ for (uint32_t i = 0; i<sec->num_extensions; i++) { struct Extension *ext = &sec->extensions[i]; const struct TALER_Extension *taler_ext; enum GNUNET_DB_QueryStatus qs; - char *config; + char *manifest; taler_ext = TALER_extensions_get_by_type (ext->type); if (NULL == taler_ext) @@ -90,10 +90,8 @@ set_extensions (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } - GNUNET_assert (NULL != ext->config); - - config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS); - if (NULL == config) + manifest = json_dumps (ext->manifest, JSON_COMPACT | JSON_SORT_KEYS); + if (NULL == manifest) { GNUNET_break (0); *mhd_ret = TALER_MHD_reply_with_error (connection, @@ -103,10 +101,12 @@ set_extensions (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } - qs = TEH_plugin->set_extension_config ( + qs = TEH_plugin->set_extension_manifest ( TEH_plugin->cls, taler_ext->name, - config); + manifest); + + free (manifest); if (qs < 0) { @@ -137,6 +137,7 @@ set_extensions (void *cls, /* All extensions configured, update the signature */ TEH_extensions_sig = sec->extensions_sig; + TEH_extensions_signed = true; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */ } @@ -150,7 +151,7 @@ verify_extensions_from_json ( const char*name; const struct TALER_Extension *extension; size_t i = 0; - json_t *blob; + json_t *manifest; GNUNET_assert (NULL != extensions); GNUNET_assert (json_is_object (extensions)); @@ -159,7 +160,7 @@ verify_extensions_from_json ( sec->extensions = GNUNET_new_array (sec->num_extensions, struct Extension); - json_object_foreach (extensions, name, blob) + json_object_foreach (extensions, name, manifest) { int critical = 0; json_t *config; @@ -175,18 +176,18 @@ verify_extensions_from_json ( } if (GNUNET_OK != - TALER_extensions_is_json_config ( - blob, &critical, &version, &config)) + TALER_extensions_parse_manifest ( + manifest, &critical, &version, &config)) return GNUNET_SYSERR; if (critical != extension->critical || 0 != strcmp (version, extension->version) // FIXME-oec: libtool compare || NULL == config - || GNUNET_OK != extension->test_json_config (config)) + || GNUNET_OK != extension->load_config (NULL, config)) return GNUNET_SYSERR; sec->extensions[i].type = extension->type; - sec->extensions[i].config = config; + sec->extensions[i].manifest = json_copy (manifest); } return GNUNET_OK; @@ -223,7 +224,8 @@ TEH_handler_management_post_extensions ( } /* Ensure we have an object */ - if (! json_is_object (extensions)) + if ((! json_is_object (extensions)) && + (! json_is_null (extensions))) { GNUNET_JSON_parse_free (top_spec); return TALER_MHD_reply_with_error ( @@ -235,13 +237,13 @@ TEH_handler_management_post_extensions ( /* Verify the signature */ { - struct TALER_ExtensionConfigHashP h_config; + struct TALER_ExtensionManifestsHashP h_manifests; if (GNUNET_OK != - TALER_JSON_extensions_config_hash (extensions, &h_config) || + TALER_JSON_extensions_manifests_hash (extensions, &h_manifests) || GNUNET_OK != - TALER_exchange_offline_extension_config_hash_verify ( - &h_config, + TALER_exchange_offline_extension_manifests_hash_verify ( + &h_manifests, &TEH_master_public_key, &sec.extensions_sig)) { @@ -298,9 +300,9 @@ TEH_handler_management_post_extensions ( CLEANUP: for (unsigned int i = 0; i < sec.num_extensions; i++) { - if (NULL != sec.extensions[i].config) + if (NULL != sec.extensions[i].manifest) { - json_decref (sec.extensions[i].config); + json_decref (sec.extensions[i].manifest); } } GNUNET_free (sec.extensions); diff --git a/src/exchange/taler-exchange-httpd_metrics.h b/src/exchange/taler-exchange-httpd_metrics.h index c1b7326ff..cae371f7c 100644 --- a/src/exchange/taler-exchange-httpd_metrics.h +++ b/src/exchange/taler-exchange-httpd_metrics.h @@ -44,7 +44,8 @@ enum TEH_MetricTypeRequest TEH_MT_REQUEST_IDEMPOTENT_MELT = 10, TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 11, TEH_MT_REQUEST_BATCH_DEPOSIT = 12, - TEH_MT_REQUEST_COUNT = 13 /* MUST BE LAST! */ + TEH_MT_REQUEST_POLICY_FULFILLMENT = 13, + TEH_MT_REQUEST_COUNT = 14 /* MUST BE LAST! */ }; /** diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c index a25d6ff43..85090cedc 100644 --- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c +++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c @@ -623,8 +623,7 @@ resolve_refreshes_reveal_denominations ( bool failed = true; /* Has been checked in handle_refreshes_reveal_json() */ - GNUNET_assert (ng == - TALER_extensions_age_restriction_num_groups ()); + GNUNET_assert (ng == TEH_age_restriction_config.num_groups); rctx->old_age_commitment = GNUNET_new (struct TALER_AgeCommitment); oac = rctx->old_age_commitment; @@ -931,7 +930,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection, /* Sanity check of age commitment: If it was provided, it _must_ be an array * of the size the # of age groups */ if (NULL != old_age_commitment_json - && TALER_extensions_age_restriction_num_groups () != + && TEH_age_restriction_config.num_groups != json_array_size (old_age_commitment_json)) { GNUNET_break_op (0); diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index a81d1b6e5..ca110ad45 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -76,7 +76,7 @@ TEH_RESPONSE_compile_transaction_history ( &h_wire, &deposit->h_contract_terms, &deposit->h_age_commitment, - NULL /* h_extensions! */, + &deposit->h_policy, &deposit->h_denom_pub, deposit->timestamp, &deposit->merchant_pub, diff --git a/src/exchangedb/common-0001.sql b/src/exchangedb/common-0001.sql index ab4f8ea91..a95d74d2a 100644 --- a/src/exchangedb/common-0001.sql +++ b/src/exchangedb/common-0001.sql @@ -907,8 +907,8 @@ BEGIN ',wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16)' ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)' ',done BOOLEAN NOT NULL DEFAULT FALSE' - ',extension_blocked BOOLEAN NOT NULL DEFAULT FALSE' - ',extension_details_serial_id INT8' -- REFERENCES extension_details (extension_details_serial_id) ON DELETE CASCADE' + ',policy_blocked BOOLEAN NOT NULL DEFAULT FALSE' + ',policy_details_serial_id INT8' -- REFERENCES policy_details (policy_details_serial_id) ON DELETE CASCADE' ') %s ;' ,table_name ,'PARTITION BY HASH (coin_pub)' @@ -2619,7 +2619,7 @@ BEGIN ALTER TABLE IF EXISTS deposits DROP CONSTRAINT IF EXISTS deposits_pkey CASCADE - ,DROP CONSTRAINT IF EXISTS deposits_extension_details_serial_id_fkey + ,DROP CONSTRAINT IF EXISTS deposits_policy_details_serial_id_fkey ,DROP CONSTRAINT IF EXISTS deposits_coin_pub_merchant_pub_h_contract_terms_key CASCADE ; diff --git a/src/exchangedb/exchange-0001-part.sql b/src/exchangedb/exchange-0001-part.sql index 99883a279..4599d2ee7 100644 --- a/src/exchangedb/exchange-0001-part.sql +++ b/src/exchangedb/exchange-0001-part.sql @@ -411,19 +411,19 @@ COMMENT ON TABLE signkey_revocations IS 'Table storing which online signing keys have been revoked'; --- ------------------------------ extension ---------------------------------------- +-- ------------------------------ extensions ---------------------------------------- CREATE TABLE IF NOT EXISTS extensions (extension_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE ,name VARCHAR NOT NULL UNIQUE - ,config BYTEA + ,manifest BYTEA ); COMMENT ON TABLE extensions IS 'Configurations of the activated extensions'; COMMENT ON COLUMN extensions.name IS 'Name of the extension'; -COMMENT ON COLUMN extensions.config - IS 'Configuration of the extension as JSON-blob, maybe NULL'; +COMMENT ON COLUMN extensions.manifest + IS 'Manifest of the extension as JSON-blob, maybe NULL. It contains common meta-information and extension-specific configuration.'; -- ------------------------------ known_coins ---------------------------------------- @@ -520,21 +520,69 @@ CREATE TABLE IF NOT EXISTS refresh_transfer_keys_default SELECT add_constraints_to_refresh_transfer_keys_partition('default'); --- ------------------------------ extension_details ---------------------------------------- - -CREATE TABLE IF NOT EXISTS extension_details - (extension_details_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY - ,extension_options VARCHAR) - PARTITION BY HASH (extension_details_serial_id); -COMMENT ON TABLE extension_details - IS 'Extensions that were provided with deposits (not yet used).'; -COMMENT ON COLUMN extension_details.extension_options - IS 'JSON object with options set that the exchange needs to consider when executing a deposit. Supported details depend on the extensions supported by the exchange.'; - -CREATE TABLE IF NOT EXISTS extension_details_default - PARTITION OF extension_details - FOR VALUES WITH (MODULUS 1, REMAINDER 0); +-- ------------------------------ policy_fulfillments ------------------------------------- +CREATE TABLE IF NOT EXISTS policy_fulfillments + (fulfillment_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY + ,fulfillment_timestamp INT8 NOT NULL + ,fulfillment_proof VARCHAR + ,h_fulfillment_proof BYTEA NOT NULL CHECK(LENGTH(h_fulfillment_proof) = 64) UNIQUE + ,policy_hash_codes BYTEA NOT NULL CHECK(0 = MOD(LENGTH(policy_hash_codes), 16)) + ); +COMMENT ON TABLE policy_fulfillments + IS 'Proofs of fulfillment of policies that were set in deposits'; +COMMENT ON COLUMN policy_fulfillments.fulfillment_timestamp + IS 'Timestamp of the arrival of a proof of fulfillment'; +COMMENT ON COLUMN policy_fulfillments.fulfillment_proof + IS 'JSON object with a proof of the fulfillment of a policy. Supported details depend on the policy extensions supported by the exchange.'; +COMMENT ON COLUMN policy_fulfillments.h_fulfillment_proof + IS 'Hash of the fulfillment_proof'; +COMMENT ON COLUMN policy_fulfillments.policy_hash_codes + IS 'Concatenation of the policy_hash_code of all policy_details that are fulfilled by this proof'; + +-- ------------------------------ policy_details ---------------------------------------- + +CREATE TABLE IF NOT EXISTS policy_details + (policy_details_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY + ,policy_hash_code BYTEA PRIMARY KEY CHECK(LENGTH(policy_hash_code)=16) + ,policy_json VARCHAR + ,deadline INT8 NOT NULL + ,commitment_val INT8 NOT NULL + ,commitment_frac INT4 NOT NULL + ,accumulated_total_val INT8 NOT NULL + ,accumulated_total_frac INT4 NOT NULL + ,fee_val INT8 NOT NULL + ,fee_frac INT4 NOT NULL + ,transferable_val INT8 NOT NULL + ,transferable_frac INT8 NOT NULL + ,fulfillment_state smallint NOT NULL CHECK(fulfillment_state between 0 and 5) + ,fulfillment_id BIGINT NULL REFERENCES policy_fulfillments (fulfillment_id) ON DELETE CASCADE + ); +COMMENT ON TABLE policy_details + IS 'Policies that were provided with deposits via policy extensions.'; +COMMENT ON COLUMN policy_details.policy_hash_code + IS 'ID (GNUNET_HashCode) that identifies a policy. Will be calculated by the policy extension based on the content'; +COMMENT ON COLUMN policy_details.policy_json + IS 'JSON object with options set that the exchange needs to consider when executing a deposit. Supported details depend on the policy extensions supported by the exchange.'; +COMMENT ON COLUMN policy_details.deadline + IS 'Deadline until the policy must be marked as fulfilled (maybe "forever")'; +COMMENT ON COLUMN policy_details.commitment_val + IS 'The amount that this policy commits to. Invariant: commitment >= fee'; +COMMENT ON COLUMN policy_details.accumulated_total_val + IS 'The sum of all contributions of all deposit that reference this policy. Invariant: The fulfilment_state must be Insufficient as long as accumulated_total < commitment'; +COMMENT ON COLUMN policy_details.fee_val + IS 'The fee for this policy, due when the policy is fulfilled or timed out'; +COMMENT ON COLUMN policy_details.transferable_val + IS 'The amount that on fulfilment or timeout will be transfered to the payto-URI''s of the corresponding deposit''s. The policy fees must have been already deducted from it. Invariant: fee+transferable <= accumulated_total. The remaining amount (accumulated_total - fee - transferable) can be refreshed by the owner of the coins when the state is Timeout or Success.'; +COMMENT ON COLUMN policy_details.fulfillment_state + IS 'State of the fulfillment: + - 0 (Failure) + - 1 (Insufficient) + - 2 (Ready) + - 4 (Success) + - 5 (Timeout)'; +COMMENT ON COLUMN policy_details.fulfillment_id + IS 'Reference to the proof of the fulfillment of this policy, if it exists. Invariant: If not NULL, this entry''s .hash_code MUST be part of the corresponding policy_fulfillments.policy_hash_codes array.'; -- ------------------------------ deposits ---------------------------------------- @@ -552,10 +600,10 @@ COMMENT ON COLUMN deposits.wire_salt IS 'Salt used when hashing the payto://-URI to get the h_wire'; COMMENT ON COLUMN deposits.done IS 'Set to TRUE once we have included this deposit in some aggregate wire transfer to the merchant'; -COMMENT ON COLUMN deposits.extension_blocked - IS 'True if the aggregation of the deposit is currently blocked by some extension mechanism. Used to filter out deposits that must not be processed by the canonical deposit logic.'; -COMMENT ON COLUMN deposits.extension_details_serial_id - IS 'References extensions table, NULL if extensions are not used'; +COMMENT ON COLUMN deposits.policy_blocked + IS 'True if the aggregation of the deposit is currently blocked by some policy extension mechanism. Used to filter out deposits that must not be processed by the canonical deposit logic.'; +COMMENT ON COLUMN deposits.policy_details_serial_id + IS 'References policy extensions table, NULL if extensions are not used'; CREATE TABLE IF NOT EXISTS deposits_default PARTITION OF deposits @@ -591,7 +639,7 @@ CREATE OR REPLACE FUNCTION deposits_insert_trigger() DECLARE is_ready BOOLEAN; BEGIN - is_ready = NOT (NEW.done OR NEW.extension_blocked); + is_ready = NOT (NEW.done OR NEW.policy_blocked); IF (is_ready) THEN @@ -635,8 +683,8 @@ DECLARE DECLARE is_ready BOOLEAN; BEGIN - was_ready = NOT (OLD.done OR OLD.extension_blocked); - is_ready = NOT (NEW.done OR NEW.extension_blocked); + was_ready = NOT (OLD.done OR OLD.policy_blocked); + is_ready = NOT (NEW.done OR NEW.policy_blocked); IF (was_ready AND NOT is_ready) THEN DELETE FROM exchange.deposits_by_ready @@ -690,7 +738,7 @@ CREATE OR REPLACE FUNCTION deposits_delete_trigger() DECLARE was_ready BOOLEAN; BEGIN - was_ready = NOT (OLD.done OR OLD.extension_blocked); + was_ready = NOT (OLD.done OR OLD.policy_blocked); IF (was_ready) THEN diff --git a/src/exchangedb/pg_insert_records_by_table.c b/src/exchangedb/pg_insert_records_by_table.c index 0ac70bae3..d6630797a 100644 --- a/src/exchangedb/pg_insert_records_by_table.c +++ b/src/exchangedb/pg_insert_records_by_table.c @@ -872,11 +872,11 @@ irbt_cb_table_deposits (struct PostgresClosure *pg, GNUNET_PQ_query_param_auto_from_type (&td->details.deposits.wire_salt), GNUNET_PQ_query_param_auto_from_type ( &td->details.deposits.wire_target_h_payto), - GNUNET_PQ_query_param_bool (td->details.deposits.extension_blocked), - 0 == td->details.deposits.extension_details_serial_id + GNUNET_PQ_query_param_bool (td->details.deposits.policy_blocked), + 0 == td->details.deposits.policy_details_serial_id ? GNUNET_PQ_query_param_null () : GNUNET_PQ_query_param_uint64 ( - &td->details.deposits.extension_details_serial_id), + &td->details.deposits.policy_details_serial_id), GNUNET_PQ_query_param_end }; @@ -898,8 +898,8 @@ irbt_cb_table_deposits (struct PostgresClosure *pg, ",coin_sig" ",wire_salt" ",wire_target_h_payto" - ",extension_blocked" - ",extension_details_serial_id" + ",policy_blocked" + ",policy_details_serial_id" ") VALUES " "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10," " $11, $12, $13, $14, $15, $16, $17);"); @@ -1217,9 +1217,9 @@ irbt_cb_table_extensions (struct PostgresClosure *pg, struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&td->serial), GNUNET_PQ_query_param_string (td->details.extensions.name), - NULL == td->details.extensions.config ? + NULL == td->details.extensions.manifest ? GNUNET_PQ_query_param_null () : - GNUNET_PQ_query_param_string (td->details.extensions.config), + GNUNET_PQ_query_param_string (td->details.extensions.manifest), GNUNET_PQ_query_param_end }; @@ -1228,7 +1228,7 @@ irbt_cb_table_extensions (struct PostgresClosure *pg, "INSERT INTO extensions" "(extension_id" ",name" - ",config" + ",manifest" ") VALUES " "($1, $2, $3);"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, @@ -1238,34 +1238,99 @@ irbt_cb_table_extensions (struct PostgresClosure *pg, /** - * Function called with extension_details records to insert into table. + * Function called with policy_details records to insert into table. * * @param pg plugin context * @param td record to insert */ static enum GNUNET_DB_QueryStatus -irbt_cb_table_extension_details (struct PostgresClosure *pg, - const struct TALER_EXCHANGEDB_TableData *td) +irbt_cb_table_policy_details (struct PostgresClosure *pg, + const struct TALER_EXCHANGEDB_TableData *td) { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&td->serial), - NULL == - td->details.extension_details.extension_options ? - GNUNET_PQ_query_param_null () : - GNUNET_PQ_query_param_string ( - td->details.extension_details.extension_options), + GNUNET_PQ_query_param_auto_from_type ( + &td->details.policy_details.hash_code), + (td->details.policy_details.no_policy_json) + ? GNUNET_PQ_query_param_null () + : TALER_PQ_query_param_json (td->details.policy_details.policy_json), + TALER_PQ_query_param_amount (&td->details.policy_details.commitment), + TALER_PQ_query_param_amount (&td->details.policy_details.accumulated_total), + TALER_PQ_query_param_amount (&td->details.policy_details.fee), + TALER_PQ_query_param_amount (&td->details.policy_details.transferable), + GNUNET_PQ_query_param_timestamp (&td->details.policy_details.deadline), + GNUNET_PQ_query_param_uint16 ( + &td->details.policy_details.fulfillment_state), + (td->details.policy_details.no_fulfillment_id) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_uint64 ( + &td->details.policy_details.fulfillment_id), GNUNET_PQ_query_param_end }; PREPARE (pg, - "insert_into_table_extension_details", - "INSERT INTO extension_details" - "(extension_details_serial_id" - ",extension_options" + "insert_into_table_policy_details", + "INSERT INTO policy_details" + "(policy_details_serial_id" + ",policy_hash_code" + ",policy_json" + ",deadline" + ",commitment_val" + ",commitment_frac" + ",accumulated_total_val" + ",accumulated_total_frac" + ",fee_val" + ",fee_frac" + ",transferable_val" + ",transferable_frac" + ",fulfillment_state" + ",fulfillment_id" ") VALUES " "($1, $2);"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_into_table_extension_details", + "insert_into_table_policy_details", + params); +} + + +/** + * Function called with policy_fulfillment records to insert into table. + * + * @param pg plugin context + * @param td record to insert + */ +static enum GNUNET_DB_QueryStatus +irbt_cb_table_policy_fulfillments (struct PostgresClosure *pg, + const struct TALER_EXCHANGEDB_TableData *td) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&td->serial), + GNUNET_PQ_query_param_timestamp ( + &td->details.policy_fulfillments.fulfillment_timestamp), + (NULL == td->details.policy_fulfillments.fulfillment_proof) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string ( + td->details.policy_fulfillments.fulfillment_proof), + GNUNET_PQ_query_param_auto_from_type ( + &td->details.policy_fulfillments.h_fulfillment_proof), + GNUNET_PQ_query_param_fixed_size ( + td->details.policy_fulfillments.policy_hash_codes, + td->details.policy_fulfillments.policy_hash_codes_count), + GNUNET_PQ_query_param_end + }; + + PREPARE (pg, + "insert_into_table_policy_fulfillments", + "INSERT INTO policy_fulfillments " + "(fulfillment_id" + ",fulfillment_timestamp" + ",fulfillment_proof" + ",h_fulfillment_proof" + ",policy_hash_codes" + ") VALUES " + "($1, $2, $3, $4, $5);"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_into_table_policy_fulfillments", params); } @@ -1900,8 +1965,11 @@ TEH_PG_insert_records_by_table (void *cls, case TALER_EXCHANGEDB_RT_EXTENSIONS: rh = &irbt_cb_table_extensions; break; - case TALER_EXCHANGEDB_RT_EXTENSION_DETAILS: - rh = &irbt_cb_table_extension_details; + case TALER_EXCHANGEDB_RT_POLICY_DETAILS: + rh = &irbt_cb_table_policy_details; + break; + case TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS: + rh = &irbt_cb_table_policy_fulfillments; break; case TALER_EXCHANGEDB_RT_PURSE_REQUESTS: rh = &irbt_cb_table_purse_requests; diff --git a/src/exchangedb/pg_lookup_records_by_table.c b/src/exchangedb/pg_lookup_records_by_table.c index 57c7c2b07..9d600f740 100644 --- a/src/exchangedb/pg_lookup_records_by_table.c +++ b/src/exchangedb/pg_lookup_records_by_table.c @@ -927,7 +927,7 @@ lrbt_cb_table_deposits (void *cls, for (unsigned int i = 0; i<num_results; i++) { - bool no_extension; + bool no_policy; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ( "serial", @@ -972,13 +972,13 @@ lrbt_cb_table_deposits (void *cls, "wire_target_h_payto", &td.details.deposits.wire_target_h_payto), GNUNET_PQ_result_spec_auto_from_type ( - "extension_blocked", - &td.details.deposits.extension_blocked), + "policy_blocked", + &td.details.deposits.policy_blocked), GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_uint64 ( - "extension_details_serial_id", - &td.details.deposits.extension_details_serial_id), - &no_extension), + "policy_details_serial_id", + &td.details.deposits.policy_details_serial_id), + &no_policy), GNUNET_PQ_result_spec_end }; @@ -1414,7 +1414,7 @@ lrbt_cb_table_extensions (void *cls, struct TALER_EXCHANGEDB_TableData td = { .table = TALER_EXCHANGEDB_RT_EXTENSIONS }; - bool no_config = false; + bool no_manifest = false; for (unsigned int i = 0; i<num_results; i++) { @@ -1424,9 +1424,9 @@ lrbt_cb_table_extensions (void *cls, GNUNET_PQ_result_spec_string ("name", &td.details.extensions.name), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("config", - &td.details.extensions.config), - &no_config), + GNUNET_PQ_result_spec_string ("manifest", + &td.details.extensions.manifest), + &no_manifest), GNUNET_PQ_result_spec_end }; @@ -1447,33 +1447,112 @@ lrbt_cb_table_extensions (void *cls, /** - * Function called with extension_details table entries. + * Function called with policy_details table entries. * * @param cls closure * @param result the postgres result * @param num_results the number of results in @a result */ static void -lrbt_cb_table_extension_details (void *cls, - PGresult *result, - unsigned int num_results) +lrbt_cb_table_policy_details (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct LookupRecordsByTableContext *ctx = cls; + struct PostgresClosure *pg = ctx->pg; + struct TALER_EXCHANGEDB_TableData td = { + .table = TALER_EXCHANGEDB_RT_POLICY_DETAILS + }; + + for (unsigned int i = 0; i<num_results; i++) + { + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id", + &td.serial), + GNUNET_PQ_result_spec_auto_from_type ("hash_code", + &td.details.policy_details. + hash_code), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("policy_json", + &td.details.policy_details. + policy_json), + &td.details.policy_details.no_policy_json), + GNUNET_PQ_result_spec_timestamp ("deadline", + &td.details.policy_details. + deadline), + TALER_PQ_RESULT_SPEC_AMOUNT ("commitment", + &td.details.policy_details. + commitment), + TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total", + &td.details.policy_details. + accumulated_total), + TALER_PQ_RESULT_SPEC_AMOUNT ("fee", + &td.details.policy_details. + fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("transferable", + &td.details.policy_details. + transferable), + GNUNET_PQ_result_spec_uint16 ("fulfillment_state", + &td.details.policy_details. + fulfillment_state), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_uint64 ("fulfillment_id", + &td.details.policy_details. + fulfillment_id), + &td.details.policy_details.no_fulfillment_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ctx->error = true; + return; + } + ctx->cb (ctx->cb_cls, + &td); + GNUNET_PQ_cleanup_result (rs); + } +} + + +/** + * Function called with policy_fulfillments table entries. + * + * @param cls closure + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +lrbt_cb_table_policy_fulfillments (void *cls, + PGresult *result, + unsigned int num_results) { struct LookupRecordsByTableContext *ctx = cls; struct TALER_EXCHANGEDB_TableData td = { - .table = TALER_EXCHANGEDB_RT_EXTENSION_DETAILS + .table = TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS }; for (unsigned int i = 0; i<num_results; i++) { - bool no_config = false; + bool no_proof = false; + bool no_timestamp = false; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("extension_details_serial_id", + GNUNET_PQ_result_spec_uint64 ("fulfillment_id", &td.serial), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("extension_options", - &td.details.extension_details. - extension_options), - &no_config), + GNUNET_PQ_result_spec_timestamp ("fulfillment_timestamp", + &td.details.policy_fulfillments. + fulfillment_timestamp), + &no_timestamp), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_string ("fulfillment_proof", + &td.details.policy_fulfillments. + fulfillment_proof), + &no_proof), GNUNET_PQ_result_spec_end }; @@ -2607,9 +2686,13 @@ TEH_PG_lookup_records_by_table (void *cls, statement = "select_above_serial_by_table_extensions"; rh = &lrbt_cb_table_extensions; break; - case TALER_EXCHANGEDB_RT_EXTENSION_DETAILS: - statement = "select_above_serial_by_table_extension_details"; - rh = &lrbt_cb_table_extension_details; + case TALER_EXCHANGEDB_RT_POLICY_DETAILS: + statement = "select_above_serial_by_table_policy_details"; + rh = &lrbt_cb_table_policy_details; + break; + case TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS: + statement = "select_above_serial_by_table_policy_fulfillments"; + rh = &lrbt_cb_table_policy_fulfillments; break; case TALER_EXCHANGEDB_RT_PURSE_REQUESTS: XPREPARE ("select_above_serial_by_table_purse_requests", diff --git a/src/exchangedb/pg_lookup_serial_by_table.c b/src/exchangedb/pg_lookup_serial_by_table.c index 202be30f8..7e150cd28 100644 --- a/src/exchangedb/pg_lookup_serial_by_table.c +++ b/src/exchangedb/pg_lookup_serial_by_table.c @@ -277,12 +277,20 @@ TEH_PG_lookup_serial_by_table (void *cls, " ORDER BY extension_id DESC" " LIMIT 1;"); break; - case TALER_EXCHANGEDB_RT_EXTENSION_DETAILS: - XPREPARE ("select_serial_by_table_extension_details", + case TALER_EXCHANGEDB_RT_POLICY_DETAILS: + XPREPARE ("select_serial_by_table_policy_details", "SELECT" - " extension_details_serial_id AS serial" - " FROM extension_details" - " ORDER BY extension_details_serial_id DESC" + " policy_details_serial_id AS serial" + " FROM policy_details" + " ORDER BY policy_details_serial_id DESC" + " LIMIT 1;"); + break; + case TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS: + XPREPARE ("select_serial_by_table_policy_fulfillments", + "SELECT" + " fulfillment_id AS serial" + " FROM policy_fulfillments" + " ORDER BY fulfillment_id DESC" " LIMIT 1;"); break; case TALER_EXCHANGEDB_RT_PURSE_REQUESTS: diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 28dbdbaa8..0b03fe4b1 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -2099,17 +2099,17 @@ prepare_statements (struct PostgresClosure *pg) " WHERE job_name=$1" " AND start_row=$2" " AND end_row=$3"), - /* Used in #postgres_set_extension_config */ + /* Used in #postgres_set_extension_manifest */ GNUNET_PQ_make_prepare ( - "set_extension_config", - "INSERT INTO extensions (name, config) VALUES ($1, $2) " + "set_extension_manifest", + "INSERT INTO extensions (name, manifest) VALUES ($1, $2) " "ON CONFLICT (name) " - "DO UPDATE SET config=$2"), - /* Used in #postgres_get_extension_config */ + "DO UPDATE SET manifest=$2"), + /* Used in #postgres_get_extension_manifest */ GNUNET_PQ_make_prepare ( - "get_extension_config", + "get_extension_manifest", "SELECT " - " config " + " manifest " "FROM extensions" " WHERE name=$1;"), /* Used in #postgres_insert_contract() */ @@ -4083,7 +4083,7 @@ compute_shard (const struct TALER_MerchantPublicKeyP *merchant_pub) * @param deposit deposit operation details * @param known_coin_id row of the coin in the known_coins table * @param h_payto hash of the merchant's bank account details - * @param extension_blocked true if an extension is blocking the wire transfer + * @param _blocked true if an extension is blocking the wire transfer * @param[in,out] exchange_timestamp time to use for the deposit (possibly updated) * @param[out] balance_ok set to true if the balance was sufficient * @param[out] in_conflict set to true if the deposit conflicted @@ -4095,7 +4095,7 @@ postgres_do_deposit ( const struct TALER_EXCHANGEDB_Deposit *deposit, uint64_t known_coin_id, const struct TALER_PaytoHashP *h_payto, - bool extension_blocked, + uint64_t *policy_details_serial_id, struct GNUNET_TIME_Timestamp *exchange_timestamp, bool *balance_ok, bool *in_conflict) @@ -4117,10 +4117,10 @@ postgres_do_deposit ( GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub), GNUNET_PQ_query_param_auto_from_type (&deposit->csig), GNUNET_PQ_query_param_uint64 (&deposit_shard), - GNUNET_PQ_query_param_bool (extension_blocked), - (NULL == deposit->extension_details) + GNUNET_PQ_query_param_bool (deposit->has_policy), + (NULL == policy_details_serial_id) ? GNUNET_PQ_query_param_null () - : TALER_PQ_query_param_json (deposit->extension_details), + : GNUNET_PQ_query_param_uint64 (policy_details_serial_id), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -4140,6 +4140,101 @@ postgres_do_deposit ( } +/* Get the details of a policy, referenced by its hash code + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param hc The hash code under which the details to a particular policy should be found + * @param[out] details The found details + * @return query execution status + * */ +static enum GNUNET_DB_QueryStatus +postgres_get_policy_details ( + void *cls, + const struct GNUNET_HashCode *hc, + struct TALER_PolicyDetails *details) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (hc), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_timestamp ("deadline", + &details->deadline), + TALER_PQ_RESULT_SPEC_AMOUNT ("commitment", + &details->commitment), + TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total", + &details->accumulated_total), + TALER_PQ_RESULT_SPEC_AMOUNT ("policy_fee", + &details->policy_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("transferable_amount", + &details->transferable_amount), + GNUNET_PQ_result_spec_auto_from_type ("state", + &details->fulfillment_state), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_uint64 ("policy_fulfillment_id", + &details->policy_fulfillment_id), + &details->no_policy_fulfillment_id), + GNUNET_PQ_result_spec_end + }; + + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "get_policy_details", + params, + rs); +} + + +/* Persist the details to a policy in the policy_details table. If there + * already exists a policy, update the fields accordingly. + * + * @param details The policy details that should be persisted. If an entry for + * the given details->hash_code exists, the values will be updated. + * @param[out] policy_details_serial_id The row ID of the policy details + * @param[out] accumulated_total The total amount accumulated in that policy + * @param[out] fulfillment_state The state of policy. If the state was Insufficient prior to the call and the provided deposit raises the accumulated_total above the commitment, it will be set to Ready. + * @return query execution status + */ +static enum GNUNET_DB_QueryStatus +postgres_persist_policy_details ( + void *cls, + const struct TALER_PolicyDetails *details, + uint64_t *policy_details_serial_id, + struct TALER_Amount *accumulated_total, + enum TALER_PolicyFulfillmentState *fulfillment_state) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&details->hash_code), + TALER_PQ_query_param_json (details->policy_json), + GNUNET_PQ_query_param_timestamp (&details->deadline), + TALER_PQ_query_param_amount (&details->commitment), + TALER_PQ_query_param_amount (&details->accumulated_total), + TALER_PQ_query_param_amount (&details->policy_fee), + TALER_PQ_query_param_amount (&details->transferable_amount), + GNUNET_PQ_query_param_auto_from_type (&details->fulfillment_state), + (details->no_policy_fulfillment_id) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_uint64 (&details->policy_fulfillment_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id", + policy_details_serial_id), + TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total", + accumulated_total), + GNUNET_PQ_result_spec_uint32 ("fulfillment_state", + fulfillment_state), + GNUNET_PQ_result_spec_end + }; + + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "call_insert_or_update_policy_details", + params, + rs); +} + + /** * Perform melt operation, checking for sufficient balance * of the coin and possibly persisting the melt details. @@ -4394,6 +4489,118 @@ postgres_do_recoup_refresh ( } +/* + * Compares two indices into an array of hash codes according to + * GNUNET_CRYPTO_hash_cmp of the content at those index positions. + * + * Used in a call qsort_t in order to generate sorted policy_hash_codes. + */ +static int +hash_code_cmp ( + const void *hc1, + const void *hc2, + void *arg) +{ + size_t i1 = *(size_t *) hc1; + size_t i2 = *(size_t *) hc2; + const struct TALER_PolicyDetails *d = arg; + + return GNUNET_CRYPTO_hash_cmp (&d[i1].hash_code, + &d[i2].hash_code); +} + + +/** + * Add a proof of fulfillment into the policy_fulfillments table + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param[out] proof_id set record id for the proof + * @return query execution status + */ +static enum GNUNET_DB_QueryStatus +postgres_add_policy_fulfillment_proof ( + void *cls, + struct TALER_PolicyFulfillmentTransactionData *fulfillment) +{ + enum GNUNET_DB_QueryStatus qs; + struct PostgresClosure *pg = cls; + size_t count = fulfillment->details_count; + struct GNUNET_HashCode hcs[count]; + + /* Create the sorted policy_hash_codes */ + { + size_t idx[count]; + for (size_t i = 0; i < count; i++) + idx[i] = i; + + /* Sort the indices according to the hash codes of the corresponding + * details. */ + qsort_r (idx, + count, + sizeof(size_t), + hash_code_cmp, + fulfillment->details); + + /* Finally, concatenate all hash_codes in sorted order */ + for (size_t i = 0; i < count; i++) + hcs[i] = fulfillment->details[idx[i]].hash_code; + } + + + /* Now, add the proof to the policy_fulfillments table, retrieve the + * record_id */ + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_timestamp (&fulfillment->timestamp), + TALER_PQ_query_param_json (fulfillment->proof), + GNUNET_PQ_query_param_auto_from_type (&fulfillment->h_proof), + GNUNET_PQ_query_param_fixed_size (hcs, + count * sizeof(struct GNUNET_HashCode)), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("fulfillment_id", + &fulfillment->fulfillment_id), + GNUNET_PQ_result_spec_end + }; + + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "insert_proof_into_policy_fulfillments", + params, + rs); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + return qs; + } + + /* Now, set the states of each entry corresponding to the hash_codes in + * policy_details accordingly */ + for (size_t i = 0; i < count; i++) + { + struct TALER_PolicyDetails *pos = &fulfillment->details[i]; + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&pos->hash_code), + GNUNET_PQ_query_param_timestamp (&pos->deadline), + TALER_PQ_query_param_amount (&pos->commitment), + TALER_PQ_query_param_amount (&pos->accumulated_total), + TALER_PQ_query_param_amount (&pos->policy_fee), + TALER_PQ_query_param_amount (&pos->transferable_amount), + GNUNET_PQ_query_param_auto_from_type (&pos->fulfillment_state), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "update_policy_details", + params); + if (qs < 0) + return qs; + } + } + + return qs; +} + + /** * Get the balance of the specified reserve. * @@ -10465,8 +10672,8 @@ postgres_delete_shard_locks (void *cls) /** - * Function called to save the configuration of an extension - * (age-restriction, peer2peer, ...). After successful storage of the + * Function called to save the manifest of an extension + * (age-restriction, policy_extension_...) After successful storage of the * configuration it triggers the corresponding event. * * @param cls the @e cls of this struct with the plugin-specific state @@ -10475,15 +10682,15 @@ postgres_delete_shard_locks (void *cls) * @return transaction status code */ enum GNUNET_DB_QueryStatus -postgres_set_extension_config (void *cls, - const char *extension_name, - const char *config) +postgres_set_extension_manifest (void *cls, + const char *extension_name, + const char *manifest) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam pcfg = - (NULL == config || 0 == *config) + (NULL == manifest || 0 == *manifest) ? GNUNET_PQ_query_param_null () - : GNUNET_PQ_query_param_string (config); + : GNUNET_PQ_query_param_string (manifest); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (extension_name), pcfg, @@ -10491,24 +10698,24 @@ postgres_set_extension_config (void *cls, }; return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "set_extension_config", + "set_extension_manifest", params); } /** - * Function called to get the configuration of an extension - * (age-restriction, peer2peer, ...) + * Function called to get the manifest of an extension + * (age-restriction, policy_extension_...) * * @param cls the @e cls of this struct with the plugin-specific state * @param extension_name the name of the extension - * @param[out] config JSON object of the configuration as string + * @param[out] manifest JSON object of the manifest as string * @return transaction status code */ enum GNUNET_DB_QueryStatus -postgres_get_extension_config (void *cls, - const char *extension_name, - char **config) +postgres_get_extension_manifest (void *cls, + const char *extension_name, + char **manifest) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -10518,15 +10725,15 @@ postgres_get_extension_config (void *cls, bool is_null; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("config", - config), + GNUNET_PQ_result_spec_string ("manifest", + manifest), &is_null), GNUNET_PQ_result_spec_end }; - *config = NULL; + *manifest = NULL; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "get_extension_config", + "get_extension_manifest", params, rs); } @@ -12311,8 +12518,11 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->get_withdraw_info = &postgres_get_withdraw_info; plugin->do_withdraw = &postgres_do_withdraw; plugin->do_batch_withdraw = &postgres_do_batch_withdraw; + plugin->get_policy_details = &postgres_get_policy_details; + plugin->persist_policy_details = &postgres_persist_policy_details; plugin->do_batch_withdraw_insert = &postgres_do_batch_withdraw_insert; plugin->do_deposit = &postgres_do_deposit; + plugin->add_policy_fulfillment_proof = &postgres_add_policy_fulfillment_proof; plugin->do_melt = &postgres_do_melt; plugin->do_refund = &postgres_do_refund; plugin->do_recoup = &postgres_do_recoup; @@ -12446,10 +12656,10 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &postgres_release_revolving_shard; plugin->delete_shard_locks = &postgres_delete_shard_locks; - plugin->set_extension_config - = &postgres_set_extension_config; - plugin->get_extension_config - = &postgres_get_extension_config; + plugin->set_extension_manifest + = &postgres_set_extension_manifest; + plugin->get_extension_manifest + = &postgres_get_extension_manifest; plugin->insert_partner = &postgres_insert_partner; plugin->insert_contract diff --git a/src/exchangedb/procedures.sql b/src/exchangedb/procedures.sql index e9075775c..30610e868 100644 --- a/src/exchangedb/procedures.sql +++ b/src/exchangedb/procedures.sql @@ -510,8 +510,8 @@ CREATE OR REPLACE FUNCTION exchange_do_deposit( IN in_coin_pub BYTEA, IN in_coin_sig BYTEA, IN in_shard INT8, - IN in_extension_blocked BOOLEAN, - IN in_extension_details VARCHAR, + IN in_policy_blocked BOOLEAN, + IN in_policy_details_serial_id INT8, OUT out_exchange_timestamp INT8, OUT out_balance_ok BOOLEAN, OUT out_conflict BOOLEAN) @@ -519,26 +519,12 @@ LANGUAGE plpgsql AS $$ DECLARE wtsi INT8; -- wire target serial id -DECLARE - xdi INT8; -- eXstension details serial id BEGIN -- Shards: INSERT extension_details (by extension_details_serial_id) -- INSERT wire_targets (by h_payto), on CONFLICT DO NOTHING; -- INSERT deposits (by coin_pub, shard), ON CONFLICT DO NOTHING; -- UPDATE known_coins (by coin_pub) -IF NOT NULL in_extension_details -THEN - INSERT INTO exchange.extension_details - (extension_options) - VALUES - (in_extension_details) - RETURNING extension_details_serial_id INTO xdi; -ELSE - xdi=NULL; -END IF; - - INSERT INTO exchange.wire_targets (wire_target_h_payto ,payto_uri) @@ -572,8 +558,8 @@ INSERT INTO exchange.deposits ,coin_sig ,wire_salt ,wire_target_h_payto - ,extension_blocked - ,extension_details_serial_id + ,policy_blocked + ,policy_details_serial_id ) VALUES (in_shard @@ -590,8 +576,8 @@ INSERT INTO exchange.deposits ,in_coin_sig ,in_wire_salt ,in_h_payto - ,in_extension_blocked - ,xdi) + ,in_policy_blocked + ,in_policy_details_serial_id) ON CONFLICT DO NOTHING; IF NOT FOUND @@ -611,6 +597,7 @@ THEN AND wire_target_h_payto=in_h_payto AND coin_pub=in_coin_pub AND coin_sig=in_coin_sig; + -- AND policy_details_serial_id=in_policy_details_serial_id; -- FIXME: is this required for idempotency? IF NOT FOUND THEN @@ -2420,5 +2407,117 @@ RETURN; END $$; +CREATE OR REPLACE FUNCTION insert_or_update_policy_details( + IN in_policy_hash_code BYTEA, + IN in_policy_json VARCHAR, + IN in_deadline INT8, + IN in_commitment_val INT8, + IN in_commitment_frac INT4, + IN in_accumulated_total_val INT8, + IN in_accumulated_total_frac INT4, + IN in_fee_val INT8, + IN in_fee_frac INT4, + IN in_transferable_val INT8, + IN in_transferable_frac INT4, + IN in_fulfillment_state SMALLINT, + OUT out_policy_details_serial_id INT8, + OUT out_accumulated_total_val INT8, + OUT out_accumulated_total_frac INT4, + OUT out_fulfillment_state SMALLINT) +LANGUAGE plpgsql +AS $$ +DECLARE + cur_commitment_val INT8; + cur_commitment_frac INT4; + cur_accumulated_total_val INT8; + cur_accumulated_total_frac INT4; +BEGIN + -- First, try to create a new entry. + INSERT INTO policy_details + (policy_hash_code, + policy_json, + deadline, + commitment_val, + commitment_frac, + accumulated_total_val, + accumulated_total_frac, + fee_val, + fee_frac, + transferable_val, + transferable_frac, + fulfillment_state) + VALUES (in_policy_hash_code, + in_policy_json, + in_deadline, + in_commitment_val, + in_commitment_frac, + in_accumulated_total_val, + in_accumulated_total_frac, + in_fee_val, + in_fee_frac, + in_transferable_val, + in_transferable_frac, + in_fulfillment_state) + ON CONFLICT (policy_hash_code) DO NOTHING + RETURNING policy_details_serial_id INTO out_policy_details_serial_id; + + -- If the insert was successful, return + -- We assume that the fullfilment_state was correct in first place. + IF FOUND THEN + out_accumulated_total_val = in_accumulated_total_val; + out_accumulated_total_frac = in_accumulated_total_frac; + out_fulfillment_state = in_fulfillment_state; + RETURN; + END IF; + + -- We had a conflict, grab the parts we need to update. + SELECT policy_details_serial_id, + commitment_val, + commitment_frac, + accumulated_total_val, + accumulated_total_frac + INTO out_policy_details_serial_id, + cur_commitment_val, + cur_commitment_frac, + cur_accumulated_total_val, + cur_accumulated_total_frac + FROM policy_details + WHERE policy_hash_code = in_policy_hash_code; + + -- calculate the new values (overflows throws exception) + out_accumulated_total_val = cur_accumulated_total_val + in_accumulated_total_val; + out_accumulated_total_frac = cur_accumulated_total_frac + in_accumulated_total_frac; + -- normalize + out_accumulated_total_val = out_accumulated_total_val + out_accumulated_total_frac / 100000000; + out_accumulated_total_frac = out_accumulated_total_frac % 100000000; + + IF (out_accumulated_total_val > (1 << 52)) + THEN + RAISE EXCEPTION 'accumulation overflow'; + END IF; + + + -- Set the fulfillment_state according to the values. + -- For now, we only update the state when it was INSUFFICIENT. + -- FIXME: What to do in case of Failure or other state? + IF (out_fullfillment_state = 1) -- INSUFFICIENT + THEN + IF (out_accumulated_total_val >= cur_commitment_val OR + (out_accumulated_total_val = cur_commitment_val AND + out_accumulated_total_frac >= cur_commitment_frac)) + THEN + out_fulfillment_state = 2; -- READY + END IF; + END IF; + + -- Now, update the record + UPDATE exchange.policy_details + SET + accumulated_val = out_accumulated_total_val, + accumulated_frac = out_accumulated_total_frac, + fulfillment_state = out_fulfillment_state + WHERE + policy_details_serial_id = out_policy_details_serial_id; +END $$; COMMIT; diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index eb990412e..111ee9365 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -112,53 +112,53 @@ mark_prepare_cb (void *cls, * Simple check that config retrieval and setting for extensions work */ static enum GNUNET_GenericReturnValue -test_extension_config (void) +test_extension_manifest (void) { - char *config; + char *manifest; FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != - plugin->get_extension_config (plugin->cls, - "fnord", - &config)); + plugin->get_extension_manifest (plugin->cls, + "fnord", + &manifest)); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->set_extension_config (plugin->cls, - "fnord", - "bar")); + plugin->set_extension_manifest (plugin->cls, + "fnord", + "bar")); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->get_extension_config (plugin->cls, - "fnord", - &config)); + plugin->get_extension_manifest (plugin->cls, + "fnord", + &manifest)); - FAILIF (0 != strcmp ("bar", config)); - GNUNET_free (config); + FAILIF (0 != strcmp ("bar", manifest)); + GNUNET_free (manifest); /* let's do this again! */ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->set_extension_config (plugin->cls, - "fnord", - "buzz")); + plugin->set_extension_manifest (plugin->cls, + "fnord", + "buzz")); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->get_extension_config (plugin->cls, - "fnord", - &config)); + plugin->get_extension_manifest (plugin->cls, + "fnord", + &manifest)); - FAILIF (0 != strcmp ("buzz", config)); - GNUNET_free (config); + FAILIF (0 != strcmp ("buzz", manifest)); + GNUNET_free (manifest); /* let's do this again, with NULL */ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->set_extension_config (plugin->cls, - "fnord", - NULL)); + plugin->set_extension_manifest (plugin->cls, + "fnord", + NULL)); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->get_extension_config (plugin->cls, - "fnord", - &config)); + plugin->get_extension_manifest (plugin->cls, + "fnord", + &manifest)); - FAILIF (NULL != config); + FAILIF (NULL != manifest); return GNUNET_OK; drop: @@ -1269,7 +1269,7 @@ run (void *cls) NULL)); /* simple extension check */ FAILIF (GNUNET_OK != - test_extension_config ()); + test_extension_manifest ()); RND_BLK (&reserve_pub); GNUNET_assert (GNUNET_OK == diff --git a/src/extensions/Makefile.am b/src/extensions/Makefile.am index 5d4ed128e..c867a9512 100644 --- a/src/extensions/Makefile.am +++ b/src/extensions/Makefile.am @@ -11,7 +11,7 @@ if USE_COVERAGE endif -# Libraries +# Basic extension handling library lib_LTLIBRARIES = \ libtalerextensions.la @@ -22,7 +22,7 @@ libtalerextensions_la_LDFLAGS = \ libtalerextensions_la_SOURCES = \ extensions.c \ - extension_age_restriction.c + age_restriction_helper.c libtalerextensions_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ @@ -31,3 +31,4 @@ libtalerextensions_la_LIBADD = \ -lgnunetutil \ -ljansson \ $(XLIB) + diff --git a/src/extensions/age_restriction/Makefile.am b/src/extensions/age_restriction/Makefile.am new file mode 100644 index 000000000..85d676538 --- /dev/null +++ b/src/extensions/age_restriction/Makefile.am @@ -0,0 +1,33 @@ +# This Makefile.am is in the public domain + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + $(LIBGCRYPT_CFLAGS) \ + $(POSTGRESQL_CPPFLAGS) + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +# Age restriction as extension library + +plugindir = $(libdir)/taler + +plugin_LTLIBRARIES = \ + libtaler_extension_age_restriction.la + +libtaler_extension_age_restriction_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + +libtaler_extension_age_restriction_la_SOURCES = \ + age_restriction.c + +libtaler_extension_age_restriction_la_LIBADD = \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) diff --git a/src/extensions/age_restriction/age_restriction.c b/src/extensions/age_restriction/age_restriction.c new file mode 100644 index 000000000..581242503 --- /dev/null +++ b/src/extensions/age_restriction/age_restriction.c @@ -0,0 +1,258 @@ +/* + This file is part of TALER + Copyright (C) 2021-2022 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file age_restriction.c + * @brief Utility functions regarding age restriction + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_extensions.h" +#include "stdint.h" + +/* ================================================== + * + * Age Restriction TALER_Extension implementation + * + * ================================================== + */ + +/** + * @brief local configuration + */ + +static struct TALER_AgeRestrictionConfig AR_config = {0}; + +/** + * @brief implements the TALER_Extension.disable interface. + * + * @param ext Pointer to the current extension + */ +static void +age_restriction_disable ( + struct TALER_Extension *ext) +{ + if (NULL == ext) + return; + + ext->enabled = false; + ext->config = NULL; + + AR_config.mask.bits = 0; + AR_config.num_groups = 0; +} + + +/** + * @brief implements the TALER_Extension.load_config interface. + * + * @param ext if NULL, only tests the configuration + * @param jconfig the configuration as json + */ +static enum GNUNET_GenericReturnValue +age_restriction_load_config ( + struct TALER_Extension *ext, + json_t *jconfig) +{ + struct TALER_AgeMask mask = {0}; + enum GNUNET_GenericReturnValue ret; + + ret = TALER_JSON_parse_age_groups (jconfig, &mask); + if (GNUNET_OK != ret) + return ret; + + /* only testing the parser */ + if (ext == NULL) + return GNUNET_OK; + + if (TALER_Extension_AgeRestriction != ext->type) + return GNUNET_SYSERR; + + if (mask.bits > 0) + { + /* if the mask is not zero, the first bit MUST be set */ + if (0 == (mask.bits & 1)) + return GNUNET_SYSERR; + + AR_config.mask.bits = mask.bits; + AR_config.num_groups = __builtin_popcount (mask.bits) - 1; + } + + ext->config = &AR_config; + ext->enabled = true; + json_decref (jconfig); + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "loaded new age restriction config with age groups: %s\n", + TALER_age_mask_to_string (&mask)); + + return GNUNET_OK; +} + + +/** + * @brief implements the TALER_Extension.manifest interface. + * + * @param ext if NULL, only tests the configuration + * @return configuration as json_t* object, maybe NULL + */ +static json_t * +age_restriction_manifest ( + const struct TALER_Extension *ext) +{ + char *mask_str; + json_t *conf; + + GNUNET_assert (NULL != ext); + + if (NULL == ext->config) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "age restriction not configured"); + return json_null (); + } + + mask_str = TALER_age_mask_to_string (&AR_config.mask); + conf = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("age_groups", mask_str) + ); + + free (mask_str); + + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_bool ("critical", ext->critical), + GNUNET_JSON_pack_string ("version", ext->version), + GNUNET_JSON_pack_object_steal ("config", conf) + ); +} + + +/* The extension for age restriction */ +struct TALER_Extension TE_age_restriction = { + .type = TALER_Extension_AgeRestriction, + .name = "age_restriction", + .critical = false, + .version = "1", + .enabled = false, /* disabled per default */ + .config = NULL, + .disable = &age_restriction_disable, + .load_config = &age_restriction_load_config, + .manifest = &age_restriction_manifest, + + /* This extension is not a policy extension */ + .create_policy_details = NULL, + .policy_get_handler = NULL, + .policy_post_handler = NULL, +}; + + +/** + * @brief implements the init() function for GNUNET_PLUGIN_load + * + * @param arg Pointer to the GNUNET_CONFIGURATION_Handle + * @return pointer to TALER_Extension on success or NULL otherwise. + */ +void * +libtaler_extension_age_restriction_init (void *arg) +{ + const struct GNUNET_CONFIGURATION_Handle *cfg = arg; + char *groups = NULL; + struct TALER_AgeMask mask = {0}; + + if ((GNUNET_YES != + GNUNET_CONFIGURATION_have_value (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "ENABLED")) + || + (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_yesno (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "ENABLED"))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "[age restriction] no section %s found in configuration\n", + TALER_EXTENSION_SECTION_AGE_RESTRICTION); + + return NULL; + } + + /* Age restriction is enabled, extract age groups */ + if ((GNUNET_YES == + GNUNET_CONFIGURATION_have_value (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "AGE_GROUPS")) + && + (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_string (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "AGE_GROUPS", + &groups))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "[age restriction] AGE_GROUPS in %s is not a string\n", + TALER_EXTENSION_SECTION_AGE_RESTRICTION); + + return NULL; + } + + mask.bits = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; + + if ((groups != NULL) && + (GNUNET_OK != TALER_parse_age_group_string (groups, &mask))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "[age restriction] couldn't parse age groups: '%s'\n", + groups); + return NULL; + } + + AR_config.mask = mask; + AR_config.num_groups = __builtin_popcount (mask.bits) - 1; /* no underflow, first bit always set */ + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "[age restriction] setting age mask to %s with #groups: %d\n", + TALER_age_mask_to_string (&AR_config.mask), + __builtin_popcount (AR_config.mask.bits) - 1); + + TE_age_restriction.config = &AR_config; + + /* Note: we do now have TE_age_restriction_config set, however the extension + * is not yet enabled! For age restriction to become active, load_config must + * have been called. */ + + GNUNET_free (groups); + return &TE_age_restriction; +} + + +/** + * @brief implements the done() function for GNUNET_PLUGIN_load + * + * @param cfg unsued + * @return pointer to TALER_Extension on success or NULL otherwise. + */ +void * +libtaler_extension_age_restriction_done (void *arg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "[age restriction] disabling and unloading"); + AR_config.mask.bits = 0; + AR_config.num_groups = 0; + return NULL; +} + + +/* end of age_restriction.c */ diff --git a/src/extensions/age_restriction_helper.c b/src/extensions/age_restriction_helper.c new file mode 100644 index 000000000..8ba835117 --- /dev/null +++ b/src/extensions/age_restriction_helper.c @@ -0,0 +1,73 @@ +/* + This file is part of TALER + Copyright (C) 2022- Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file age_restriction_helper.c + * @brief Helper functions for age restriction + * @author Özgür Kesim + */ + +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_extensions.h" +#include "stdint.h" + + +const struct TALER_AgeRestrictionConfig * +TALER_extensions_get_age_restriction_config () +{ + const struct TALER_Extension *ext; + + ext = TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); + if (NULL == ext) + return NULL; + + return ext->config; +} + + +bool +TALER_extensions_is_age_restriction_enabled () +{ + const struct TALER_Extension *ext; + + ext = TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); + if (NULL == ext) + return false; + + return ext->enabled; +} + + +struct TALER_AgeMask +TALER_extensions_get_age_restriction_mask () +{ + const struct TALER_Extension *ext; + const struct TALER_AgeRestrictionConfig *conf; + + ext = TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); + + if ((NULL == ext) || + (NULL == ext->config)) + return (struct TALER_AgeMask) {0} + ; + + conf = ext->config; + return conf->mask; +} + + +/* end age_restriction_helper.c */ diff --git a/src/extensions/extension_age_restriction.c b/src/extensions/extension_age_restriction.c deleted file mode 100644 index 00a038415..000000000 --- a/src/extensions/extension_age_restriction.c +++ /dev/null @@ -1,409 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2021-2022 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ -/** - * @file extension_age_restriction.c - * @brief Utility functions regarding age restriction - * @author Özgür Kesim - */ -#include "platform.h" -#include "taler_util.h" -#include "taler_extensions.h" -#include "stdint.h" - -/** - * Carries all the information we need for age restriction - */ -struct age_restriction_config -{ - struct TALER_AgeMask mask; - size_t num_groups; -}; - -/** - * Global config for this extension - */ -static struct age_restriction_config TE_age_restriction_config = {0}; - -enum GNUNET_GenericReturnValue -TALER_parse_age_group_string ( - const char *groups, - struct TALER_AgeMask *mask) -{ - - const char *pos = groups; - unsigned int prev = 0; - unsigned int val = 0; - char c; - - while (*pos) - { - c = *pos++; - if (':' == c) - { - if (prev >= val) - return GNUNET_SYSERR; - - mask->bits |= 1 << val; - prev = val; - val = 0; - continue; - } - - if ('0'>c || '9'<c) - return GNUNET_SYSERR; - - val = 10 * val + c - '0'; - - if (0>=val || 32<=val) - return GNUNET_SYSERR; - } - - if (32<=val || prev>=val) - return GNUNET_SYSERR; - - mask->bits |= (1 << val); - mask->bits |= 1; // mark zeroth group, too - - return GNUNET_OK; -} - - -char * -TALER_age_mask_to_string ( - const struct TALER_AgeMask *mask) -{ - uint32_t bits = mask->bits; - unsigned int n = 0; - char *buf = GNUNET_malloc (32 * 3); // max characters possible - char *pos = buf; - - if (NULL == buf) - { - return buf; - } - - while (bits != 0) - { - bits >>= 1; - n++; - if (0 == (bits & 1)) - { - continue; - } - - if (n > 9) - { - *(pos++) = '0' + n / 10; - } - *(pos++) = '0' + n % 10; - - if (0 != (bits >> 1)) - { - *(pos++) = ':'; - } - } - return buf; -} - - -/* ================================================== - * - * Age Restriction TALER_Extension implementation - * - * ================================================== - */ - -/** - * @brief implements the TALER_Extension.disable interface. - * - * @param ext Pointer to the current extension - */ -static void -age_restriction_disable ( - struct TALER_Extension *ext) -{ - if (NULL == ext) - return; - - ext->config = NULL; - - if (NULL != ext->config_json) - { - json_decref (ext->config_json); - ext->config_json = NULL; - } - - TE_age_restriction_config.mask.bits = 0; - TE_age_restriction_config.num_groups = 0; -} - - -/** - * @brief implements the TALER_Extension.load_taler_config interface. - * - * @param ext Pointer to the current extension - * @param cfg Handle to the GNUNET configuration - * @return Error if extension for age restriction was set, but age groups were - * invalid, OK otherwise. - */ -static enum GNUNET_GenericReturnValue -age_restriction_load_taler_config ( - struct TALER_Extension *ext, - const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - char *groups = NULL; - enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; - struct TALER_AgeMask mask = {0}; - - if ((GNUNET_YES != - GNUNET_CONFIGURATION_have_value (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "ENABLED")) - || - (GNUNET_YES != - GNUNET_CONFIGURATION_get_value_yesno (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "ENABLED"))) - { - /* Age restriction is not enabled */ - ext->config = NULL; - ext->config_json = NULL; - return GNUNET_OK; - } - - /* Age restriction is enabled, extract age groups */ - if ((GNUNET_YES == - GNUNET_CONFIGURATION_have_value (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "AGE_GROUPS")) - && - (GNUNET_YES != - GNUNET_CONFIGURATION_get_value_string (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "AGE_GROUPS", - &groups))) - return GNUNET_SYSERR; - - - mask.bits = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; - ret = GNUNET_OK; - - if (groups != NULL) - { - ret = TALER_parse_age_group_string (groups, &mask); - if (GNUNET_OK != ret) - mask.bits = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; - } - - if (GNUNET_OK == ret) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "setting age mask to %x with #groups: %d\n", mask.bits, - __builtin_popcount (mask.bits) - 1); - TE_age_restriction_config.mask.bits = mask.bits; - TE_age_restriction_config.num_groups = __builtin_popcount (mask.bits) - 1; /* no underflow, first bit always set */ - ext->config = &TE_age_restriction_config; - - /* Note: we do now have TE_age_restriction_config set, however - * ext->config_json is NOT set, i.e. the extension is not yet active! For - * age restriction to become active, load_json_config must have been - * called. */ - } - - - GNUNET_free (groups); - return ret; -} - - -/** - * @brief implements the TALER_Extension.load_json_config interface. - * - * @param ext if NULL, only tests the configuration - * @param jconfig the configuration as json - */ -static enum GNUNET_GenericReturnValue -age_restriction_load_json_config ( - struct TALER_Extension *ext, - json_t *jconfig) -{ - struct TALER_AgeMask mask = {0}; - enum GNUNET_GenericReturnValue ret; - - ret = TALER_JSON_parse_age_groups (jconfig, &mask); - if (GNUNET_OK != ret) - return ret; - - /* only testing the parser */ - if (ext == NULL) - return GNUNET_OK; - - if (TALER_Extension_AgeRestriction != ext->type) - return GNUNET_SYSERR; - - TE_age_restriction_config.mask.bits = mask.bits; - TE_age_restriction_config.num_groups = 0; - - if (mask.bits > 0) - { - /* if the mask is not zero, the first bit MUST be set */ - if (0 == (mask.bits & 1)) - return GNUNET_SYSERR; - - TE_age_restriction_config.num_groups = __builtin_popcount (mask.bits) - 1; - } - - ext->config = &TE_age_restriction_config; - - if (NULL != ext->config_json) - json_decref (ext->config_json); - - ext->config_json = jconfig; - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "loaded new age restriction config with age groups: %s\n", - TALER_age_mask_to_string (&mask)); - - return GNUNET_OK; -} - - -/** - * @brief implements the TALER_Extension.config_to_json interface. - * - * @param ext if NULL, only tests the configuration - * @return configuration as json_t* object, maybe NULL - */ -static json_t * -age_restriction_config_to_json ( - const struct TALER_Extension *ext) -{ - char *mask_str; - json_t *conf; - - GNUNET_assert (NULL != ext); - - if (NULL == ext->config) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "age restriction not configured"); - return json_null (); - } - - if (NULL != ext->config_json) - { - return json_copy (ext->config_json); - } - - mask_str = TALER_age_mask_to_string (&TE_age_restriction_config.mask); - conf = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("age_groups", mask_str) - ); - - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_bool ("critical", ext->critical), - GNUNET_JSON_pack_string ("version", ext->version), - GNUNET_JSON_pack_object_steal ("config", conf) - ); -} - - -/** - * @brief implements the TALER_Extension.test_json_config interface. - * - * @param config configuration as json_t* to test - * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise. - */ -static enum GNUNET_GenericReturnValue -age_restriction_test_json_config ( - const json_t *config) -{ - struct TALER_AgeMask mask = {0}; - - return TALER_JSON_parse_age_groups (config, &mask); -} - - -/* The extension for age restriction */ -struct TALER_Extension TE_age_restriction = { - .next = NULL, - .type = TALER_Extension_AgeRestriction, - .name = "age_restriction", - .critical = false, - .version = "1", - .config = NULL, // disabled per default - .config_json = NULL, - .disable = &age_restriction_disable, - .test_json_config = &age_restriction_test_json_config, - .load_json_config = &age_restriction_load_json_config, - .config_to_json = &age_restriction_config_to_json, - .load_taler_config = &age_restriction_load_taler_config, -}; - -enum GNUNET_GenericReturnValue -TALER_extension_age_restriction_register () -{ - return TALER_extensions_add (&TE_age_restriction); -} - - -bool -TALER_extensions_age_restriction_is_configured () -{ - return (0 != TE_age_restriction_config.mask.bits); -} - - -struct TALER_AgeMask -TALER_extensions_age_restriction_ageMask () -{ - return TE_age_restriction_config.mask; -} - - -size_t -TALER_extensions_age_restriction_num_groups () -{ - return TE_age_restriction_config.num_groups; -} - - -enum GNUNET_GenericReturnValue -TALER_JSON_parse_age_groups (const json_t *root, - struct TALER_AgeMask *mask) -{ - enum GNUNET_GenericReturnValue ret; - const char *str; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("age_groups", - &str), - GNUNET_JSON_spec_end () - }; - - ret = GNUNET_JSON_parse (root, - spec, - NULL, - NULL); - if (GNUNET_OK == ret) - TALER_parse_age_group_string (str, mask); - - GNUNET_JSON_parse_free (spec); - - return ret; -} - - -/* end of extension_age_restriction.c */ diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c index 0df0bae36..2ed973d92 100644 --- a/src/extensions/extensions.c +++ b/src/extensions/extensions.c @@ -24,51 +24,53 @@ #include "taler_extensions.h" #include "stdint.h" - /* head of the list of all registered extensions */ -static struct TALER_Extension *TE_extensions = NULL; - +static struct TALER_Extensions TE_extensions = { + .next = NULL, + .extension = NULL, +}; -const struct TALER_Extension * +const struct TALER_Extensions * TALER_extensions_get_head () { - return TE_extensions; + return &TE_extensions; } -enum GNUNET_GenericReturnValue -TALER_extensions_add ( - struct TALER_Extension *extension) +static enum GNUNET_GenericReturnValue +add_extension ( + const struct TALER_Extension *extension) { /* Sanity checks */ if ((NULL == extension) || (NULL == extension->name) || (NULL == extension->version) || (NULL == extension->disable) || - (NULL == extension->test_json_config) || - (NULL == extension->load_json_config) || - (NULL == extension->config_to_json) || - (NULL == extension->load_taler_config)) + (NULL == extension->load_config) || + (NULL == extension->manifest)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "invalid extension\n"); return GNUNET_SYSERR; } - if (NULL == TE_extensions) /* first extension ?*/ - TE_extensions = (struct TALER_Extension *) extension; + if (NULL == TE_extensions.extension) /* first extension ?*/ + TE_extensions.extension = extension; else { - struct TALER_Extension *iter; - struct TALER_Extension *last; + struct TALER_Extensions *iter; + struct TALER_Extensions *last; /* Check for collisions */ - for (iter = TE_extensions; NULL != iter; iter = iter->next) + for (iter = &TE_extensions; + NULL != iter && NULL != iter->extension; + iter = iter->next) { + const struct TALER_Extension *ext = iter->extension; last = iter; - if (extension->type == iter->type || + if (extension->type == ext->type || 0 == strcasecmp (extension->name, - iter->name)) + ext->name)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "extension collision for `%s'\n", @@ -78,7 +80,11 @@ TALER_extensions_add ( } /* No collisions found, so add this extension to the list */ - last->next = extension; + { + struct TALER_Extensions *extn = GNUNET_new (struct TALER_Extensions); + extn->extension = extension; + last->next = extn; + } } return GNUNET_OK; @@ -89,12 +95,12 @@ const struct TALER_Extension * TALER_extensions_get_by_type ( enum TALER_Extension_Type type) { - for (const struct TALER_Extension *it = TE_extensions; - NULL != it; + for (const struct TALER_Extensions *it = &TE_extensions; + NULL != it && NULL != it->extension; it = it->next) { - if (it->type == type) - return it; + if (it->extension->type == type) + return it->extension; } /* No extension found. */ @@ -109,8 +115,7 @@ TALER_extensions_is_enabled_type ( const struct TALER_Extension *ext = TALER_extensions_get_by_type (type); - return (NULL != ext && - TALER_extensions_is_enabled (ext)); + return (NULL != ext && ext->enabled); } @@ -118,33 +123,34 @@ const struct TALER_Extension * TALER_extensions_get_by_name ( const char *name) { - for (const struct TALER_Extension *it = TE_extensions; + for (const struct TALER_Extensions *it = &TE_extensions; NULL != it; it = it->next) { - if (0 == strcasecmp (name, it->name)) - return it; + if (0 == strcasecmp (name, it->extension->name)) + return it->extension; } - /* No extension found. */ + /* No extension found, try to load it. */ + return NULL; } enum GNUNET_GenericReturnValue -TALER_extensions_verify_json_config_signature ( - json_t *extensions, +TALER_extensions_verify_manifests_signature ( + json_t *manifests, struct TALER_MasterSignatureP *extensions_sig, struct TALER_MasterPublicKeyP *master_pub) { - struct TALER_ExtensionConfigHashP h_config; + struct TALER_ExtensionManifestsHashP h_manifests; if (GNUNET_OK != - TALER_JSON_extensions_config_hash (extensions, - &h_config)) + TALER_JSON_extensions_manifests_hash (manifests, + &h_manifests)) return GNUNET_SYSERR; if (GNUNET_OK != - TALER_exchange_offline_extension_config_hash_verify ( - &h_config, + TALER_exchange_offline_extension_manifests_hash_verify ( + &h_manifests, master_pub, extensions_sig)) return GNUNET_NO; @@ -178,7 +184,8 @@ configure_extension ( { struct LoadConfClosure *col = cls; const char *name; - const struct TALER_Extension *extension; + char *lib_name; + struct TALER_Extension *extension; if (GNUNET_OK != col->error) return; @@ -190,33 +197,49 @@ configure_extension ( name = section + sizeof(TALER_EXTENSION_SECTION_PREFIX) - 1; - if (NULL == - (extension = TALER_extensions_get_by_name (name))) + + /* Load the extension library */ + GNUNET_asprintf (&lib_name, + "libtaler_extension_%s", + name); + extension = GNUNET_PLUGIN_load ( + lib_name, + (void *) col->cfg); + if (NULL == extension) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unsupported extension `%s` (section [%s]).\n", name, + "Couldn't load extension library to `%s` (section [%s]).\n", + name, section); col->error = GNUNET_SYSERR; return; } - if (GNUNET_OK != - extension->load_taler_config ( - (struct TALER_Extension *) extension, - col->cfg)) + + if (GNUNET_OK != add_extension (extension)) { + /* TODO: Ignoring return values here */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Couldn't parse configuration for extension `%s` (section [%s]).\n", + "Couldn't add extension `%s` (section [%s]).\n", name, section); col->error = GNUNET_SYSERR; + GNUNET_PLUGIN_unload ( + lib_name, + (void *) col->cfg); return; } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "extension library '%s' loaded\n", + lib_name); } +static bool extensions_loaded = false; + enum GNUNET_GenericReturnValue -TALER_extensions_load_taler_config ( +TALER_extensions_init ( const struct GNUNET_CONFIGURATION_Handle *cfg) { struct LoadConfClosure col = { @@ -224,15 +247,22 @@ TALER_extensions_load_taler_config ( .error = GNUNET_OK, }; + if (extensions_loaded) + return GNUNET_OK; + GNUNET_CONFIGURATION_iterate_sections (cfg, &configure_extension, &col); + + if (GNUNET_OK == col.error) + extensions_loaded = true; + return col.error; } enum GNUNET_GenericReturnValue -TALER_extensions_is_json_config ( +TALER_extensions_parse_manifest ( json_t *obj, int *critical, const char **version, @@ -265,22 +295,23 @@ TALER_extensions_is_json_config ( enum GNUNET_GenericReturnValue -TALER_extensions_load_json_config ( +TALER_extensions_load_manifests ( json_t *extensions) { const char*name; - json_t *blob; + json_t *manifest; GNUNET_assert (NULL != extensions); GNUNET_assert (json_is_object (extensions)); - json_object_foreach (extensions, name, blob) + json_object_foreach (extensions, name, manifest) { int critical; const char *version; json_t *config; - const struct TALER_Extension *extension = - TALER_extensions_get_by_name (name); + struct TALER_Extension *extension = (struct + TALER_Extension *) + TALER_extensions_get_by_name (name); if (NULL == extension) { @@ -291,45 +322,109 @@ TALER_extensions_load_json_config ( /* load and verify criticality, version, etc. */ if (GNUNET_OK != - TALER_extensions_is_json_config ( - blob, &critical, &version, &config)) + TALER_extensions_parse_manifest ( + manifest, &critical, &version, &config)) return GNUNET_SYSERR; if (critical != extension->critical || 0 != strcmp (version, extension->version) // TODO: libtool compare? || NULL == config - || GNUNET_OK != extension->test_json_config (config)) + || GNUNET_OK != extension->load_config (NULL, config)) return GNUNET_SYSERR; /* This _should_ work now */ if (GNUNET_OK != - extension->load_json_config ((struct TALER_Extension *) extension, - config)) + extension->load_config (extension, config)) return GNUNET_SYSERR; + + extension->enabled = true; } /* make sure to disable all extensions that weren't mentioned in the json */ - for (const struct TALER_Extension *it = TALER_extensions_get_head (); + for (const struct TALER_Extensions *it = TALER_extensions_get_head (); NULL != it; it = it->next) { - if (NULL == json_object_get (extensions, it->name)) - it->disable ((struct TALER_Extension *) it); + if (NULL == json_object_get (extensions, it->extension->name)) + it->extension->disable ((struct TALER_Extension *) it); } return GNUNET_OK; } -bool -TALER_extensions_age_restriction_is_enabled () +/* + * Policy related + */ + +static char *fulfillment2str[] = { + [TALER_PolicyFulfillmentReady] = "Ready", + [TALER_PolicyFulfillmentSuccess] = "Success", + [TALER_PolicyFulfillmentFailure] = "Failure", + [TALER_PolicyFulfillmentTimeout] = "Timeout", + [TALER_PolicyFulfillmentInsufficient] = "Insufficient", +}; + +const char * +TALER_policy_fulfillment_state_str ( + enum TALER_PolicyFulfillmentState state) { - const struct TALER_Extension *age = - TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); + GNUNET_assert (TALER_PolicyFulfillmentStateCount > state); + return fulfillment2str[state]; +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_create_policy_details ( + const json_t *policy_options, + struct TALER_PolicyDetails *details, + const char **error_hint) +{ + enum GNUNET_GenericReturnValue ret; + const struct TALER_Extension *extension; + const json_t *jtype; + const char *type; + + *error_hint = NULL; + + if ((NULL == policy_options) || + (! json_is_object (policy_options))) + { + *error_hint = "invalid policy object"; + return GNUNET_SYSERR; + } + + jtype = json_object_get (policy_options, "type"); + if (NULL == jtype) + { + *error_hint = "no type in policy object"; + return GNUNET_SYSERR; + } + + type = json_string_value (jtype); + if (NULL == type) + { + *error_hint = "invalid type in policy object"; + return GNUNET_SYSERR; + } + + extension = TALER_extensions_get_by_name (type); + if ((NULL == extension) || + (NULL == extension->create_policy_details)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unsupported extension policy '%s' requested\n", + type); + return GNUNET_NO; + } + + details->deadline = GNUNET_TIME_UNIT_FOREVER_TS; + ret = extension->create_policy_details (policy_options, + details, + error_hint); + return ret; - return (NULL != age && - NULL != age->config_json && - TALER_extensions_age_restriction_is_configured ()); } diff --git a/src/include/taler_auditor_service.h b/src/include/taler_auditor_service.h index 30d18e6e9..c20b789cc 100644 --- a/src/include/taler_auditor_service.h +++ b/src/include/taler_auditor_service.h @@ -233,7 +233,7 @@ typedef void * * @param auditor the auditor handle; the auditor must be ready to operate * @param h_wire hash of merchant wire details - * @param h_extensions hash over the extensions, if any + * @param h_policy hash over the policy, if any * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) * @param exchange_timestamp timestamp when the contract was finalized, must not be too far in the future * @param wire_deadline date until which the exchange should wire the funds @@ -257,7 +257,7 @@ struct TALER_AUDITOR_DepositConfirmationHandle * TALER_AUDITOR_deposit_confirmation ( struct TALER_AUDITOR_Handle *auditor, const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, const struct TALER_PrivateContractHashP *h_contract_terms, struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, diff --git a/src/include/taler_auditordb_plugin.h b/src/include/taler_auditordb_plugin.h index 9f46004c1..cf27668b6 100644 --- a/src/include/taler_auditordb_plugin.h +++ b/src/include/taler_auditordb_plugin.h @@ -382,9 +382,9 @@ struct TALER_AUDITORDB_DepositConfirmation struct TALER_PrivateContractHashP h_contract_terms; /** - * Hash over the extensions for the deposit. + * Hash over the policy extension for the deposit. */ - struct TALER_ExtensionContractHashP h_extensions; + struct TALER_ExtensionPolicyHashP h_policy; /** * Hash over the wiring information of the merchant. diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 4c478cefd..6b8000933 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -638,10 +638,9 @@ struct TALER_PrivateContractHashP /** - * Hash used to represent the "public" extensions to - * a contract that is shared with the exchange. + * Hash used to represent the policy extension to a deposit */ -struct TALER_ExtensionContractHashP +struct TALER_ExtensionPolicyHashP { /** * Actual hash value. @@ -727,10 +726,10 @@ struct TALER_PickupIdentifierP /** - * @brief Salted hash over the JSON object representing the configuration of an - * extension. + * @brief Salted hash over the JSON object representing the manifests of + * extensions. */ -struct TALER_ExtensionConfigHashP +struct TALER_ExtensionManifestsHashP { /** * Actual hash value. @@ -3213,7 +3212,7 @@ TALER_wallet_reserve_attest_request_verify ( * @param h_wire hash of the merchant’s account details * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) * @param h_age_commitment hash over the age commitment, if applicable to the denomination (maybe NULL) - * @param h_extensions hash over the extensions + * @param h_policy hash over the policy extension * @param h_denom_pub hash of the coin denomination's public key * @param coin_priv coin’s private key * @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future @@ -3228,7 +3227,7 @@ TALER_wallet_deposit_sign ( const struct TALER_MerchantWireHashP *h_wire, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_AgeCommitmentHash *h_age_commitment, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, const struct TALER_DenominationHashP *h_denom_pub, struct GNUNET_TIME_Timestamp wallet_timestamp, const struct TALER_MerchantPublicKeyP *merchant_pub, @@ -3245,7 +3244,7 @@ TALER_wallet_deposit_sign ( * @param h_wire hash of the merchant’s account details * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) * @param h_age_commitment hash over the age commitment (maybe all zeroes, if not applicable to the denomination) - * @param h_extensions hash over the extensions + * @param h_policy hash over the policy extension * @param h_denom_pub hash of the coin denomination's public key * @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) @@ -3261,7 +3260,7 @@ TALER_wallet_deposit_verify ( const struct TALER_MerchantWireHashP *h_wire, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_AgeCommitmentHash *h_age_commitment, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, const struct TALER_DenominationHashP *h_denom_pub, struct GNUNET_TIME_Timestamp wallet_timestamp, const struct TALER_MerchantPublicKeyP *merchant_pub, @@ -3666,7 +3665,7 @@ typedef enum TALER_ErrorCode * @param scb function to call to create the signature * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) * @param h_wire hash of the merchant’s account details - * @param h_extensions hash over the extensions, can be NULL + * @param h_policy hash over the policy extension, can be NULL * @param exchange_timestamp timestamp when the contract was finalized, must not be too far off * @param wire_deadline date until which the exchange should wire the funds * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline @@ -3682,7 +3681,7 @@ TALER_exchange_online_deposit_confirmation_sign ( TALER_ExchangeSignCallback scb, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, @@ -3698,7 +3697,7 @@ TALER_exchange_online_deposit_confirmation_sign ( * * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) * @param h_wire hash of the merchant’s account details - * @param h_extensions hash over the extensions, can be NULL + * @param h_policy hash over the policy extension, can be NULL * @param exchange_timestamp timestamp when the contract was finalized, must not be too far off * @param wire_deadline date until which the exchange should wire the funds * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline @@ -3713,7 +3712,7 @@ enum GNUNET_GenericReturnValue TALER_exchange_online_deposit_confirmation_verify ( const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, @@ -5257,31 +5256,31 @@ TALER_merchant_contract_sign ( /* **************** /management/extensions offline signing **************** */ /** - * Create a signature for the hash of the configuration of an extension + * Create a signature for the hash of the manifests of extensions * - * @param h_config hash of the JSON object representing the configuration + * @param h_manifests hash of the JSON object representing the manifests * @param master_priv private key to sign with * @param[out] master_sig where to write the signature */ void -TALER_exchange_offline_extension_config_hash_sign ( - const struct TALER_ExtensionConfigHashP *h_config, +TALER_exchange_offline_extension_manifests_hash_sign ( + const struct TALER_ExtensionManifestsHashP *h_manifests, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig); /** * Verify the signature in @a master_sig of the given hash, taken over the JSON - * blob representing the configuration of an extension + * blob representing the manifests of extensions * - * @param h_config hash of the JSON blob of a configuration of an extension + * @param h_manifest hash of the JSON blob of manifests of extensions * @param master_pub master public key of the exchange * @param master_sig signature of the exchange * @return #GNUNET_OK if signature is valid */ enum GNUNET_GenericReturnValue -TALER_exchange_offline_extension_config_hash_verify ( - const struct TALER_ExtensionConfigHashP *h_config, +TALER_exchange_offline_extension_manifests_hash_verify ( + const struct TALER_ExtensionManifestsHashP *h_manifest, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig ); diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index aa9a5b607..e3a349a92 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -894,9 +894,9 @@ struct TALER_EXCHANGE_DepositContractDetail struct TALER_PrivateContractHashP h_contract_terms; /** - * Extension-specific details about the deposit relevant to the exchange. + * Policy extension specific details about the deposit relevant to the exchange. */ - const json_t *extension_details; + json_t *policy_details; /** * Timestamp when the contract was finalized, must match approximately the diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 9640e052f..f21301e7e 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -26,6 +26,7 @@ #include <gnunet/gnunet_db_lib.h> #include "taler_json_lib.h" #include "taler_signatures.h" +#include "taler_extensions_policy.h" /** @@ -220,7 +221,8 @@ enum TALER_EXCHANGEDB_ReplicatedTable TALER_EXCHANGEDB_RT_RECOUP, TALER_EXCHANGEDB_RT_RECOUP_REFRESH, TALER_EXCHANGEDB_RT_EXTENSIONS, - TALER_EXCHANGEDB_RT_EXTENSION_DETAILS, + TALER_EXCHANGEDB_RT_POLICY_DETAILS, + TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS, TALER_EXCHANGEDB_RT_PURSE_REQUESTS, TALER_EXCHANGEDB_RT_PURSE_DECISION, TALER_EXCHANGEDB_RT_PURSE_MERGES, @@ -438,8 +440,8 @@ struct TALER_EXCHANGEDB_TableData struct TALER_CoinSpendSignatureP coin_sig; struct TALER_WireSaltP wire_salt; struct TALER_PaytoHashP wire_target_h_payto; - bool extension_blocked; - uint64_t extension_details_serial_id; + bool policy_blocked; + uint64_t policy_details_serial_id; } deposits; struct @@ -510,13 +512,32 @@ struct TALER_EXCHANGEDB_TableData struct { char *name; - char *config; + char *manifest; } extensions; struct { - char *extension_options; - } extension_details; + struct GNUNET_HashCode hash_code; + json_t *policy_json; + bool no_policy_json; + struct GNUNET_TIME_Timestamp deadline; + struct TALER_Amount commitment; + struct TALER_Amount accumulated_total; + struct TALER_Amount fee; + struct TALER_Amount transferable; + uint16_t fulfillment_state; /* will also be recomputed */ + uint64_t fulfillment_id; + bool no_fulfillment_id; + } policy_details; + + struct + { + struct GNUNET_TIME_Timestamp fulfillment_timestamp; + char *fulfillment_proof; + struct GNUNET_HashCode h_fulfillment_proof; + struct GNUNET_HashCode *policy_hash_codes; + size_t policy_hash_codes_count; + } policy_fulfillments; struct { @@ -1512,12 +1533,6 @@ struct TALER_EXCHANGEDB_Deposit char *receiver_wire_account; /** - * Additional details for extensions relevant for this - * deposit operation, possibly NULL! - */ - json_t *extension_details; - - /** * Time when this request was generated. Used, for example, to * assess when (roughly) the income was achieved for tax purposes. * Note that the Exchange will only check that the timestamp is not "too @@ -1558,6 +1573,16 @@ struct TALER_EXCHANGEDB_Deposit */ struct TALER_Amount deposit_fee; + /* + * True if @e policy_json was provided + */ + bool has_policy; + + /** + * Hash over the policy data for this deposit (remains unknown to the + * Exchange). Needed for the verification of the deposit's signature + */ + struct TALER_ExtensionPolicyHashP h_policy; }; @@ -1656,6 +1681,17 @@ struct TALER_EXCHANGEDB_DepositListEntry */ struct TALER_Amount deposit_fee; + /* + * True if a policy was provided with the deposit request + */ + bool has_policy; + + /** + * Hash over the policy data for this deposit (remains unknown to the + * Exchange). Needed for the verification of the deposit's signature + */ + struct TALER_ExtensionPolicyHashP h_policy; + /** * Has the deposit been wired? */ @@ -3530,6 +3566,40 @@ struct TALER_EXCHANGEDB_Plugin bool *conflict, bool *nonce_reuse); + /** + * Retrieve the details to a policy given by its hash_code + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param hc Hash code that identifies the policy + * @param[out] detail retrieved policy details + * @return query execution status + */ + enum GNUNET_DB_QueryStatus + (*get_policy_details)( + void *cls, + const struct GNUNET_HashCode *hc, + struct TALER_PolicyDetails *detail); + + /** + * Persist the policy details that extends a deposit. The particular policy + * - referenced by details->hash_code - might already exist in the table, in + * which case the call will update the contents of the record with @e details + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param details The parsed `struct TALER_PolicyDetails` according to the responsible policy extension. + * @param[out] policy_details_serial_id The ID of the entry in the policy_details table + * @param[out] accumulated_total The total amount accumulated in that policy + * @param[out] fulfillment_state The state of policy. If the state was Insufficient prior to the call and the provided deposit raises the accumulated_total above the commitment, it will be set to Ready. + * @return query execution status + */ + enum GNUNET_DB_QueryStatus + (*persist_policy_details)( + void *cls, + const struct TALER_PolicyDetails *details, + uint64_t *policy_details_serial_id, + struct TALER_Amount *accumulated_total, + enum TALER_PolicyFulfillmentState *fulfillment_state); + /** * Perform deposit operation, checking for sufficient balance @@ -3539,7 +3609,7 @@ struct TALER_EXCHANGEDB_Plugin * @param deposit deposit operation details * @param known_coin_id row of the coin in the known_coins table * @param h_payto hash of the merchant's payto URI - * @param extension_blocked true if an extension is blocking the wire transfer + * @param policy_details_serial_id (pointer to) the row ID of the policy details, maybe NULL * @param[in,out] exchange_timestamp time to use for the deposit (possibly updated) * @param[out] balance_ok set to true if the balance was sufficient * @param[out] in_conflict set to true if the deposit conflicted @@ -3551,7 +3621,7 @@ struct TALER_EXCHANGEDB_Plugin const struct TALER_EXCHANGEDB_Deposit *deposit, uint64_t known_coin_id, const struct TALER_PaytoHashP *h_payto, - bool extension_blocked, + uint64_t *policy_details_serial_id, struct GNUNET_TIME_Timestamp *exchange_timestamp, bool *balance_ok, bool *in_conflict); @@ -3581,6 +3651,19 @@ struct TALER_EXCHANGEDB_Plugin /** + * Add a proof of fulfillment of an policy + * + * @param cls the plugin-specific state + * @param[in,out] fulfillment The proof of fulfillment and serial_ids of the policy_details along with their new state and potential new amounts. + * @return query execution status + */ + enum GNUNET_DB_QueryStatus + (*add_policy_fulfillment_proof)( + void *cls, + struct TALER_PolicyFulfillmentTransactionData *fulfillment); + + + /** * Check if the given @a nonce was properly locked to the given @a old_coin_pub. If so, check if we already * created CS signatures for the given @a nonce and @a new_denom_pub_hashes, * and if so, return them in @a s_scalars. Otherwise, persist the @@ -5559,33 +5642,33 @@ struct TALER_EXCHANGEDB_Plugin /** - * Function called to save the configuration of an extension - * (age-restriction, peer2peer, ...) + * Function called to save the manifest of an extension + * (age-restriction, policy-extension, ...) * * @param cls the @e cls of this struct with the plugin-specific state * @param extension_name the name of the extension - * @param config JSON object of the configuration as string, maybe NULL (== disabled extension) + * @param manifest JSON object of the Manifest as string, maybe NULL (== disabled extension) * @return transaction status code */ enum GNUNET_DB_QueryStatus - (*set_extension_config)(void *cls, - const char *extension_name, - const char *config); + (*set_extension_manifest)(void *cls, + const char *extension_name, + const char *manifest); /** - * Function called to retrieve the configuration of an extension - * (age-restriction, peer2peer, ...) + * Function called to retrieve the manifest of an extension + * (age-restriction, policy-extension, ...) * * @param cls the @e cls of this struct with the plugin-specific state * @param extension_name the name of the extension - * @param[out] config JSON object of the configuration as string, maybe NULL (== disabled extension) + * @param[out] manifest Manifest of the extension in JSON encoding, maybe NULL (== disabled extension) * @return transaction status code */ enum GNUNET_DB_QueryStatus - (*get_extension_config)(void *cls, - const char *extension_name, - char **config); + (*get_extension_manifest)(void *cls, + const char *extension_name, + char **manifest); /** diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index 8e1823cce..5e53d27f8 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2022 Taler Systems SA TALER 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 @@ -24,48 +24,195 @@ #include <gnunet/gnunet_util_lib.h> #include "taler_crypto_lib.h" #include "taler_json_lib.h" +#include "taler_mhd_lib.h" +#include "taler_extensions_policy.h" #define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-" enum TALER_Extension_Type { - TALER_Extension_AgeRestriction = 0, - TALER_Extension_MaxPredefined = 1 // Must be last of the predefined + TALER_Extension_PolicyNull = 0, + + TALER_Extension_AgeRestriction = 1, + TALER_Extension_PolicyMerchantRefund = 2, + TALER_Extension_PolicyBrandtVickeryAuction = 3, + TALER_Extension_PolicyEscrowedPayment = 4, + + TALER_Extension_MaxPredefined = 5 // Must be last of the predefined }; + +/* Forward declarations */ +enum TALER_PolicyFulfillmentState; +struct TALER_PolicyFulfillmentOutcome; + /* * @brief Represents the implementation of an extension. * - * TODO: add documentation + * An "Extension" is an optional feature for the Exchange. + * There are only two types of extensions: + * + * a) Age restriction: This is a special feature that directly interacts with + * denominations and coins, but is not define policies during deposits, see b). + * The implementation of this extension doesn't have to implement any of the + * http- or depost-handlers in the struct. + * + * b) Policies for deposits: These are extensions that define policies (such + * as refund, escrow or auctions) for deposit requests. These extensions have + * to implement at least the deposit- and post-http-handler in the struct to be + * functional. + * + * In addition to the handlers defined in this struct, an extension must also + * be a plugin in the GNUNET_Plugin sense. That is, it must implement the + * functions + * 1: (void *ext)libtaler_extension_<name>_init(void *cfg) + * and + * 2: (void *)libtaler_extension_<name>_done(void *) + * + * In 1:, the input will be the GNUNET_CONFIGURATION_Handle to the TALER + * configuration and the output must be the struct TALER_Extension * on + * success, NULL otherwise. + * + * In 2:, no arguments are passed and NULL is expected to be returned. */ struct TALER_Extension { - /* simple linked list */ - struct TALER_Extension *next; - + /** + * Type of the extension. Only one extension of a type can be loaded + * at any time. + */ enum TALER_Extension_Type type; + + /** + * The name of the extension, must be unique among all loaded extensions. It + * is used in URLs for /extension/$NAME as well. + */ char *name; + + /** + * Criticality of the extension. It has the same semantics as "critical" has + * for extensions in X.509: + * - if "true", the client must "understand" the extension before proceeding, + * - if "false", clients can safely skip extensions they do not understand. + * (see https://datatracker.ietf.org/doc/html/rfc5280#section-4.2) + */ bool critical; + + /** + * Version of the extension must be provided in Taler's protocol verison ranges notation, see + * https://docs.taler.net/core/api-common.html#protocol-version-ranges + */ char *version; + + /** + * If the extension is marked as enabled, it will be listed in the + * "extensions" field in the "/keys" response. + */ + bool enabled; + + /** + * Opaque (public) configuration object, set by the extension. + */ void *config; - json_t *config_json; - void (*disable)(struct TALER_Extension *ext); - enum GNUNET_GenericReturnValue (*test_json_config)( - const json_t *config); + /** + * @brief Handler to to disable the extension. + * + * @param ext The current extension object + */ + void (*disable)(struct TALER_Extension *ext); - enum GNUNET_GenericReturnValue (*load_json_config)( + /** + * @brief Handler to read an extension-specific configuration in JSON + * encoding and enable the extension. Must be implemented by the extension. + * + * @param ext The extension object. If NULL, the configuration will only be checked. + * @param config A JSON blob + * @return GNUNET_OK if the json was a valid configuration for the extension. + */ + enum GNUNET_GenericReturnValue (*load_config)( struct TALER_Extension *ext, json_t *config); - json_t *(*config_to_json)( + /** + * @brief Handler to return the manifest of the extension in JSON encoding. + * + * See + * https://docs.taler.net/design-documents/006-extensions.html#tsref-type-Extension + * for the definition. + * + * @param ext The extension object + * @return The JSON encoding of the extension, if enabled, NULL otherwise. + */ + json_t *(*manifest)( const struct TALER_Extension *ext); - enum GNUNET_GenericReturnValue (*load_taler_config)( - struct TALER_Extension *ext, - const struct GNUNET_CONFIGURATION_Handle *cfg); + /* ========================= + * Policy related handlers + * ========================= + */ + + /** + * @brief Handler to check an incoming policy and create a + * TALER_PolicyDetails. Can be NULL; + * + * When a deposit request refers to this extension in its policy + * (see https://docs.taler.net/core/api-exchange.html#deposit), this handler + * will be called before the deposit transaction. + * + * @param[in] policy_json Details about the policy, provided by the client + * during a deposit request. + * @param[out] details On success, will contain the details to the policy, + * evaluated by the corresponding policy handler. + * @param[out] error_hint On error, will contain a hint + * @return GNUNET_OK if the data was accepted by the extension. + */ + enum GNUNET_GenericReturnValue (*create_policy_details)( + const json_t *policy_json, + struct TALER_PolicyDetails *details, + const char **error_hint); + + /** + * @brief Handler for POST-requests to the /extensions/$name endpoint. Can be NULL. + * + * @param[in] root The JSON body from the request + * @param[in] args Additional query parameters of the request. + * @param[in,out] details List of policy details related to the incoming fulfillment proof + * @param[in] details_len Size of the list @e details + * @param[out] output JSON output to return to the client + * @return GNUNET_OK on success. + */ + enum GNUNET_GenericReturnValue (*policy_post_handler)( + const json_t *root, + const char *const args[], + struct TALER_PolicyDetails *details, + size_t details_len, + json_t **output); + + /** + * @brief Handler for GET-requests to the /extensions/$name endpoint. Can be NULL. + * + * @param connection The current connection + * @param root The JSON body from the request + * @param args Additional query parameters of the request. + * @return MDH result + */ + MHD_RESULT (*policy_get_handler)( + struct MHD_Connection *connection, + const char *const args[]); +}; + + +/* + * @brief simply linked list of extensions + */ + +struct TALER_Extensions +{ + struct TALER_Extensions *next; + const struct TALER_Extension *extension; }; /** @@ -73,70 +220,57 @@ struct TALER_Extension */ /* - * @brief Sets the configuration of the extensions from the given TALER - * configuration. + * @brief Loads the extensions as shared libraries, as specified in the given + * TALER configuration. * * @param cfg Handle to the TALER configuration * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found * or any particular configuration couldn't be parsed. */ enum GNUNET_GenericReturnValue -TALER_extensions_load_taler_config ( +TALER_extensions_init ( const struct GNUNET_CONFIGURATION_Handle *cfg); /* - * @brief Checks the given obj to be a valid extension object and fill the - * fields accordingly. + * @brief Parses a given JSON object as an extension manifest. * - * @param[in] obj Object to verify is a valid extension + * @param[in] obj JSON object to parse as an extension manifest * @param{out] critical will be set to 1 if the extension is critical according to obj * @param[out] version will be set to the version of the extension according to obj * @param[out] config will be set to the configuration of the extension according to obj * @return OK on success, Error otherwise */ enum GNUNET_GenericReturnValue -TALER_extensions_is_json_config ( +TALER_extensions_parse_manifest ( json_t *obj, int *critical, const char **version, json_t **config); /* - * @brief Sets the configuration of the extensions from a given JSON object. + * @brief Loads extensions according to the manifests. * - * The JSON object must be of type ExchangeKeysResponse as described in - * https://docs.taler.net/design-documents/006-extensions.html#exchange + * The JSON object must be of type ExtensionsManifestsResponse as described + * in https://docs.taler.net/design-documents/006-extensions.html#exchange * - * @param cfg JSON object containing the configuration for all extensions + * @param cfg JSON object containing the manifests for all extensions * @return #GNUNET_OK on success, #GNUNET_SYSERR if unknown extensions were * found or any particular configuration couldn't be parsed. */ enum GNUNET_GenericReturnValue -TALER_extensions_load_json_config ( - json_t *cfg); +TALER_extensions_load_manifests ( + json_t *manifests); /* * @brief Returns the head of the linked list of extensions. */ -const struct TALER_Extension * +const struct TALER_Extensions * TALER_extensions_get_head (); -/* - * @brief Adds an extension to the linked list of extensions. - * - * @param new_extension the new extension to be added - * @return GNUNET_OK on success, GNUNET_SYSERR if the extension is invalid - * (missing fields), GNUNET_NO if there is already an extension with that name - * or type. - */ -enum GNUNET_GenericReturnValue -TALER_extensions_add ( - struct TALER_Extension *new_extension); - /** * @brief Finds and returns a supported extension by a given type. * - * @param type type of the extension to lookup + * @param type of the extension to lookup * @return extension found, or NULL (should not happen!) */ const struct TALER_Extension * @@ -154,8 +288,6 @@ const struct TALER_Extension * TALER_extensions_get_by_name ( const char *name); -#define TALER_extensions_is_enabled(ext) (NULL != (ext)->config) - /** * @brief Check if a given type of an extension is enabled * @@ -166,12 +298,21 @@ bool TALER_extensions_is_enabled_type ( enum TALER_Extension_Type type); +/** + * @brief Check if an extension is enabled + * + * @param extension The extension handler. + * @return true enabled, false if not enabled, will assert if type is not found. + */ +bool +TALER_extensions_is_enabled ( + const struct TALER_Extension *extension); /* * Verify the signature of a given JSON object for extensions with the master * key of the exchange. * - * The JSON object must be of type ExchangeKeysResponse as described in + * The JSON object must be of type ExtensionsManifestsResponse as described in * https://docs.taler.net/design-documents/006-extensions.html#exchange * * @param extensions JSON object with the extension configuration @@ -181,14 +322,19 @@ TALER_extensions_is_enabled_type ( * and GNUNET_NO if the signature couldn't be verified. */ enum GNUNET_GenericReturnValue -TALER_extensions_verify_json_config_signature ( - json_t *extensions, +TALER_extensions_verify_manifests_signature ( + json_t *manifests, struct TALER_MasterSignatureP *extensions_sig, struct TALER_MasterPublicKeyP *master_pub); /* * TALER Age Restriction Extension + * + * This extension is special insofar as it directly interacts with coins and + * denominations. + * + * At the same time, it doesn't implement and http- or deposit-handlers. */ #define TALER_EXTENSION_SECTION_AGE_RESTRICTION (TALER_EXTENSION_SECTION_PREFIX \ @@ -204,102 +350,39 @@ TALER_extensions_verify_json_config_signature ( | 1 << 21) #define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21" -/** - * @brief Registers the extension for age restriction to the list extensions - */ -enum GNUNET_GenericReturnValue -TALER_extension_age_restriction_register (); -/** - * @brief Parses a string as a list of age groups. - * - * The string must consist of a colon-separated list of increasing integers - * between 0 and 31. Each entry represents the beginning of a new age group. - * F.e. the string - * - * "8:10:12:14:16:18:21" - * - * represents the following list of eight age groups: - * - * | Group | Ages | - * | -----:|:------------- | - * | 0 | 0, 1, ..., 7 | - * | 1 | 8, 9 | - * | 2 | 10, 11 | - * | 3 | 12, 13 | - * | 4 | 14, 15 | - * | 5 | 16, 17 | - * | 6 | 18, 19, 20 | - * | 7 | 21, ... | - * - * which is then encoded as a bit mask with the corresponding bits set: - * - * 31 24 16 8 0 - * | | | | | - * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1 - * - * @param groups String representation of age groups - * @param[out] mask Mask representation for age restriction. - * @return Error, if age groups were invalid, OK otherwise. +/* + * @brief Configuration for Age Restriction */ -enum GNUNET_GenericReturnValue -TALER_parse_age_group_string ( - const char *groups, - struct TALER_AgeMask *mask); +struct TALER_AgeRestrictionConfig +{ + struct TALER_AgeMask mask; + uint8_t num_groups; +}; -/** - * @brief Encodes the age mask into a string, like "8:10:12:14:16:18:21" - * - * @param mask Age mask - * @return String representation of the age mask, allocated by GNUNET_malloc. - * Can be used as value in the TALER config. - */ -char * -TALER_age_mask_to_string ( - const struct TALER_AgeMask *mask); /** - * @brief Returns true when age restriction is configured and enabled. + * @brief Retrieve the age restriction configuration + * + * @return age restriction configuration if present, otherwise NULL. */ -bool -TALER_extensions_age_restriction_is_enabled (); +const struct TALER_AgeRestrictionConfig * +TALER_extensions_get_age_restriction_config (); /** - * @brief Returns true when age restriction is configured (might not be - * _enabled_, though). + * @brief Check if age restriction is enabled + * + * @return true, if age restriction is loaded, configured and enabled; otherwise false. */ bool -TALER_extensions_age_restriction_is_configured (); - -/** - * @brief Returns the currently set age mask. Note that even if age - * restriction is not enabled, the age mask might be have a non-zero value. - */ -struct TALER_AgeMask -TALER_extensions_age_restriction_ageMask (); - +TALER_extensions_is_age_restriction_enabled (); /** - * @brief Returns the amount of age groups defined. 0 means no age restriction - * enabled. - */ -size_t -TALER_extensions_age_restriction_num_groups (); - -/** - * @brief Parses a JSON object { "age_groups": "a:b:...y:z" }. + * @brief Return the age mask for age restriction * - * @param root is the json object - * @param[out] mask on success, will contain the age mask - * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure. - */ -enum GNUNET_GenericReturnValue -TALER_JSON_parse_age_groups (const json_t *root, - struct TALER_AgeMask *mask); - - -/* - * TODO: Add Peer2Peer Extension + * @return configured age mask, if age restriction is loaded, configured and enabled; otherwise zero mask. */ +struct TALER_AgeMask +TALER_extensions_get_age_restriction_mask (); #endif diff --git a/src/include/taler_extensions_policy.h b/src/include/taler_extensions_policy.h new file mode 100644 index 000000000..14a581f3c --- /dev/null +++ b/src/include/taler_extensions_policy.h @@ -0,0 +1,198 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file include/taler_extensions_policy.h + * @brief Interface for policy extensions + * @author Özgür Kesim + */ +#ifndef TALER_EXTENSIONS_POLICY_H +#define TALER_EXTENSIONS_POLICY_H + +#include <gnunet/gnunet_util_lib.h> +#include "taler_crypto_lib.h" +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" + +/* + * @brief Describes the states of fulfillment of a policy bound to a deposit + */ +enum TALER_PolicyFulfillmentState +{ + /* General error state of an fulfillment. */ + TALER_PolicyFulfillmentFailure = 0, + + /* The policy is not yet ready due to insufficient funding. More deposits are + * necessary for it to become ready . */ + TALER_PolicyFulfillmentInsufficient = 1, + + /* The policy is funded and ready, pending */ + TALER_PolicyFulfillmentReady = 2, + + /* Policy is provably fulfilled. */ + TALER_PolicyFulfillmentSuccess = 3, + + /* Policy fulfillment has timed out */ + TALER_PolicyFulfillmentTimeout = 4, + + TALER_PolicyFulfillmentStateCount = TALER_PolicyFulfillmentTimeout + 1 +}; + + +/* + * @brief Returns a string representation of the state of a policy fulfillment + */ +const char * +TALER_policy_fulfillment_state_str (enum TALER_PolicyFulfillmentState state); + + +/* @brief Details of a policy for a deposit request */ +struct TALER_PolicyDetails +{ + /* Hash code that should be used for the .policy_hash_code field when + * this policy is saved in the policy_details table. */ + struct GNUNET_HashCode hash_code; + + /* Content of the policy in its original JSON form */ + json_t *policy_json; + + /* When the deadline is meat and the policy is still in "Ready" state, + * a timeout-handler will transfer the amount + * (total_amount - policy_fee - refreshable_amount) + * to the payto-URI from the corresponding deposit. The value + * amount_refreshable will be refreshable by the owner of the + * associated deposits's coins */ + struct GNUNET_TIME_Timestamp deadline; + + /* The amount to which this policy commits to. It must be at least as + * large as @e policy_fee. */ + struct TALER_Amount commitment; + + /* The total sum of contributions from coins so far to fund this + * policy. It must be at least as large as @commitment in order to be + * sufficiently funded. */ + struct TALER_Amount accumulated_total; + + /* The fee from the exchange for handling the policy. It is due when + * the state changes to Timeout or Success. */ + struct TALER_Amount policy_fee; + + /* The amount that will be transfered to the payto-URIs from the + * corresponding deposits when the fulfillment state changes to Timeout + * or Success. Note that a fulfillment handler can alter this upon + * arrival of a proof of fulfillment. The remaining amount + * (accumulated_amount - policy_amount - transferable_amount) */ + struct TALER_Amount transferable_amount; + + /* The state of fulfillment of a policy. + * - If the state is Insufficient, the client is required to call + * /deposit -maybe multiple times- with enough coins and the same + * policy details in order to reach the required amount. The state is + * then changed to Ready. + * - If the state changes to Timeout or Success, a handler will transfer + * the amount (total_amount - policy_fee - refreshable_amount) to the + * payto-URI from the corresponding deposit. The value + * amount_refreshable will be refreshable by the owner of the + * associated deposits's coins. */ + enum TALER_PolicyFulfillmentState fulfillment_state; + + /* If there is a proof of fulfillment, the row ID from the + * policy_fulfillment table */ + uint64_t policy_fulfillment_id; + bool no_policy_fulfillment_id; +}; + +/* + * @brief All information required for the database transaction when handling a + * proof of fulfillment request. + */ +struct TALER_PolicyFulfillmentTransactionData +{ + /* The incoming proof, provided by a client */ + const json_t *proof; + + /* The Hash of the proof */ + struct GNUNET_HashCode h_proof; + + /* The timestamp of retrieval of the proof */ + struct GNUNET_TIME_Timestamp timestamp; + + /* The ID of the proof in the policy_fulfillment table. Will be set + * during the transaction. Needed to fill the table + * policy_details_fulfillments. */ + uint64_t fulfillment_id; + + /* The list of policy details. Will be updated by the policy handler */ + struct TALER_PolicyDetails *details; + size_t details_count; +}; + + +/* + * @brief Extracts policy details from the deposit's policy options and the policy extensions + * + * @param[in] policy_options JSON of the policy options from a deposit request + * @param[out] details On GNUNET_OK, the parsed details + * @param[out] error_hint On GNUNET_SYSERR, will contain a hint for the reason why it failed + * @return GNUNET_OK on success, GNUNET_NO, when no extension was found. GNUNET_SYSERR when the JSON was + * invalid, with *error_hint maybe non-NULL. + */ +enum GNUNET_GenericReturnValue +TALER_extensions_create_policy_details ( + const json_t *policy_options, + struct TALER_PolicyDetails *details, + const char **error_hint); + + +/* + * ================================ + * Merchant refund policy + * ================================ + */ +struct TALER_ExtensionPolicyMerchantRefundPolicyConfig +{ + struct GNUNET_TIME_Relative max_timeout; +}; + +/* + * ================================ + * Brandt-Vickrey Auctions policy + * ================================ + */ +/* + * @brief Configuration for Brandt-Vickrey auctions policy + */ +struct TALER_ExtensionPolicyBrandtVickreyAuctionConfig +{ + uint16_t max_bidders; + uint16_t max_prices; + struct TALER_Amount auction_fee; +}; + + +/* + * ================================ + * Escrowed Payments policy + * ================================ + */ +/* + * @brief Configuration for escrowed payments policy + */ +struct TALER_ExtensionPolicyEscrowedPaymentsConfig +{ + struct GNUNET_TIME_Relative max_timeout; +}; + +#endif diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index 12526e954..d0527cc74 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -765,25 +765,25 @@ TALER_JSON_wire_to_payto (const json_t *wire_s); /** - * Hash @a extensions in deposits. + * Hash @a policy extensions in deposits. * - * @param extensions contract extensions to hash - * @param[out] ech where to write the extension hash + * @param policy contract policy extension to hash + * @param[out] ech where to write the policy hash */ void -TALER_deposit_extension_hash (const json_t *extensions, - struct TALER_ExtensionContractHashP *ech); +TALER_deposit_policy_hash (const json_t *extensions, + struct TALER_ExtensionPolicyHashP *ech); /** - * Hash the @a config of an extension, given as JSON + * Hash the @a manifests of extensions, given as JSON * - * @param config configuration of the extension - * @param[out] eh where to write the extension hash + * @param manifests Manifests of the extensions + * @param[out] eh where to write the hash * @return GNUNET_OK on success, GNUNET_SYSERR on failure */ enum GNUNET_GenericReturnValue -TALER_JSON_extensions_config_hash (const json_t *config, - struct TALER_ExtensionConfigHashP *eh); +TALER_JSON_extensions_manifests_hash (const json_t *manifests, + struct TALER_ExtensionManifestsHashP *eh); /** * Canonicalize a JSON input to a string according to RFC 8785. diff --git a/src/include/taler_util.h b/src/include/taler_util.h index 079f72ed6..1580f3dca 100644 --- a/src/include/taler_util.h +++ b/src/include/taler_util.h @@ -492,5 +492,63 @@ char *strchrnul (const char *s, int c); #endif +/** + * @brief Parses a string as a list of age groups. + * + * The string must consist of a colon-separated list of increasing integers + * between 0 and 31. Each entry represents the beginning of a new age group. + * F.e. the string + * + * "8:10:12:14:16:18:21" + * + * represents the following list of eight age groups: + * + * | Group | Ages | + * | -----:|:------------- | + * | 0 | 0, 1, ..., 7 | + * | 1 | 8, 9 | + * | 2 | 10, 11 | + * | 3 | 12, 13 | + * | 4 | 14, 15 | + * | 5 | 16, 17 | + * | 6 | 18, 19, 20 | + * | 7 | 21, ... | + * + * which is then encoded as a bit mask with the corresponding bits set: + * + * 31 24 16 8 0 + * | | | | | + * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1 + * + * @param groups String representation of age groups + * @param[out] mask Mask representation for age restriction. + * @return Error, if age groups were invalid, OK otherwise. + */ +enum GNUNET_GenericReturnValue +TALER_parse_age_group_string ( + const char *groups, + struct TALER_AgeMask *mask); + +/** + * @brief Encodes the age mask into a string, like "8:10:12:14:16:18:21" + * + * @param mask Age mask + * @return String representation of the age mask, allocated by GNUNET_malloc. + * Can be used as value in the TALER config. + */ +char * +TALER_age_mask_to_string ( + const struct TALER_AgeMask *mask); + +/** + * @brief Parses a JSON object { "age_groups": "a:b:...y:z" }. + * + * @param root is the json object + * @param[out] mask on success, will contain the age mask + * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure. + */ +enum GNUNET_GenericReturnValue +TALER_JSON_parse_age_groups (const json_t *root, + struct TALER_AgeMask *mask); #endif diff --git a/src/json/json.c b/src/json/json.c index d4ac37489..7d7e4ecba 100644 --- a/src/json/json.c +++ b/src/json/json.c @@ -1008,12 +1008,12 @@ TALER_JSON_get_error_code2 (const void *data, void -TALER_deposit_extension_hash (const json_t *extensions, - struct TALER_ExtensionContractHashP *ech) +TALER_deposit_policy_hash (const json_t *policy, + struct TALER_ExtensionPolicyHashP *ech) { GNUNET_assert (GNUNET_OK == - dump_and_hash (extensions, - "taler-contract-extensions", + dump_and_hash (policy, + "taler-extensions-policy", &ech->hash)); } @@ -1037,11 +1037,11 @@ TALER_JSON_canonicalize (const json_t *input) enum GNUNET_GenericReturnValue -TALER_JSON_extensions_config_hash (const json_t *config, - struct TALER_ExtensionConfigHashP *ech) +TALER_JSON_extensions_manifests_hash (const json_t *manifests, + struct TALER_ExtensionManifestsHashP *ech) { - return dump_and_hash (config, - "taler-extension-configuration", + return dump_and_hash (manifests, + "taler-extensions-manifests", &ech->hash); } diff --git a/src/lib/auditor_api_deposit_confirmation.c b/src/lib/auditor_api_deposit_confirmation.c index c4542d0eb..82537e25a 100644 --- a/src/lib/auditor_api_deposit_confirmation.c +++ b/src/lib/auditor_api_deposit_confirmation.c @@ -153,7 +153,7 @@ handle_deposit_confirmation_finished (void *cls, * Verify signature information about the deposit-confirmation. * * @param h_wire hash of merchant wire details - * @param h_extensions hash over the extensions, if any + * @param h_policy hash over the policy extension, if any * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) * @param exchange_timestamp timestamp when the deposit was received by the wallet * @param wire_deadline by what time must the amount be wired to the merchant @@ -172,7 +172,7 @@ handle_deposit_confirmation_finished (void *cls, */ static enum GNUNET_GenericReturnValue verify_signatures (const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, const struct TALER_PrivateContractHashP *h_contract_terms, struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, @@ -192,7 +192,7 @@ verify_signatures (const struct TALER_MerchantWireHashP *h_wire, TALER_exchange_online_deposit_confirmation_verify ( h_contract_terms, h_wire, - h_extensions, + h_policy, exchange_timestamp, wire_deadline, refund_deadline, @@ -239,7 +239,7 @@ struct TALER_AUDITOR_DepositConfirmationHandle * TALER_AUDITOR_deposit_confirmation ( struct TALER_AUDITOR_Handle *auditor, const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, const struct TALER_PrivateContractHashP *h_contract_terms, struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, @@ -266,7 +266,7 @@ TALER_AUDITOR_deposit_confirmation ( TALER_AUDITOR_handle_is_ready_ (auditor)); if (GNUNET_OK != verify_signatures (h_wire, - h_extensions, + h_policy, h_contract_terms, exchange_timestamp, wire_deadline, @@ -290,8 +290,8 @@ TALER_AUDITOR_deposit_confirmation ( = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("h_wire", h_wire), - GNUNET_JSON_pack_data_auto ("h_extensions", - h_extensions), + GNUNET_JSON_pack_data_auto ("h_policy", + h_policy), GNUNET_JSON_pack_data_auto ("h_contract_terms", h_contract_terms), GNUNET_JSON_pack_timestamp ("exchange_timestamp", diff --git a/src/lib/exchange_api_batch_deposit.c b/src/lib/exchange_api_batch_deposit.c index 39c1c9b4f..ca5c3c618 100644 --- a/src/lib/exchange_api_batch_deposit.c +++ b/src/lib/exchange_api_batch_deposit.c @@ -99,7 +99,7 @@ struct TALER_EXCHANGE_BatchDepositHandle /** * Hash over the extensions, or all zero. */ - struct TALER_ExtensionContractHashP h_extensions; + struct TALER_ExtensionPolicyHashP h_policy; /** * Time when this confirmation was generated / when the exchange received @@ -185,7 +185,7 @@ auditor_cb (void *cls, aie->dch = TALER_AUDITOR_deposit_confirmation ( ah, &dh->h_wire, - &dh->h_extensions, + &dh->h_policy, &dh->dcd.h_contract_terms, dh->exchange_timestamp, dh->dcd.wire_deadline, @@ -317,7 +317,7 @@ handle_deposit_finished (void *cls, TALER_exchange_online_deposit_confirmation_verify ( &dh->dcd.h_contract_terms, &dh->h_wire, - &dh->h_extensions, + &dh->h_policy, dh->exchange_timestamp, dh->dcd.wire_deadline, dh->dcd.refund_deadline, @@ -492,9 +492,9 @@ TALER_EXCHANGE_batch_deposit ( * sizeof (*cdds)); dh->num_cdds = num_cdds; dh->dcd = *dcd; - if (NULL != dcd->extension_details) - TALER_deposit_extension_hash (dcd->extension_details, - &dh->h_extensions); + if (NULL != dcd->policy_details) + TALER_deposit_policy_hash (dcd->policy_details, + &dh->h_policy); TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri, &dcd->wire_salt, &dh->h_wire); @@ -533,7 +533,7 @@ TALER_EXCHANGE_batch_deposit ( if (GNUNET_OK != TALER_EXCHANGE_verify_deposit_signature_ (dcd, - &dh->h_extensions, + &dh->h_policy, &dh->h_wire, cdd, dki)) @@ -586,8 +586,8 @@ TALER_EXCHANGE_batch_deposit ( GNUNET_JSON_pack_array_steal ("coins", deposits), GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_steal ("extension_details", - NULL)), /* FIXME #7270-Oec */ + GNUNET_JSON_pack_object_steal ("policy_details", + dcd->policy_details)), GNUNET_JSON_pack_timestamp ("timestamp", dcd->timestamp), GNUNET_JSON_pack_data_auto ("merchant_pub", diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 3041e6e0e..3807b997f 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -844,7 +844,8 @@ help_deposit (struct CoinHistoryParseContext *pc, { struct TALER_MerchantWireHashP h_wire; struct TALER_PrivateContractHashP h_contract_terms; - // struct TALER_ExtensionContractHashP h_extensions; // FIXME #7270! + struct TALER_ExtensionPolicyHashP h_policy; + bool no_h_policy; struct GNUNET_TIME_Timestamp wallet_timestamp; struct TALER_MerchantPublicKeyP merchant_pub; struct GNUNET_TIME_Timestamp refund_deadline = {0}; @@ -863,6 +864,10 @@ help_deposit (struct CoinHistoryParseContext *pc, GNUNET_JSON_spec_fixed_auto ("h_age_commitment", &hac), &no_hac), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_policy", + &h_policy), + &no_h_policy), GNUNET_JSON_spec_timestamp ("timestamp", &wallet_timestamp), GNUNET_JSON_spec_mark_optional ( @@ -891,7 +896,7 @@ help_deposit (struct CoinHistoryParseContext *pc, &h_wire, &h_contract_terms, no_hac ? NULL : &hac, - NULL /* h_extensions! */, + no_h_policy ? NULL : &h_policy, &pc->dk->h_key, wallet_timestamp, &merchant_pub, @@ -2143,7 +2148,7 @@ TALER_EXCHANGE_get_min_denomination_ ( enum GNUNET_GenericReturnValue TALER_EXCHANGE_verify_deposit_signature_ ( const struct TALER_EXCHANGE_DepositContractDetail *dcd, - const struct TALER_ExtensionContractHashP *ech, + const struct TALER_ExtensionPolicyHashP *ech, const struct TALER_MerchantWireHashP *h_wire, const struct TALER_EXCHANGE_CoinDepositDetail *cdd, const struct TALER_EXCHANGE_DenomPublicKey *dki) diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h index a75ed3ed2..80c36daf1 100644 --- a/src/lib/exchange_api_common.h +++ b/src/lib/exchange_api_common.h @@ -203,7 +203,7 @@ TALER_EXCHANGE_get_min_denomination_ ( * Verify signature information about the deposit. * * @param dcd contract details - * @param ech hashed contract (passed to avoid recomputation) + * @param ech hashed policy (passed to avoid recomputation) * @param h_wire hashed wire details (passed to avoid recomputation) * @param cdd coin-specific details * @param dki denomination of the coin @@ -212,7 +212,7 @@ TALER_EXCHANGE_get_min_denomination_ ( enum GNUNET_GenericReturnValue TALER_EXCHANGE_verify_deposit_signature_ ( const struct TALER_EXCHANGE_DepositContractDetail *dcd, - const struct TALER_ExtensionContractHashP *ech, + const struct TALER_ExtensionPolicyHashP *ech, const struct TALER_MerchantWireHashP *h_wire, const struct TALER_EXCHANGE_CoinDepositDetail *cdd, const struct TALER_EXCHANGE_DenomPublicKey *dki); diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c index 3ba986b2d..8f179b72c 100644 --- a/src/lib/exchange_api_deposit.c +++ b/src/lib/exchange_api_deposit.c @@ -97,9 +97,9 @@ struct TALER_EXCHANGE_DepositHandle struct TALER_MerchantWireHashP h_wire; /** - * Hash over the extensions, or all zero. + * Hash over the policy extension, or all zero. */ - struct TALER_ExtensionContractHashP h_extensions; + struct TALER_ExtensionPolicyHashP h_policy; /** * Time when this confirmation was generated / when the exchange received @@ -177,7 +177,7 @@ auditor_cb (void *cls, aie->dch = TALER_AUDITOR_deposit_confirmation ( ah, &dh->h_wire, - &dh->h_extensions, + &dh->h_policy, &dh->dcd.h_contract_terms, dh->exchange_timestamp, dh->dcd.wire_deadline, @@ -277,7 +277,7 @@ handle_deposit_finished (void *cls, TALER_exchange_online_deposit_confirmation_verify ( &dh->dcd.h_contract_terms, &dh->h_wire, - &dh->h_extensions, + &dh->h_policy, dh->exchange_timestamp, dh->dcd.wire_deadline, dh->dcd.refund_deadline, @@ -446,15 +446,15 @@ TALER_EXCHANGE_deposit ( dh->cb_cls = cb_cls; dh->cdd = *cdd; dh->dcd = *dcd; - if (NULL != dcd->extension_details) - TALER_deposit_extension_hash (dcd->extension_details, - &dh->h_extensions); + if (NULL != dcd->policy_details) + TALER_deposit_policy_hash (dcd->policy_details, + &dh->h_policy); TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri, &dcd->wire_salt, &dh->h_wire); if (GNUNET_OK != TALER_EXCHANGE_verify_deposit_signature_ (dcd, - &dh->h_extensions, + &dh->h_policy, &dh->h_wire, cdd, dki)) diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index d6e12cc17..14ba73174 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -898,17 +898,20 @@ decode_keys_json (const json_t *resp_obj, /* TODO: maybe lift all this into a FP in TALER_Extension ? */ { struct TALER_MasterSignatureP extensions_sig = {0}; - json_t *extensions = NULL; + json_t *manifests = NULL; + bool no_extensions = false; + bool no_signature = false; + struct GNUNET_JSON_Specification ext_spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("extensions", - &extensions), - NULL), + &manifests), + &no_extensions), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ( "extensions_sig", &extensions_sig), - NULL), + &no_signature), GNUNET_JSON_spec_end () }; @@ -918,22 +921,27 @@ decode_keys_json (const json_t *resp_obj, ext_spec, NULL, NULL)); - if (NULL != extensions) + + if (! no_extensions && no_signature) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "found extensions without signature\n"); + + if (! no_extensions && ! no_signature) { /* 2. We have an extensions object. Verify its signature. */ EXITIF (GNUNET_OK != - TALER_extensions_verify_json_config_signature ( - extensions, + TALER_extensions_verify_manifests_signature ( + manifests, &extensions_sig, &key_data->master_pub)); /* 3. Parse and set the the configuration of the extensions accordingly */ EXITIF (GNUNET_OK != - TALER_extensions_load_json_config (extensions)); + TALER_extensions_load_manifests (manifests)); } /* 4. assuming we might have now a new value for age_mask, set it in key_data */ - key_data->age_mask = TALER_extensions_age_restriction_ageMask (); + key_data->age_mask = TALER_extensions_get_age_restriction_mask (); } /** diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c index a937c18af..855b4fcc7 100644 --- a/src/lib/exchange_api_refund.c +++ b/src/lib/exchange_api_refund.c @@ -236,7 +236,8 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, struct TALER_PrivateContractHashP h_contract_terms; struct TALER_AgeCommitmentHash h_age_commitment; bool no_hac; - // struct TALER_ExtensionContractHashP h_extensions; // FIXME #7270! + struct TALER_ExtensionPolicyHashP h_policy; + bool no_h_policy; struct GNUNET_TIME_Timestamp wallet_timestamp; struct TALER_MerchantPublicKeyP merchant_pub; struct GNUNET_TIME_Timestamp refund_deadline; @@ -252,6 +253,10 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, GNUNET_JSON_spec_fixed_auto ("h_age_commitment", &h_age_commitment), &no_hac), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_policy", + &h_policy), + &no_h_policy), GNUNET_JSON_spec_timestamp ("timestamp", &wallet_timestamp), GNUNET_JSON_spec_timestamp ("refund_deadline", @@ -277,10 +282,8 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, &deposit_fee, &h_wire, &h_contract_terms, - no_hac - ? NULL - : &h_age_commitment, - NULL /* FIXME #7270-OEC: h_extensions! */, + no_hac ? NULL : &h_age_commitment, + no_h_policy ? NULL: &h_policy, &h_denom_pub, wallet_timestamp, &merchant_pub, diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c index dd39ee940..ec9ccb741 100644 --- a/src/testing/test_exchange_api.c +++ b/src/testing/test_exchange_api.c @@ -1312,9 +1312,6 @@ main (int argc, "INFO", NULL); - GNUNET_assert (GNUNET_OK == - TALER_extension_age_restriction_register ()); - cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]); GNUNET_assert (NULL != cipher); uses_cs = (0 == strcmp (cipher, "cs")); diff --git a/src/testing/test_exchange_p2p.c b/src/testing/test_exchange_p2p.c index 27d47bfd4..f159b2f1b 100644 --- a/src/testing/test_exchange_p2p.c +++ b/src/testing/test_exchange_p2p.c @@ -522,9 +522,6 @@ main (int argc, "INFO", NULL); - GNUNET_assert (GNUNET_OK == - TALER_extension_age_restriction_register ()); - cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]); GNUNET_assert (NULL != cipher); uses_cs = (0 == strcmp (cipher, "cs")); diff --git a/src/testing/testing_api_cmd_auditor_deposit_confirmation.c b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c index d99b12937..293ecba27 100644 --- a/src/testing/testing_api_cmd_auditor_deposit_confirmation.c +++ b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c @@ -199,7 +199,7 @@ deposit_confirmation_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { - static struct TALER_ExtensionContractHashP no_h_extensions; + static struct TALER_ExtensionPolicyHashP no_h_policy; struct DepositConfirmationState *dcs = cls; const struct TALER_TESTING_Command *deposit_cmd; struct TALER_MerchantWireHashP h_wire; @@ -310,7 +310,7 @@ deposit_confirmation_run (void *cls, } dcs->dc = TALER_AUDITOR_deposit_confirmation (dcs->auditor, &h_wire, - &no_h_extensions, + &no_h_policy, &h_contract_terms, *exchange_timestamp, *wire_deadline, diff --git a/src/testing/testing_api_cmd_batch_deposit.c b/src/testing/testing_api_cmd_batch_deposit.c index 967a5ac33..54a20cdb2 100644 --- a/src/testing/testing_api_cmd_batch_deposit.c +++ b/src/testing/testing_api_cmd_batch_deposit.c @@ -381,7 +381,7 @@ batch_deposit_run (void *cls, .merchant_payto_uri = payto_uri, .wire_salt = wire_salt, .h_contract_terms = h_contract_terms, - .extension_details = NULL /* FIXME #7270-OEC */, + .policy_details = NULL /* FIXME #7270-OEC */, .timestamp = ds->wallet_timestamp, .merchant_pub = merchant_pub, .refund_deadline = ds->refund_deadline diff --git a/src/testing/testing_api_cmd_batch_withdraw.c b/src/testing/testing_api_cmd_batch_withdraw.c index b68ca99f7..a5229ae9c 100644 --- a/src/testing/testing_api_cmd_batch_withdraw.c +++ b/src/testing/testing_api_cmd_batch_withdraw.c @@ -490,7 +490,7 @@ TALER_TESTING_cmd_batch_withdraw (const char *label, acp = GNUNET_new (struct TALER_AgeCommitmentProof); hac = GNUNET_new (struct TALER_AgeCommitmentHash); - mask = TALER_extensions_age_restriction_ageMask (); + mask = TALER_extensions_get_age_restriction_mask (); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, &seed, sizeof(seed)); diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c index f66947678..16ac139f4 100644 --- a/src/testing/testing_api_cmd_deposit.c +++ b/src/testing/testing_api_cmd_deposit.c @@ -468,7 +468,7 @@ deposit_run (void *cls, .merchant_payto_uri = payto_uri, .wire_salt = wire_salt, .h_contract_terms = h_contract_terms, - .extension_details = NULL /* FIXME #7270-OEC */, + .policy_details = NULL /* FIXME #7270-OEC */, .timestamp = ds->wallet_timestamp, .merchant_pub = merchant_pub, .refund_deadline = ds->refund_deadline diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index ce2c49ccc..7a81d1c33 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -590,7 +590,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, acp = GNUNET_new (struct TALER_AgeCommitmentProof); hac = GNUNET_new (struct TALER_AgeCommitmentHash); - mask = TALER_extensions_age_restriction_ageMask (); + mask = TALER_extensions_get_age_restriction_mask (); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, &seed, sizeof(seed)); diff --git a/src/testing/testing_api_helpers_exchange.c b/src/testing/testing_api_helpers_exchange.c index d813021bd..7fe0038ad 100644 --- a/src/testing/testing_api_helpers_exchange.c +++ b/src/testing/testing_api_helpers_exchange.c @@ -314,8 +314,13 @@ sign_keys_for_exchange (void *cls, char *exchange_master_pub; int ret; - /* Load the age restriction mask from the configuration */ - TALER_extensions_load_taler_config (cfg); + /* Load the extensions */ + if (GNUNET_OK != TALER_extensions_init (cfg)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "couldn't load extensions"); + return GNUNET_SYSERR; + } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 94edac021..acafdae57 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -105,6 +105,7 @@ libtalerutil_la_SOURCES = \ libtalerutil_la_LIBADD = \ -lgnunetutil \ + -lgnunetjson \ -lsodium \ -ljansson \ $(LIBGCRYPT_LIBS) \ diff --git a/src/util/age_restriction.c b/src/util/age_restriction.c index b87c8543e..f4ac9abea 100644 --- a/src/util/age_restriction.c +++ b/src/util/age_restriction.c @@ -21,6 +21,7 @@ #include "platform.h" #include "taler_util.h" #include "taler_signatures.h" +#include <gnunet/gnunet_json_lib.h> #include <gcrypt.h> void @@ -436,3 +437,113 @@ TALER_age_commitment_proof_free ( cp->commitment.keys = NULL; } } + + +enum GNUNET_GenericReturnValue +TALER_JSON_parse_age_groups (const json_t *root, + struct TALER_AgeMask *mask) +{ + enum GNUNET_GenericReturnValue ret; + const char *str; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("age_groups", + &str), + GNUNET_JSON_spec_end () + }; + + ret = GNUNET_JSON_parse (root, + spec, + NULL, + NULL); + if (GNUNET_OK == ret) + TALER_parse_age_group_string (str, mask); + + GNUNET_JSON_parse_free (spec); + + return ret; +} + + +enum GNUNET_GenericReturnValue +TALER_parse_age_group_string ( + const char *groups, + struct TALER_AgeMask *mask) +{ + + const char *pos = groups; + unsigned int prev = 0; + unsigned int val = 0; + char c; + + while (*pos) + { + c = *pos++; + if (':' == c) + { + if (prev >= val) + return GNUNET_SYSERR; + + mask->bits |= 1 << val; + prev = val; + val = 0; + continue; + } + + if ('0'>c || '9'<c) + return GNUNET_SYSERR; + + val = 10 * val + c - '0'; + + if (0>=val || 32<=val) + return GNUNET_SYSERR; + } + + if (32<=val || prev>=val) + return GNUNET_SYSERR; + + mask->bits |= (1 << val); + mask->bits |= 1; // mark zeroth group, too + + return GNUNET_OK; +} + + +char * +TALER_age_mask_to_string ( + const struct TALER_AgeMask *mask) +{ + uint32_t bits = mask->bits; + unsigned int n = 0; + char *buf = GNUNET_malloc (32 * 3); // max characters possible + char *pos = buf; + + if (NULL == buf) + { + return buf; + } + + while (bits != 0) + { + bits >>= 1; + n++; + if (0 == (bits & 1)) + { + continue; + } + + if (n > 9) + { + *(pos++) = '0' + n / 10; + } + *(pos++) = '0' + n % 10; + + if (0 != (bits >> 1)) + { + *(pos++) = ':'; + } + } + return buf; +} + + +/* end util/age_restriction.c */ diff --git a/src/util/exchange_signatures.c b/src/util/exchange_signatures.c index 3169cb84c..1762d5a50 100644 --- a/src/util/exchange_signatures.c +++ b/src/util/exchange_signatures.c @@ -48,10 +48,10 @@ struct TALER_DepositConfirmationPS struct TALER_MerchantWireHashP h_wire GNUNET_PACKED; /** - * Hash over the extension options of the deposit, 0 if there - * were not extension options. + * Hash over the optional policy extension of the deposit, 0 if there + * was no policy. */ - struct TALER_ExtensionContractHashP h_extensions GNUNET_PACKED; + struct TALER_ExtensionPolicyHashP h_policy GNUNET_PACKED; /** * Time when this confirmation was generated / when the exchange received @@ -101,7 +101,7 @@ TALER_exchange_online_deposit_confirmation_sign ( TALER_ExchangeSignCallback scb, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, @@ -123,8 +123,8 @@ TALER_exchange_online_deposit_confirmation_sign ( .merchant_pub = *merchant_pub }; - if (NULL != h_extensions) - dcs.h_extensions = *h_extensions; + if (NULL != h_policy) + dcs.h_policy = *h_policy; TALER_amount_hton (&dcs.amount_without_fee, amount_without_fee); return scb (&dcs.purpose, @@ -137,7 +137,7 @@ enum GNUNET_GenericReturnValue TALER_exchange_online_deposit_confirmation_verify ( const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, @@ -159,8 +159,8 @@ TALER_exchange_online_deposit_confirmation_verify ( .merchant_pub = *merchant_pub }; - if (NULL != h_extensions) - dcs.h_extensions = *h_extensions; + if (NULL != h_policy) + dcs.h_policy = *h_policy; TALER_amount_hton (&dcs.amount_without_fee, amount_without_fee); if (GNUNET_OK != diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c index b316c8ba7..d0b644e7f 100644 --- a/src/util/offline_signatures.c +++ b/src/util/offline_signatures.c @@ -926,10 +926,10 @@ TALER_exchange_offline_global_fee_verify ( GNUNET_NETWORK_STRUCT_BEGIN /** - * @brief Signature made by the exchange offline key over the - * configuration of an extension. + * @brief Signature made by the exchange offline key over the manifest of + * an extension. */ -struct TALER_MasterExtensionConfigurationPS +struct TALER_MasterExtensionManifestPS { /** * Purpose is #TALER_SIGNATURE_MASTER_EXTENSION. Signed @@ -938,24 +938,24 @@ struct TALER_MasterExtensionConfigurationPS struct GNUNET_CRYPTO_EccSignaturePurpose purpose; /** - * Hash of the JSON object that represents the configuration of an extension. + * Hash of the JSON object that represents the manifests of extensions. */ - struct TALER_ExtensionConfigHashP h_config GNUNET_PACKED; + struct TALER_ExtensionManifestsHashP h_manifest GNUNET_PACKED; }; GNUNET_NETWORK_STRUCT_END void -TALER_exchange_offline_extension_config_hash_sign ( - const struct TALER_ExtensionConfigHashP *h_config, +TALER_exchange_offline_extension_manifests_hash_sign ( + const struct TALER_ExtensionManifestsHashP *h_manifest, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { - struct TALER_MasterExtensionConfigurationPS ec = { + struct TALER_MasterExtensionManifestPS ec = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), .purpose.size = htonl (sizeof(ec)), - .h_config = *h_config + .h_manifest = *h_manifest }; GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, &ec, @@ -964,16 +964,16 @@ TALER_exchange_offline_extension_config_hash_sign ( enum GNUNET_GenericReturnValue -TALER_exchange_offline_extension_config_hash_verify ( - const struct TALER_ExtensionConfigHashP *h_config, +TALER_exchange_offline_extension_manifests_hash_verify ( + const struct TALER_ExtensionManifestsHashP *h_manifest, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig ) { - struct TALER_MasterExtensionConfigurationPS ec = { + struct TALER_MasterExtensionManifestPS ec = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), .purpose.size = htonl (sizeof(ec)), - .h_config = *h_config + .h_manifest = *h_manifest }; return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION, diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c index 6c8124d03..5efcc5d64 100644 --- a/src/util/wallet_signatures.c +++ b/src/util/wallet_signatures.c @@ -49,9 +49,9 @@ struct TALER_DepositRequestPS struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED; /** - * Hash over extension attributes shared with the exchange. + * Hash over optional policy extension attributes shared with the exchange. */ - struct TALER_ExtensionContractHashP h_extensions GNUNET_PACKED; + struct TALER_ExtensionPolicyHashP h_policy GNUNET_PACKED; /** * Hash over the wiring information of the merchant. @@ -120,7 +120,7 @@ TALER_wallet_deposit_sign ( const struct TALER_MerchantWireHashP *h_wire, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_AgeCommitmentHash *h_age_commitment, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, const struct TALER_DenominationHashP *h_denom_pub, const struct GNUNET_TIME_Timestamp wallet_timestamp, const struct TALER_MerchantPublicKeyP *merchant_pub, @@ -141,8 +141,8 @@ TALER_wallet_deposit_sign ( if (NULL != h_age_commitment) dr.h_age_commitment = *h_age_commitment; - if (NULL != h_extensions) - dr.h_extensions = *h_extensions; + if (NULL != h_policy) + dr.h_policy = *h_policy; TALER_amount_hton (&dr.amount_with_fee, amount); TALER_amount_hton (&dr.deposit_fee, @@ -160,7 +160,7 @@ TALER_wallet_deposit_verify ( const struct TALER_MerchantWireHashP *h_wire, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_AgeCommitmentHash *h_age_commitment, - const struct TALER_ExtensionContractHashP *h_extensions, + const struct TALER_ExtensionPolicyHashP *h_policy, const struct TALER_DenominationHashP *h_denom_pub, struct GNUNET_TIME_Timestamp wallet_timestamp, const struct TALER_MerchantPublicKeyP *merchant_pub, @@ -178,13 +178,13 @@ TALER_wallet_deposit_verify ( .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline), .merchant = *merchant_pub, .h_age_commitment = {{{0}}}, - .h_extensions = {{{0}}} + .h_policy = {{{0}}} }; if (NULL != h_age_commitment) dr.h_age_commitment = *h_age_commitment; - if (NULL != h_extensions) - dr.h_extensions = *h_extensions; + if (NULL != h_policy) + dr.h_policy = *h_policy; TALER_amount_hton (&dr.amount_with_fee, amount); TALER_amount_hton (&dr.deposit_fee, |