add code to sanity-check KYC configuration and KYC decisions
This commit is contained in:
parent
4d2d0473c3
commit
87a78c6f8c
@ -1 +1 @@
|
||||
Subproject commit 3a616a04f1cd946bf0641b54cd71f1b858174f74
|
||||
Subproject commit 1ec4596bf4925ee24fc06d3e74d2a553b8239870
|
@ -26,14 +26,138 @@
|
||||
#include <pthread.h>
|
||||
#include "taler_json_lib.h"
|
||||
#include "taler_mhd_lib.h"
|
||||
#include "taler_kyclogic_lib.h"
|
||||
#include "taler_signatures.h"
|
||||
#include "taler-exchange-httpd_responses.h"
|
||||
|
||||
|
||||
/**
|
||||
* How often do we try the DB operation at most?
|
||||
* Closure for #make_aml_decision()
|
||||
*/
|
||||
#define MAX_RETRIES 10
|
||||
struct DecisionContext
|
||||
{
|
||||
/**
|
||||
* Justification given for the decision.
|
||||
*/
|
||||
const char *justification;
|
||||
|
||||
/**
|
||||
* When was the decision taken.
|
||||
*/
|
||||
struct GNUNET_TIME_Timestamp decision_time;
|
||||
|
||||
/**
|
||||
* New threshold for revising the decision.
|
||||
*/
|
||||
struct TALER_Amount new_threshold;
|
||||
|
||||
/**
|
||||
* Hash of payto://-URI of affected account.
|
||||
*/
|
||||
struct TALER_PaytoHashP h_payto;
|
||||
|
||||
/**
|
||||
* New AML state.
|
||||
*/
|
||||
enum TALER_AmlDecisionState new_state;
|
||||
|
||||
/**
|
||||
* Signature affirming the decision.
|
||||
*/
|
||||
struct TALER_AmlOfficerSignatureP officer_sig;
|
||||
|
||||
/**
|
||||
* Public key of the AML officer.
|
||||
*/
|
||||
const struct TALER_AmlOfficerPublicKeyP *officer_pub;
|
||||
|
||||
/**
|
||||
* KYC requirements imposed, NULL for none.
|
||||
*/
|
||||
json_t *kyc_requirements;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function implementing AML decision database transaction.
|
||||
*
|
||||
* Runs the transaction logic; IF it returns a non-error code, the
|
||||
* transaction logic MUST NOT queue a MHD response. IF it returns an hard
|
||||
* error, the transaction logic MUST queue a MHD response and set @a mhd_ret.
|
||||
* IF it returns the soft error code, the function MAY be called again to
|
||||
* retry and MUST not queue a MHD response.
|
||||
*
|
||||
* @param cls closure with a `struct DecisionContext`
|
||||
* @param connection MHD request which triggered the transaction
|
||||
* @param[out] mhd_ret set to MHD response status for @a connection,
|
||||
* if transaction failed (!)
|
||||
* @return transaction status
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
make_aml_decision (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct DecisionContext *dc = cls;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
struct GNUNET_TIME_Timestamp last_date;
|
||||
bool invalid_officer;
|
||||
|
||||
qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
|
||||
&dc->h_payto,
|
||||
&dc->new_threshold,
|
||||
dc->new_state,
|
||||
dc->decision_time,
|
||||
dc->justification,
|
||||
dc->kyc_requirements,
|
||||
dc->officer_pub,
|
||||
&dc->officer_sig,
|
||||
&invalid_officer,
|
||||
&last_date);
|
||||
if (qs <= 0)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
"insert_aml_decision");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
if (invalid_officer)
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
if (GNUNET_TIME_timestamp_cmp (last_date,
|
||||
>=,
|
||||
dc->decision_time))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_CONFLICT,
|
||||
TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
if (NULL != dc->kyc_requirements)
|
||||
{
|
||||
// FIXME: act on these!
|
||||
}
|
||||
|
||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||
}
|
||||
|
||||
|
||||
MHD_RESULT
|
||||
@ -43,31 +167,27 @@ TEH_handler_post_aml_decision (
|
||||
const json_t *root)
|
||||
{
|
||||
struct MHD_Connection *connection = rc->connection;
|
||||
const char *justification;
|
||||
struct GNUNET_TIME_Timestamp decision_time;
|
||||
struct TALER_Amount new_threshold;
|
||||
struct TALER_PaytoHashP h_payto;
|
||||
struct DecisionContext dc = {
|
||||
.officer_pub = officer_pub
|
||||
};
|
||||
uint32_t new_state32;
|
||||
enum TALER_AmlDecisionState new_state;
|
||||
struct TALER_AmlOfficerSignatureP officer_sig;
|
||||
json_t *kyc_requirements = NULL;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("officer_sig",
|
||||
&officer_sig),
|
||||
&dc.officer_sig),
|
||||
GNUNET_JSON_spec_fixed_auto ("h_payto",
|
||||
&h_payto),
|
||||
&dc.h_payto),
|
||||
TALER_JSON_spec_amount ("new_threshold",
|
||||
TEH_currency,
|
||||
&new_threshold),
|
||||
&dc.new_threshold),
|
||||
GNUNET_JSON_spec_string ("justification",
|
||||
&justification),
|
||||
&dc.justification),
|
||||
GNUNET_JSON_spec_timestamp ("decision_time",
|
||||
&decision_time),
|
||||
&dc.decision_time),
|
||||
GNUNET_JSON_spec_uint32 ("new_state",
|
||||
&new_state32),
|
||||
GNUNET_JSON_spec_mark_optional (
|
||||
GNUNET_JSON_spec_json ("kyc_requirements",
|
||||
&kyc_requirements),
|
||||
&dc.kyc_requirements),
|
||||
NULL),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
@ -86,17 +206,17 @@ TEH_handler_post_aml_decision (
|
||||
return MHD_YES; /* failure */
|
||||
}
|
||||
}
|
||||
new_state = (enum TALER_AmlDecisionState) new_state32;
|
||||
dc.new_state = (enum TALER_AmlDecisionState) new_state32;
|
||||
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
|
||||
if (GNUNET_OK !=
|
||||
TALER_officer_aml_decision_verify (justification,
|
||||
decision_time,
|
||||
&new_threshold,
|
||||
&h_payto,
|
||||
new_state,
|
||||
kyc_requirements,
|
||||
officer_pub,
|
||||
&officer_sig))
|
||||
TALER_officer_aml_decision_verify (dc.justification,
|
||||
dc.decision_time,
|
||||
&dc.new_threshold,
|
||||
&dc.h_payto,
|
||||
dc.new_state,
|
||||
dc.kyc_requirements,
|
||||
dc.officer_pub,
|
||||
&dc.officer_sig))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (
|
||||
@ -106,62 +226,67 @@ TEH_handler_post_aml_decision (
|
||||
NULL);
|
||||
}
|
||||
|
||||
// FIXME: check kyc_requirements is well-formed!
|
||||
if (NULL != dc.kyc_requirements)
|
||||
{
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
struct GNUNET_TIME_Timestamp last_date;
|
||||
bool invalid_officer;
|
||||
unsigned int retries_left = MAX_RETRIES;
|
||||
size_t index;
|
||||
json_t *elem;
|
||||
|
||||
do {
|
||||
qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
|
||||
&h_payto,
|
||||
&new_threshold,
|
||||
new_state,
|
||||
decision_time,
|
||||
justification,
|
||||
kyc_requirements,
|
||||
officer_pub,
|
||||
&officer_sig,
|
||||
&invalid_officer,
|
||||
&last_date);
|
||||
if (0 == --retries_left)
|
||||
break;
|
||||
} 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,
|
||||
"add aml_decision");
|
||||
}
|
||||
if (NULL != kyc_requirements)
|
||||
{
|
||||
// FIXME: act on these!
|
||||
}
|
||||
|
||||
if (invalid_officer)
|
||||
if (! json_is_array (dc.kyc_requirements))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
|
||||
NULL);
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"kyc_requirements must be an array");
|
||||
}
|
||||
if (GNUNET_TIME_timestamp_cmp (last_date,
|
||||
>=,
|
||||
decision_time))
|
||||
|
||||
json_array_foreach (dc.kyc_requirements, index, elem)
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_CONFLICT,
|
||||
TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
|
||||
NULL);
|
||||
const char *val;
|
||||
|
||||
if (! json_is_string (elem))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||
"kyc_requirements array members must be strings");
|
||||
}
|
||||
val = json_string_value (elem);
|
||||
if (GNUNET_SYSERR ==
|
||||
TALER_KYCLOGIC_check_satisfiable (val))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_EXCHANGE_AML_DECISION_UNKNOWN_CHECK,
|
||||
val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MHD_RESULT mhd_ret;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TEH_DB_run_transaction (connection,
|
||||
"make-aml-decision",
|
||||
TEH_MT_REQUEST_OTHER,
|
||||
&mhd_ret,
|
||||
&make_aml_decision,
|
||||
&dc))
|
||||
{
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return mhd_ret;
|
||||
}
|
||||
}
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TALER_MHD_reply_static (
|
||||
connection,
|
||||
MHD_HTTP_NO_CONTENT,
|
||||
|
@ -296,6 +296,20 @@ TALER_KYCLOGIC_kyc_get_details (
|
||||
void *cb_cls);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a given @a check_name is a legal name (properly
|
||||
* configured) and can be satisfied in principle.
|
||||
*
|
||||
* @param logic_name name of the logic to match
|
||||
* @return #GNUNET_OK if the check can be satisfied,
|
||||
* #GNUNET_NO if the check can never be satisfied,
|
||||
* #GNUNET_SYSERR if the type of the check is unknown
|
||||
*/
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_KYCLOGIC_check_satisfiable (
|
||||
const char *check_name);
|
||||
|
||||
|
||||
/**
|
||||
* Obtain the provider logic for a given set of @a requirements.
|
||||
*
|
||||
|
@ -21,6 +21,12 @@
|
||||
#include "platform.h"
|
||||
#include "taler_kyclogic_lib.h"
|
||||
|
||||
/**
|
||||
* Name of the KYC check that may never be passed. Useful if some
|
||||
* operations/amounts are categorically forbidden.
|
||||
*/
|
||||
#define KYC_CHECK_IMPOSSIBLE "impossible"
|
||||
|
||||
/**
|
||||
* Information about a KYC provider.
|
||||
*/
|
||||
@ -265,6 +271,21 @@ TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut)
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_KYCLOGIC_check_satisfiable (
|
||||
const char *check_name)
|
||||
{
|
||||
for (unsigned int i = 0; i<num_kyc_checks; i++)
|
||||
if (0 == strcmp (check_name,
|
||||
kyc_checks[i]->name))
|
||||
return GNUNET_OK;
|
||||
if (0 == strcmp (check_name,
|
||||
KYC_CHECK_IMPOSSIBLE))
|
||||
return GNUNET_NO;
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load KYC logic plugin.
|
||||
*
|
||||
@ -331,9 +352,8 @@ add_check (const char *check)
|
||||
|
||||
|
||||
/**
|
||||
* Parse list of checks from @a checks and build an
|
||||
* array of aliases into the global checks array
|
||||
* in @a provided_checks.
|
||||
* Parse list of checks from @a checks and build an array of aliases into the
|
||||
* global checks array in @a provided_checks.
|
||||
*
|
||||
* @param[in,out] checks list of checks; clobbered
|
||||
* @param[out] p_checks where to put array of aliases
|
||||
@ -585,6 +605,29 @@ add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg,
|
||||
GNUNET_array_append (kyc_triggers,
|
||||
num_kyc_triggers,
|
||||
kt);
|
||||
for (unsigned int i = 0; i<kt->num_checks; i++)
|
||||
{
|
||||
const struct TALER_KYCLOGIC_KycCheck *ck = kt->required_checks[i];
|
||||
|
||||
if (0 != ck->num_providers)
|
||||
continue;
|
||||
if (0 == strcmp (ck->name,
|
||||
KYC_CHECK_IMPOSSIBLE))
|
||||
continue;
|
||||
{
|
||||
char *msg;
|
||||
|
||||
GNUNET_asprintf (&msg,
|
||||
"Required check `%s' cannot be satisfied: not provided by any provider",
|
||||
ck->name);
|
||||
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
|
||||
section,
|
||||
"REQUIRED_CHECKS",
|
||||
msg);
|
||||
GNUNET_free (msg);
|
||||
}
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
}
|
||||
return GNUNET_OK;
|
||||
}
|
||||
@ -614,8 +657,8 @@ struct SectionContext
|
||||
* @param section name of the section
|
||||
*/
|
||||
static void
|
||||
handle_section (void *cls,
|
||||
const char *section)
|
||||
handle_provider_section (void *cls,
|
||||
const char *section)
|
||||
{
|
||||
struct SectionContext *sc = cls;
|
||||
|
||||
@ -629,6 +672,21 @@ handle_section (void *cls,
|
||||
sc->result = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function to iterate over configuration sections.
|
||||
*
|
||||
* @param cls a `struct SectionContext *`
|
||||
* @param section name of the section
|
||||
*/
|
||||
static void
|
||||
handle_trigger_section (void *cls,
|
||||
const char *section)
|
||||
{
|
||||
struct SectionContext *sc = cls;
|
||||
|
||||
if (0 == strncasecmp (section,
|
||||
"kyc-legitimization-",
|
||||
strlen ("kyc-legitimization-")))
|
||||
@ -680,7 +738,10 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
|
||||
};
|
||||
|
||||
GNUNET_CONFIGURATION_iterate_sections (cfg,
|
||||
&handle_section,
|
||||
&handle_provider_section,
|
||||
&sc);
|
||||
GNUNET_CONFIGURATION_iterate_sections (cfg,
|
||||
&handle_trigger_section,
|
||||
&sc);
|
||||
if (! sc.result)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user