add persona attribute conversion logic

This commit is contained in:
Christian Grothoff 2023-01-27 22:39:16 +01:00
parent 85e44ceea6
commit 0eb6f73176
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
2 changed files with 411 additions and 212 deletions

View File

@ -61,12 +61,31 @@ extern "C" {
#define TALER_ATTRIBUTE_PEP "pep" #define TALER_ATTRIBUTE_PEP "pep"
/** /**
* Phone number (of business or individual). * Street-level address. Usually includes the street and the house number. May
* consist of multiple lines (separated by '\n'). Identifies a house in a city. The city is not
* part of the street.
*/
#define TALER_ATTRIBUTE_ADDRESS_STREET "street"
/**
* City including postal code. If available, a 2-letter country-code prefixes
* the postal code, which is before the city (e.g. "DE-42289 Wuppertal"). If
* the country code is unknown, the "CC-" prefix is missing. If the ZIP code
* is unknown, the hyphen is followed by a space ("DE- Wuppertal"). If only
* the city name is known, it is prefixed by a space (" ").
* If the city name is unknown, a space is at the end of the value.
*/
#define TALER_ATTRIBUTE_ADDRESS_CITY "city"
/**
* Phone number (of business or individual). Should come with the "+CC"
* prefix including the country code.
*/ */
#define TALER_ATTRIBUTE_PHONE "phone" #define TALER_ATTRIBUTE_PHONE "phone"
/** /**
* Email address (of business or individual). * Email address (of business or individual). Should be
* in the format "user@hostname".
*/ */
#define TALER_ATTRIBUTE_EMAIL "email" #define TALER_ATTRIBUTE_EMAIL "email"

View File

@ -1,6 +1,6 @@
/* /*
This file is part of GNU Taler This file is part of GNU Taler
Copyright (C) 2022 Taler Systems SA Copyright (C) 2022, 2023 Taler Systems SA
Taler is free software; you can redistribute it and/or modify it under the 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 terms of the GNU Affero General Public License as published by the Free Software
@ -19,6 +19,7 @@
* @author Christian Grothoff * @author Christian Grothoff
*/ */
#include "platform.h" #include "platform.h"
#include "taler_attributes.h"
#include "taler_kyclogic_plugin.h" #include "taler_kyclogic_plugin.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler_curl_lib.h" #include "taler_curl_lib.h"
@ -62,8 +63,9 @@ struct PluginState
struct GNUNET_CURL_RescheduleContext *curl_rc; struct GNUNET_CURL_RescheduleContext *curl_rc;
/** /**
* Authorization token to use when receiving webhooks from the Persona service. Optional. Note that * Authorization token to use when receiving webhooks from the Persona
* webhooks are *global* and not per template. * service. Optional. Note that webhooks are *global* and not per
* template.
*/ */
char *webhook_token; char *webhook_token;
@ -923,9 +925,229 @@ proof_reply_error (struct TALER_KYCLOGIC_ProofHandle *ph,
} }
/**
* Convert KYC attribute data from Persona response.
*
* @param attr json array with Persona attribute data
* @return KYC attribute data
*/
static json_t *
convert_attributes (const json_t *attr)
{
const char *country_code = NULL;
const char *name_first = NULL;
const char *name_middle = NULL;
const char *name_last = NULL;
const char *address_street_1 = NULL;
const char *address_street_2 = NULL;
const char *address_city = NULL;
const char *address_postal_code = NULL;
const char *birthdate = NULL;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("country_code",
&country_code),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("name_first",
&name_first),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("name_middle",
&name_middle),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("name_last",
&name_last),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("address_street_1",
&address_street_1),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("address_street_2",
&address_street_2),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("address_city",
&address_city),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("address_postal_code",
&address_postal_code),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("birthdate",
&birthdate),
NULL),
GNUNET_JSON_spec_end ()
};
json_t *ret;
if (GNUNET_OK !=
GNUNET_JSON_parse (attr,
spec,
NULL, NULL))
{
GNUNET_break (0);
return NULL;
}
{
char *name = NULL;
char *address_street = NULL;
char *address_city = NULL;
if ( (NULL != name_last) ||
(NULL != name_first) ||
(NULL != name_middle) )
{
GNUNET_asprintf (&name,
"%s, %s %s",
(NULL != name_last)
? name_last
: "",
(NULL != name_first)
? name_first
: "",
(NULL != name_middle)
? name_middle
: "");
}
if ( (NULL != address_city) ||
(NULL != address_postal_code) )
{
GNUNET_asprintf (&address_city,
"%s%s%s %s",
(NULL != country_code)
? country_code
: "",
(NULL != country_code)
? "-"
: "",
(NULL != address_postal_code)
? address_postal_code
: "",
(NULL != address_city)
? address_city
: "");
}
if ( (NULL != address_street_1) ||
(NULL != address_street_2) )
{
GNUNET_asprintf (&address_street,
"%s%s%s",
(NULL != address_street_1)
? address_street_1
: "",
( (NULL != address_street_1) &&
(NULL != address_street_2) )
? "\n"
: "",
(NULL != address_street_2)
? address_street_2
: "");
}
ret = GNUNET_JSON_PACK (
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_BIRTHDATE,
birthdate)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_FULL_NAME,
name)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_ADDRESS_STREET,
address_street)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_ADDRESS_CITY,
address_city)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_RESIDENCES,
country_code))
);
GNUNET_free (name);
}
return ret;
}
/**
* Extract and convert KYC attribute data from
* Persona response.
*
* @param included json array with various data
* @return KYC attribute data
*/
static json_t *
extract_attributes (const json_t *included)
{
size_t idx;
json_t *obj;
json_array_foreach (included, idx, obj)
{
const char *type = json_string_value (json_object_get (obj,
"type"));
json_t *attr;
if (0 != strcmp (type,
"verification/database"))
continue;
attr = json_object_get (obj,
"attributes");
return convert_attributes (attr);
}
return NULL;
}
/**
* Return a response for the @a ph request indicating a
* protocol violation by the Persona server.
*
* @param[in,out] ph request we are processing
* @param response_code HTTP status returned by Persona
* @param inquiry_id ID of the inquiry this is about
* @param detail where the response was wrong
* @param data full response data to output
*/
static void
return_invalid_response (struct TALER_KYCLOGIC_ProofHandle *ph,
unsigned int response_code,
const char *inquiry_id,
const char *detail,
const json_t *data)
{
json_dumpf (data,
stderr,
JSON_INDENT (2));
proof_reply_error (
ph,
inquiry_id,
MHD_HTTP_BAD_GATEWAY,
"persona-invalid-response",
GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 ("persona_http_status",
response_code),
GNUNET_JSON_pack_string ("persona_inquiry_id",
inquiry_id),
TALER_JSON_pack_ec (
TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
GNUNET_JSON_pack_string ("detail",
detail),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("data",
(json_t *)
data))));
}
/** /**
* Function called when we're done processing the * Function called when we're done processing the
* HTTP "/api/v1/verifications/{verification-id}" request. * HTTP "/api/v1/inquiries/{inquiry-id}" request.
* *
* @param cls the `struct TALER_KYCLOGIC_InitiateHandle` * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
* @param response_code HTTP response code, 0 on error * @param response_code HTTP response code, 0 on error
@ -950,6 +1172,8 @@ handle_proof_finished (void *cls,
const char *account_id; const char *account_id;
const char *type = NULL; const char *type = NULL;
json_t *attributes; json_t *attributes;
json_t *relationships;
json_t *included;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("type", GNUNET_JSON_spec_string ("type",
&type), &type),
@ -957,6 +1181,10 @@ handle_proof_finished (void *cls,
&inquiry_id), &inquiry_id),
GNUNET_JSON_spec_json ("attributes", GNUNET_JSON_spec_json ("attributes",
&attributes), &attributes),
GNUNET_JSON_spec_json ("relationships",
&relationships),
GNUNET_JSON_spec_json ("included",
&included),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
@ -969,25 +1197,12 @@ handle_proof_finished (void *cls,
"inquiry")) ) "inquiry")) )
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
json_dumpf (j, return_invalid_response (ph,
stderr, response_code,
JSON_INDENT (2));
proof_reply_error (ph,
inquiry_id, inquiry_id,
MHD_HTTP_BAD_GATEWAY, "data",
"persona-logic-failure", data);
GNUNET_JSON_PACK ( GNUNET_JSON_parse_free (spec);
GNUNET_JSON_pack_uint64 ("persona_http_status",
response_code),
GNUNET_JSON_pack_string ("persona_inquiry_id",
inquiry_id),
TALER_JSON_pack_ec (
TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
GNUNET_JSON_pack_string ("detail",
"data"),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("data",
(json_t *) data))));
break; break;
} }
@ -1013,25 +1228,11 @@ handle_proof_finished (void *cls,
NULL, NULL)) NULL, NULL))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
json_dumpf (j, return_invalid_response (ph,
stderr, response_code,
JSON_INDENT (2));
proof_reply_error (ph,
inquiry_id, inquiry_id,
MHD_HTTP_BAD_GATEWAY, "data-attributes",
"persona-invalid-response", data);
GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 ("persona_http_status",
response_code),
GNUNET_JSON_pack_string ("persona_inquiry_id",
inquiry_id),
TALER_JSON_pack_ec (
TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
GNUNET_JSON_pack_string ("detail",
"data-attributes"),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("data",
(json_t *) data))));
GNUNET_JSON_parse_free (ispec); GNUNET_JSON_parse_free (ispec);
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
break; break;
@ -1047,23 +1248,11 @@ handle_proof_finished (void *cls,
(idr != ph->process_row) ) (idr != ph->process_row) )
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
proof_reply_error (ph, return_invalid_response (ph,
response_code,
inquiry_id, inquiry_id,
MHD_HTTP_BAD_GATEWAY, "data-attributes-reference_id",
"persona-invalid-response", data);
GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 ("persona_http_status",
response_code),
GNUNET_JSON_pack_string ("persona_inquiry_id",
inquiry_id),
TALER_JSON_pack_ec (
TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
GNUNET_JSON_pack_string ("detail",
"data-attributes-reference_id"),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("data",
(json_t *)
data))));
GNUNET_JSON_parse_free (ispec); GNUNET_JSON_parse_free (ispec);
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
break; break;
@ -1074,23 +1263,11 @@ handle_proof_finished (void *cls,
ph->inquiry_id)) ph->inquiry_id))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
proof_reply_error (ph, return_invalid_response (ph,
response_code,
inquiry_id, inquiry_id,
MHD_HTTP_BAD_GATEWAY, "data-id",
"persona-invalid-response", data);
GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 ("persona_http_status",
response_code),
GNUNET_JSON_pack_string ("persona_inquiry_id",
inquiry_id),
TALER_JSON_pack_ec (
TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
GNUNET_JSON_pack_string ("detail",
"data-id"),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("data",
(json_t *)
data))));
GNUNET_JSON_parse_free (ispec); GNUNET_JSON_parse_free (ispec);
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
break; break;
@ -1100,9 +1277,7 @@ handle_proof_finished (void *cls,
json_object_get ( json_object_get (
json_object_get ( json_object_get (
json_object_get ( json_object_get (
json_object_get ( relationships,
data,
"relationships"),
"account"), "account"),
"data"), "data"),
"id")); "id"));
@ -1110,7 +1285,8 @@ handle_proof_finished (void *cls,
if (0 != strcmp (status, if (0 != strcmp (status,
"completed")) "completed"))
{ {
proof_generic_reply (ph, proof_generic_reply (
ph,
TALER_KYCLOGIC_STATUS_FAILED, TALER_KYCLOGIC_STATUS_FAILED,
account_id, account_id,
inquiry_id, inquiry_id,
@ -1133,33 +1309,30 @@ handle_proof_finished (void *cls,
if (NULL == account_id) if (NULL == account_id)
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
json_dumpf (data, return_invalid_response (ph,
stderr, response_code,
JSON_INDENT (2));
proof_reply_error (ph,
inquiry_id, inquiry_id,
MHD_HTTP_BAD_GATEWAY, "data-relationships-account-data-id",
"persona-invalid-response", data);
GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 ("persona_http_status",
response_code),
GNUNET_JSON_pack_string ("persona_inquiry_id",
inquiry_id),
TALER_JSON_pack_ec (
TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
GNUNET_JSON_pack_string ("detail",
"data-relationships-account-data-id"),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("data",
(json_t *)
data))));
break; break;
} }
{ {
struct MHD_Response *resp; struct MHD_Response *resp;
struct GNUNET_TIME_Absolute expiration; struct GNUNET_TIME_Absolute expiration;
json_t *attr;
attr = extract_attributes (included);
if (NULL == attr)
{
GNUNET_break_op (0);
return_invalid_response (ph,
response_code,
inquiry_id,
"data-relationships-account-data-id",
data);
break;
}
expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity); expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity);
resp = MHD_create_response_from_buffer (0, resp = MHD_create_response_from_buffer (0,
"", "",
@ -1174,9 +1347,10 @@ handle_proof_finished (void *cls,
account_id, account_id,
inquiry_id, inquiry_id,
expiration, expiration,
NULL, /* FIXME: return attributes! */ attr,
MHD_HTTP_SEE_OTHER, MHD_HTTP_SEE_OTHER,
resp); resp);
json_decref (attr);
} }
GNUNET_JSON_parse_free (ispec); GNUNET_JSON_parse_free (ispec);
} }
@ -1194,7 +1368,8 @@ handle_proof_finished (void *cls,
json_dumpf (j, json_dumpf (j,
stderr, stderr,
JSON_INDENT (2)); JSON_INDENT (2));
proof_reply_error (ph, proof_reply_error (
ph,
ph->inquiry_id, ph->inquiry_id,
MHD_HTTP_BAD_GATEWAY, MHD_HTTP_BAD_GATEWAY,
"persona-logic-failure", "persona-logic-failure",
@ -1214,7 +1389,8 @@ handle_proof_finished (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Refused access with HTTP status code %u\n", "Refused access with HTTP status code %u\n",
(unsigned int) response_code); (unsigned int) response_code);
proof_reply_error (ph, proof_reply_error (
ph,
ph->inquiry_id, ph->inquiry_id,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
"persona-exchange-unauthorized", "persona-exchange-unauthorized",
@ -1233,8 +1409,8 @@ handle_proof_finished (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Refused access with HTTP status code %u\n", "Refused access with HTTP status code %u\n",
(unsigned int) response_code); (unsigned int) response_code);
proof_reply_error (
proof_reply_error (ph, ph,
ph->inquiry_id, ph->inquiry_id,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
"persona-exchange-unpaid", "persona-exchange-unpaid",
@ -1256,7 +1432,8 @@ handle_proof_finished (void *cls,
json_dumpf (j, json_dumpf (j,
stderr, stderr,
JSON_INDENT (2)); JSON_INDENT (2));
proof_reply_error (ph, proof_reply_error (
ph,
ph->inquiry_id, ph->inquiry_id,
MHD_HTTP_GATEWAY_TIMEOUT, MHD_HTTP_GATEWAY_TIMEOUT,
"persona-network-timeout", "persona-network-timeout",
@ -1278,7 +1455,8 @@ handle_proof_finished (void *cls,
json_dumpf (j, json_dumpf (j,
stderr, stderr,
JSON_INDENT (2)); JSON_INDENT (2));
proof_reply_error (ph, proof_reply_error (
ph,
ph->inquiry_id, ph->inquiry_id,
MHD_HTTP_SERVICE_UNAVAILABLE, MHD_HTTP_SERVICE_UNAVAILABLE,
"persona-load-failure", "persona-load-failure",
@ -1300,7 +1478,8 @@ handle_proof_finished (void *cls,
json_dumpf (j, json_dumpf (j,
stderr, stderr,
JSON_INDENT (2)); JSON_INDENT (2));
proof_reply_error (ph, proof_reply_error (
ph,
ph->inquiry_id, ph->inquiry_id,
MHD_HTTP_BAD_GATEWAY, MHD_HTTP_BAD_GATEWAY,
"persona-provider-failure", "persona-provider-failure",
@ -1322,7 +1501,8 @@ handle_proof_finished (void *cls,
json_dumpf (j, json_dumpf (j,
stderr, stderr,
JSON_INDENT (2)); JSON_INDENT (2));
proof_reply_error (ph, proof_reply_error (
ph,
ph->inquiry_id, ph->inquiry_id,
MHD_HTTP_BAD_GATEWAY, MHD_HTTP_BAD_GATEWAY,
"persona-invalid-response", "persona-invalid-response",