Compare commits

...

6 Commits

12 changed files with 285 additions and 31 deletions

View File

@ -30,6 +30,8 @@
#include "taler_kyclogic_lib.h" #include "taler_kyclogic_lib.h"
#include "taler_templating_lib.h" #include "taler_templating_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-exchange-httpd_age-withdraw.h"
#include "taler-exchange-httpd_age-withdraw_reveal.h"
#include "taler-exchange-httpd_aml-decision.h" #include "taler-exchange-httpd_aml-decision.h"
#include "taler-exchange-httpd_auditors.h" #include "taler-exchange-httpd_auditors.h"
#include "taler-exchange-httpd_batch-deposit.h" #include "taler-exchange-httpd_batch-deposit.h"
@ -571,6 +573,46 @@ handle_get_aml (struct TEH_RequestContext *rc,
} }
/**
* Handle a "/age-withdraw/$ACH/reveal" POST request. Parses the "ACH"
* hash of the commitment from a previous call to
* /reserves/$reserve_pub/age-withdraw
*
* @param rc request context
* @param root uploaded JSON data
* @param args array of additional options
* @return MHD result code
*/
static MHD_RESULT
handle_post_age_withdraw (struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[2])
{
struct TALER_AgeWithdrawCommitmentHashP ach;
if (0 != strcmp ("reveal", args[1]))
return r404 (rc->connection,
args[1]);
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[0],
strlen (args[0]),
&ach,
sizeof (ach)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
args[0]);
}
return TEH_handler_age_withdraw_reveal (rc,
&ach,
root);
}
/** /**
* Signature of functions that handle operations on reserves. * Signature of functions that handle operations on reserves.
* *
@ -617,6 +659,10 @@ handle_post_reserves (struct TEH_RequestContext *rc,
.op = "batch-withdraw", .op = "batch-withdraw",
.handler = &TEH_handler_batch_withdraw .handler = &TEH_handler_batch_withdraw
}, },
{
.op = "age-withdraw",
.handler = &TEH_handler_age_withdraw
},
{ {
.op = "withdraw", .op = "withdraw",
.handler = &TEH_handler_withdraw .handler = &TEH_handler_withdraw
@ -1454,6 +1500,12 @@ handle_mhd_request (void *cls,
.handler.post = &handle_post_reserves, .handler.post = &handle_post_reserves,
.nargs = 2 .nargs = 2
}, },
{
.url = "age-withdraw",
.method = MHD_HTTP_METHOD_POST,
.handler.post = &handle_post_age_withdraw,
.nargs = 2
},
{ {
.url = "reserves-attest", .url = "reserves-attest",
.method = MHD_HTTP_METHOD_GET, .method = MHD_HTTP_METHOD_GET,

View File

@ -18,8 +18,8 @@
* @brief Handle /age-withdraw/$ACH/reveal requests * @brief Handle /age-withdraw/$ACH/reveal requests
* @author Özgür Kesim * @author Özgür Kesim
*/ */
#ifndef TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H #ifndef TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_REVEAL_H
#define TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H #define TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_REVEAL_H
#include <microhttpd.h> #include <microhttpd.h>
#include "taler-exchange-httpd.h" #include "taler-exchange-httpd.h"

View File

@ -311,7 +311,7 @@ batch_withdraw_transaction (void *cls,
bool found = false; bool found = false;
bool balance_ok = false; bool balance_ok = false;
bool age_ok = false; bool age_ok = false;
uint16_t required_age = 0; uint16_t allowed_maximum_age = 0;
char *kyc_required; char *kyc_required;
struct TALER_PaytoHashP reserve_h_payto; struct TALER_PaytoHashP reserve_h_payto;
@ -479,7 +479,7 @@ batch_withdraw_transaction (void *cls,
&found, &found,
&balance_ok, &balance_ok,
&age_ok, &age_ok,
&required_age, &allowed_maximum_age,
&ruuid); &ruuid);
if (0 > qs) if (0 > qs)
{ {
@ -508,7 +508,7 @@ batch_withdraw_transaction (void *cls,
* of the required age */ * of the required age */
uint16_t lowest_age = TALER_get_lowest_age ( uint16_t lowest_age = TALER_get_lowest_age (
&TEH_age_restriction_config.mask, &TEH_age_restriction_config.mask,
required_age); allowed_maximum_age);
TEH_plugin->rollback (TEH_plugin->cls); TEH_plugin->rollback (TEH_plugin->cls);
*mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required ( *mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required (

View File

@ -19,9 +19,12 @@
* @author Christian Grothoff * @author Christian Grothoff
*/ */
#include "platform.h" #include "platform.h"
#include "taler-exchange-httpd.h"
#include "taler-exchange-httpd_common_kyc.h" #include "taler-exchange-httpd_common_kyc.h"
#include "taler_attributes.h" #include "taler_attributes.h"
#include "taler_error_codes.h"
#include "taler_exchangedb_plugin.h" #include "taler_exchangedb_plugin.h"
#include <gnunet/gnunet_common.h>
struct TEH_KycAmlTrigger struct TEH_KycAmlTrigger
{ {
@ -114,7 +117,7 @@ kyc_aml_finished (void *cls,
size_t eas; size_t eas;
void *ea; void *ea;
const char *birthdate; const char *birthdate;
unsigned int birthday; unsigned int birthday = 0;
struct GNUNET_ShortHashCode kyc_prox; struct GNUNET_ShortHashCode kyc_prox;
struct GNUNET_AsyncScopeSave old_scope; struct GNUNET_AsyncScopeSave old_scope;
@ -125,9 +128,29 @@ kyc_aml_finished (void *cls,
&kyc_prox); &kyc_prox);
birthdate = json_string_value (json_object_get (kat->attributes, birthdate = json_string_value (json_object_get (kat->attributes,
TALER_ATTRIBUTE_BIRTHDATE)); TALER_ATTRIBUTE_BIRTHDATE));
birthday = 0; (void) birthdate; // FIXME-Oec: calculate birthday here...
// Convert 'birthdate' to time after 1970, then compute days. if (TEH_age_restriction_enabled)
// Then compare against max age-restriction, and if before, set to 0. {
enum GNUNET_GenericReturnValue ret;
ret = TALER_parse_coarse_date (birthdate,
&TEH_age_restriction_config.mask,
&birthday);
if (GNUNET_OK != ret)
{
GNUNET_break (0);
if (NULL != kat->response)
MHD_destroy_response (kat->response);
kat->http_status = MHD_HTTP_BAD_REQUEST;
kat->response = TALER_MHD_make_error (
TALER_EC_GENERIC_PARAMETER_MALFORMED,
TALER_ATTRIBUTE_BIRTHDATE);
/* FIXME-Christian: shouldn't we return in the error case? */
}
}
TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key, TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
kat->attributes, kat->attributes,
&ea, &ea,
@ -159,6 +182,8 @@ kyc_aml_finished (void *cls,
kat->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; kat->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
kat->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, kat->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"do_insert_kyc_attributes"); "do_insert_kyc_attributes");
/* FIXME-Christian: shouldn't we return in the error case? */
} }
/* Finally, return result to main handler */ /* Finally, return result to main handler */
kat->cb (kat->cb_cls, kat->cb (kat->cb_cls,

View File

@ -31,7 +31,7 @@ BEGIN
',current_balance_frac INT4 NOT NULL DEFAULT(0)' ',current_balance_frac INT4 NOT NULL DEFAULT(0)'
',purses_active INT8 NOT NULL DEFAULT(0)' ',purses_active INT8 NOT NULL DEFAULT(0)'
',purses_allowed INT8 NOT NULL DEFAULT(0)' ',purses_allowed INT8 NOT NULL DEFAULT(0)'
',birthdate INT4 NOT NULL DEFAULT(0)' ',birthday INT4 NOT NULL DEFAULT(0)'
',expiration_date INT8 NOT NULL' ',expiration_date INT8 NOT NULL'
',gc_date INT8 NOT NULL' ',gc_date INT8 NOT NULL'
') %s ;' ') %s ;'
@ -82,7 +82,7 @@ BEGIN
); );
PERFORM comment_partitioned_column( PERFORM comment_partitioned_column(
'Birthday of the user in days after 1970, or 0 if user is an adult and is not subject to age restrictions' 'Birthday of the user in days after 1970, or 0 if user is an adult and is not subject to age restrictions'
,'birthdate' ,'birthday'
,table_name ,table_name
,partition_suffix ,partition_suffix
); );

View File

@ -26,7 +26,7 @@ CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw(
OUT reserve_found BOOLEAN, OUT reserve_found BOOLEAN,
OUT balance_ok BOOLEAN, OUT balance_ok BOOLEAN,
OUT age_ok BOOLEAN, OUT age_ok BOOLEAN,
OUT allowed_maximum_age INT4, -- in years OUT allowed_maximum_age INT2, -- in years
OUT ruuid INT8) OUT ruuid INT8)
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
@ -74,7 +74,7 @@ END IF;
-- Check if age requirements are present -- Check if age requirements are present
IF ((NOT do_age_check) OR (reserve_birthday = 0)) IF ((NOT do_age_check) OR (reserve_birthday = 0))
THEN THEN
age_ok = OK; age_ok = TRUE;
allowed_maximum_age = -1; allowed_maximum_age = -1;
ELSE ELSE
-- Age requirements are formally not met: The exchange is setup to support -- Age requirements are formally not met: The exchange is setup to support

View File

@ -45,9 +45,9 @@ TEH_PG_do_batch_withdraw (
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
TALER_PQ_query_param_amount (amount), TALER_PQ_query_param_amount (amount),
GNUNET_PQ_query_param_auto_from_type (reserve_pub), GNUNET_PQ_query_param_auto_from_type (reserve_pub),
GNUNET_PQ_query_param_bool (do_age_check),
GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_timestamp (&now),
GNUNET_PQ_query_param_timestamp (&gc), GNUNET_PQ_query_param_timestamp (&gc),
GNUNET_PQ_query_param_bool (do_age_check),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
@ -77,10 +77,10 @@ TEH_PG_do_batch_withdraw (
" reserve_found" " reserve_found"
",balance_ok" ",balance_ok"
",age_ok" ",age_ok"
",required_age" ",allowed_maximum_age"
",ruuid" ",ruuid"
" FROM exchange_do_batch_withdraw" " FROM exchange_do_batch_withdraw"
" ($1,$2,$3,$4,$5);"); " ($1,$2,$3,$4,$5,$6);");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_batch_withdraw", "call_batch_withdraw",
params, params,

View File

@ -2243,15 +2243,26 @@ TALER_TESTING_cmd_proof_kyc_oauth2 (
/** /**
* Starts a fake OAuth 2.0 service on @a port for testing * Starts a fake OAuth 2.0 service on @a port for testing
* KYC processes. * KYC processes which also provides a @a birthdate in a response
* *
* @param label command label * @param label command label
* @param port the TCP port to listen on * @param port the TCP port to listen on
*/ */
struct TALER_TESTING_Command struct TALER_TESTING_Command
TALER_TESTING_cmd_oauth (const char *label, TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
const char *birthdate,
uint16_t port); uint16_t port);
/**
* Starts a fake OAuth 2.0 service on @a port for testing
* KYC processes.
*
* @param label command label
* @param port the TCP port to listen on
*/
#define TALER_TESTING_cmd_oauth(label, port) \
TALER_TESTING_cmd_oauth_with_birthdate ((label), NULL, (port))
/* ****************** P2P payment commands ****************** */ /* ****************** P2P payment commands ****************** */

View File

@ -21,6 +21,7 @@
#ifndef TALER_UTIL_H #ifndef TALER_UTIL_H
#define TALER_UTIL_H #define TALER_UTIL_H
#include <gnunet/gnunet_common.h>
#define __TALER_UTIL_LIB_H_INSIDE__ #define __TALER_UTIL_LIB_H_INSIDE__
#include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_util_lib.h>
@ -510,6 +511,33 @@ char *strchrnul (const char *s, int c);
#endif #endif
/**
* @brief Parses a date information into days after 1970-01-01 (or 0)
*
* The input MUST be of the form
*
* 1) YYYY-MM-DD, representing a valid date
* 2) YYYY-MM-00, representing a valid month in a particular year
* 3) YYYY-00-00, representing a valid year.
*
* In the cases 2) and 3) the out parameter is set to the beginning of the
* time, f.e. 1950-00-00 == 1950-01-01 and 1888-03-00 == 1888-03-01
*
* The output will set to the number of days after 1970-01-01 or 0, if the input
* represents a date belonging to the largest allowed age group.
*
* @param in Input string representation of the date
* @param mask Age mask
* @param[out] out Where to write the result
* @return GNUNET_OK on success, GNUNET_SYSERR otherwise
*/
enum GNUNET_GenericReturnValue
TALER_parse_coarse_date (
const char *in,
const struct TALER_AgeMask *mask,
uint32_t *out);
/** /**
* @brief Parses a string as a list of age groups. * @brief Parses a string as a list of age groups.
* *

View File

@ -39,6 +39,11 @@ struct OAuthState
*/ */
struct MHD_Daemon *mhd; struct MHD_Daemon *mhd;
/**
* Birthdate that the oauth server should return in a response, may be NULL
*/
const char *birthdate;
/** /**
* Port to listen on. * Port to listen on.
*/ */
@ -172,28 +177,33 @@ handler_cb (void *cls,
void **con_cls) void **con_cls)
{ {
struct RequestCtx *rc = *con_cls; struct RequestCtx *rc = *con_cls;
struct OAuthState *oas = cls;
unsigned int hc; unsigned int hc;
json_t *body; json_t *body;
(void) cls;
(void) version; (void) version;
if (0 == strcasecmp (method, if (0 == strcasecmp (method,
MHD_HTTP_METHOD_GET)) MHD_HTTP_METHOD_GET))
{ {
body = GNUNET_JSON_PACK ( json_t *data =
GNUNET_JSON_pack_string (
"status",
"success"),
GNUNET_JSON_pack_object_steal (
"data",
GNUNET_JSON_PACK ( GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("id", GNUNET_JSON_pack_string ("id",
"XXXID12345678"), "XXXID12345678"),
GNUNET_JSON_pack_string ("first_name", GNUNET_JSON_pack_string ("first_name",
"Bob"), "Bob"),
GNUNET_JSON_pack_string ("last_name", GNUNET_JSON_pack_string ("last_name",
"Builder") "Builder"));
))); if (NULL != oas->birthdate)
json_object_set_new (data,
"birthdate",
json_string_nocheck (oas->birthdate));
body = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string (
"status",
"success"),
GNUNET_JSON_pack_object_steal (
"data", data));
return TALER_MHD_reply_json_steal (connection, return TALER_MHD_reply_json_steal (connection,
body, body,
MHD_HTTP_OK); MHD_HTTP_OK);
@ -368,13 +378,15 @@ oauth_cleanup (void *cls,
struct TALER_TESTING_Command struct TALER_TESTING_Command
TALER_TESTING_cmd_oauth (const char *label, TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
const char *birthdate,
uint16_t port) uint16_t port)
{ {
struct OAuthState *oas; struct OAuthState *oas;
oas = GNUNET_new (struct OAuthState); oas = GNUNET_new (struct OAuthState);
oas->port = port; oas->port = port;
oas->birthdate = birthdate;
{ {
struct TALER_TESTING_Command cmd = { struct TALER_TESTING_Command cmd = {
.cls = oas, .cls = oas,

View File

@ -710,4 +710,57 @@ TALER_age_restriction_from_secret (
} }
enum GNUNET_GenericReturnValue
TALER_parse_coarse_date (
const char *in,
const struct TALER_AgeMask *mask,
uint32_t *out)
{
struct tm date = {0};
struct tm limit = {0};
time_t seconds;
if (NULL == in)
{
/* FIXME[oec]: correct behaviour? */
*out = 0;
return GNUNET_OK;
}
GNUNET_assert (NULL !=mask);
GNUNET_assert (NULL !=out);
if (NULL == strptime (in, "%Y-%0m-%0d", &date))
{
if (NULL == strptime (in, "%Y-%0m-00", &date))
if (NULL == strptime (in, "%Y-00-00", &date))
return GNUNET_SYSERR;
/* turns out that the day is off by one in the last two cases */
date.tm_mday += 1;
}
seconds = mktime (&date);
if (-1 == seconds)
return GNUNET_SYSERR;
/* calculate the limit date for the largest age group */
localtime_r (&(time_t){time (NULL)}, &limit);
limit.tm_year -= TALER_get_lowest_age (mask, 255);
GNUNET_assert (-1 != mktime (&limit));
if ((limit.tm_year < date.tm_year)
|| ((limit.tm_year == date.tm_year)
&& (limit.tm_mon < date.tm_mon))
|| ((limit.tm_year == date.tm_year)
&& (limit.tm_mon == date.tm_mon)
&& (limit.tm_mday < date.tm_mday)))
*out = seconds / 60 / 60 / 24;
else
*out = 0;
return GNUNET_OK;
}
/* end util/age_restriction.c */ /* end util/age_restriction.c */

View File

@ -129,6 +129,77 @@ test_groups (void)
} }
enum GNUNET_GenericReturnValue
test_dates (void)
{
struct TALER_AgeMask mask = {
.bits = 1 | 1 << 5 | 1 << 9 | 1 << 13 | 1 << 17 | 1 << 21
};
struct
{
char *date;
uint32_t expected;
enum GNUNET_GenericReturnValue ret;
}
test [] = {
{.date = "abcd-00-00", .expected = 0, .ret = GNUNET_SYSERR},
{.date = "1900-00-01", .expected = 0, .ret = GNUNET_SYSERR},
{.date = "19000001", .expected = 0, .ret = GNUNET_SYSERR},
{.date = "2001-33-05", .expected = 0, .ret = GNUNET_SYSERR},
{.date = "2001-33-35", .expected = 0, .ret = GNUNET_SYSERR},
{.date = "1900-00-00", .expected = 0, .ret = GNUNET_OK},
{.date = "2001-00-00", .expected = 0, .ret = GNUNET_OK},
{.date = "2001-03-00", .expected = 0, .ret = GNUNET_OK},
{.date = "2001-03-05", .expected = 0, .ret = GNUNET_OK},
/* These dates should be far enough for the near future so that
* the expected values are correct. Will need adjustment in 2044 :) */
{.date = "2023-06-26", .expected = 19533, .ret = GNUNET_OK },
{.date = "2023-06-01", .expected = 19508, .ret = GNUNET_OK },
{.date = "2023-06-00", .expected = 19508, .ret = GNUNET_OK },
{.date = "2023-01-01", .expected = 19357, .ret = GNUNET_OK },
{.date = "2023-00-00", .expected = 19357, .ret = GNUNET_OK },
};
for (uint8_t t = 0; t < sizeof(test) / sizeof(test[0]); t++)
{
uint32_t d;
enum GNUNET_GenericReturnValue ret;
ret = TALER_parse_coarse_date (test[t].date,
&mask,
&d);
if (ret != test[t].ret)
{
printf (
"dates[%d] for date `%s` expected parser to return: %d, got: %d\n",
t, test[t].date, test[t].ret, ret);
return GNUNET_SYSERR;
}
if (ret == GNUNET_SYSERR)
continue;
if (d != test[t].expected)
{
printf (
"dates[%d] for date `%s` expected value %d, but got %d\n",
t, test[t].date, test[t].expected, d);
return GNUNET_SYSERR;
}
printf ("dates[%d] for date `%s` got expected value %d\n",
t, test[t].date, d);
}
printf ("done with dates\n");
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
test_lowest (void) test_lowest (void)
{ {
@ -308,6 +379,8 @@ main (int argc,
GNUNET_break (0); GNUNET_break (0);
return 3; return 3;
} }
if (GNUNET_OK != test_dates ())
return 4;
return 0; return 0;
} }