From faf3f57ce3582a5b9f1070d689827bccd0183cd4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 16 Jan 2023 17:45:05 +0100 Subject: [PATCH 01/15] do not 500 on empty reserve history by not ignoring undecided purses that may have caused reserve to be created in the first place --- src/exchange/taler-exchange-httpd_responses.c | 6 ++++++ src/exchange/taler-exchange-httpd_withdraw.c | 2 ++ src/exchangedb/exchange_do_reserve_purse.sql | 4 ---- src/exchangedb/pg_get_reserve_history.c | 9 +++++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index ab9b81390..33bc13985 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -704,6 +704,7 @@ TEH_RESPONSE_compile_reserve_history ( json_t *json_history; json_history = json_array (); + GNUNET_assert (NULL != json_history); for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh; NULL != pos; pos = pos->next) @@ -1012,10 +1013,14 @@ reply_reserve_insufficient_funds ( json_history = TEH_RESPONSE_compile_reserve_history (rh); if (NULL == json_history) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to compile reserve history\n"); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_EXCHANGE_RESERVE_HISTORY_ERROR_INSUFFICIENT_FUNDS, NULL); + } return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_CONFLICT, @@ -1061,6 +1066,7 @@ TEH_RESPONSE_reply_reserve_insufficient_balance ( if ( (qs < 0) || (NULL == rh) ) { + GNUNET_break (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c index 253773639..05153bfc4 100644 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ b/src/exchange/taler-exchange-httpd_withdraw.c @@ -215,6 +215,8 @@ withdraw_transaction (void *cls, if (! balance_ok) { TEH_plugin->rollback (TEH_plugin->cls); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Balance insufficient for /withdraw\n"); *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( connection, TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS, diff --git a/src/exchangedb/exchange_do_reserve_purse.sql b/src/exchangedb/exchange_do_reserve_purse.sql index 4f65c6606..0476e60d1 100644 --- a/src/exchangedb/exchange_do_reserve_purse.sql +++ b/src/exchangedb/exchange_do_reserve_purse.sql @@ -161,7 +161,3 @@ END $$; COMMENT ON FUNCTION exchange_do_reserve_purse(BYTEA, BYTEA, INT8, INT8, INT8, BYTEA, BOOLEAN, INT8, INT4, BYTEA, BYTEA) IS 'Create a purse for a reserve.'; - - - - diff --git a/src/exchangedb/pg_get_reserve_history.c b/src/exchangedb/pg_get_reserve_history.c index 89701ae71..6c12abc61 100644 --- a/src/exchangedb/pg_get_reserve_history.c +++ b/src/exchangedb/pg_get_reserve_history.c @@ -797,14 +797,14 @@ TEH_PG_get_reserve_history (void *cls, " FROM purse_merges pm" " JOIN purse_requests pr" " USING (purse_pub)" - " JOIN purse_decision pdes" + " LEFT JOIN purse_decision pdes" " USING (purse_pub)" " JOIN account_merges am" " ON (am.purse_pub = pm.purse_pub AND" " am.reserve_pub = pm.reserve_pub)" " WHERE pm.reserve_pub=$1" " AND COALESCE(pm.partner_serial_id,0)=0" /* must be local! */ - " AND NOT pdes.refunded;"); + " AND NOT COALESCE (pdes.refunded, FALSE);"); PREPARE (pg, "history_by_reserve", "SELECT" @@ -855,7 +855,12 @@ TEH_PG_get_reserve_history (void *cls, &rhc); if ( (0 > qs) || (GNUNET_OK != rhc.status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to compile reserve history at `%s'\n", + work[i].statement); break; + } } if ( (qs < 0) || (rhc.status != GNUNET_OK) ) From ab7c676f49bd9ba2871bc1e39dff524b14045aa5 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 16 Jan 2023 17:47:16 +0100 Subject: [PATCH 02/15] -typos --- src/exchangedb/0003-withdraw_age_commitments.sql | 2 +- src/exchangedb/exchange_do_batch4_reserves_in_insert.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exchangedb/0003-withdraw_age_commitments.sql b/src/exchangedb/0003-withdraw_age_commitments.sql index 1aac8bb26..6064880bd 100644 --- a/src/exchangedb/0003-withdraw_age_commitments.sql +++ b/src/exchangedb/0003-withdraw_age_commitments.sql @@ -75,7 +75,7 @@ BEGIN ,partition_suffix ); PERFORM comment_partitioned_column( - 'Timestamp of when the withdraw-age resquest was received by the exchange' + 'Timestamp with the time when the withdraw-age request was received by the exchange' ,'timestamp' ,table_name ,partition_suffix diff --git a/src/exchangedb/exchange_do_batch4_reserves_in_insert.sql b/src/exchangedb/exchange_do_batch4_reserves_in_insert.sql index a70521bcd..603e893cc 100644 --- a/src/exchangedb/exchange_do_batch4_reserves_in_insert.sql +++ b/src/exchangedb/exchange_do_batch4_reserves_in_insert.sql @@ -267,7 +267,7 @@ BEGIN END IF; k=k+1; END LOOP; - /**ROLLBACK TRANSACTION IN SOTRED PROCEDURE IS IT PROSSIBLE ?**/ + /**ROLLBACK TRANSACTION IN SORTED PROCEDURE IS IT PROSSIBLE ?**/ /*IF transaction_duplicate OR transaction_duplicate2 OR transaction_duplicate3 From 190a1b68b0a952b5e96ced4824628aa1669e060b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 16 Jan 2023 18:17:15 +0100 Subject: [PATCH 03/15] fix #7594: skip test if DB not setup --- src/exchange/test_taler_exchange_httpd.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/exchange/test_taler_exchange_httpd.sh b/src/exchange/test_taler_exchange_httpd.sh index e8dc46af8..0fe71f3ad 100755 --- a/src/exchange/test_taler_exchange_httpd.sh +++ b/src/exchange/test_taler_exchange_httpd.sh @@ -1,7 +1,7 @@ #!/bin/bash # # This file is part of TALER -# Copyright (C) 2015-2020 Taler Systems SA +# Copyright (C) 2015-2020, 2023 Taler Systems SA # # TALER is free software; you can redistribute it and/or modify it under the # terms of the GNU Affero General Public License as published by the Free Software @@ -23,6 +23,8 @@ # Clear environment from variables that override config. unset XDG_DATA_HOME unset XDG_CONFIG_HOME + +set -eu # echo -n "Launching exchange ..." PREFIX= @@ -30,7 +32,7 @@ PREFIX= #PREFIX="valgrind --leak-check=yes --track-fds=yes --error-exitcode=1 --log-file=valgrind.%p" # Setup database -taler-exchange-dbinit -c test_taler_exchange_httpd.conf &> /dev/null +taler-exchange-dbinit -c test_taler_exchange_httpd.conf &> /dev/null || exit 77 # Run Exchange HTTPD (in background) $PREFIX taler-exchange-httpd -c test_taler_exchange_httpd.conf 2> test-exchange.log & From 9091c32c0f2306a0b125951f5f4caa029001705d Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 16 Jan 2023 18:59:28 +0100 Subject: [PATCH 04/15] move state to inner URL --- src/kyclogic/plugin_kyclogic_oauth2.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c index 259217ce8..5709b18f8 100644 --- a/src/kyclogic/plugin_kyclogic_oauth2.c +++ b/src/kyclogic/plugin_kyclogic_oauth2.c @@ -474,17 +474,17 @@ initiate_task (void *cls) hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto, sizeof (ih->h_payto)); GNUNET_asprintf (&redirect_uri, - "%skyc-proof/%s", + "%skyc-proof/%s?state=%s", ps->exchange_base_url, - pd->section); + pd->section, + hps); redirect_uri_encoded = TALER_urlencode (redirect_uri); GNUNET_free (redirect_uri); GNUNET_asprintf (&url, - "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s", + "%s?response_type=code&client_id=%s&redirect_uri=%s", pd->login_url, pd->client_id, - redirect_uri_encoded, - hps); + redirect_uri_encoded); GNUNET_free (redirect_uri_encoded); ih->cb (ih->cb_cls, TALER_EC_NONE, @@ -1012,21 +1012,19 @@ oauth2_proof (void *cls, char *redirect_uri; char *client_secret; char *authorization_code; - char *redirect_uri_encoded; char *hps; hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto, sizeof (ph->h_payto)); - GNUNET_asprintf (&redirect_uri, - "%skyc-proof/%s", + "%skyc-proof/%s?state=%s", ps->exchange_base_url, - pd->section); + pd->section, + hps); redirect_uri_encoded = TALER_urlencode (redirect_uri); GNUNET_free (redirect_uri); GNUNET_assert (NULL != redirect_uri_encoded); - client_id = curl_easy_escape (ph->eh, pd->client_id, 0); @@ -1047,8 +1045,8 @@ oauth2_proof (void *cls, authorization_code); curl_free (authorization_code); curl_free (client_secret); - curl_free (redirect_uri_encoded); - curl_free (hps); + GNUNET_free (redirect_uri_encoded); + GNUNET_free (hps); curl_free (client_id); } GNUNET_assert (CURLE_OK == From 4e7d4aa4b22f7300f59f2a543720a0bce04c3b78 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 17 Jan 2023 15:06:55 +0100 Subject: [PATCH 05/15] bumping version to v0.9.1 --- configure.ac | 4 ++-- debian/changelog | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index eb78c891c..af023efe2 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # # This file is part of TALER -# Copyright (C) 2014-2021 Taler Systems SA +# Copyright (C) 2014-2023 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 @@ -17,7 +17,7 @@ # # AC_PREREQ([2.69]) -AC_INIT([taler-exchange],[0.9.0],[taler-bug@gnunet.org]) +AC_INIT([taler-exchange],[0.9.1],[taler-bug@gnunet.org]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([src/util/util.c]) AC_CONFIG_HEADERS([taler_config.h]) diff --git a/debian/changelog b/debian/changelog index 157d3423c..fcdb135c4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +taler-exchange (0.9.1) unstable; urgency=low + + * Packaging latest release. + + -- Christian Grothoff Tue, 17 Jan 2023 11:50:12 +0200 + taler-exchange (0.9.0) unstable; urgency=low * Packaging latest release. From abb692f02dfc3c69b6545f7eb69552c0a047c3a2 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 17 Jan 2023 15:33:36 +0100 Subject: [PATCH 06/15] -run fetch-transactions in auditor test to avoid non-deterministic failure --- src/auditor/test-auditor.sh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/auditor/test-auditor.sh b/src/auditor/test-auditor.sh index 60cc00bd2..7f4ec1cab 100755 --- a/src/auditor/test-auditor.sh +++ b/src/auditor/test-auditor.sh @@ -301,9 +301,9 @@ function run_audit () { echo -n "Running taler-exchange-offline drain " taler-exchange-offline -L DEBUG -c "${CONF}" \ - drain TESTKUDOS:0.1 exchange-account-1 payto://iban/SANDBOXX/DE360679?receiver-name=Exchange+Drain \ - upload \ - 2> ${MY_TMP_DIR}/taler-exchange-offline-drain.log || exit_fail "offline draining failed" + drain TESTKUDOS:0.1 exchange-account-1 payto://iban/SANDBOXX/DE360679?receiver-name=Exchange+Drain \ + upload \ + 2> ${MY_TMP_DIR}/taler-exchange-offline-drain.log || exit_fail "offline draining failed" kill -TERM $EPID wait $EPID || true unset EPID @@ -325,11 +325,15 @@ function run_audit () { echo -n "Payment likely already submitted, running submit-payments without UUID anyway ..." libeufin-cli accounts submit-payments exchange-nexus else - echo -n "Running submitting payment ${PAIN_UUID} ..." + echo -n "Running payment submission for transaction ${PAIN_UUID} ..." libeufin-cli accounts submit-payments --payment-uuid ${PAIN_UUID} exchange-nexus fi - cd $ORIGIN echo " DONE" + echo -n "Import outgoing transactions..." + libeufin-cli accounts fetch-transactions \ + --range-type since-last --level report exchange-nexus + echo " DONE" + cd $ORIGIN fi audit_only post_audit From c60e6184fd6552d5bb67f70a5a8a2502c9a74ee4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 17 Jan 2023 17:52:39 +0100 Subject: [PATCH 07/15] terminate taler-exchange-offline on invalid inputs --- src/exchange-tools/taler-exchange-offline.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index a8a25bd3c..eb64a6c9d 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -3322,7 +3322,7 @@ tofu_check (const struct TALER_SecurityModulePublicKeySetP *secmset) * @param signkeys keys to output * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue show_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, const json_t *signkeys) { @@ -4353,6 +4353,9 @@ cmd_handler (char *const *args, cmds[i].name, cmds[i].help); } + json_decref (out); + out = NULL; + GNUNET_SCHEDULER_shutdown (); } From cda751eaa50bfc25726402be4487dfe72625d92d Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 17 Jan 2023 17:53:16 +0100 Subject: [PATCH 08/15] eventually kick out /keys clients also if ksh exists but krd array is empty --- src/exchange/taler-exchange-httpd_keys.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index b11832e72..3db4ef7ca 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -3232,7 +3232,8 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc, const struct KeysResponseData *krd; ksh = TEH_keys_get_state (); - if (NULL == ksh) + if ( (NULL == ksh) || + (0 == ksh->krd_array_length) ) { if ( ( (SKR_LIMIT == skr_size) && (rc->connection == skr_connection) ) || From 999209518d3eb5ddc7c83011253ec1d2bb32b170 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 18 Jan 2023 14:25:58 +0100 Subject: [PATCH 09/15] new AML APIs (libtalerexchange) --- src/include/taler_exchange_service.h | 181 ++++++++++++- src/lib/exchange_api_add_aml_decision.c | 240 ++++++++++++++++++ src/lib/exchange_api_management_add_partner.c | 232 +++++++++++++++++ ...change_api_management_update_aml_officer.c | 228 +++++++++++++++++ 4 files changed, 880 insertions(+), 1 deletion(-) create mode 100644 src/lib/exchange_api_add_aml_decision.c create mode 100644 src/lib/exchange_api_management_add_partner.c create mode 100644 src/lib/exchange_api_management_update_aml_officer.c diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index b2d0bf710..ff1698cca 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -4240,6 +4240,185 @@ TALER_EXCHANGE_management_revoke_signing_key_cancel ( struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh); +/** + * Function called with information about the change to + * an AML officer status. + * + * @param cls closure + * @param hr HTTP response data + */ +typedef void +(*TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback) ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr); + + +/** + * @brief Handle for a POST /management/aml-officers/$OFFICER_PUB request. + */ +struct TALER_EXCHANGE_ManagementUpdateAmlOfficer; + + +/** + * Inform the exchange that the status of an AML officer has changed. + * + * @param ctx the context + * @param url HTTP base URL for the exchange + * @param officer_pub the public signing key of the officer + * @param officer_name name of the officer + * @param change_date when to affect the status change + * @param is_active true to enable the officer + * @param read_only true to only allow read-only access + * @param master_sig signature affirming the change + * @param cb function to call with the exchange's result + * @param cb_cls closure for @a cb + * @return the request handle; NULL upon error + */ +struct TALER_EXCHANGE_ManagementUpdateAmlOfficer * +TALER_EXCHANGE_management_update_aml_officer ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const char *officer_name, + struct GNUNET_TIME_Timestamp change_date, + bool is_active, + bool read_only, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb, + void *cb_cls); + + +/** + * Cancel #TALER_EXCHANGE_management_update_aml_officer() operation. + * + * @param rh handle of the operation to cancel + */ +void +TALER_EXCHANGE_management_update_aml_officer_cancel ( + struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *rh); + + +/** + * Function called with information about storing an + * an AML decision. + * + * @param cls closure + * @param hr HTTP response data + */ +typedef void +(*TALER_EXCHANGE_AddAmlDecisionCallback) ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr); + + +/** + * @brief Handle for a POST /aml-decision/$OFFICER_PUB request. + */ +struct TALER_EXCHANGE_AddAmlDecision; + + +/** + * Inform the exchange that an AML decision has been taken. + * + * @param ctx the context + * @param url HTTP base URL for the exchange + * @param justification human-readable justification + * @param decision_time when was the decision made + * @param new_threshold at what monthly amount threshold + * should a revision be triggered + * @param h_payto payto URI hash of the account the + * decision is about + * @param new_state updated AML state + * @param officer_priv private key of the deciding AML officer + * @param cb function to call with the exchange's result + * @param cb_cls closure for @a cb + * @return the request handle; NULL upon error + */ +struct TALER_EXCHANGE_AddAmlDecision * +TALER_EXCHANGE_add_aml_decision ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const char *justification, + struct GNUNET_TIME_Timestamp decision_time, + const struct TALER_Amount *new_threshold, + const struct TALER_PaytoHashP *h_payto, + enum TALER_AmlDecisionState new_state, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + TALER_EXCHANGE_AddAmlDecisionCallback cb, + void *cb_cls); + + +/** + * Cancel #TALER_EXCHANGE_add_aml_decision() operation. + * + * @param rh handle of the operation to cancel + */ +void +TALER_EXCHANGE_add_aml_decision_cancel ( + struct TALER_EXCHANGE_AddAmlDecision *rh); + + +/** + * Function called with information about the change to + * an AML officer status. + * + * @param cls closure + * @param hr HTTP response data + */ +typedef void +(*TALER_EXCHANGE_ManagementAddPartnerCallback) ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr); + + +/** + * @brief Handle for a POST /management/partners/$PARTNER_PUB request. + */ +struct TALER_EXCHANGE_ManagementAddPartner; + + +/** + * Inform the exchange that the status of a partnering + * exchange was defined. + * + * @param ctx the context + * @param url HTTP base URL for the exchange + * @param partner_pub the offline signing key of the partner + * @param start_date validity period start + * @param end_date validity period end + * @param wad_frequency how often will we do wad transfers to this partner + * @param wad_fee what is the wad fee to this partner + * @param partner_base_url what is the base URL of the @a partner_pub exchange + * @param master_sig the signature the signature + * @param cb function to call with the exchange's result + * @param cb_cls closure for @a cb + * @return the request handle; NULL upon error + */ +struct TALER_EXCHANGE_ManagementAddPartner * +TALER_EXCHANGE_management_add_partner ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_MasterPublicKeyP *partner_pub, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + struct GNUNET_TIME_Relative wad_frequency, + const struct TALER_Amount *wad_fee, + const char *partner_base_url, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementAddPartnerCallback cb, + void *cb_cls); + + +/** + * Cancel #TALER_EXCHANGE_management_update_aml_officer() operation. + * + * @param rh handle of the operation to cancel + */ +void +TALER_EXCHANGE_management_add_partner_cancel ( + struct TALER_EXCHANGE_ManagementAddPartner *rh); + + /** * Function called with information about the auditor setup operation result. * diff --git a/src/lib/exchange_api_add_aml_decision.c b/src/lib/exchange_api_add_aml_decision.c new file mode 100644 index 000000000..7230c5ed4 --- /dev/null +++ b/src/lib/exchange_api_add_aml_decision.c @@ -0,0 +1,240 @@ +/* + This file is part of TALER + Copyright (C) 2023 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 + +*/ +/** + * @file lib/exchange_api_add_aml_decision.c + * @brief functions to add an AML decision by an AML officer + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "taler_json_lib.h" + + +struct TALER_EXCHANGE_AddAmlDecision +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_AddAmlDecisionCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /aml-decision/$OFFICER_PUB request. + * + * @param cls the `struct TALER_EXCHANGE_AddAmlDecision *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_add_aml_decision_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_AddAmlDecision *wh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + wh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange AML decision\n", + (unsigned int) response_code, + (int) hr.ec); + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + &hr); + wh->cb = NULL; + } + TALER_EXCHANGE_add_aml_decision_cancel (wh); +} + + +struct TALER_EXCHANGE_AddAmlDecision * +TALER_EXCHANGE_add_aml_decision ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const char *justification, + struct GNUNET_TIME_Timestamp decision_time, + const struct TALER_Amount *new_threshold, + const struct TALER_PaytoHashP *h_payto, + enum TALER_AmlDecisionState new_state, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + TALER_EXCHANGE_AddAmlDecisionCallback cb, + void *cb_cls) +{ + struct TALER_AmlOfficerPrivateKeyP officer_pub; + struct TALER_AmlOfficerSignatureP officer_sig; + struct TALER_EXCHANGE_AddAmlDecision *wh; + CURL *eh; + json_t *body; + + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &officer_pub.eddsa_pub); + TALER_officer_aml_decision_sign (justification, + decision_time, + h_payto, + new_state, + officer_priv, + &officer_sig); + wh = GNUNET_new (struct TALER_EXCHANGE_AddAmlDecision); + wh->cb = cb; + wh->cb_cls = cb_cls; + wh->ctx = ctx; + { + char *path; + char opus[sizeof (officer_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_pub, + sizeof (officer_pub), + opus, + sizeof (opus)); + *end = '\0'; + GNUNET_asprintf (&path, + "aml-decision/%s", + opus); + wh->url = TALER_url_join (url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == wh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wh); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("justification", + justification), + GNUNET_JSON_pack_data_auto ("officer_sig", + &officer_sig), + GNUNET_JSON_pack_data_auto ("h_payto", + h_payto), + GNUNET_JSON_pack_data_uint64 ("state", + (uint32_t) new_state), + TALER_JSON_pack_amount ("new_threshold", + new_threshold), + GNUNET_JSON_pack_timestamp ("decision_time", + decision_time)); + eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&wh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (wh->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wh->url); + wh->job = GNUNET_CURL_job_add2 (ctx, + eh, + wh->post_ctx.headers, + &handle_add_aml_decision_finished, + wh); + if (NULL == wh->job) + { + TALER_EXCHANGE_add_aml_decision_cancel (wh); + return NULL; + } + return wh; +} + + +void +TALER_EXCHANGE_add_aml_decision_cancel ( + struct TALER_EXCHANGE_AddAmlDecision *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + TALER_curl_easy_post_finished (&wh->post_ctx); + GNUNET_free (wh->url); + GNUNET_free (wh); +} diff --git a/src/lib/exchange_api_management_add_partner.c b/src/lib/exchange_api_management_add_partner.c new file mode 100644 index 000000000..264fd664e --- /dev/null +++ b/src/lib/exchange_api_management_add_partner.c @@ -0,0 +1,232 @@ +/* + This file is part of TALER + Copyright (C) 2023 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 + +*/ +/** + * @file lib/exchange_api_management_add_partner.c + * @brief functions to add an partner by an AML officer + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementAddPartner +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementAddPartnerCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /aml-decision/$OFFICER_PUB request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementAddPartner *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_add_partner_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementAddPartner *wh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + wh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for adding exchange partner\n", + (unsigned int) response_code, + (int) hr.ec); + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + &hr); + wh->cb = NULL; + } + TALER_EXCHANGE_management_add_partner_cancel (wh); +} + + +struct TALER_EXCHANGE_ManagementAddPartner * +TALER_EXCHANGE_management_add_partner ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_MasterPublicKeyP *partner_pub, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + struct GNUNET_TIME_Relative wad_frequency, + const struct TALER_Amount *wad_fee, + const char *partner_base_url, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementAddPartnerCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementAddPartner *wh; + CURL *eh; + json_t *body; + + wh = GNUNET_new (struct TALER_EXCHANGE_ManagementAddPartner); + wh->cb = cb; + wh->cb_cls = cb_cls; + wh->ctx = ctx; + { + char *path; + char opus[sizeof (*partner_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + partner_pub, + sizeof (*partner_pub), + opus, + sizeof (opus)); + *end = '\0'; + GNUNET_asprintf (&path, + "management/partners/%s", + opus); + wh->url = TALER_url_join (url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == wh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wh); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("partner_base_url", + partner_base_url), + GNUNET_JSON_pack_timestamp ("start_date", + start_date), + GNUNET_JSON_pack_timestamp ("end_date", + end_date), + GNUNET_JSON_pack_time_rel ("wad_frequency", + wad_frequency), + GNUNET_JSON_pack_data_auto ("master_sig", + &master_sig), + TALER_JSON_pack_amount ("wad_fee", + wad_fee) + ); + eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&wh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (wh->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wh->url); + wh->job = GNUNET_CURL_job_add2 (ctx, + eh, + wh->post_ctx.headers, + &handle_add_partner_finished, + wh); + if (NULL == wh->job) + { + TALER_EXCHANGE_management_add_partner_cancel (wh); + return NULL; + } + return wh; +} + + +void +TALER_EXCHANGE_management_add_partner_cancel ( + struct TALER_EXCHANGE_ManagementAddPartner *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + TALER_curl_easy_post_finished (&wh->post_ctx); + GNUNET_free (wh->url); + GNUNET_free (wh); +} diff --git a/src/lib/exchange_api_management_update_aml_officer.c b/src/lib/exchange_api_management_update_aml_officer.c new file mode 100644 index 000000000..bdc0dbe4a --- /dev/null +++ b/src/lib/exchange_api_management_update_aml_officer.c @@ -0,0 +1,228 @@ +/* + This file is part of TALER + Copyright (C) 2023 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 + +*/ +/** + * @file lib/exchange_api_management_update_aml_officer.c + * @brief functions to update AML officer status + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementUpdateAmlOfficer +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/wire request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_update_aml_officer_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + wh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management update AML officer\n", + (unsigned int) response_code, + (int) hr.ec); + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + &hr); + wh->cb = NULL; + } + TALER_EXCHANGE_management_update_aml_officer_cancel (wh); +} + + +struct TALER_EXCHANGE_ManagementUpdateAmlOfficer * +TALER_EXCHANGE_management_update_aml_officer ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const char *officer_name, + struct GNUNET_TIME_Timestamp change_date, + bool is_active, + bool read_only, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh; + CURL *eh; + json_t *body; + + wh = GNUNET_new (struct TALER_EXCHANGE_ManagementUpdateAmlOfficer); + wh->cb = cb; + wh->cb_cls = cb_cls; + wh->ctx = ctx; + { + char *path; + char opus[sizeof (*officer_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + officer_pub, + sizeof (*officer_pub), + opus, + sizeof (opus)); + *end = '\0'; + GNUNET_asprintf (&path, + "management/aml-officers/%s", + opus); + wh->url = TALER_url_join (url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == wh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wh); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("officer_name", + officer_name), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_data_bool ("is_active", + is_active), + GNUNET_JSON_pack_data_bool ("read_only", + read_only), + GNUNET_JSON_pack_timestamp ("change_date", + change_date)); + eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&wh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (wh->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wh->url); + wh->job = GNUNET_CURL_job_add2 (ctx, + eh, + wh->post_ctx.headers, + &handle_update_aml_officer_finished, + wh); + if (NULL == wh->job) + { + TALER_EXCHANGE_management_update_aml_officer_cancel (wh); + return NULL; + } + return wh; +} + + +void +TALER_EXCHANGE_management_update_aml_officer_cancel ( + struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + TALER_curl_easy_post_finished (&wh->post_ctx); + GNUNET_free (wh->url); + GNUNET_free (wh); +} From 3a7045bfca00e668bc996da9d46fb5836c03484f Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 18 Jan 2023 14:26:25 +0100 Subject: [PATCH 10/15] -fix warning --- src/exchange/taler-exchange-httpd_kyc-proof.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.h b/src/exchange/taler-exchange-httpd_kyc-proof.h index 98551557b..d40ea90a9 100644 --- a/src/exchange/taler-exchange-httpd_kyc-proof.h +++ b/src/exchange/taler-exchange-httpd_kyc-proof.h @@ -43,7 +43,7 @@ TEH_kyc_proof_cleanup (void); MHD_RESULT TEH_handler_kyc_proof ( struct TEH_RequestContext *rc, - const char *const args[3]); + const char *const args[1]); #endif From c30ee88336ef7c26d9f6b74b47bd0acea751c2ee Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 20 Jan 2023 14:46:33 +0100 Subject: [PATCH 11/15] skeleton for AML decision server handler --- .../taler-exchange-httpd_aml-decision.c | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/exchange/taler-exchange-httpd_aml-decision.c diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c new file mode 100644 index 000000000..0526e79d8 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_aml-decision.c @@ -0,0 +1,149 @@ +/* + This file is part of TALER + Copyright (C) 2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_aml-decision.c + * @brief Handle request about an AML decision. + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" +#include "taler_signatures.h" +#include "taler-exchange-httpd_responses.h" + + + +MHD_RESULT +TEH_handler_management_post_aml_decision ( + struct MHD_Connection *connection, + const json_t *root) +{ + const char *justification; + struct GNUNET_TIME_Timestamp decision_time; + struct TALER_Amount new_threshold; + struct TALER_PaytoHashP h_payto; + uint32_t new_state32; + enum TALER_AmlDecisionState new_state; + struct TALER_AmlOfficerPublicKeyP officer_pub; + struct TALER_AmlOfficerSignatureP officer_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("officer_pub", + &officer_pub), + GNUNET_JSON_spec_fixed_auto ("officer_sig", + &officer_sig), + GNUNET_JSON_spec_fixed_auto ("h_payto", + &h_payto), + TALER_JSON_spec_amount ("new_threshold", + &new_threshold), + GNUNET_JSON_spec_string ("justification", + &justification), + GNUNET_JSON_spec_timestamp ("decision_time", + &decision_time), + GNUNET_JSON_spec_uint32 ("new_state", + &new_state32), + GNUNET_JSON_spec_end () + }; + + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_SYSERR == res) + return MHD_NO; /* hard failure */ + if (GNUNET_NO == res) + return MHD_YES; /* failure */ + } + new_state = (enum TALER_AmlDecisionState) new_state32; + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (GNUNET_OK != + TALER_exchange_aml_decision_verify (justification, + decision_time, + &new_threshold, + &h_payto, + new_state, + &officer_pub, + &officer_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_AML_DECISION_ADD_SIGNATURE_INVALID, + NULL); + } + { + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Timestamp last_date; + bool invalid_officer; + + do { + qs = TEH_plugin->add_aml_decision (TEH_plugin->cls, + justification, + decision_time, + &new_threshold, + &h_payto, + new_state, + &officer_pub, + &officer_sig, + &invalid_officer, + &last_date); + } while (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (qs < 0) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "add aml_decision"); + return qs; + } + if (invalid_officer) + { + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER, + NULL); + } + if (GNUNET_TIME_timestamp_cmp (last_date, + >, + validity_start)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT, + NULL); + } + } + return TALER_MHD_reply_static ( + connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); +} + + +/* end of taler-exchange-httpd_aml-decision.c */ From ebb260127842e4dbefa14143be5cf1dbebd487f0 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 20 Jan 2023 15:13:22 +0100 Subject: [PATCH 12/15] skeleton for AML officer update --- ...r-exchange-httpd_management_aml-officers.c | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/exchange/taler-exchange-httpd_management_aml-officers.c diff --git a/src/exchange/taler-exchange-httpd_management_aml-officers.c b/src/exchange/taler-exchange-httpd_management_aml-officers.c new file mode 100644 index 000000000..139ccdb25 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_management_aml-officers.c @@ -0,0 +1,133 @@ +/* + This file is part of TALER + Copyright (C) 2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_management_aml-officers.c + * @brief Handle request to update AML officer status + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" +#include "taler_signatures.h" +#include "taler-exchange-httpd_management.h" +#include "taler-exchange-httpd_responses.h" + + +MHD_RESULT +TEH_handler_management_aml_officers ( + struct MHD_Connection *connection, + const json_t *root) +{ + struct TALER_AmlOfficerPublicKeyP officer_pub; + const char *officer_name; + struct GNUNET_TIME_Timestamp change_date; + bool is_active; + bool read_only; + struct TALER_MasterSignatureP master_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("officer_pub", + &officer_pub), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &master_sig), + GNUNET_JSON_spec_bool ("is_active", + &is_active), + GNUNET_JSON_spec_bool ("read_only", + &read_only), + GNUNET_JSON_spec_string ("officer_name", + &officer_name), + GNUNET_JSON_spec_timestamp ("change_date", + &change_date), + GNUNET_JSON_spec_end () + }; + + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_SYSERR == res) + return MHD_NO; /* hard failure */ + if (GNUNET_NO == res) + return MHD_YES; /* failure */ + } + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (GNUNET_OK != + TALER_exchange_offline_aml_officer_status_verify ( + &officer_pub, + officer_name, + change_date, + is_active, + read_only, + &TEH_master_public_key, + &master_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_MANAGEMENT_UPDATE_AML_OFFICER_SIGNATURE_INVALID, + NULL); + } + { + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Timestamp last_date; + + do { + qs = TEH_plugin->set_aml_officer (TEH_plugin->cls, + &officer_pub, + officer_name, + change_date, + is_active, + read_only, + &master_sig, + &last_date); + } while (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (qs < 0) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "XXX"); + } + if (GNUNET_TIME_timestamp_cmp (last_date, + >, + change_date)) + { + GNUNER_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_MANAGEMENT_AML_OFFICERS_MORE_RECENT_PRESENT, + NULL); + } + } + return TALER_MHD_reply_static ( + connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); +} + + +/* end of taler-exchange-httpd_management_aml-officers.c */ From 56cdb7e9e61d1cbfb7de5d183063baf563e12be0 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 20 Jan 2023 15:20:19 +0100 Subject: [PATCH 13/15] skeleton for adding partners --- ...taler-exchange-httpd_management_partners.c | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/exchange/taler-exchange-httpd_management_partners.c diff --git a/src/exchange/taler-exchange-httpd_management_partners.c b/src/exchange/taler-exchange-httpd_management_partners.c new file mode 100644 index 000000000..5d860120a --- /dev/null +++ b/src/exchange/taler-exchange-httpd_management_partners.c @@ -0,0 +1,123 @@ +/* + This file is part of TALER + Copyright (C) 2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_management_partners.c + * @brief Handle request to add exchange partner + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" +#include "taler_signatures.h" +#include "taler-exchange-httpd_management.h" +#include "taler-exchange-httpd_responses.h" + + +MHD_RESULT +TEH_handler_management_partners ( + struct MHD_Connection *connection, + const json_t *root) +{ + struct TALER_MasterPublicKeyP partner_pub; + struct GNUNET_TIME_Timestamp start_date; + struct GNUNET_TIME_Timestamp end_date; + struct GNUNET_TIME_Relative wad_frequency; + struct TALER_Amount wad_fee; + const char *partner_base_url; + struct TALER_MasterSignatureP master_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("partner_pub", + &partner_pub), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &master_sig), + GNUNET_JSON_spec_string ("partner_base_url", + &partner_base_url), + TALER_JSON_spec_amount ("wad_fee", + &wad_fee), + GNUNET_JSON_spec_timestamp ("start_date", + &start_date), + GNUNET_JSON_spec_timestamp ("end_date", + &start_date), + GNUNET_JSON_spec_time_rel ("wad_frequency", + &wad_frequency), + GNUNET_JSON_spec_end () + }; + + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_SYSERR == res) + return MHD_NO; /* hard failure */ + if (GNUNET_NO == res) + return MHD_YES; /* failure */ + } + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (GNUNET_OK != + TALER_exchange_offline_partner_details_verify ( + &partner_pub, + start_date, + end_date, + wad_frequency, + &wad_fee, + partner_base_url, + &TEH_master_public_key, + &master_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_SIGNATURE_INVALID, + NULL); + } + { + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->add_partner (TEH_plugin->cls, + &partner_pub, + start_date, + end_date, + wad_frequency, + &wad_fee, + partner_base_url, + &master_sig); + if (qs < 0) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "add_partner"); + } + } + return TALER_MHD_reply_static ( + connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); +} + + +/* end of taler-exchange-httpd_management_partners.c */ From c78261526257dbc78e824320ef7887ff7a0f3ef6 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 20 Jan 2023 15:20:51 +0100 Subject: [PATCH 14/15] -indent --- .../taler-exchange-httpd_aml-decision.c | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c index 0526e79d8..c93c10668 100644 --- a/src/exchange/taler-exchange-httpd_aml-decision.c +++ b/src/exchange/taler-exchange-httpd_aml-decision.c @@ -30,7 +30,6 @@ #include "taler-exchange-httpd_responses.h" - MHD_RESULT TEH_handler_management_post_aml_decision ( struct MHD_Connection *connection, @@ -45,6 +44,7 @@ TEH_handler_management_post_aml_decision ( struct TALER_AmlOfficerPublicKeyP officer_pub; struct TALER_AmlOfficerSignatureP officer_sig; struct GNUNET_JSON_Specification spec[] = { + // FIXME: officer_pub is in URL path, not in JSON body! GNUNET_JSON_spec_fixed_auto ("officer_pub", &officer_pub), GNUNET_JSON_spec_fixed_auto ("officer_sig", @@ -120,21 +120,21 @@ TEH_handler_management_post_aml_decision ( if (invalid_officer) { return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER, - NULL); - } + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER, + NULL); + } if (GNUNET_TIME_timestamp_cmp (last_date, >, validity_start)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT, - NULL); + connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT, + NULL); } } return TALER_MHD_reply_static ( From 5b26bd3b83d1b24d937fb8d78135514897ddfb69 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 20 Jan 2023 19:11:36 +0100 Subject: [PATCH 15/15] -new testing APIs --- src/include/taler_testing_lib.h | 43 +++- src/testing/testing_api_cmd_set_officer.c | 204 ++++++++++++++++++ .../testing_api_cmd_take_aml_decision.c | 204 ++++++++++++++++++ 3 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 src/testing/testing_api_cmd_set_officer.c create mode 100644 src/testing/testing_api_cmd_take_aml_decision.c diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 846be4183..a2df5c33f 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2018-2022 Taler Systems SA + (C) 2018-2023 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 @@ -2710,6 +2710,47 @@ TALER_TESTING_cmd_purse_deposit_coins ( ...); +/** + * Setup AML officer. + * + * @param label command label + * @param ref_cmd command that previously created the + * officer, NULL to create one this time + * @param name full legal name of the officer to use + * @param is_active true to set the officer to active + * @param read_only true to restrict the officer to read-only + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_set_officer ( + const char *label, + const char *ref_cmd, + const char *name, + bool is_active, + bool read_only); + + +/** + * Make AML decision. + * + * @param label command label + * @param ref_officer command that previously created an + * officer + * @param ref_operation command that previously created an + * h_payto which to make an AML decision about + * @param new_threshold new threshold to set + * @param block set to true to block the account + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_take_aml_decision ( + const char *label, + const char *ref_officer, + const char *ref_operation, + const char *new_threshold, + bool block); + + /* *** Generic trait logic for implementing traits ********* */ diff --git a/src/testing/testing_api_cmd_set_officer.c b/src/testing/testing_api_cmd_set_officer.c new file mode 100644 index 000000000..2606a6e47 --- /dev/null +++ b/src/testing/testing_api_cmd_set_officer.c @@ -0,0 +1,204 @@ +/* + This file is part of TALER + Copyright (C) 2023 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 + +*/ +/** + * @file testing/testing_api_cmd_set_officer.c + * @brief command for testing /management/XXX + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + + +/** + * State for a "set_officer" CMD. + */ +struct SetOfficerState +{ + + /** + * Auditor enable handle while operation is running. + */ + struct TALER_EXCHANGE_ManagementAuditorEnableHandle *dh; + + /** + * Our interpreter. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Reference to command to previous set officer + * to update, or NULL. + */ + const char *ref_cmd; + + /** + * Name to use for the officer. + */ + const char *name; + + // FIXME: add trait with officer-priv here! + + /** + * Is the officer supposed to be enabled? + */ + bool is_active; + + /** + * Is access supposed to be read-only? + */ + bool read_only; + +}; + + +/** + * Callback to analyze the /management/XXX response, just used to check + * if the response code is acceptable. + * + * @param cls closure. + * @param hr HTTP response details + */ +static void +set_officer_cb (void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr) +{ + struct SetOfficerState *ds = cls; + + ds->dh = NULL; + if (MHD_HTTP_NO_CONTENT != hr->response_code) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unexpected response code %u to command %s in %s:%u\n", + hr->http_status, + ds->is->commands[ds->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (hr->reply, + stderr, + 0); + TALER_TESTING_interpreter_fail (ds->is); + return; + } + TALER_TESTING_interpreter_next (ds->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +set_officer_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct SetOfficerState *ds = cls; + struct GNUNET_TIME_Timestamp now; + struct TALER_MasterSignatureP master_sig; + + (void) cmd; + now = GNUNET_TIME_timestamp_get (); + ds->is = is; + TALER_exchange_offline_set_officer_sign (&is->auditor_pub, + is->auditor_url, + now, + &is->master_priv, + &master_sig); + ds->dh = TALER_EXCHANGE_management_enable_auditor ( + is->ctx, + is->exchange_url, + &is->auditor_pub, + is->auditor_url, + "test-case auditor", /* human-readable auditor name */ + now, + &master_sig, + &set_officer_cb, + ds); + if (NULL == ds->dh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state of a "set_officer" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, must be a `struct SetOfficerState`. + * @param cmd the command which is being cleaned up. + */ +static void +set_officer_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct SetOfficerState *ds = cls; + + if (NULL != ds->dh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + ds->is->ip, + cmd->label); + TALER_EXCHANGE_management_enable_auditor_cancel (ds->dh); + ds->dh = NULL; + } + GNUNET_free (ds); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_set_officer ( + const char *label, + const char *ref_cmd, + const char *name, + bool is_active, + bool read_only) +{ + struct SetOfficerState *ds; + + ds = GNUNET_new (struct SetOfficerState); + ds->ref_cmd = ref_cmd; + ds->name = name; + ds->is_active = is_active; + ds->read_only = read_only; + { + struct TALER_TESTING_Command cmd = { + .cls = ds, + .label = label, + .run = &set_officer_run, + .cleanup = &set_officer_cleanup + // FIXME: expose trait with officer-priv here! + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_set_officer.c */ diff --git a/src/testing/testing_api_cmd_take_aml_decision.c b/src/testing/testing_api_cmd_take_aml_decision.c new file mode 100644 index 000000000..4abb585e4 --- /dev/null +++ b/src/testing/testing_api_cmd_take_aml_decision.c @@ -0,0 +1,204 @@ +/* + This file is part of TALER + Copyright (C) 2023 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 + +*/ +/** + * @file testing/testing_api_cmd_take_aml_decision.c + * @brief command for testing /management/XXX + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + + +/** + * State for a "take_aml_decision" CMD. + */ +struct AmlDecisionState +{ + + /** + * Auditor enable handle while operation is running. + */ + struct TALER_EXCHANGE_ManagementAuditorEnableHandle *dh; + + /** + * Our interpreter. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Reference to command to previous set officer + * to update, or NULL. + */ + const char *ref_cmd; + + /** + * Name to use for the officer. + */ + const char *name; + + // FIXME: add trait with officer-priv here! + + /** + * Is the officer supposed to be enabled? + */ + bool is_active; + + /** + * Is access supposed to be read-only? + */ + bool read_only; + +}; + + +/** + * Callback to analyze the /management/XXX response, just used to check + * if the response code is acceptable. + * + * @param cls closure. + * @param hr HTTP response details + */ +static void +take_aml_decision_cb (void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr) +{ + struct AmlDecisionState *ds = cls; + + ds->dh = NULL; + if (MHD_HTTP_NO_CONTENT != hr->response_code) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unexpected response code %u to command %s in %s:%u\n", + hr->http_status, + ds->is->commands[ds->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (hr->reply, + stderr, + 0); + TALER_TESTING_interpreter_fail (ds->is); + return; + } + TALER_TESTING_interpreter_next (ds->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +take_aml_decision_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct AmlDecisionState *ds = cls; + struct GNUNET_TIME_Timestamp now; + struct TALER_MasterSignatureP master_sig; + + (void) cmd; + now = GNUNET_TIME_timestamp_get (); + ds->is = is; + TALER_exchange_offline_take_aml_decision_sign (&is->auditor_pub, + is->auditor_url, + now, + &is->master_priv, + &master_sig); + ds->dh = TALER_EXCHANGE_management_enable_auditor ( + is->ctx, + is->exchange_url, + &is->auditor_pub, + is->auditor_url, + "test-case auditor", /* human-readable auditor name */ + now, + &master_sig, + &take_aml_decision_cb, + ds); + if (NULL == ds->dh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state of a "take_aml_decision" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, must be a `struct AmlDecisionState`. + * @param cmd the command which is being cleaned up. + */ +static void +take_aml_decision_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct AmlDecisionState *ds = cls; + + if (NULL != ds->dh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + ds->is->ip, + cmd->label); + TALER_EXCHANGE_management_enable_auditor_cancel (ds->dh); + ds->dh = NULL; + } + GNUNET_free (ds); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_take_aml_decision ( + const char *label, + const char *ref_officer, + const char *ref_operation, + const char *new_threshold, + bool block) +{ + struct AmlDecisionState *ds; + + ds = GNUNET_new (struct AmlDecisionState); + ds->ref_cmd = ref_cmd; + ds->name = name; + ds->is_active = is_active; + ds->read_only = read_only; + { + struct TALER_TESTING_Command cmd = { + .cls = ds, + .label = label, + .run = &take_aml_decision_run, + .cleanup = &take_aml_decision_cleanup + // FIXME: expose trait with officer-priv here! + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_take_aml_decision.c */