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 <pthread.h>
|
||||||
#include "taler_json_lib.h"
|
#include "taler_json_lib.h"
|
||||||
#include "taler_mhd_lib.h"
|
#include "taler_mhd_lib.h"
|
||||||
|
#include "taler_kyclogic_lib.h"
|
||||||
#include "taler_signatures.h"
|
#include "taler_signatures.h"
|
||||||
#include "taler-exchange-httpd_responses.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
|
MHD_RESULT
|
||||||
@ -43,31 +167,27 @@ TEH_handler_post_aml_decision (
|
|||||||
const json_t *root)
|
const json_t *root)
|
||||||
{
|
{
|
||||||
struct MHD_Connection *connection = rc->connection;
|
struct MHD_Connection *connection = rc->connection;
|
||||||
const char *justification;
|
struct DecisionContext dc = {
|
||||||
struct GNUNET_TIME_Timestamp decision_time;
|
.officer_pub = officer_pub
|
||||||
struct TALER_Amount new_threshold;
|
};
|
||||||
struct TALER_PaytoHashP h_payto;
|
|
||||||
uint32_t new_state32;
|
uint32_t new_state32;
|
||||||
enum TALER_AmlDecisionState new_state;
|
|
||||||
struct TALER_AmlOfficerSignatureP officer_sig;
|
|
||||||
json_t *kyc_requirements = NULL;
|
|
||||||
struct GNUNET_JSON_Specification spec[] = {
|
struct GNUNET_JSON_Specification spec[] = {
|
||||||
GNUNET_JSON_spec_fixed_auto ("officer_sig",
|
GNUNET_JSON_spec_fixed_auto ("officer_sig",
|
||||||
&officer_sig),
|
&dc.officer_sig),
|
||||||
GNUNET_JSON_spec_fixed_auto ("h_payto",
|
GNUNET_JSON_spec_fixed_auto ("h_payto",
|
||||||
&h_payto),
|
&dc.h_payto),
|
||||||
TALER_JSON_spec_amount ("new_threshold",
|
TALER_JSON_spec_amount ("new_threshold",
|
||||||
TEH_currency,
|
TEH_currency,
|
||||||
&new_threshold),
|
&dc.new_threshold),
|
||||||
GNUNET_JSON_spec_string ("justification",
|
GNUNET_JSON_spec_string ("justification",
|
||||||
&justification),
|
&dc.justification),
|
||||||
GNUNET_JSON_spec_timestamp ("decision_time",
|
GNUNET_JSON_spec_timestamp ("decision_time",
|
||||||
&decision_time),
|
&dc.decision_time),
|
||||||
GNUNET_JSON_spec_uint32 ("new_state",
|
GNUNET_JSON_spec_uint32 ("new_state",
|
||||||
&new_state32),
|
&new_state32),
|
||||||
GNUNET_JSON_spec_mark_optional (
|
GNUNET_JSON_spec_mark_optional (
|
||||||
GNUNET_JSON_spec_json ("kyc_requirements",
|
GNUNET_JSON_spec_json ("kyc_requirements",
|
||||||
&kyc_requirements),
|
&dc.kyc_requirements),
|
||||||
NULL),
|
NULL),
|
||||||
GNUNET_JSON_spec_end ()
|
GNUNET_JSON_spec_end ()
|
||||||
};
|
};
|
||||||
@ -86,17 +206,17 @@ TEH_handler_post_aml_decision (
|
|||||||
return MHD_YES; /* failure */
|
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]++;
|
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
|
||||||
if (GNUNET_OK !=
|
if (GNUNET_OK !=
|
||||||
TALER_officer_aml_decision_verify (justification,
|
TALER_officer_aml_decision_verify (dc.justification,
|
||||||
decision_time,
|
dc.decision_time,
|
||||||
&new_threshold,
|
&dc.new_threshold,
|
||||||
&h_payto,
|
&dc.h_payto,
|
||||||
new_state,
|
dc.new_state,
|
||||||
kyc_requirements,
|
dc.kyc_requirements,
|
||||||
officer_pub,
|
dc.officer_pub,
|
||||||
&officer_sig))
|
&dc.officer_sig))
|
||||||
{
|
{
|
||||||
GNUNET_break_op (0);
|
GNUNET_break_op (0);
|
||||||
return TALER_MHD_reply_with_error (
|
return TALER_MHD_reply_with_error (
|
||||||
@ -106,62 +226,67 @@ TEH_handler_post_aml_decision (
|
|||||||
NULL);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: check kyc_requirements is well-formed!
|
if (NULL != dc.kyc_requirements)
|
||||||
{
|
{
|
||||||
enum GNUNET_DB_QueryStatus qs;
|
size_t index;
|
||||||
struct GNUNET_TIME_Timestamp last_date;
|
json_t *elem;
|
||||||
bool invalid_officer;
|
|
||||||
unsigned int retries_left = MAX_RETRIES;
|
|
||||||
|
|
||||||
do {
|
if (! json_is_array (dc.kyc_requirements))
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
GNUNET_break_op (0);
|
GNUNET_break_op (0);
|
||||||
|
GNUNET_JSON_parse_free (spec);
|
||||||
return TALER_MHD_reply_with_error (
|
return TALER_MHD_reply_with_error (
|
||||||
connection,
|
connection,
|
||||||
MHD_HTTP_FORBIDDEN,
|
MHD_HTTP_BAD_REQUEST,
|
||||||
TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
|
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||||
NULL);
|
"kyc_requirements must be an array");
|
||||||
}
|
}
|
||||||
if (GNUNET_TIME_timestamp_cmp (last_date,
|
|
||||||
>=,
|
json_array_foreach (dc.kyc_requirements, index, elem)
|
||||||
decision_time))
|
{
|
||||||
|
const char *val;
|
||||||
|
|
||||||
|
if (! json_is_string (elem))
|
||||||
{
|
{
|
||||||
GNUNET_break_op (0);
|
GNUNET_break_op (0);
|
||||||
|
GNUNET_JSON_parse_free (spec);
|
||||||
return TALER_MHD_reply_with_error (
|
return TALER_MHD_reply_with_error (
|
||||||
connection,
|
connection,
|
||||||
MHD_HTTP_CONFLICT,
|
MHD_HTTP_BAD_REQUEST,
|
||||||
TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
|
TALER_EC_GENERIC_PARAMETER_MALFORMED,
|
||||||
NULL);
|
"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 (
|
return TALER_MHD_reply_static (
|
||||||
connection,
|
connection,
|
||||||
MHD_HTTP_NO_CONTENT,
|
MHD_HTTP_NO_CONTENT,
|
||||||
|
@ -296,6 +296,20 @@ TALER_KYCLOGIC_kyc_get_details (
|
|||||||
void *cb_cls);
|
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.
|
* Obtain the provider logic for a given set of @a requirements.
|
||||||
*
|
*
|
||||||
|
@ -21,6 +21,12 @@
|
|||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "taler_kyclogic_lib.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.
|
* 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.
|
* Load KYC logic plugin.
|
||||||
*
|
*
|
||||||
@ -331,9 +352,8 @@ add_check (const char *check)
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse list of checks from @a checks and build an
|
* Parse list of checks from @a checks and build an array of aliases into the
|
||||||
* array of aliases into the global checks array
|
* global checks array in @a provided_checks.
|
||||||
* in @a provided_checks.
|
|
||||||
*
|
*
|
||||||
* @param[in,out] checks list of checks; clobbered
|
* @param[in,out] checks list of checks; clobbered
|
||||||
* @param[out] p_checks where to put array of aliases
|
* @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,
|
GNUNET_array_append (kyc_triggers,
|
||||||
num_kyc_triggers,
|
num_kyc_triggers,
|
||||||
kt);
|
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;
|
return GNUNET_OK;
|
||||||
}
|
}
|
||||||
@ -614,7 +657,7 @@ struct SectionContext
|
|||||||
* @param section name of the section
|
* @param section name of the section
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
handle_section (void *cls,
|
handle_provider_section (void *cls,
|
||||||
const char *section)
|
const char *section)
|
||||||
{
|
{
|
||||||
struct SectionContext *sc = cls;
|
struct SectionContext *sc = cls;
|
||||||
@ -629,6 +672,21 @@ handle_section (void *cls,
|
|||||||
sc->result = false;
|
sc->result = false;
|
||||||
return;
|
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,
|
if (0 == strncasecmp (section,
|
||||||
"kyc-legitimization-",
|
"kyc-legitimization-",
|
||||||
strlen ("kyc-legitimization-")))
|
strlen ("kyc-legitimization-")))
|
||||||
@ -680,7 +738,10 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
|
|||||||
};
|
};
|
||||||
|
|
||||||
GNUNET_CONFIGURATION_iterate_sections (cfg,
|
GNUNET_CONFIGURATION_iterate_sections (cfg,
|
||||||
&handle_section,
|
&handle_provider_section,
|
||||||
|
&sc);
|
||||||
|
GNUNET_CONFIGURATION_iterate_sections (cfg,
|
||||||
|
&handle_trigger_section,
|
||||||
&sc);
|
&sc);
|
||||||
if (! sc.result)
|
if (! sc.result)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user