first draft of implementation of GET AML decisions endpoint

This commit is contained in:
Christian Grothoff 2023-02-02 11:40:44 +01:00
parent f8ff9c996f
commit 915542e69c
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
12 changed files with 346 additions and 32 deletions

View File

@ -124,6 +124,7 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd.c taler-exchange-httpd.h \
taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.h \
taler-exchange-httpd_aml-decision.c taler-exchange-httpd_aml-decision.h \
taler-exchange-httpd_aml-decisions-get.c \
taler-exchange-httpd_batch-deposit.c taler-exchange-httpd_batch-deposit.h \
taler-exchange-httpd_batch-withdraw.c taler-exchange-httpd_batch-withdraw.h \
taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \

View File

@ -446,14 +446,14 @@ handle_get_aml (struct TEH_RequestContext *rc,
AmlOpGetHandler handler;
} h[] = {
#if FIXME_AML_GET_DECISIONS_NOT_IMPLEMENTED
{
.op = "decisions",
.handler = &TEH_handler_get_aml_decisions
.handler = &TEH_handler_aml_decisions_get
},
#if FIXME_AML_GET_DECISIONS_NOT_IMPLEMENTED
{
.op = "decision",
.handler = &TEH_handler_get_aml_decision
.handler = &TEH_handler_aml_decision_get
},
#endif
{

View File

@ -26,7 +26,7 @@
/**
* Handle an "/aml/$OFFICER_PUB/decision" request. Parses the decision
* Handle a POST "/aml/$OFFICER_PUB/decision" request. Parses the decision
* details, checks the signatures and if appropriately authorized executes
* the decision.
*
@ -42,4 +42,20 @@ TEH_handler_post_aml_decision (
const json_t *root);
/**
* Handle a GET "/aml/$OFFICER_PUB/decisions" request. Parses the request
* details, checks the signatures and if appropriately authorized returns
* the matching decisions.
*
* @param rc request context
* @param officer_pub public key of the AML officer who made the request
* @param args GET arguments (should be none)
* @return MHD result code
*/
MHD_RESULT
TEH_handler_aml_decisions_get (
struct TEH_RequestContext *rc,
const struct TALER_AmlOfficerPublicKeyP *officer_pub,
const char *const args[]);
#endif

View File

@ -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 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 <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_aml-decisions-get.c
* @brief Return summary information about AML decisions
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
#include "taler-exchange-httpd.h"
#include "taler_exchangedb_plugin.h"
#include "taler-exchange-httpd_aml-decision.h"
#include "taler-exchange-httpd_metrics.h"
/**
* Maximum number of records we return per request.
*/
#define MAX_RECORDS 1024
/**
* Return AML status.
*
* @param cls closure
* @param row_id current row in AML status table
* @param h_payto account for which the attribute data is stored
* @param threshold currently monthly threshold that would trigger an AML check
* @param status what is the current AML decision
*/
static void
record_cb (
void *cls,
uint64_t row_id,
const struct TALER_PaytoHashP *h_payto,
const struct TALER_Amount *threshold,
enum TALER_AmlDecisionState status)
{
json_t *records = cls;
GNUNET_assert (
0 ==
json_array_append (
records,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("h_payto",
h_payto),
TALER_JSON_pack_amount ("threshold",
threshold),
GNUNET_JSON_pack_int64 ("current_state",
status),
GNUNET_JSON_pack_int64 ("rowid",
row_id)
)));
}
MHD_RESULT
TEH_handler_aml_decisions_get (
struct TEH_RequestContext *rc,
const struct TALER_AmlOfficerPublicKeyP *officer_pub,
const char *const args[])
{
enum GNUNET_GenericReturnValue res;
const char *sig_hdr;
struct TALER_AmlOfficerSignatureP officer_sig;
bool frozen = false;
bool pending = false;
bool normal = false;
int delta = -20;
unsigned long long start = INT64_MAX;
if (NULL != args[0])
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
args[0]);
}
sig_hdr = MHD_lookup_connection_value (rc->connection,
MHD_HEADER_KIND,
TALER_AML_OFFICER_SIGNATURE_HEADER);
if ( (NULL == sig_hdr) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (sig_hdr,
strlen (sig_hdr),
&officer_sig,
sizeof (officer_sig))) ||
(GNUNET_OK !=
TALER_officer_aml_query_verify (officer_pub,
&officer_sig)) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_GET_SIGNATURE_INVALID,
sig_hdr);
}
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
{
const char *p;
p = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"frozen");
if (NULL != p)
frozen = (0 == strcasecmp (p,
"yes"));
p = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"pending");
if (NULL != p)
pending = (0 == strcasecmp (p,
"yes"));
p = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"normal");
if (NULL != p)
normal = (0 == strcasecmp (p,
"yes"));
p = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"start");
if (NULL != p)
{
char dummy;
if (1 != sscanf (p,
"%llu%c",
&start,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"start");
}
}
p = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"delta");
if (NULL != p)
{
char dummy;
if (1 != sscanf (p,
"%d%c",
&delta,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"delta");
}
}
}
{
json_t *records;
enum GNUNET_DB_QueryStatus qs;
enum TALER_AmlDecisionState decision = 42; // FIXME!
records = json_array ();
GNUNET_assert (NULL != records);
if (INT_MIN == delta)
delta = INT_MIN + 1;
qs = TEH_plugin->select_aml_process (TEH_plugin->cls,
decision,
start,
delta > 0,
GNUNET_MIN (MAX_RECORDS,
delta > 0 ? delta :
-delta),
&record_cb,
records);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
json_decref (records);
GNUNET_break (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL);
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
return TALER_MHD_reply_static (
rc->connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_array_steal ("records",
records));
}
}
/* end of taler-exchange-httpd_deposits_get.c */

View File

@ -112,6 +112,7 @@ TEH_PG_select_aml_process (
void *cls,
enum TALER_AmlDecisionState decision,
uint64_t row_off,
uint64_t limit,
bool forward,
TALER_EXCHANGEDB_AmlStatusCallback cb,
void *cb_cls)
@ -120,6 +121,7 @@ TEH_PG_select_aml_process (
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint32 (&decision),
GNUNET_PQ_query_param_uint64 (&row_off),
GNUNET_PQ_query_param_uint64 (&limit),
GNUNET_PQ_query_param_end
};
struct AmlProcessResultContext ctx = {
@ -144,7 +146,8 @@ TEH_PG_select_aml_process (
" FROM aml_status"
" WHERE aml_status_serial_id > $2"
" AND $1 = status & $1"
" ORDER BY aml_status_serial_id INC");
" ORDER BY aml_status_serial_id INC"
" LIMIT $3");
PREPARE (pg,
"select_aml_process_dec",
"SELECT"
@ -156,7 +159,8 @@ TEH_PG_select_aml_process (
" FROM aml_status"
" WHERE aml_status_serial_id < $2"
" AND $1 = status & $1"
" ORDER BY aml_status_serial_id DESC");
" ORDER BY aml_status_serial_id DESC"
" LIMIT $3");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
stmt,
params,

View File

@ -32,6 +32,7 @@
* @param cls closure
* @param decision which decision states to filter by
* @param row_off offset to start from
* @param limit how many rows to return at most
* @param forward true to go forward in time, false to go backwards
* @param cb callback to invoke on each match
* @param cb_cls closure for @a cb
@ -42,6 +43,7 @@ TEH_PG_select_aml_process (
void *cls,
enum TALER_AmlDecisionState decision,
uint64_t row_off,
uint64_t limit,
bool forward,
TALER_EXCHANGEDB_AmlStatusCallback cb,
void *cb_cls);

View File

@ -4381,7 +4381,7 @@ struct TALER_EXCHANGE_LookupAmlDecisions;
* Inform the exchange that an AML decision has been taken.
*
* @param ctx the context
* @param url HTTP base URL for the exchange
* @param exchange_url HTTP base URL for the exchange
* @param start row number starting point (exclusive rowid)
* @param delta number of records to return, negative for descending, positive for ascending from start
* @param filter_frozen true to only return frozen accounts
@ -4395,7 +4395,7 @@ struct TALER_EXCHANGE_LookupAmlDecisions;
struct TALER_EXCHANGE_LookupAmlDecisions *
TALER_EXCHANGE_lookup_aml_decisions (
struct GNUNET_CURL_Context *ctx,
const char *url,
const char *exchange_url,
uint64_t start,
int delta,
bool filter_frozen,
@ -4532,7 +4532,7 @@ struct TALER_EXCHANGE_LookupAmlDecision;
* Inform the exchange that an AML decision has been taken.
*
* @param ctx the context
* @param url HTTP base URL for the exchange
* @param exchange_url HTTP base URL for the exchange
* @param h_payto which account to return the decision history for
* @param officer_priv private key of the deciding AML officer
* @param cb function to call with the exchange's result
@ -4542,10 +4542,10 @@ struct TALER_EXCHANGE_LookupAmlDecision;
struct TALER_EXCHANGE_LookupAmlDecision *
TALER_EXCHANGE_lookup_aml_decision (
struct GNUNET_CURL_Context *ctx,
const char *url,
const char *exchange_url,
const struct TALER_PaytoHashP *h_payto,
const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
TALER_EXCHANGE_LookupAmlDecisionsCallback cb,
TALER_EXCHANGE_LookupAmlDecisionCallback cb,
void *cb_cls);

View File

@ -6693,6 +6693,7 @@ struct TALER_EXCHANGEDB_Plugin
void *cls,
enum TALER_AmlDecisionState decision,
uint64_t row_off,
uint64_t limit,
bool forward,
TALER_EXCHANGEDB_AmlStatusCallback cb,
void *cb_cls);

View File

@ -32,7 +32,7 @@
* Version of the Taler API, in hex.
* Thus 0.8.4-1 = 0x00080401.
*/
#define TALER_API_VERSION 0x00080401
#define TALER_API_VERSION 0x00090200
/**
* Stringify operator.
@ -80,6 +80,12 @@
} while (0)
/**
* HTTP header with an AML officer signature to approve the inquiry.
* Used only in GET Requests.
*/
#define TALER_AML_OFFICER_SIGNATURE_HEADER "Taler-AML-Officer-Signature"
/**
* Log an error message at log-level 'level' that indicates
* a failure of the command 'cmd' with the message given

View File

@ -38,6 +38,8 @@ libtalerexchange_la_SOURCES = \
exchange_api_kyc_proof.c \
exchange_api_kyc_wallet.c \
exchange_api_link.c \
exchange_api_lookup_aml_decision.c \
exchange_api_lookup_aml_decisions.c \
exchange_api_management_add_partner.c \
exchange_api_management_auditor_disable.c \
exchange_api_management_auditor_enable.c \

View File

@ -56,6 +56,10 @@ struct TALER_EXCHANGE_LookupAmlDecision
*/
void *decision_cb_cls;
/**
* HTTP headers for the job.
*/
struct curl_slist *job_headers;
};
@ -118,7 +122,7 @@ handle_lookup_finished (void *cls,
break;
}
GNUNET_assert (NULL == lh->decision_cb);
TALER_EXCHANGE_link_cancel (lh);
TALER_EXCHANGE_lookup_aml_decision_cancel (lh);
return;
case MHD_HTTP_BAD_REQUEST:
lr.hr.ec = TALER_JSON_get_error_code (j);
@ -152,14 +156,14 @@ handle_lookup_finished (void *cls,
if (NULL != lh->decision_cb)
lh->decision_cb (lh->decision_cb_cls,
&lr);
TALER_EXCHANGE_link_cancel (lh);
TALER_EXCHANGE_lookup_aml_decision_cancel (lh);
}
struct TALER_EXCHANGE_LookupAmlDecision *
TALER_EXCHANGE_lookup_aml_decision (
struct GNUNET_CURL_Context *ctx,
const char *url,
const char *exchange_url,
const struct TALER_PaytoHashP *h_payto,
const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
TALER_EXCHANGE_LookupAmlDecisionCallback cb,
@ -202,7 +206,7 @@ TALER_EXCHANGE_lookup_aml_decision (
lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecision);
lh->decision_cb = cb;
lh->decision_cb_cls = cb_cls;
lh->url = TALER_URL_join (exchange_url,
lh->url = TALER_url_join (exchange_url,
arg_str,
NULL);
if (NULL == lh->url)
@ -218,11 +222,33 @@ TALER_EXCHANGE_lookup_aml_decision (
GNUNET_free (lh);
return NULL;
}
// FIXME: add authorization header to 'eh' based on officer_sig!
lh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
eh,
&handle_lookup_finished,
lh);
{
char *hdr;
char sig_str[sizeof (officer_sig) * 2];
char *end;
end = GNUNET_STRINGS_data_to_string (
&officer_sig,
sizeof (officer_sig),
sig_str,
sizeof (sig_str));
*end = '\0';
GNUNET_asprintf (&hdr,
"%s: %s",
TALER_AML_OFFICER_SIGNATURE_HEADER,
sig_str);
lh->job_headers = curl_slist_append (NULL,
hdr);
GNUNET_free (hdr);
lh->job_headers = curl_slist_append (lh->job_headers,
"Content-type: application/json");
lh->job = GNUNET_CURL_job_add2 (ctx,
eh,
lh->job_headers,
&handle_lookup_finished,
lh);
}
return lh;
}
@ -236,6 +262,7 @@ TALER_EXCHANGE_lookup_aml_decision_cancel (
GNUNET_CURL_job_cancel (lh->job);
lh->job = NULL;
}
curl_slist_free_all (lh->job_headers);
GNUNET_free (lh->url);
GNUNET_free (lh);
}

View File

@ -56,6 +56,10 @@ struct TALER_EXCHANGE_LookupAmlDecisions
*/
void *decisions_cb_cls;
/**
* HTTP headers for the job.
*/
struct curl_slist *job_headers;
};
@ -118,7 +122,7 @@ handle_lookup_finished (void *cls,
break;
}
GNUNET_assert (NULL == lh->decisions_cb);
TALER_EXCHANGE_link_cancel (lh);
TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
return;
case MHD_HTTP_BAD_REQUEST:
lr.hr.ec = TALER_JSON_get_error_code (j);
@ -152,14 +156,14 @@ handle_lookup_finished (void *cls,
if (NULL != lh->decisions_cb)
lh->decisions_cb (lh->decisions_cb_cls,
&lr);
TALER_EXCHANGE_link_cancel (lh);
TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
}
struct TALER_EXCHANGE_LookupAmlDecisions *
TALER_EXCHANGE_lookup_aml_decisions (
struct GNUNET_CURL_Context *ctx,
const char *url,
const char *exchange_url,
uint64_t start,
int delta,
bool filter_frozen,
@ -180,7 +184,7 @@ TALER_EXCHANGE_lookup_aml_decisions (
TALER_officer_aml_query_sign (officer_priv,
&officer_sig);
{
char pub_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2];
char pub_str[sizeof (officer_pub) * 2];
char *end;
end = GNUNET_STRINGS_data_to_string (
@ -197,10 +201,10 @@ TALER_EXCHANGE_lookup_aml_decisions (
lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions);
lh->decisions_cb = cb;
lh->decisions_cb_cls = cb_cls;
lh->url = TALER_URL_join (exchange_url,
lh->url = TALER_url_join (exchange_url,
arg_str,
"frozen",
filter_fozen ? "yes" : NULL,
filter_frozen ? "yes" : NULL,
"pending",
filter_pending ? "yes" : NULL,
"normal",
@ -219,11 +223,33 @@ TALER_EXCHANGE_lookup_aml_decisions (
GNUNET_free (lh);
return NULL;
}
// FIXME: add authorization header to 'eh' based on officer_sig!
lh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
eh,
&handle_lookup_finished,
lh);
{
char *hdr;
char sig_str[sizeof (officer_sig) * 2];
char *end;
end = GNUNET_STRINGS_data_to_string (
&officer_sig,
sizeof (officer_sig),
sig_str,
sizeof (sig_str));
*end = '\0';
GNUNET_asprintf (&hdr,
"%s: %s",
TALER_AML_OFFICER_SIGNATURE_HEADER,
sig_str);
lh->job_headers = curl_slist_append (NULL,
hdr);
GNUNET_free (hdr);
lh->job_headers = curl_slist_append (lh->job_headers,
"Content-type: application/json");
lh->job = GNUNET_CURL_job_add2 (ctx,
eh,
lh->job_headers,
&handle_lookup_finished,
lh);
}
return lh;
}
@ -237,6 +263,7 @@ TALER_EXCHANGE_lookup_aml_decisions_cancel (
GNUNET_CURL_job_cancel (lh->job);
lh->job = NULL;
}
curl_slist_free_all (lh->job_headers);
GNUNET_free (lh->url);
GNUNET_free (lh);
}