Merge branch 'master' into age-withdraw

This commit is contained in:
Özgür Kesim 2023-06-03 10:45:31 +02:00
commit 80a1b8f524
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
117 changed files with 2293 additions and 1169 deletions

View File

@ -70,7 +70,8 @@ EXTRA_DIST = \
$(rdata_DATA) \ $(rdata_DATA) \
coverage.sh \ coverage.sh \
gnunet.tag \ gnunet.tag \
microhttpd.tag microhttpd.tag \
packages
# Change the set of supported languages here. You should # Change the set of supported languages here. You should
# also update tos'XX'data and EXTRA_DIST accordingly. # also update tos'XX'data and EXTRA_DIST accordingly.

@ -1 +1 @@
Subproject commit 85736484cb0da26aded705ebb1e944e8bb1b8504 Subproject commit f9ea79a6aae074928f44960f69bc3c2d0c9c7e1d

View File

@ -0,0 +1 @@
# This configuration will be changed by tooling. Do not touch it manually.

View File

@ -0,0 +1,49 @@
# Main entry point for the GNU Taler configuration.
#
# Structure:
# - taler.conf is the main configuration entry point
# used by all Taler components (the file you are currently
# looking at.
# - overrides.conf contains configuration overrides that are
# set by some tools that help with the configuration,
# and should not be edited by humans. Comments in this file
# are not preserved.
# - conf.d/ contains configuration files for
# Taler components, which can be read by all
# users of the system and are included by the main
# configuration.
# - secrets/ contains configuration snippets
# with secrets for particular services.
# These files should have restrictive permissions
# so that only users of the relevant services
# can read it. All files in it should end with
# ".secret.conf".
[taler]
# Currency of the Taler deployment. This setting applies to all Taler
# components that only support a single currency.
#currency = KUDOS
# Smallest currency unit handled by the underlying bank system. Taler payments
# can make payments smaller than this units, but interactions with external
# systems is always rounded to this unit.
#currency_round_unit = KUDOS:0.01
# Monthly amount that mandatorily triggers an AML check
#AML_THRESHOLD = KUDOS:10000000
[paths]
TALER_HOME = /var/lib/taler
TALER_RUNTIME_DIR = /run/taler
TALER_CACHE_HOME = /var/cache/taler
TALER_CONFIG_HOME = /etc/taler
TALER_DATA_HOME = /var/lib/taler
# Inline configurations from all Taler components.
@inline-matching@ conf.d/*.conf
# Overrides from tools that help with configuration.
@inline@ overrides.conf

View File

@ -0,0 +1,4 @@
<Location "/taler-auditor/">
ProxyPass "unix:/var/lib/taler-auditor/auditor.sock|http://example.com/"
RequestHeader add "X-Forwarded-Proto" "https"
</Location>

View File

@ -0,0 +1,18 @@
server {
listen 80;
listen [::]:80;
server_name localhost;
access_log /var/log/nginx/auditor.log;
error_log /var/log/nginx/auditor.err;
location /taler-auditor/ {
proxy_pass http://unix:/var/lib/taler-auditor/auditor.sock;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host "localhost";
#proxy_set_header X-Forwarded-Proto "https";
}
}

View File

@ -0,0 +1,12 @@
# Read secret sections into configuration, but only
# if we have permission to do so.
@inline-secret@ auditordb-postgres ../secrets/auditor-db.secret.conf
[auditor]
# Debian package is configured to use a reverse proxy with a UNIX
# domain socket. See nginx/apache configuration files.
SERVE = UNIX
UNIXPATH = /var/lib/taler-auditor/auditor.sock
# Only supported database is Postgres right now.
DATABASE = postgres

View File

@ -0,0 +1,10 @@
# Database configuration for the Taler auditor.
[auditordb-postgres]
# Typically, there should only be a single line here, of the form:
CONFIG=postgres:///DATABASE
# The details of the URI depend on where the database lives and how
# access control was configured.

View File

@ -0,0 +1,4 @@
<Location "/taler-exchange/">
ProxyPass "unix:/run/taler/exchange-httpd/exchange-http.sock|http://example.com/"
RequestHeader add "X-Forwarded-Proto" "https"
</Location>

View File

@ -0,0 +1,17 @@
server {
listen 80;
listen [::]:80;
server_name localhost;
access_log /var/log/nginx/exchange.log;
error_log /var/log/nginx/exchange.err;
location /taler-exchange/ {
proxy_pass http://unix:/run/taler/exchange-httpd/exchange-http.sock:/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host "localhost";
#proxy_set_header X-Forwarded-Proto "https";
}
}

View File

@ -0,0 +1,50 @@
# Configuration for business-level aspects of the exchange.
[exchange]
# Here you MUST add the master public key of the offline system
# which you can get using `taler-exchange-offline setup`.
# This is just an example, your key will be different!
# MASTER_PUBLIC_KEY = YE6Q6TR1EDB7FD0S68TGDZGF1P0GHJD2S0XVV8R2S62MYJ6HJ4ZG
# MASTER_PUBLIC_KEY =
# Publicly visible base URL of the exchange.
# BASE_URL = https://example.com/
# BASE_URL =
# Here you MUST configure the amount above which transactions are
# always subject to manual AML review.
# AML_THRESHOLD =
# Attribute encryption key for storing attributes encrypted
# in the database. Should be a high-entropy nonce.
ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
# For your terms of service and privacy policy, you should specify
# an Etag that must be updated whenever there are significant
# changes to either document. The format is up to you, what matters
# is that the value is updated and never re-used. See the HTTP
# specification on Etags.
# TERMS_ETAG =
# PRIVACY_ETAG =
SERVE = unix
UNIXPATH_MODE = 666
# Bank accounts used by the exchange should be specified here:
[exchange-account-1]
ENABLE_CREDIT = NO
ENABLE_DEBIT = NO
# Account identifier in the form of an RFC-8905 payto:// URI.
# For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
# Make sure to URL-encode spaces in $NAME!
PAYTO_URI =
# Credentials to access the account are in a separate
# config file with restricted permissions.
@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials-1.secret.conf

View File

@ -0,0 +1,33 @@
#
# This configuration file specifies the various denominations offered by your
# exchange.
#
# Each denomination must be specified in a sections starting with
# "coin_".
#
# What follows is an example.
#
# [coin_FOO]
## Actual value of the coin
#VALUE = KUDOS:1
## How long will one key be used for withdrawals?
#DURATION_WITHDRAW = 7 days
## How long do users have to spend their coins?
#DURATION_SPEND = 2 years
## How long does the exchange keep the proofs around for legal disputes?
#DURATION_LEGAL = 6 years
## Fees charged. Note that for the lowest denomination, the
## fee must precisely be the lowest denomination, or zero.
#FEE_WITHDRAW = KUDOS:0
#FEE_DEPOSIT = KUDOS:0
#FEE_REFRESH = KUDOS:0
#FEE_REFUND = KUDOS:0
## How long should the RSA keys be. Do not change unless you really know
## what you are doing (consult your local cryptographer first!).
#RSA_KEYSIZE = 2048

View File

@ -0,0 +1,13 @@
# Configuration settings for system parameters of the exchange.
# Read secret sections into configuration, but only
# if we have permission to do so.
@inline-secret@ exchangedb-postgres ../secrets/exchange-db.secret.conf
[exchange]
# Only supported database is Postgres right now.
DATABASE = postgres

View File

@ -0,0 +1,17 @@
# This file contains the secret credentials
# to access the Taler Wire Gateway API (usually
# provided by LibEuFin) for the exchange accounts.
#
# Each exchange-account-* section should have a matching
# exchange-accountcredentials-* section here.
#
# Each of those sections must be imported via @inline-secret@,
# usually in conf.d/exchange-business.conf.
[exchange-accountcredentials-1]
wire_gateway_auth_method = basic
password =
username =
wire_gateway_url =

View File

@ -0,0 +1,10 @@
# Database configuration for the Taler exchange.
[exchangedb-postgres]
# Typically, there should only be a single line here, of the form:
# CONFIG=postgres:///DATABASE
# The details of the URI depend on where the database lives and how
# access control was configured.

View File

@ -0,0 +1,12 @@
[Unit]
Description=GNU Taler payment system auditor REST API
After=postgres.service network.target
[Service]
User=taler-auditor-httpd
Type=simple
Restart=on-failure
ExecStart=/usr/bin/taler-auditor-httpd -c /etc/taler/taler.conf
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,18 @@
[Unit]
Description=GNU Taler payment system exchange aggregator service
PartOf=taler-exchange.target
After=postgres.service
[Service]
User=taler-exchange-aggregator
Type=simple
Restart=always
RestartSec=1s
ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -0,0 +1,17 @@
[Unit]
Description=GNU Taler payment system exchange aggregator service
PartOf=taler-exchange.target
[Service]
User=taler-exchange-aggregator
Type=simple
Restart=always
RestartSec=1s
ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -0,0 +1,18 @@
[Unit]
Description=GNU Taler payment system exchange closer service
PartOf=taler-exchange.target
After=network.target postgres.service
[Service]
User=taler-exchange-closer
Type=simple
Restart=always
RestartSec=1s
ExecStart=/usr/bin/taler-exchange-closer -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -0,0 +1,18 @@
[Unit]
Description=GNU Taler payment system exchange expire service
PartOf=taler-exchange.target
After=postgres.service
[Service]
User=taler-exchange-expire
Type=simple
Restart=always
RestartSec=1s
ExecStart=/usr/bin/taler-exchange-expire -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -0,0 +1,33 @@
[Unit]
Description=GNU Taler payment system exchange REST API
AssertPathExists=/run/taler/exchange-httpd
Requires=taler-exchange-httpd.socket taler-exchange-secmod-cs.service taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
After=postgres.service network.target taler-exchange-secmod-cs.service taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
PartOf=taler-exchange.target
[Service]
User=taler-exchange-httpd
Type=simple
# Depending on the configuration, the service process kills itself and then
# needs to be restarted. Thus no significant delay on restarts.
Restart=always
RestartSec=1ms
# Disable the service if more than 5 restarts are encountered within 5s.
# These are usually the systemd defaults, but can be overwritten, thus we set
# them here explicitly, as the exchange code assumes StartLimitInterval
# to be >=5s.
StartLimitBurst=5
StartLimitInterval=5s
ExecStart=/usr/bin/taler-exchange-httpd -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=no
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,27 @@
% This is a systemd service template.
[Unit]
Description=GNU Taler payment system exchange REST API at %I
AssertPathExists=/run/taler/exchange-httpd
Requires=taler-exchange-httpd@%i.socket taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
After=postgres.service network.target taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
PartOf=taler-exchange.target
[Service]
User=taler-exchange-httpd
Type=simple
# Depending on the configuration, the service suicides and then
# needs to be restarted.
Restart=always
# Do not dally on restarts.
RestartSec=1ms
EnvironmentFile=/etc/environment
ExecStart=/usr/bin/taler-exchange-httpd -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=no
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,18 @@
[Unit]
Description=GNU Taler payment system exchange CS security module
AssertPathExists=/run/taler/exchange-secmod-cs
PartOf=taler-exchange.target
[Service]
User=taler-exchange-secmod-cs
Type=simple
Restart=always
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-secmod-cs -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=no
PrivateDevices=yes
ProtectSystem=full
IPAddressDeny=any
Slice=taler-exchange.slice

View File

@ -0,0 +1,19 @@
[Unit]
Description=GNU Taler payment system exchange EdDSA security module
AssertPathExists=/run/taler/exchange-secmod-eddsa
PartOf=taler-exchange.target
[Service]
User=taler-exchange-secmod-eddsa
Type=simple
Restart=always
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-secmod-eddsa -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=no
PrivateDevices=yes
ProtectSystem=full
IPAddressDeny=any
Slice=taler-exchange.slice

View File

@ -0,0 +1,18 @@
[Unit]
Description=GNU Taler payment system exchange RSA security module
AssertPathExists=/run/taler/exchange-secmod-rsa
PartOf=taler-exchange.target
[Service]
User=taler-exchange-secmod-rsa
Type=simple
Restart=always
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-secmod-rsa -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=no
PrivateDevices=yes
ProtectSystem=full
IPAddressDeny=any
Slice=taler-exchange.slice

View File

@ -0,0 +1,18 @@
[Unit]
Description=Taler Exchange Transfer Service
After=network.target postgres.service
PartOf=taler-exchange.target
[Service]
User=taler-exchange-wire
Type=simple
Restart=always
RestartSec=1s
ExecStart=/usr/bin/taler-exchange-transfer -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -0,0 +1,18 @@
[Unit]
Description=GNU Taler payment system exchange wirewatch service
After=network.target postgres.service
PartOf=taler-exchange.target
[Service]
User=taler-exchange-wire
Type=simple
Restart=always
RestartSec=1s
RuntimeMaxSec=3600s
ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice

View File

@ -0,0 +1,18 @@
[Unit]
Description=GNU Taler payment system exchange wirewatch service
After=network.target
PartOf=taler-exchange.target
[Service]
User=taler-exchange-wire
Type=simple
Restart=always
RestartSec=1s
ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -4,7 +4,7 @@
exec 1>&2 exec 1>&2
RET=0 RET=0
changed=$(git diff --cached --name-only | grep -v mustach) changed=$(git diff --cached --name-only | grep -v mustach | grep -v templating/test./)
crustified="" crustified=""
for f in $changed; for f in $changed;
@ -28,7 +28,7 @@ done
if [ $RET = 1 ]; if [ $RET = 1 ];
then then
echo "Run" echo "Run"
echo "uncrustify --no-backup -c uncrustify.cfg ${crustified}" echo "uncrustify --replace -c uncrustify.cfg ${crustified}"
echo "before committing." echo "before committing."
fi fi
exit $RET exit $RET

View File

@ -12,6 +12,14 @@
# BASE_URL = https://example.com/ # BASE_URL = https://example.com/
# BASE_URL = # BASE_URL =
# Here you MUST configure the amount above which transactions are
# always subject to manual AML review.
# AML_THRESHOLD =
# Attribute encryption key for storing attributes encrypted
# in the database. Should be a high-entropy nonce.
ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
# For your terms of service and privacy policy, you should specify # For your terms of service and privacy policy, you should specify
# an Etag that must be updated whenever there are significant # an Etag that must be updated whenever there are significant
# changes to either document. The format is up to you, what matters # changes to either document. The format is up to you, what matters
@ -26,14 +34,17 @@ UNIXPATH_MODE = 666
# Bank accounts used by the exchange should be specified here: # Bank accounts used by the exchange should be specified here:
[exchange-account-1] [exchange-account-1]
enable_credit = no ENABLE_CREDIT = NO
enable_debit = no ENABLE_DEBIT = NO
# Account identifier in the form of an RFC-8905 payto:// URI. # Account identifier in the form of an RFC-8905 payto:// URI.
# For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME # For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
# Make sure to URL-encode spaces in $NAME! # Make sure to URL-encode spaces in $NAME!
payto_uri = PAYTO_URI =
# Credentials to access the account are in a separate # Credentials to access the account are in a separate
# config file with restricted permissions. # config file with restricted permissions.
@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials-1.secret.conf @inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials-1.secret.conf

View File

@ -8,3 +8,6 @@
# Only supported database is Postgres right now. # Only supported database is Postgres right now.
DATABASE = postgres DATABASE = postgres

View File

@ -24,6 +24,7 @@ man_MANS = \
prebuilt/man/taler-exchange-drain.1 \ prebuilt/man/taler-exchange-drain.1 \
prebuilt/man/taler-exchange-expire.1 \ prebuilt/man/taler-exchange-expire.1 \
prebuilt/man/taler-exchange-httpd.1 \ prebuilt/man/taler-exchange-httpd.1 \
prebuilt/man/taler-exchange-kyc-aml-pep-trigger.1 \
prebuilt/man/taler-exchange-kyc-tester.1 \ prebuilt/man/taler-exchange-kyc-tester.1 \
prebuilt/man/taler-exchange-offline.1 \ prebuilt/man/taler-exchange-offline.1 \
prebuilt/man/taler-exchange-router.1\ prebuilt/man/taler-exchange-router.1\

@ -1 +1 @@
Subproject commit 8452f991dd967328207fab52a99beb19e2cb4dff Subproject commit 66e99d09d4351bb6e6c5fd442f14ec7cf1363a81

View File

@ -213,7 +213,7 @@ main (int argc,
? "Could not remove exchange from database: entry already absent\n" ? "Could not remove exchange from database: entry already absent\n"
: "Could not add exchange to database: entry already exists\n"); : "Could not add exchange to database: entry already exists\n");
TALER_AUDITORDB_plugin_unload (adb); TALER_AUDITORDB_plugin_unload (adb);
return EXIT_FAILURE; return EXIT_SUCCESS;
} }
} }
TALER_AUDITORDB_plugin_unload (adb); TALER_AUDITORDB_plugin_unload (adb);

View File

@ -12,6 +12,7 @@ CURRENCY_ROUND_UNIT = EUR:0.01
[exchange] [exchange]
AML_THRESHOLD = EUR:99999999
SIGNKEY_LEGAL_DURATION = 2 years SIGNKEY_LEGAL_DURATION = 2 years
# HTTP port the exchange listens to # HTTP port the exchange listens to
@ -50,6 +51,7 @@ HTTP_PORT = 8082
SERVE = http SERVE = http
MAX_DEBT = EUR:100000000000.0 MAX_DEBT = EUR:100000000000.0
MAX_DEBT_BANK = EUR:1000000000000000.0 MAX_DEBT_BANK = EUR:1000000000000000.0
DATABASE = bank-db.sqlite3
[benchmark] [benchmark]
USER_PAYTO_URI = payto://x-taler-bank/localhost:8082/42 USER_PAYTO_URI = payto://x-taler-bank/localhost:8082/42

View File

@ -12,6 +12,7 @@ CURRENCY_ROUND_UNIT = EUR:0.01
[exchange] [exchange]
AML_THRESHOLD = EUR:99999999
SIGNKEY_LEGAL_DURATION = 2 years SIGNKEY_LEGAL_DURATION = 2 years
# HTTP port the exchange listens to # HTTP port the exchange listens to
@ -50,6 +51,7 @@ HTTP_PORT = 8082
SERVE = http SERVE = http
MAX_DEBT = EUR:100000000000.0 MAX_DEBT = EUR:100000000000.0
MAX_DEBT_BANK = EUR:1000000000000000.0 MAX_DEBT_BANK = EUR:1000000000000000.0
DATABASE = bank-db.sqlite3
[benchmark] [benchmark]
USER_PAYTO_URI = payto://x-taler-bank/localhost:8082/42 USER_PAYTO_URI = payto://x-taler-bank/localhost:8082/42

View File

@ -521,7 +521,7 @@ launch_fakebank (void *cls)
* @param config_file configuration file to use * @param config_file configuration file to use
* @return #GNUNET_OK on success * @return #GNUNET_OK on success
*/ */
static int static enum GNUNET_GenericReturnValue
parallel_benchmark (TALER_TESTING_Main main_cb, parallel_benchmark (TALER_TESTING_Main main_cb,
void *main_cb_cls, void *main_cb_cls,
const char *config_file) const char *config_file)
@ -565,7 +565,7 @@ parallel_benchmark (TALER_TESTING_Main main_cb,
if (GNUNET_OK != if (GNUNET_OK !=
TALER_TESTING_prepare_bank (cfg_filename, TALER_TESTING_prepare_bank (cfg_filename,
GNUNET_NO, GNUNET_NO,
"exchange-account-2", "exchange-account-test",
&bc)) &bc))
{ {
return 1; return 1;
@ -1061,32 +1061,20 @@ main (int argc,
} }
if ( (MODE_EXCHANGE == mode) || (MODE_BOTH == mode) ) if ( (MODE_EXCHANGE == mode) || (MODE_BOTH == mode) )
{ {
struct GNUNET_OS_Process *compute_wire_response;
compute_wire_response
= GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-exchange-wire",
"taler-exchange-wire",
"-c", cfg_filename,
NULL);
if (NULL == compute_wire_response)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to run `taler-exchange-wire`, is your PATH correct?\n");
GNUNET_free (cfg_filename);
return BAD_CONFIG_FILE;
}
GNUNET_OS_process_wait (compute_wire_response);
GNUNET_OS_process_destroy (compute_wire_response);
/* If we use the fakebank, we MUST reset the database as the fakebank /* If we use the fakebank, we MUST reset the database as the fakebank
will have forgotten everything... */ will have forgotten everything... */
GNUNET_assert (GNUNET_OK == if (GNUNET_OK !=
TALER_TESTING_prepare_exchange (cfg_filename, TALER_TESTING_prepare_exchange (cfg_filename,
(GNUNET_YES == use_fakebank) (GNUNET_YES == use_fakebank)
? GNUNET_YES ? GNUNET_YES
: GNUNET_NO, : GNUNET_NO,
&ec)); &ec))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to prepare the exchange for launch\n");
GNUNET_free (cfg_filename);
return BAD_CONFIG_FILE;
}
} }
else else
{ {

View File

@ -3397,14 +3397,28 @@ do_set_global_fee (char *const *args)
(NULL == args[3]) || (NULL == args[3]) ||
(NULL == args[4]) || (NULL == args[4]) ||
(NULL == args[5]) || (NULL == args[5]) ||
(NULL == args[6]) || (NULL == args[6]) )
( (1 != sscanf (args[0], {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must use YEAR, HISTORY-FEE, ACCOUNT-FEE, PURSE-FEE, PURSE-TIMEOUT, HISTORY-EXPIRATION and PURSE-ACCOUNT-LIMIT as arguments for this subcommand\n");
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if ( (1 != sscanf (args[0],
"%u%c", "%u%c",
&year, &year,
&dummy)) && &dummy)) &&
(0 != strcasecmp ("now", (0 != strcasecmp ("now",
args[0])) ) || args[0])) )
(GNUNET_OK != {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid YEAR given for 'global-fee' subcommand\n");
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if ( (GNUNET_OK !=
TALER_string_to_amount (args[1], TALER_string_to_amount (args[1],
&fees.history)) || &fees.history)) ||
(GNUNET_OK != (GNUNET_OK !=
@ -3412,20 +3426,34 @@ do_set_global_fee (char *const *args)
&fees.account)) || &fees.account)) ||
(GNUNET_OK != (GNUNET_OK !=
TALER_string_to_amount (args[3], TALER_string_to_amount (args[3],
&fees.purse)) || &fees.purse)) )
(GNUNET_OK != {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid amount given for 'global-fee' subcommand\n");
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if ( (GNUNET_OK !=
GNUNET_STRINGS_fancy_time_to_relative (args[4], GNUNET_STRINGS_fancy_time_to_relative (args[4],
&purse_timeout)) || &purse_timeout)) ||
(GNUNET_OK != (GNUNET_OK !=
GNUNET_STRINGS_fancy_time_to_relative (args[5], GNUNET_STRINGS_fancy_time_to_relative (args[5],
&history_expiration)) || &history_expiration)) )
(1 != sscanf (args[6],
"%u%c",
&purse_account_limit,
&dummy)) )
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must use YEAR, HISTORY-FEE, ACCOUNT-FEE, PURSE-FEE, PURSE-TIMEOUT, HISTORY-EXPIRATION and PURSE-ACCOUNT-LIMIT as arguments for this subcommand\n"); "Invalid delay given for 'global-fee' subcommand\n");
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if (1 != sscanf (args[6],
"%u%c",
&purse_account_limit,
&dummy))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid purse account limit given for 'global-fee' subcommand\n");
test_shutdown (); test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT; global_ret = EXIT_INVALIDARGUMENT;
return; return;

View File

@ -15,6 +15,8 @@ pkgcfg_DATA = \
exchange.conf exchange.conf
# Programs # Programs
bin_SCRIPTS = \
taler-exchange-kyc-aml-pep-trigger.sh
bin_PROGRAMS = \ bin_PROGRAMS = \
taler-exchange-aggregator \ taler-exchange-aggregator \
@ -131,6 +133,7 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_age-withdraw.c taler-exchange-httpd_age-withdraw.h \ taler-exchange-httpd_age-withdraw.c taler-exchange-httpd_age-withdraw.h \
taler-exchange-httpd_age-withdraw_reveal.c taler-exchange-httpd_age-withdraw_reveal.h \ taler-exchange-httpd_age-withdraw_reveal.c taler-exchange-httpd_age-withdraw_reveal.h \
taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \ taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \
taler-exchange-httpd_common_kyc.c taler-exchange-httpd_common_kyc.h \
taler-exchange-httpd_config.c taler-exchange-httpd_config.h \ taler-exchange-httpd_config.c taler-exchange-httpd_config.h \
taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \ taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \
taler-exchange-httpd_csr.c taler-exchange-httpd_csr.h \ taler-exchange-httpd_csr.c taler-exchange-httpd_csr.h \
@ -227,4 +230,5 @@ EXTRA_DIST = \
test_taler_exchange_httpd.get \ test_taler_exchange_httpd.get \
test_taler_exchange_httpd.post \ test_taler_exchange_httpd.post \
exchange.conf \ exchange.conf \
$(bin_SCRIPTS) \
$(check_SCRIPTS) $(check_SCRIPTS)

View File

@ -6,10 +6,23 @@
# This must be adjusted to your actual installation. # This must be adjusted to your actual installation.
# MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG # MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
# Must be set to the threshold above which transactions
# are flagged for AML review.
# AML_THRESHOLD =
# Specifies a program (binary) to run on KYC attribute data to decide
# whether we should immediately flag an account for AML review.
# The KYC attribute data will be passed on standard-input.
# Return non-zero to trigger AML review of the new user.
KYC_AML_TRIGGER = true
# Attribute encryption key for storing attributes encrypted # Attribute encryption key for storing attributes encrypted
# in the database. Should be a high-entropy nonce. # in the database. Should be a high-entropy nonce.
ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
# Set to NO to disable tipping.
ENABLE_TIPPING = YES
# How long do we allow /keys to be cached at most? The actual # How long do we allow /keys to be cached at most? The actual
# limit is the minimum of this value and the first expected # limit is the minimum of this value and the first expected
# significant change in /keys based on the expiration times. # significant change in /keys based on the expiration times.

View File

@ -303,17 +303,17 @@ parse_aggregator_config (void)
(TALER_amount_is_zero (&currency_round_unit)) ) (TALER_amount_is_zero (&currency_round_unit)) )
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Need non-zero amount in section `TALER' under `CURRENCY_ROUND_UNIT'\n"); "Need non-zero amount in section `taler' under `CURRENCY_ROUND_UNIT'\n");
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
if (GNUNET_OK != if (GNUNET_OK !=
TALER_config_get_amount (cfg, TALER_config_get_amount (cfg,
"taler", "exchange",
"AML_THRESHOLD", "AML_THRESHOLD",
&aml_threshold)) &aml_threshold))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Need amount in section `TALER' under `AML_THRESHOLD'\n"); "Need amount in section `exchange' under `AML_THRESHOLD'\n");
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }

View File

@ -153,6 +153,16 @@ struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
*/ */
char *TEH_currency; char *TEH_currency;
/**
* Name of the KYC-AML-trigger evaluation binary.
*/
char *TEH_kyc_aml_trigger;
/**
* Option set to #GNUNET_YES if tipping is enabled.
*/
int TEH_enable_tipping;
/** /**
* What is the largest amount we allow a peer to * What is the largest amount we allow a peer to
* merge into a reserve before always triggering * merge into a reserve before always triggering
@ -1844,6 +1854,17 @@ exchange_serve_process_config (void)
"valid relative time expected"); "valid relative time expected");
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
"exchange",
"KYC_AML_TRIGGER",
&TEH_kyc_aml_trigger))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange",
"KYC_AML_TRIGGER");
return GNUNET_SYSERR;
}
if (GNUNET_OK != if (GNUNET_OK !=
TALER_config_get_currency (TEH_cfg, TALER_config_get_currency (TEH_cfg,
&TEH_currency)) &TEH_currency))
@ -1855,19 +1876,30 @@ exchange_serve_process_config (void)
} }
if (GNUNET_OK != if (GNUNET_OK !=
TALER_config_get_amount (TEH_cfg, TALER_config_get_amount (TEH_cfg,
"taler", "exchange",
"AML_THRESHOLD", "AML_THRESHOLD",
&TEH_aml_threshold)) &TEH_aml_threshold))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Need amount in section `TALER' under `AML_THRESHOLD'\n"); "Need amount in section `exchange' under `AML_THRESHOLD'\n");
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
if (0 != strcmp (TEH_currency, if (0 != strcmp (TEH_currency,
TEH_aml_threshold.currency)) TEH_aml_threshold.currency))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Amount in section `TALER' under `AML_THRESHOLD' uses the wrong currency!\n"); "Amount in section `exchange' under `AML_THRESHOLD' uses the wrong currency!\n");
return GNUNET_SYSERR;
}
TEH_enable_tipping
= GNUNET_CONFIGURATION_get_value_yesno (
TEH_cfg,
"exchange",
"ENABLE_TIPPING");
if (GNUNET_SYSERR == TEH_enable_tipping)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Need YES or NO in section `exchange' under `ENABLE_TIPPING'\n");
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
if (GNUNET_OK != if (GNUNET_OK !=

View File

@ -64,6 +64,11 @@ extern int TEH_check_invariants_flag;
*/ */
extern int TEH_allow_keys_timetravel; extern int TEH_allow_keys_timetravel;
/**
* Option set to #GNUNET_YES if tipping is enabled.
*/
extern int TEH_enable_tipping;
/** /**
* Main directory with revocation data. * Main directory with revocation data.
*/ */
@ -97,6 +102,11 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
*/ */
extern char *TEH_currency; extern char *TEH_currency;
/**
* Name of the KYC-AML-trigger evaluation binary.
*/
extern char *TEH_kyc_aml_trigger;
/** /**
* What is the largest amount we allow a peer to * What is the largest amount we allow a peer to
* merge into a reserve before always triggering * merge into a reserve before always triggering

View File

@ -43,8 +43,6 @@
* @param[in,out] cls closure with a `json_t *` array to update * @param[in,out] cls closure with a `json_t *` array to update
* @param h_payto account for which the attribute data is stored * @param h_payto account for which the attribute data is stored
* @param provider_section provider that must be checked * @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected * @param collection_time when was the data collected
* @param expiration_time when does the data expire * @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes * @param enc_attributes_size number of bytes in @a enc_attributes
@ -55,7 +53,6 @@ kyc_attribute_cb (
void *cls, void *cls,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const char *provider_section, const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time, struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time, struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size, size_t enc_attributes_size,

View File

@ -0,0 +1,239 @@
/*
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_common_kyc.c
* @brief shared logic for finishing a KYC process
* @author Christian Grothoff
*/
#include "platform.h"
#include "taler-exchange-httpd_common_kyc.h"
#include "taler_attributes.h"
#include "taler_exchangedb_plugin.h"
struct TEH_KycAmlTrigger
{
/**
* Our logging scope.
*/
struct GNUNET_AsyncScopeId scope;
/**
* account the operation is about
*/
struct TALER_PaytoHashP account_id;
/**
* until when is the KYC data valid
*/
struct GNUNET_TIME_Absolute expiration;
/**
* legitimization process the KYC data is about
*/
uint64_t process_row;
/**
* name of the configuration section of the logic that was run
*/
char *provider_section;
/**
* set to user ID at the provider, or NULL if not supported or unknown
*/
char *provider_user_id;
/**
* provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
*/
char *provider_legitimization_id;
/**
* function to call with the result
*/
TEH_KycAmlTriggerCallback cb;
/**
* closure for @e cb
*/
void *cb_cls;
/**
* user attributes returned by the provider
*/
json_t *attributes;
/**
* response to return to the HTTP client
*/
struct MHD_Response *response;
/**
* Handle to an external process that evaluates the
* need to run AML on the account.
*/
struct TALER_JSON_ExternalConversion *kyc_aml;
/**
* HTTP status code of @e response
*/
unsigned int http_status;
};
/**
* Type of a callback that receives a JSON @a result.
*
* @param cls closure of type `struct TEH_KycAmlTrigger *`
* @param status_type how did the process die
* @param code termination status code from the process
* @param result some JSON result, NULL if we failed to get an JSON output
*/
static void
kyc_aml_finished (void *cls,
enum GNUNET_OS_ProcessStatusType status_type,
unsigned long code,
const json_t *result)
{
struct TEH_KycAmlTrigger *kat = cls;
enum GNUNET_DB_QueryStatus qs;
size_t eas;
void *ea;
const char *birthdate;
unsigned int birthday;
struct GNUNET_ShortHashCode kyc_prox;
struct GNUNET_AsyncScopeSave old_scope;
kat->kyc_aml = NULL;
GNUNET_async_scope_enter (&kat->scope,
&old_scope);
TALER_CRYPTO_attributes_to_kyc_prox (kat->attributes,
&kyc_prox);
birthdate = json_string_value (json_object_get (kat->attributes,
TALER_ATTRIBUTE_BIRTHDATE));
birthday = 0; (void) birthdate; // FIXME-Oec: calculate birthday here...
// Convert 'birthdate' to time after 1970, then compute days.
// Then compare against max age-restriction, and if before, set to 0.
TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
kat->attributes,
&ea,
&eas);
qs = TEH_plugin->insert_kyc_attributes (
TEH_plugin->cls,
kat->process_row,
&kat->account_id,
&kyc_prox,
kat->provider_section,
birthday,
GNUNET_TIME_timestamp_get (),
kat->provider_user_id,
kat->provider_legitimization_id,
kat->expiration,
eas,
ea,
0 != code);
GNUNET_free (ea);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
if (NULL != kat->response)
MHD_destroy_response (kat->response);
kat->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
kat->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"do_insert_kyc_attributes");
}
/* Finally, return result to main handler */
kat->cb (kat->cb_cls,
kat->http_status,
kat->response);
kat->response = NULL;
TEH_kyc_finished_cancel (kat);
GNUNET_async_scope_restore (&old_scope);
}
struct TEH_KycAmlTrigger *
TEH_kyc_finished (const struct GNUNET_AsyncScopeId *scope,
uint64_t process_row,
const struct TALER_PaytoHashP *account_id,
const char *provider_section,
const char *provider_user_id,
const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration,
const json_t *attributes,
unsigned int http_status,
struct MHD_Response *response,
TEH_KycAmlTriggerCallback cb,
void *cb_cls)
{
struct TEH_KycAmlTrigger *kat;
kat = GNUNET_new (struct TEH_KycAmlTrigger);
kat->scope = *scope;
kat->process_row = process_row;
kat->account_id = *account_id;
kat->provider_section
= GNUNET_strdup (provider_section);
if (NULL != provider_user_id)
kat->provider_user_id
= GNUNET_strdup (provider_user_id);
if (NULL != provider_legitimization_id)
kat->provider_legitimization_id
= GNUNET_strdup (provider_legitimization_id);
kat->expiration = expiration;
kat->attributes = json_incref ((json_t*) attributes);
kat->http_status = http_status;
kat->response = response;
kat->cb = cb;
kat->cb_cls = cb_cls;
kat->kyc_aml
= TALER_JSON_external_conversion_start (
attributes,
&kyc_aml_finished,
kat,
TEH_kyc_aml_trigger,
TEH_kyc_aml_trigger,
NULL);
if (NULL == kat->kyc_aml)
{
GNUNET_break (0);
TEH_kyc_finished_cancel (kat);
return NULL;
}
return kat;
}
void
TEH_kyc_finished_cancel (struct TEH_KycAmlTrigger *kat)
{
if (NULL != kat->kyc_aml)
{
TALER_JSON_external_conversion_stop (kat->kyc_aml);
kat->kyc_aml = NULL;
}
GNUNET_free (kat->provider_section);
GNUNET_free (kat->provider_user_id);
GNUNET_free (kat->provider_legitimization_id);
json_decref (kat->attributes);
if (NULL != kat->response)
{
MHD_destroy_response (kat->response);
kat->response = NULL;
}
GNUNET_free (kat);
}

View File

@ -0,0 +1,99 @@
/*
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_common_kyc.h
* @brief shared logic for finishing a KYC process
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_COMMON_KYC_H
#define TALER_EXCHANGE_HTTPD_COMMON_KYC_H
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd.h"
/**
* Function called after the KYC-AML trigger is done.
*
* @param cls closure
* @param http_status final HTTP status to return
* @param[in] response final HTTP ro return
*/
typedef void
(*TEH_KycAmlTriggerCallback) (
void *cls,
unsigned int http_status,
struct MHD_Response *response);
/**
* Handle for an asynchronous operation to finish
* a KYC process after running the AML trigger.
*/
struct TEH_KycAmlTrigger;
// FIXME: also pass async log context and set it!
/**
* We have finished a KYC process and obtained new
* @a attributes for a given @a account_id.
* Check with the KYC-AML trigger to see if we need
* to initiate an AML process, and store the attributes
* in the database. Then call @a cb.
*
* @param scope the HTTP request logging scope
* @param process_row legitimization process the webhook was about
* @param account_id account the webhook was about
* @param provider_section name of the configuration section of the logic that was run
* @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
* @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
* @param expiration until when is the KYC check valid
* @param attributes user attributes returned by the provider
* @param http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client
* @param cb function to call with the result
* @param cb_cls closure for @a cb
* @return handle to cancel the operation
*/
struct TEH_KycAmlTrigger *
TEH_kyc_finished (const struct GNUNET_AsyncScopeId *scope,
uint64_t process_row,
const struct TALER_PaytoHashP *account_id,
const char *provider_section,
const char *provider_user_id,
const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration,
const json_t *attributes,
unsigned int http_status,
struct MHD_Response *response,
TEH_KycAmlTriggerCallback cb,
void *cb_cls);
/**
* Cancel KYC finish operation.
*
* @param[in] kat operation to abort
*/
void
TEH_kyc_finished_cancel (struct TEH_KycAmlTrigger *kat);
#endif

View File

@ -1857,6 +1857,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
TEH_currency), TEH_currency),
GNUNET_JSON_pack_string ("asset_type", GNUNET_JSON_pack_string ("asset_type",
asset_type), asset_type),
GNUNET_JSON_pack_bool ("tipping_allowed",
GNUNET_YES == TEH_enable_tipping),
GNUNET_JSON_pack_data_auto ("master_public_key", GNUNET_JSON_pack_data_auto ("master_public_key",
&TEH_master_public_key), &TEH_master_public_key),
GNUNET_JSON_pack_time_rel ("reserve_closing_delay", GNUNET_JSON_pack_time_rel ("reserve_closing_delay",

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2021-2022 Taler Systems SA Copyright (C) 2021-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
@ -23,11 +23,11 @@
#include <gnunet/gnunet_json_lib.h> #include <gnunet/gnunet_json_lib.h>
#include <jansson.h> #include <jansson.h>
#include <microhttpd.h> #include <microhttpd.h>
#include <pthread.h>
#include "taler_attributes.h" #include "taler_attributes.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_kyclogic_lib.h" #include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-exchange-httpd_common_kyc.h"
#include "taler-exchange-httpd_kyc-proof.h" #include "taler-exchange-httpd_kyc-proof.h"
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
@ -68,6 +68,11 @@ struct KycProofContext
*/ */
struct TALER_KYCLOGIC_ProofHandle *ph; struct TALER_KYCLOGIC_ProofHandle *ph;
/**
* KYC AML trigger operation.
*/
struct TEH_KycAmlTrigger *kat;
/** /**
* Process information about the user for the plugin from the database, can * Process information about the user for the plugin from the database, can
* be NULL. * be NULL.
@ -159,6 +164,28 @@ TEH_kyc_proof_cleanup (void)
} }
/**
* Function called after the KYC-AML trigger is done.
*
* @param cls closure
* @param http_status final HTTP status to return
* @param[in] response final HTTP ro return
*/
static void
proof_finish (
void *cls,
unsigned int http_status,
struct MHD_Response *response)
{
struct KycProofContext *kpc = cls;
kpc->kat = NULL;
kpc->response_code = http_status;
kpc->response = response;
kpc_resume (kpc);
}
/** /**
* Function called with the result of a proof check operation. * Function called with the result of a proof check operation.
* *
@ -192,74 +219,40 @@ proof_cb (
kpc->ph = NULL; kpc->ph = NULL;
GNUNET_async_scope_enter (&rc->async_scope_id, GNUNET_async_scope_enter (&rc->async_scope_id,
&old_scope); &old_scope);
if (TALER_KYCLOGIC_STATUS_SUCCESS == status) if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
{ {
enum GNUNET_DB_QueryStatus qs; kpc->kat = TEH_kyc_finished (&rc->async_scope_id,
size_t eas;
void *ea;
const char *birthdate;
struct GNUNET_ShortHashCode kyc_prox;
TALER_CRYPTO_attributes_to_kyc_prox (attributes,
&kyc_prox);
birthdate = json_string_value (json_object_get (attributes,
TALER_ATTRIBUTE_BIRTHDATE));
TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
attributes,
&ea,
&eas);
qs = TEH_plugin->insert_kyc_attributes (
TEH_plugin->cls,
&kpc->h_payto,
&kyc_prox,
kpc->provider_section,
birthdate,
GNUNET_TIME_timestamp_get (),
GNUNET_TIME_absolute_to_timestamp (expiration),
eas,
ea);
GNUNET_free (ea);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
if (NULL != response)
MHD_destroy_response (response);
kpc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
kpc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_attributes");
GNUNET_async_scope_restore (&old_scope);
return;
}
qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
kpc->process_row, kpc->process_row,
kpc->provider_section,
&kpc->h_payto, &kpc->h_payto,
kpc->provider_section,
provider_user_id, provider_user_id,
provider_legitimization_id, provider_legitimization_id,
expiration); expiration,
if (GNUNET_DB_STATUS_HARD_ERROR == qs) attributes,
http_status,
response,
&proof_finish,
kpc);
if (NULL == kpc->kat)
{ {
GNUNET_break (0); http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
if (NULL != response) if (NULL != response)
MHD_destroy_response (response); MHD_destroy_response (response);
kpc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; response = TALER_MHD_make_error (
kpc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
"set_kyc_ok"); "[exchange] AML_KYC_TRIGGER");
GNUNET_async_scope_restore (&old_scope);
return;
} }
} }
else if (NULL == kpc->kat)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC process #%llu failed with status %d\n", "KYC process #%llu failed with status %d\n",
(unsigned long long) kpc->process_row, (unsigned long long) kpc->process_row,
status); status);
proof_finish (kpc,
http_status,
response);
} }
kpc->response_code = http_status;
kpc->response = response;
kpc_resume (kpc);
GNUNET_async_scope_restore (&old_scope); GNUNET_async_scope_restore (&old_scope);
} }
@ -279,6 +272,11 @@ clean_kpc (struct TEH_RequestContext *rc)
kpc->logic->proof_cancel (kpc->ph); kpc->logic->proof_cancel (kpc->ph);
kpc->ph = NULL; kpc->ph = NULL;
} }
if (NULL != kpc->kat)
{
TEH_kyc_finished_cancel (kpc->kat);
kpc->kat = NULL;
}
if (NULL != kpc->response) if (NULL != kpc->response)
{ {
MHD_destroy_response (kpc->response); MHD_destroy_response (kpc->response);

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of 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
@ -28,6 +28,7 @@
#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_kyclogic_lib.h"
#include "taler-exchange-httpd_common_kyc.h"
#include "taler-exchange-httpd_kyc-webhook.h" #include "taler-exchange-httpd_kyc-webhook.h"
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
@ -53,6 +54,11 @@ struct KycWebhookContext
*/ */
struct TEH_RequestContext *rc; struct TEH_RequestContext *rc;
/**
* Handle for the KYC-AML trigger interaction.
*/
struct TEH_KycAmlTrigger *kat;
/** /**
* Plugin responsible for the webhook. * Plugin responsible for the webhook.
*/ */
@ -140,6 +146,28 @@ TEH_kyc_webhook_cleanup (void)
} }
/**
* Function called after the KYC-AML trigger is done.
*
* @param cls closure with a `struct KycWebhookContext *`
* @param http_status final HTTP status to return
* @param[in] response final HTTP ro return
*/
static void
kyc_aml_webhook_finished (
void *cls,
unsigned int http_status,
struct MHD_Response *response)
{
struct KycWebhookContext *kwh = cls;
kwh->kat = NULL;
kwh->response = response;
kwh->response_code = http_status;
kwh_resume (kwh);
}
/** /**
* Function called with the result of a KYC webhook operation. * Function called with the result of a KYC webhook operation.
* *
@ -178,58 +206,27 @@ webhook_finished_cb (
switch (status) switch (status)
{ {
case TALER_KYCLOGIC_STATUS_SUCCESS: case TALER_KYCLOGIC_STATUS_SUCCESS:
/* _successfully_ resumed case */ kwh->kat = TEH_kyc_finished (
{ &kwh->rc->async_scope_id,
enum GNUNET_DB_QueryStatus qs;
size_t eas;
void *ea;
const char *birthdate;
struct GNUNET_ShortHashCode kyc_prox;
TALER_CRYPTO_attributes_to_kyc_prox (attributes,
&kyc_prox);
birthdate = json_string_value (json_object_get (attributes,
TALER_ATTRIBUTE_BIRTHDATE));
TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
attributes,
&ea,
&eas);
qs = TEH_plugin->insert_kyc_attributes (
TEH_plugin->cls,
account_id,
&kyc_prox,
provider_section,
birthdate,
GNUNET_TIME_timestamp_get (),
GNUNET_TIME_absolute_to_timestamp (expiration),
eas,
ea);
GNUNET_free (ea);
if (qs < 0)
{
GNUNET_break (0);
kwh->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_attributes");
kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
kwh_resume (kwh);
return;
}
qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
process_row, process_row,
provider_section,
account_id, account_id,
provider_section,
provider_user_id, provider_user_id,
provider_legitimization_id, provider_legitimization_id,
expiration); expiration,
if (qs < 0) attributes,
http_status,
response,
&kyc_aml_webhook_finished,
kwh);
if (NULL == kwh->kat)
{ {
GNUNET_break (0); http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
kwh->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, if (NULL != response)
"set_kyc_ok"); MHD_destroy_response (response);
kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; response = TALER_MHD_make_error (
kwh_resume (kwh); TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
return; "[exchange] AML_KYC_TRIGGER");
}
} }
break; break;
default: default:
@ -241,9 +238,10 @@ webhook_finished_cb (
status); status);
break; break;
} }
kwh->response = response; if (NULL == kwh->kat)
kwh->response_code = http_status; kyc_aml_webhook_finished (kwh,
kwh_resume (kwh); http_status,
response);
} }
@ -262,6 +260,11 @@ clean_kwh (struct TEH_RequestContext *rc)
kwh->plugin->webhook_cancel (kwh->wh); kwh->plugin->webhook_cancel (kwh->wh);
kwh->wh = NULL; kwh->wh = NULL;
} }
if (NULL != kwh->kat)
{
TEH_kyc_finished_cancel (kwh->kat);
kwh->kat = NULL;
}
if (NULL != kwh->response) if (NULL != kwh->response)
{ {
MHD_destroy_response (kwh->response); MHD_destroy_response (kwh->response);

View File

@ -158,8 +158,6 @@ reply_reserve_attest_success (struct MHD_Connection *connection,
* @param cls our `struct ReserveAttestContext *` * @param cls our `struct ReserveAttestContext *`
* @param h_payto account for which the attribute data is stored * @param h_payto account for which the attribute data is stored
* @param provider_section provider that must be checked * @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected * @param collection_time when was the data collected
* @param expiration_time when does the data expire * @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes * @param enc_attributes_size number of bytes in @a enc_attributes
@ -169,7 +167,6 @@ static void
kyc_process_cb (void *cls, kyc_process_cb (void *cls,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const char *provider_section, const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time, struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time, struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size, size_t enc_attributes_size,

View File

@ -64,8 +64,6 @@ struct ReserveAttestContext
* @param cls our `struct ReserveAttestContext *` * @param cls our `struct ReserveAttestContext *`
* @param h_payto account for which the attribute data is stored * @param h_payto account for which the attribute data is stored
* @param provider_section provider that must be checked * @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected * @param collection_time when was the data collected
* @param expiration_time when does the data expire * @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes * @param enc_attributes_size number of bytes in @a enc_attributes
@ -75,7 +73,6 @@ static void
kyc_process_cb (void *cls, kyc_process_cb (void *cls,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const char *provider_section, const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time, struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time, struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size, size_t enc_attributes_size,

View File

@ -0,0 +1,7 @@
#!/bin/sh
# This file is in the public domain.
# This is an example of how to trigger AML if the
# KYC attributes include '{"pep":true}'
#
# To be used as a script for the KYC_AML_TRIGGER.
test "false" = $(jq .pep -)

View File

@ -7,13 +7,14 @@ TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
# Currency supported by the exchange (can only be one) # Currency supported by the exchange (can only be one)
CURRENCY = EUR CURRENCY = EUR
CURRENCY_ROUND_UNIT = EUR:0.01 CURRENCY_ROUND_UNIT = EUR:0.01
AML_THRESHOLD = EUR:1000000
[auditor] [auditor]
TINY_AMOUNT = EUR:0.01 TINY_AMOUNT = EUR:0.01
[exchange] [exchange]
AML_THRESHOLD = EUR:1000000
# Directory with our terms of service. # Directory with our terms of service.
TERMS_DIR = ../../contrib/tos TERMS_DIR = ../../contrib/tos

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)'
',max_age INT4 NOT NULL DEFAULT(120)' ',max_age 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 ;'
@ -80,6 +80,12 @@ BEGIN
,table_name ,table_name
,partition_suffix ,partition_suffix
); );
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'
,'max_age'
,table_name
,partition_suffix
);
END END
$$; $$;

View File

@ -0,0 +1,44 @@
--
-- 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 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 General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
CREATE OR REPLACE FUNCTION master_table_kyc_attributes_V2()
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
table_name VARCHAR DEFAULT 'kyc_attributes';
BEGIN
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
' DROP COLUMN birthdate;'
);
END $$;
COMMENT ON FUNCTION master_table_kyc_attributes_V2
IS 'Removes birthdate column from the kyc_attributes table';
INSERT INTO exchange_tables
(name
,version
,action
,partitioned
,by_range)
VALUES
('kyc_attributes_V2'
,'exchange-0004'
,'master'
,TRUE
,FALSE);

View File

@ -144,7 +144,6 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_aggregate.h pg_aggregate.c \ pg_aggregate.h pg_aggregate.c \
pg_create_aggregation_transient.h pg_create_aggregation_transient.c \ pg_create_aggregation_transient.h pg_create_aggregation_transient.c \
pg_insert_kyc_attributes.h pg_insert_kyc_attributes.c \ pg_insert_kyc_attributes.h pg_insert_kyc_attributes.c \
pg_update_kyc_attributes.h pg_update_kyc_attributes.c \
pg_select_similar_kyc_attributes.h pg_select_similar_kyc_attributes.c \ pg_select_similar_kyc_attributes.h pg_select_similar_kyc_attributes.c \
pg_select_kyc_attributes.h pg_select_kyc_attributes.c \ pg_select_kyc_attributes.h pg_select_kyc_attributes.c \
pg_insert_aml_officer.h pg_insert_aml_officer.c \ pg_insert_aml_officer.h pg_insert_aml_officer.c \

View File

@ -1,6 +1,6 @@
-- --
-- This file is part of TALER -- This file is part of TALER
-- Copyright (C) 2014--2023 Taler Systems SA -- Copyright (C) 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 General Public License as published by the Free Software -- terms of the GNU General Public License as published by the Free Software
@ -19,6 +19,7 @@ BEGIN;
SELECT _v.register_patch('exchange-0004', NULL, NULL); SELECT _v.register_patch('exchange-0004', NULL, NULL);
SET search_path TO exchange; SET search_path TO exchange;
#include "0004-kyc_attributes.sql"
#include "0004-wire_accounts.sql" #include "0004-wire_accounts.sql"
COMMIT; COMMIT;

View File

@ -0,0 +1,92 @@
--
-- 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 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 General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
CREATE OR REPLACE FUNCTION exchange_do_insert_kyc_attributes(
IN in_process_row INT8,
IN in_h_payto BYTEA,
IN in_kyc_prox BYTEA,
IN in_provider_section VARCHAR,
IN in_birthday INT4,
IN in_provider_account_id VARCHAR,
IN in_provider_legitimization_id VARCHAR,
IN in_collection_time_ts INT8,
IN in_expiration_time INT8,
IN in_expiration_time_ts INT8,
IN in_enc_attributes BYTEA,
IN in_require_aml BOOLEAN,
IN in_kyc_completed_notify_s VARCHAR,
OUT out_ok BOOLEAN)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO exchange.kyc_attributes
(h_payto
,kyc_prox
,provider
,collection_time
,expiration_time
,encrypted_attributes
) VALUES
(in_h_payto
,in_kyc_prox
,in_provider_section
,in_collection_time_ts
,in_expiration_time_ts
,in_enc_attributes);
-- FIXME-Oec: modify to 'return' the reserve_pub here
-- (requires of course to modify other code to store
-- the reserve pub in the right table in the first place)
UPDATE exchange.legitimization_processes
SET provider_user_id=in_provider_account_id
,provider_legitimization_id=in_provider_legitimization_id
,expiration_time=GREATEST(expiration_time,in_expiration_time)
WHERE h_payto=in_h_payto
AND legitimization_process_serial_id=in_process_row
AND provider_section=in_provider_section;
out_ok = FOUND;
-- FIXME-Oec: update exchange reserve table to store in_birthday here!
-- UPDATE exchange.reserves SET max_age=in_birthday WHERE reserve_pub=X;
IF in_require_aml
THEN
INSERT INTO exchange.aml_status
(h_payto
,status)
VALUES
(in_h_payto
,1)
ON CONFLICT (h_payto) DO
UPDATE SET status=EXCLUDED.status | 1;
END IF;
-- Wake up everyone who might care...
PERFORM pg_notify (in_kyc_completed_notify_s, NULL);
INSERT INTO kyc_alerts
(h_payto
,trigger_type)
VALUES
(in_h_payto,1);
END $$;
COMMENT ON FUNCTION exchange_do_insert_kyc_attributes(INT8, BYTEA, BYTEA, VARCHAR, INT4, VARCHAR, VARCHAR, INT8, INT8, INT8, BYTEA, BOOL, VARCHAR)
IS 'Inserts new KYC attributes and updates the status of the legitimization process and the AML status for the account';

View File

@ -963,3 +963,139 @@ BEGIN
CLOSE curs_transaction_exist; CLOSE curs_transaction_exist;
RETURN; RETURN;
END $$; END $$;
DO $$
BEGIN
CREATE TYPE exchange_do_array_reserve_insert_return_type
AS
(transaction_duplicate BOOLEAN
,ruuid INT8);
EXCEPTION
WHEN duplicate_object THEN null;
END
$$;
CREATE OR REPLACE FUNCTION exchange_do_array_reserves_insert(
IN in_gc_date INT8,
IN in_reserve_expiration INT8,
IN ina_reserve_pub BYTEA[],
IN ina_wire_ref INT8[],
IN ina_credit_val INT8[],
IN ina_credit_frac INT4[],
IN ina_exchange_account_name VARCHAR[],
IN ina_execution_date INT8[],
IN ina_wire_source_h_payto BYTEA[],
IN ina_payto_uri VARCHAR[],
IN ina_notify TEXT[])
RETURNS SETOF exchange_do_array_reserve_insert_return_type
LANGUAGE plpgsql
AS $$
DECLARE
curs REFCURSOR;
DECLARE
conflict BOOL;
DECLARE
dup BOOL;
DECLARE
uuid INT8;
DECLARE
i RECORD;
BEGIN
INSERT INTO wire_targets
(wire_target_h_payto
,payto_uri)
SELECT
wire_source_h_payto
,payto_uri
FROM
UNNEST (ina_wire_source_h_payto) AS wire_source_h_payto
,UNNEST (ina_payto_uri) AS payto_uri
ON CONFLICT DO NOTHING;
FOR i IN
SELECT
reserve_pub
,wire_ref
,credit_val
,credit_frac
,exchange_account_name
,execution_date
,wire_source_h_payto
,payto_uri
,notify
FROM
UNNEST (ina_reserve_pub) AS reserve_pub
,UNNEST (ina_wire_ref) AS wire_ref
,UNNEST (ina_credit_val) AS credit_val
,UNNEST (ina_credit_frac) AS credit_frac
,UNNEST (ina_exchange_account_name) AS exchange_account_name
,UNNEST (ina_execution_date) AS execution_date
,UNNEST (ina_wire_source_h_payto) AS wire_source_h_payto
,UNNEST (ina_notify) AS notify
LOOP
INSERT INTO reserves
(reserve_pub
,current_balance_val
,current_balance_frac
,expiration_date
,gc_date
) VALUES (
i.reserve_pub
,i.credit_val
,i.credit_frac
,in_reserve_expiration
,in_gc_date
)
ON CONFLICT DO NOTHING
RETURNING reserve_uuid
INTO uuid;
conflict = NOT FOUND;
INSERT INTO reserves_in
(reserve_pub
,wire_reference
,credit_val
,credit_frac
,exchange_account_section
,wire_source_h_payto
,execution_date
) VALUES (
i.reserve_pub
,i.wire_reference
,i.credit_val
,i.credit_frac
,i.exchange_account_section
,i.wire_source_h_payto
,i.execution_date
)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
IF conflict
THEN
dup = TRUE;
else
dup = FALSE;
END IF;
ELSE
IF NOT conflict
THEN
EXECUTE FORMAT (
'NOTIFY %s'
,i.notify);
END IF;
dup = FALSE;
END IF;
RETURN NEXT (dup,uuid);
END LOOP;
RETURN;
END $$;

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of 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 General Public License as published by the Free Software terms of the GNU General Public License as published by the Free Software
@ -29,43 +29,72 @@
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
TEH_PG_insert_kyc_attributes ( TEH_PG_insert_kyc_attributes (
void *cls, void *cls,
uint64_t process_row,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox, const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section, const char *provider_section,
const char *birthdate, uint32_t birthday,
struct GNUNET_TIME_Timestamp collection_time, struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time, const char *provider_account_id,
const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration_time,
size_t enc_attributes_size, size_t enc_attributes_size,
const void *enc_attributes) const void *enc_attributes,
bool require_aml)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Timestamp expiration
= GNUNET_TIME_absolute_to_timestamp (expiration_time);
struct TALER_KycCompletedEventP rep = {
.header.size = htons (sizeof (rep)),
.header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
.h_payto = *h_payto
};
char *kyc_completed_notify_s
= GNUNET_PG_get_event_notify_channel (&rep.header);
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&process_row),
GNUNET_PQ_query_param_auto_from_type (h_payto), GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_auto_from_type (kyc_prox), GNUNET_PQ_query_param_auto_from_type (kyc_prox),
GNUNET_PQ_query_param_string (provider_section), GNUNET_PQ_query_param_string (provider_section),
(NULL == birthdate) GNUNET_PQ_query_param_uint32 (&birthday),
(NULL == provider_account_id)
? GNUNET_PQ_query_param_null () ? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (birthdate), : GNUNET_PQ_query_param_string (provider_account_id),
(NULL == provider_legitimization_id)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (provider_legitimization_id),
GNUNET_PQ_query_param_timestamp (&collection_time), GNUNET_PQ_query_param_timestamp (&collection_time),
GNUNET_PQ_query_param_timestamp (&expiration_time), GNUNET_PQ_query_param_absolute_time (&expiration_time),
GNUNET_PQ_query_param_timestamp (&expiration),
GNUNET_PQ_query_param_fixed_size (enc_attributes, GNUNET_PQ_query_param_fixed_size (enc_attributes,
enc_attributes_size), enc_attributes_size),
GNUNET_PQ_query_param_bool (require_aml),
GNUNET_PQ_query_param_string (kyc_completed_notify_s),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
bool ok;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_bool ("out_ok",
&ok),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
PREPARE (pg, PREPARE (pg,
"insert_kyc_attributes", "insert_kyc_attributes",
"INSERT INTO kyc_attributes " "SELECT "
"(h_payto" " out_ok"
",kyc_prox" " FROM exchange_do_insert_kyc_attributes "
",provider" "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);");
",birthdate" qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
",collection_time"
",expiration_time"
",encrypted_attributes"
") VALUES "
"($1, $2, $3, $4, $5, $6, $7);");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_kyc_attributes", "insert_kyc_attributes",
params); params,
rs);
GNUNET_free (kyc_completed_notify_s);
if (qs < 0)
return qs;
if (! ok)
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
return qs;
} }

View File

@ -27,30 +27,39 @@
/** /**
* Store KYC attribute data. * Store KYC attribute data, update KYC process status and
* AML status for the given account.
* *
* @param cls closure * @param cls closure
* @param process_row KYC process row to update
* @param h_payto account for which the attribute data is stored * @param h_payto account for which the attribute data is stored
* @param kyc_prox key for similarity search * @param kyc_prox key for similarity search
* @param provider_section provider that must be checked * @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL; * @param provider_account_id provider account ID
* digits can be 0 if exact day, month or year are unknown * @param provider_legitimization_id provider legitimization ID
* @param birthday birthdate of user, in days after 1990, or 0 if unknown or definitively adult
* @param collection_time when was the data collected * @param collection_time when was the data collected
* @param expiration_time when does the data expire * @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes * @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data * @param enc_attributes encrypted attribute data
* @param require_aml true to trigger AML
* @return database transaction status * @return database transaction status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
TEH_PG_insert_kyc_attributes ( TEH_PG_insert_kyc_attributes (
void *cls, void *cls,
uint64_t process_row,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox, const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section, const char *provider_section,
const char *birthdate, uint32_t birthday,
struct GNUNET_TIME_Timestamp collection_time, struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time, const char *provider_account_id,
const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration_time,
size_t enc_attributes_size, size_t enc_attributes_size,
const void *enc_attributes); const void *enc_attributes,
bool require_aml);
#endif #endif

View File

@ -1995,10 +1995,6 @@ irbt_cb_table_kyc_attributes (struct PostgresClosure *pg,
&td->details.kyc_attributes.kyc_prox), &td->details.kyc_attributes.kyc_prox),
GNUNET_PQ_query_param_string ( GNUNET_PQ_query_param_string (
td->details.kyc_attributes.provider), td->details.kyc_attributes.provider),
(NULL == td->details.kyc_attributes.birthdate)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (
td->details.kyc_attributes.birthdate),
GNUNET_PQ_query_param_timestamp ( GNUNET_PQ_query_param_timestamp (
&td->details.kyc_attributes.collection_time), &td->details.kyc_attributes.collection_time),
GNUNET_PQ_query_param_timestamp ( GNUNET_PQ_query_param_timestamp (
@ -2016,12 +2012,11 @@ irbt_cb_table_kyc_attributes (struct PostgresClosure *pg,
",h_payto" ",h_payto"
",kyc_prox" ",kyc_prox"
",provider" ",provider"
",birthdate"
",collection_time" ",collection_time"
",expiration_time" ",expiration_time"
",encrypted_attributes" ",encrypted_attributes"
") VALUES " ") VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8);"); "($1, $2, $3, $4, $5, $6, $7);");
return GNUNET_PQ_eval_prepared_non_select (pg->conn, return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_into_table_kyc_attributes", "insert_into_table_kyc_attributes",
params); params);

View File

@ -2684,11 +2684,6 @@ lrbt_cb_table_kyc_attributes (void *cls,
GNUNET_PQ_result_spec_string ( GNUNET_PQ_result_spec_string (
"provider", "provider",
&td.details.kyc_attributes.provider), &td.details.kyc_attributes.provider),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string (
"birthdate",
&td.details.kyc_attributes.birthdate),
NULL),
GNUNET_PQ_result_spec_timestamp ( GNUNET_PQ_result_spec_timestamp (
"collection_time", "collection_time",
&td.details.kyc_attributes.collection_time), &td.details.kyc_attributes.collection_time),
@ -3577,7 +3572,6 @@ TEH_PG_lookup_records_by_table (void *cls,
",h_payto" ",h_payto"
",kyc_prox" ",kyc_prox"
",provider" ",provider"
",birthdate"
",collection_time" ",collection_time"
",expiration_time" ",expiration_time"
",encrypted_attributes" ",encrypted_attributes"

View File

@ -615,3 +615,284 @@ TEH_PG_reserves_in_insert (
GNUNET_free (rrs[i].notify_s); GNUNET_free (rrs[i].notify_s);
return qs; return qs;
} }
#if 0
/**
* Closure for our helper_cb()
*/
struct Context
{
/**
* Array of reserve UUIDs to initialize.
*/
uint64_t *reserve_uuids;
/**
* Array with entries set to 'true' for duplicate transactions.
*/
bool *transaction_duplicates;
/**
* Array with entries set to 'true' for rows with conflicts.
*/
bool *conflicts;
/**
* Set to #GNUNET_SYSERR on failures.
*/
struct GNUNET_GenericReturnValue status;
/**
* Single value (no array) set to true if we need
* to follow-up with an update.
*/
bool *needs_update;
};
/**
* Helper function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls closure of type `struct Context *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
helper_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct Context *ctx = cls;
for (unsigned int i = 0; i<num_results; i++)
{
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_bool (
"transaction_duplicate",
&ctx->transaction_duplicates[i]),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_uint64 ("ruuid",
&ctx->reserve_uuids[i]),
&ctx->conflicts[i]),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
ctx->status = GNUNET_SYSERR;
return;
}
*ctx->need_update |= ctx->conflicts[i];
}
}
enum GNUNET_DB_QueryStatus
TEH_PG_reserves_in_insertN (
void *cls,
const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
unsigned int reserves_length,
enum GNUNET_DB_QueryStatus *results)
{
struct PostgresClosure *pg = cls;
struct TALER_PaytoHashP h_paytos[GNUNET_NZL (reserves_length)];
char *notify_s[GNUNET_NZL (reserves_length)];
struct TALER_ReservePublicKeyP *reserve_pubs[GNUNET_NZL (reserves_length)];
struct TALER_Amount *balances[GNUNET_NZL (reserves_length)];
struct GNUNET_TIME_Timestamp execution_times[GNUNET_NZL (reserves_length)];
const char *sender_account_details[GNUNET_NZL (reserves_length)];
const char *exchange_account_names[GNUNET_NZL (reserves_length)];
uint64_t wire_references[GNUNET_NZL (reserves_length)];
uint64_t reserve_uuids[GNUNET_NZL (reserves_length)];
bool transaction_duplicates[GNUNET_NZL (reserves_length)];
bool conflicts[GNUNET_NZL (reserves_length)];
struct GNUNET_TIME_Timestamp reserve_expiration
= GNUNET_TIME_relative_to_timestamp (pg->idle_reserve_expiration_time);
struct GNUNET_TIME_Timestamp gc
= GNUNET_TIME_relative_to_timestamp (pg->legal_reserve_expiration_time);
bool needs_update = false;
enum GNUNET_DB_QueryStatus qs;
for (unsigned int i = 0; i<reserves_length; i++)
{
const struct TALER_EXCHANGEDB_ReserveInInfo *reserve = &reserves[i];
TALER_payto_hash (reserve->sender_account_details,
&h_paytos[i]);
notify_s[i] = compute_notify_on_reserve (reserve->reserve_pub);
reserve_pubs[i] = &reserve->reserve_pub;
balances[i] = &reserve->balance;
execution_times[i] = reserve->execution_time;
sender_account_details[i] = reserve->sender_account_details;
exchange_account_names[i] = reserve->exchange_account_name;
wire_references[i] = reserve->wire_reference;
}
/* NOTE: kind-of pointless to explicitly start a transaction here... */
if (GNUNET_OK !=
TEH_PG_preflight (pg))
{
GNUNET_break (0);
qs = GNUNET_DB_STATUS_HARD_ERROR;
goto finished;
}
if (GNUNET_OK !=
TEH_PG_start_read_committed (pg,
"READ_COMMITED"))
{
GNUNET_break (0);
qs = GNUNET_DB_STATUS_HARD_ERROR;
goto finished;
}
PREPARE (pg,
"reserves_insert_with_array",
"SELECT"
" transaction_duplicate"
",ruuid"
"FROM exchange_do_array_reserves_insert"
" ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_timestamp (&gc),
GNUNET_PQ_query_param_timestamp (&reserve_expiration),
GNUNET_PQ_query_param_array_auto_from_type (reserves_length,
reserve_pubs,
pg->conn),
GNUNET_PQ_query_param_array_uint64 (reserves_length,
wire_references,
pg->conn),
TALER_PQ_query_param_array_amount (reserves_length,
balances,
pg->conn),
GNUNET_PQ_query_param_array_string (reserves_length,
exchange_account_names,
pg->conn),
GNUNET_PQ_query_param_array_timestamp (reserves_length,
execution_times,
pg->conn),
GNUNET_PQ_query_param_array_bytes_same_size_cont_auto (
reserves_length,
h_paytos,
sizeof (struct GNUNET_PaytoHashP),
pg->conn),
GNUNET_PQ_query_param_array_string (reserves_length,
sender_account_details,
pg->conn),
GNUNET_PQ_query_param_array_string (reserves_length,
notify_s,
pg->conn),
GNUNET_PQ_query_param_end
};
struct Context ctx = {
.reserve_uuids = reserve_uuids,
.transaction_duplicates = transaction_duplicates,
.conflicts = conflicts,
.needs_update = &needs_update,
.status = GNUNET_OK
};
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"reserves_insert_with_array",
params,
&multi_res,
&ctx);
if ( (qs < 0) ||
(GNUNET_OK != ctx.status) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to insert into reserves (%d)\n",
qs);
goto finished;
}
}
{
enum GNUNET_DB_QueryStatus cs;
cs = TEH_PG_commit (pg);
if (cs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to commit\n");
qs = cs;
goto finished;
}
}
for (unsigned int i = 0; i<reserves_length; i++)
{
results[i] = transaction_duplicates[i]
? GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
: GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
if (! need_update)
{
qs = reserves_length;
goto finished;
}
if (GNUNET_OK !=
TEH_PG_start (pg,
"reserve-insert-continued"))
{
GNUNET_break (0);
qs = GNUNET_DB_STATUS_HARD_ERROR;
goto finished;
}
for (unsigned int i = 0; i<reserves_length; i++)
{
if (! conflicts[i])
continue;
{
bool duplicate;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (reserve_pubs[i]),
GNUNET_PQ_query_param_timestamp (&reserve_expiration),
GNUNET_PQ_query_param_uint64 (&wire_reference[i]),
TALER_PQ_query_param_amount (balances[i]),
GNUNET_PQ_query_param_string (exchange_account_names[i]),
GNUNET_PQ_query_param_auto_from_type (h_paytos[i]),
GNUNET_PQ_query_param_string (notify_s[i]),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_bool ("duplicate",
&duplicate),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"reserves_update",
params,
rs);
if (qs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to update reserves (%d)\n",
qs);
results[i] = qs;
goto finished;
}
results[i] = duplicate
? GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
: GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
}
finished:
for (unsigned int i = 0; i<reserves_length; i++)
GNUNET_free (rrs[i].notify_s);
return qs;
}
#endif

View File

@ -80,14 +80,9 @@ get_attributes_cb (void *cls,
size_t enc_attributes_size; size_t enc_attributes_size;
void *enc_attributes; void *enc_attributes;
char *provider; char *provider;
char *birthdate = NULL;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("provider", GNUNET_PQ_result_spec_string ("provider",
&provider), &provider),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("birthdate",
&birthdate),
NULL),
GNUNET_PQ_result_spec_timestamp ("collection_time", GNUNET_PQ_result_spec_timestamp ("collection_time",
&collection_time), &collection_time),
GNUNET_PQ_result_spec_timestamp ("expiration_time", GNUNET_PQ_result_spec_timestamp ("expiration_time",
@ -110,7 +105,6 @@ get_attributes_cb (void *cls,
ctx->cb (ctx->cb_cls, ctx->cb (ctx->cb_cls,
ctx->h_payto, ctx->h_payto,
provider, provider,
birthdate,
collection_time, collection_time,
expiration_time, expiration_time,
enc_attributes_size, enc_attributes_size,
@ -145,7 +139,6 @@ TEH_PG_select_kyc_attributes (
"select_kyc_attributes", "select_kyc_attributes",
"SELECT " "SELECT "
" provider" " provider"
",birthdate"
",collection_time" ",collection_time"
",expiration_time" ",expiration_time"
",encrypted_attributes" ",encrypted_attributes"

View File

@ -76,16 +76,11 @@ get_attributes_cb (void *cls,
size_t enc_attributes_size; size_t enc_attributes_size;
void *enc_attributes; void *enc_attributes;
char *provider; char *provider;
char *birthdate = NULL;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("h_payto", GNUNET_PQ_result_spec_auto_from_type ("h_payto",
&h_payto), &h_payto),
GNUNET_PQ_result_spec_string ("provider", GNUNET_PQ_result_spec_string ("provider",
&provider), &provider),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("birthdate",
&birthdate),
NULL),
GNUNET_PQ_result_spec_timestamp ("collection_time", GNUNET_PQ_result_spec_timestamp ("collection_time",
&collection_time), &collection_time),
GNUNET_PQ_result_spec_timestamp ("expiration_time", GNUNET_PQ_result_spec_timestamp ("expiration_time",
@ -108,7 +103,6 @@ get_attributes_cb (void *cls,
ctx->cb (ctx->cb_cls, ctx->cb (ctx->cb_cls,
&h_payto, &h_payto,
provider, provider,
birthdate,
collection_time, collection_time,
expiration_time, expiration_time,
enc_attributes_size, enc_attributes_size,
@ -143,7 +137,6 @@ TEH_PG_select_similar_kyc_attributes (
"SELECT " "SELECT "
" h_payto" " h_payto"
",provider" ",provider"
",birthdate"
",collection_time" ",collection_time"
",expiration_time" ",expiration_time"
",encrypted_attributes" ",encrypted_attributes"

View File

@ -1,68 +0,0 @@
/*
This file is part of TALER
Copyright (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file exchangedb/pg_update_kyc_attributes.c
* @brief Implementation of the update_kyc_attributes function for Postgres
* @author Christian Grothoff
*/
#include "platform.h"
#include "taler_error_codes.h"
#include "taler_dbevents.h"
#include "taler_pq_lib.h"
#include "pg_update_kyc_attributes.h"
#include "pg_helper.h"
enum GNUNET_DB_QueryStatus
TEH_PG_update_kyc_attributes (
void *cls,
const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,
const void *enc_attributes)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_auto_from_type (kyc_prox),
GNUNET_PQ_query_param_string (provider_section),
(NULL == birthdate)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (birthdate),
GNUNET_PQ_query_param_timestamp (&collection_time),
GNUNET_PQ_query_param_timestamp (&expiration_time),
GNUNET_PQ_query_param_fixed_size (enc_attributes,
enc_attributes_size),
GNUNET_PQ_query_param_end
};
PREPARE (pg,
"update_kyc_attributes",
"UPDATE kyc_attributes SET "
" kyc_prox=$2"
",birthdate=$4"
",collection_time=$5"
",expiration_time=$6"
",encrypted_attributes=$7"
" WHERE h_payto=$1 AND provider_section=$3;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_kyc_attributes",
params);
}

View File

@ -1,57 +0,0 @@
/*
This file is part of TALER
Copyright (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file exchangedb/pg_update_kyc_attributes.h
* @brief implementation of the update_kyc_attributes function for Postgres
* @author Christian Grothoff
*/
#ifndef PG_UPDATE_KYC_ATTRIBUTES_H
#define PG_UPDATE_KYC_ATTRIBUTES_H
#include "taler_util.h"
#include "taler_json_lib.h"
#include "taler_exchangedb_plugin.h"
/**
* Update KYC attribute data.
*
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @param kyc_prox key for similarity search
* @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected
* @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
TEH_PG_update_kyc_attributes (
void *cls,
const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,
const void *enc_attributes);
#endif

View File

@ -68,7 +68,8 @@ TEH_PG_update_kyc_process_by_row (
if (qs <= 0) if (qs <= 0)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to update legitimization process: %d\n", "Failed to update legitimization process %llu: %d\n",
(unsigned long long) process_row,
qs); qs);
return qs; return qs;
} }

View File

@ -207,7 +207,6 @@
#include "pg_setup_wire_target.h" #include "pg_setup_wire_target.h"
#include "pg_compute_shard.h" #include "pg_compute_shard.h"
#include "pg_insert_kyc_attributes.h" #include "pg_insert_kyc_attributes.h"
#include "pg_update_kyc_attributes.h"
#include "pg_select_similar_kyc_attributes.h" #include "pg_select_similar_kyc_attributes.h"
#include "pg_select_kyc_attributes.h" #include "pg_select_kyc_attributes.h"
#include "pg_insert_aml_officer.h" #include "pg_insert_aml_officer.h"
@ -754,8 +753,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &TEH_PG_set_purse_balance; = &TEH_PG_set_purse_balance;
plugin->insert_kyc_attributes plugin->insert_kyc_attributes
= &TEH_PG_insert_kyc_attributes; = &TEH_PG_insert_kyc_attributes;
plugin->update_kyc_attributes
= &TEH_PG_update_kyc_attributes;
plugin->select_similar_kyc_attributes plugin->select_similar_kyc_attributes
= &TEH_PG_select_similar_kyc_attributes; = &TEH_PG_select_similar_kyc_attributes;
plugin->select_kyc_attributes plugin->select_kyc_attributes

View File

@ -39,6 +39,7 @@ SET search_path TO exchange;
#include "exchange_do_insert_or_update_policy_details.sql" #include "exchange_do_insert_or_update_policy_details.sql"
#include "exchange_do_insert_aml_decision.sql" #include "exchange_do_insert_aml_decision.sql"
#include "exchange_do_insert_aml_officer.sql" #include "exchange_do_insert_aml_officer.sql"
#include "exchange_do_insert_kyc_attributes.sql"
#include "exchange_do_reserves_in_insert.sql" #include "exchange_do_reserves_in_insert.sql"
#include "exchange_do_batch_reserves_update.sql" #include "exchange_do_batch_reserves_update.sql"
#include "exchange_do_refund_by_coin.sql" #include "exchange_do_refund_by_coin.sql"

View File

@ -402,7 +402,7 @@ TALER_amount_multiply (struct TALER_Amount *result,
* #GNUNET_NO if value was already normalized * #GNUNET_NO if value was already normalized
* #GNUNET_SYSERR if value was invalid or could not be normalized * #GNUNET_SYSERR if value was invalid or could not be normalized
*/ */
int enum GNUNET_GenericReturnValue
TALER_amount_normalize (struct TALER_Amount *amount); TALER_amount_normalize (struct TALER_Amount *amount);

View File

@ -366,6 +366,10 @@ struct TALER_EXCHANGE_Keys
*/ */
char *asset_type; char *asset_type;
/**
* Set to true if tipping is allowed at this exchange.
*/
bool tipping_allowed;
}; };

View File

@ -756,7 +756,6 @@ struct TALER_EXCHANGEDB_TableData
struct TALER_PaytoHashP h_payto; struct TALER_PaytoHashP h_payto;
struct GNUNET_ShortHashCode kyc_prox; struct GNUNET_ShortHashCode kyc_prox;
char *provider; char *provider;
char *birthdate; /* NULL allowed! */
struct GNUNET_TIME_Timestamp collection_time; struct GNUNET_TIME_Timestamp collection_time;
struct GNUNET_TIME_Timestamp expiration_time; struct GNUNET_TIME_Timestamp expiration_time;
void *encrypted_attributes; void *encrypted_attributes;
@ -2429,8 +2428,6 @@ typedef void
* @param cls closure * @param cls closure
* @param h_payto account for which the attribute data is stored * @param h_payto account for which the attribute data is stored
* @param provider_section provider that must be checked * @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected * @param collection_time when was the data collected
* @param expiration_time when does the data expire * @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes * @param enc_attributes_size number of bytes in @a enc_attributes
@ -2441,7 +2438,6 @@ typedef void
void *cls, void *cls,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const char *provider_section, const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time, struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time, struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size, size_t enc_attributes_size,
@ -6765,59 +6761,39 @@ struct TALER_EXCHANGEDB_Plugin
/** /**
* Store KYC attribute data. * Store KYC attribute data, update KYC process status and
* AML status for the given account.
* *
* @param cls closure * @param cls closure
* @param process_row KYC process row to update
* @param h_payto account for which the attribute data is stored * @param h_payto account for which the attribute data is stored
* @param kyc_prox key for similarity search * @param kyc_prox key for similarity search
* @param provider_section provider that must be checked * @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL; * @param provider_account_id provider account ID
* digits can be 0 if exact day, month or year are unknown * @param provider_legitimization_id provider legitimization ID
* @param birthday birthdate of user, in days after 1990, or 0 if unknown or definitively adult
* @param collection_time when was the data collected * @param collection_time when was the data collected
* @param expiration_time when does the data expire * @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes * @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data * @param enc_attributes encrypted attribute data
* @param require_aml true to trigger AML
* @return database transaction status * @return database transaction status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
(*insert_kyc_attributes)( (*insert_kyc_attributes)(
void *cls, void *cls,
uint64_t process_row,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox, const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section, const char *provider_section,
const char *birthdate, uint32_t birthday,
struct GNUNET_TIME_Timestamp collection_time, struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time, const char *provider_account_id,
const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration_time,
size_t enc_attributes_size, size_t enc_attributes_size,
const void *enc_attributes); const void *enc_attributes,
bool require_aml);
/**
* Update KYC attribute data.
*
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @param kyc_prox key for similarity search
* @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected
* @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*update_kyc_attributes)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,
const void *enc_attributes);
/** /**

View File

@ -20,6 +20,7 @@ EXTRA_DIST = \
persona-sample-reply.json persona-sample-reply.json
bin_SCRIPTS = \ bin_SCRIPTS = \
taler-exchange-kyc-kycaid-converter.sh \
taler-exchange-kyc-persona-converter.sh taler-exchange-kyc-persona-converter.sh
lib_LTLIBRARIES = \ lib_LTLIBRARIES = \

View File

@ -12,6 +12,10 @@ PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
# How long is the KYC check valid? # How long is the KYC check valid?
KYC_KYCAID_VALIDITY = forever KYC_KYCAID_VALIDITY = forever
# Program that converts Persona KYC data into the
# GNU Taler format.
KYC_KYCAID_CONVERTER_HELPER = taler-exchange-kyc-kycaid-converter.sh
# Authentication token to use. # Authentication token to use.
KYC_KYCAID_AUTH_TOKEN = XXX KYC_KYCAID_AUTH_TOKEN = XXX

View File

@ -13,11 +13,11 @@ PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
KYC_OAUTH2_VALIDITY = forever KYC_OAUTH2_VALIDITY = forever
# URL where we initiate the user's login process # URL where we initiate the user's login process
KYC_OAUTH2_LOGIN_URL = http://kyc.example.com/login KYC_OAUTH2_AUTHORIZE_URL = https://kyc.example.com/authorize
# URL where we send the user's authentication information # URL where we send the user's authentication information
KYC_OAUTH2_AUTH_URL = http://kyc.example.com/auth KYC_OAUTH2_TOKEN_URL = https://kyc.example.com/token
# URL of the user info access point. # URL of the user info access point.
KYC_OAUTH2_INFO_URL = http://kyc.example.com/info KYC_OAUTH2_INFO_URL = https://kyc.example.com/info
# Where does the client get redirected upon completion? # Where does the client get redirected upon completion?
KYC_OAUTH2_POST_URL = http://example.com/thank-you KYC_OAUTH2_POST_URL = http://example.com/thank-you

View File

@ -87,6 +87,12 @@ struct TALER_KYCLOGIC_ProviderDetails
*/ */
char *form_id; char *form_id;
/**
* Helper binary to convert attributes returned by
* KYCAID into our internal format.
*/
char *conversion_helper;
/** /**
* Validity time for a successful KYC process. * Validity time for a successful KYC process.
*/ */
@ -215,6 +221,12 @@ struct TALER_KYCLOGIC_WebhookHandle
*/ */
struct PluginState *ps; struct PluginState *ps;
/**
* Handle to helper process to extract attributes
* we care about.
*/
struct TALER_JSON_ExternalConversion *econ;
/** /**
* Our configuration details. * Our configuration details.
*/ */
@ -225,6 +237,11 @@ struct TALER_KYCLOGIC_WebhookHandle
*/ */
struct MHD_Connection *connection; struct MHD_Connection *connection;
/**
* JSON response we got back, or NULL for none.
*/
json_t *json_response;
/** /**
* Verification ID from the service. * Verification ID from the service.
*/ */
@ -261,6 +278,11 @@ struct TALER_KYCLOGIC_WebhookHandle
*/ */
uint64_t process_row; uint64_t process_row;
/**
* HTTP response code we got from KYCAID.
*/
unsigned int kycaid_response_code;
/** /**
* HTTP response code to return asynchronously. * HTTP response code to return asynchronously.
*/ */
@ -277,6 +299,7 @@ static void
kycaid_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd) kycaid_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
{ {
curl_slist_free_all (pd->slist); curl_slist_free_all (pd->slist);
GNUNET_free (pd->conversion_helper);
GNUNET_free (pd->auth_token); GNUNET_free (pd->auth_token);
GNUNET_free (pd->form_id); GNUNET_free (pd->form_id);
GNUNET_free (pd->section); GNUNET_free (pd->section);
@ -337,6 +360,18 @@ kycaid_load_configuration (void *cls,
kycaid_unload_configuration (pd); kycaid_unload_configuration (pd);
return NULL; return NULL;
} }
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (ps->cfg,
provider_section_name,
"KYC_KYCAID_CONVERTER_HELPER",
&pd->conversion_helper))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
"KYC_KYCAID_CONVERTER_HELPER");
kycaid_unload_configuration (pd);
return NULL;
}
{ {
char *auth; char *auth;
@ -695,11 +730,21 @@ kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
GNUNET_SCHEDULER_cancel (wh->task); GNUNET_SCHEDULER_cancel (wh->task);
wh->task = NULL; wh->task = NULL;
} }
if (NULL != wh->econ)
{
TALER_JSON_external_conversion_stop (wh->econ);
wh->econ = NULL;
}
if (NULL != wh->job) if (NULL != wh->job)
{ {
GNUNET_CURL_job_cancel (wh->job); GNUNET_CURL_job_cancel (wh->job);
wh->job = NULL; wh->job = NULL;
} }
if (NULL != wh->json_response)
{
json_decref (wh->json_response);
wh->json_response = NULL;
}
GNUNET_free (wh->verification_id); GNUNET_free (wh->verification_id);
GNUNET_free (wh->applicant_id); GNUNET_free (wh->applicant_id);
GNUNET_free (wh->url); GNUNET_free (wh->url);
@ -750,6 +795,97 @@ log_failure (json_t *verifications)
} }
/**
* Type of a callback that receives a JSON @a result.
*
* @param cls closure our `struct TALER_KYCLOGIC_WebhookHandle *`
* @param status_type how did the process die
* @param code termination status code from the process
* @param result converted attribute data, NULL on failure
*/
static void
webhook_conversion_cb (void *cls,
enum GNUNET_OS_ProcessStatusType status_type,
unsigned long code,
const json_t *result)
{
struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
struct GNUNET_TIME_Absolute expiration;
struct MHD_Response *resp;
wh->econ = NULL;
if ( (0 == code) ||
(NULL == result) )
{
/* No result, but *our helper* was OK => bad input */
GNUNET_break_op (0);
json_dumpf (wh->json_response,
stderr,
JSON_INDENT (2));
resp = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_uint64 ("kycaid_http_status",
wh->kycaid_response_code),
GNUNET_JSON_pack_object_incref ("kycaid_body",
(json_t *) wh->json_response));
wh->cb (wh->cb_cls,
wh->process_row,
&wh->h_payto,
wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
NULL,
MHD_HTTP_BAD_GATEWAY,
resp);
kycaid_webhook_cancel (wh);
return;
}
if (NULL == result)
{
/* Failure in our helper */
GNUNET_break (0);
json_dumpf (wh->json_response,
stderr,
JSON_INDENT (2));
resp = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_uint64 ("kycaid_http_status",
wh->kycaid_response_code),
GNUNET_JSON_pack_object_incref ("kycaid_body",
(json_t *) wh->json_response));
wh->cb (wh->cb_cls,
wh->process_row,
&wh->h_payto,
wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
NULL,
MHD_HTTP_BAD_GATEWAY,
resp);
kycaid_webhook_cancel (wh);
return;
}
expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
resp = MHD_create_response_from_buffer (0,
"",
MHD_RESPMEM_PERSISTENT);
wh->cb (wh->cb_cls,
wh->process_row,
&wh->h_payto,
wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_SUCCESS,
expiration,
result,
MHD_HTTP_NO_CONTENT,
resp);
kycaid_webhook_cancel (wh);
}
/** /**
* Function called when we're done processing the * Function called when we're done processing the
* HTTP "/applicants/{verification_id}" request. * HTTP "/applicants/{verification_id}" request.
@ -768,246 +904,20 @@ handle_webhook_finished (void *cls,
struct MHD_Response *resp; struct MHD_Response *resp;
wh->job = NULL; wh->job = NULL;
wh->kycaid_response_code = response_code;
wh->json_response = json_incref ((json_t *) j);
switch (response_code) switch (response_code)
{ {
case MHD_HTTP_OK: case MHD_HTTP_OK:
{ {
const char *type;
const char *profile_status; const char *profile_status;
const char *first_name = NULL;
const char *last_name = NULL;
const char *middle_name = NULL;
const char *dob = NULL;
const char *residence_country = NULL;
const char *gender = NULL;
bool pep = false;
bool no_pep = false;
const char *company_name = NULL;
const char *business_activity_id = NULL;
const char *registration_country = NULL;
const char *email = NULL;
const char *phone = NULL;
json_t *addresses = NULL;
json_t *documents = NULL;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("type",
&type),
GNUNET_JSON_spec_string ("profile_status",
&profile_status), /* valid, invalid, pending */
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("email",
&email),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("phone",
&phone),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("addresses",
&addresses),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("documents",
&documents),
NULL),
GNUNET_JSON_spec_end ()
};
struct GNUNET_JSON_Specification bspec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("company_name",
&company_name),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("business_activity_id",
&business_activity_id),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("registration_country",
&registration_country),
NULL),
GNUNET_JSON_spec_end ()
};
struct GNUNET_JSON_Specification pspec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("first_name",
&first_name),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("middle_name",
&middle_name),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("last_name",
&last_name),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("dob",
&dob),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("residence_country",
&residence_country),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("gender",
&gender),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_bool ("pep",
&pep),
&no_pep),
GNUNET_JSON_spec_end ()
};
struct GNUNET_JSON_Specification *ispec = NULL;
struct GNUNET_TIME_Absolute expiration;
bool no_parse;
enum TALER_KYCLOGIC_KycUserType ut;
no_parse = (GNUNET_OK != profile_status = json_string_value (
GNUNET_JSON_parse (j, json_object_get (
spec, j,
NULL, NULL)); "profile_status"));
if (! no_parse) if (0 != strcasecmp ("valid",
{
ut = (0 == strcasecmp ("person",
type))
? TALER_KYCLOGIC_KYC_UT_INDIVIDUAL
: TALER_KYCLOGIC_KYC_UT_BUSINESS;
ispec = (ut == TALER_KYCLOGIC_KYC_UT_INDIVIDUAL)
? pspec
: bspec;
no_parse = (GNUNET_OK !=
GNUNET_JSON_parse (j,
ispec,
NULL, NULL));
}
if (no_parse)
{
GNUNET_break_op (0);
json_dumpf (j,
stderr,
JSON_INDENT (2));
resp = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_uint64 ("kycaid_http_status",
response_code),
GNUNET_JSON_pack_object_incref ("kycaid_body",
(json_t *) j));
wh->cb (wh->cb_cls,
wh->process_row,
&wh->h_payto,
wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
NULL,
MHD_HTTP_BAD_GATEWAY,
resp);
break;
}
if (0 == strcasecmp ("valid",
profile_status)) profile_status))
{
log_failure (json_object_get (j,
"decline_reasons"));
}
resp = MHD_create_response_from_buffer (0,
"",
MHD_RESPMEM_PERSISTENT);
if (0 == strcasecmp ("valid",
profile_status))
{
json_t *attr;
if (ut == TALER_KYCLOGIC_KYC_UT_INDIVIDUAL)
{
char *name = NULL;
if ( (NULL != last_name) ||
(NULL != first_name) ||
(NULL != middle_name) )
{
GNUNET_asprintf (&name,
"%s, %s %s",
(NULL != last_name)
? last_name
: "",
(NULL != first_name)
? first_name
: "",
(NULL != middle_name)
? middle_name
: "");
}
attr = GNUNET_JSON_PACK (
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_BIRTHDATE,
dob)),
GNUNET_JSON_pack_allow_null (
no_pep
? GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_PEP,
NULL)
: GNUNET_JSON_pack_bool (
TALER_ATTRIBUTE_PEP,
pep)),
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_PHONE,
phone)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_EMAIL,
email)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_RESIDENCES,
residence_country))
);
GNUNET_free (name);
}
else
{
attr = GNUNET_JSON_PACK (
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_COMPANY_NAME,
company_name)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_PHONE,
phone)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_EMAIL,
email)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_REGISTRATION_COUNTRY,
residence_country))
);
}
// FIXME: do something about addresses & documents!
expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
wh->cb (wh->cb_cls,
wh->process_row,
&wh->h_payto,
wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_SUCCESS,
expiration,
attr,
MHD_HTTP_NO_CONTENT,
resp);
json_decref (attr);
}
else
{ {
enum TALER_KYCLOGIC_KycStatus ks; enum TALER_KYCLOGIC_KycStatus ks;
@ -1015,6 +925,9 @@ handle_webhook_finished (void *cls,
profile_status)) profile_status))
? TALER_KYCLOGIC_STATUS_PENDING ? TALER_KYCLOGIC_STATUS_PENDING
: TALER_KYCLOGIC_STATUS_USER_ABORTED; : TALER_KYCLOGIC_STATUS_USER_ABORTED;
resp = MHD_create_response_from_buffer (0,
"",
MHD_RESPMEM_PERSISTENT);
wh->cb (wh->cb_cls, wh->cb (wh->cb_cls,
wh->process_row, wh->process_row,
&wh->h_payto, &wh->h_payto,
@ -1026,9 +939,19 @@ handle_webhook_finished (void *cls,
NULL, NULL,
MHD_HTTP_NO_CONTENT, MHD_HTTP_NO_CONTENT,
resp); resp);
break;
} }
GNUNET_JSON_parse_free (ispec); wh->econ
GNUNET_JSON_parse_free (spec); = TALER_JSON_external_conversion_start (
j,
&webhook_conversion_cb,
wh,
wh->pd->conversion_helper,
wh->pd->conversion_helper,
"-a",
wh->pd->auth_token,
NULL);
return;
} }
break; break;
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:

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
@ -75,15 +75,21 @@ struct TALER_KYCLOGIC_ProviderDetails
char *section; char *section;
/** /**
* URL of the OAuth2.0 endpoint for KYC checks. * URL of the Challenger ``/setup`` endpoint for
* (token/auth) * approving address validations. NULL if not used.
*/ */
char *auth_url; char *setup_url;
/** /**
* URL of the OAuth2.0 endpoint for KYC checks. * URL of the OAuth2.0 endpoint for KYC checks.
*/ */
char *login_url; char *authorize_url;
/**
* URL of the OAuth2.0 endpoint for KYC checks.
* (token/auth)
*/
char *token_url;
/** /**
* URL of the user info access endpoint. * URL of the user info access endpoint.
@ -147,6 +153,11 @@ struct TALER_KYCLOGIC_InitiateHandle
*/ */
struct GNUNET_SCHEDULER_Task *task; struct GNUNET_SCHEDULER_Task *task;
/**
* Handle for the OAuth 2.0 setup request.
*/
struct GNUNET_CURL_Job *job;
/** /**
* Continuation to call. * Continuation to call.
*/ */
@ -283,8 +294,9 @@ static void
oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd) oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
{ {
GNUNET_free (pd->section); GNUNET_free (pd->section);
GNUNET_free (pd->auth_url); GNUNET_free (pd->token_url);
GNUNET_free (pd->login_url); GNUNET_free (pd->setup_url);
GNUNET_free (pd->authorize_url);
GNUNET_free (pd->info_url); GNUNET_free (pd->info_url);
GNUNET_free (pd->client_id); GNUNET_free (pd->client_id);
GNUNET_free (pd->client_secret); GNUNET_free (pd->client_secret);
@ -327,12 +339,12 @@ oauth2_load_configuration (void *cls,
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg, GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name, provider_section_name,
"KYC_OAUTH2_AUTH_URL", "KYC_OAUTH2_TOKEN_URL",
&s)) &s))
{ {
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name, provider_section_name,
"KYC_OAUTH2_AUTH_URL"); "KYC_OAUTH2_TOKEN_URL");
oauth2_unload_configuration (pd); oauth2_unload_configuration (pd);
return NULL; return NULL;
} }
@ -346,23 +358,23 @@ oauth2_load_configuration (void *cls,
{ {
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
provider_section_name, provider_section_name,
"KYC_OAUTH2_AUTH_URL", "KYC_OAUTH2_TOKEN_URL",
"not a valid URL"); "not a valid URL");
GNUNET_free (s); GNUNET_free (s);
oauth2_unload_configuration (pd); oauth2_unload_configuration (pd);
return NULL; return NULL;
} }
pd->auth_url = s; pd->token_url = s;
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg, GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name, provider_section_name,
"KYC_OAUTH2_LOGIN_URL", "KYC_OAUTH2_AUTHORIZE_URL",
&s)) &s))
{ {
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name, provider_section_name,
"KYC_OAUTH2_LOGIN_URL"); "KYC_OAUTH2_AUTHORIZE_URL");
oauth2_unload_configuration (pd); oauth2_unload_configuration (pd);
return NULL; return NULL;
} }
@ -376,13 +388,41 @@ oauth2_load_configuration (void *cls,
{ {
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
provider_section_name, provider_section_name,
"KYC_OAUTH2_LOGIN_URL", "KYC_OAUTH2_AUTHORIZE_URL",
"not a valid URL"); "not a valid URL");
oauth2_unload_configuration (pd); oauth2_unload_configuration (pd);
GNUNET_free (s); GNUNET_free (s);
return NULL; return NULL;
} }
pd->login_url = s; if (NULL != strchr (s, '#'))
{
const char *extra = strchr (s, '#');
const char *slash = strrchr (s, '/');
if ( (0 != strcmp (extra,
"#setup")) ||
(NULL == slash) )
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
"KYC_OAUTH2_AUTHORIZE_URL",
"not a valid authorze URL (bad fragment)");
oauth2_unload_configuration (pd);
GNUNET_free (s);
return NULL;
}
pd->authorize_url = GNUNET_strndup (s,
extra - s);
GNUNET_asprintf (&pd->setup_url,
"%.*s/setup",
(int) (slash - s),
s);
GNUNET_free (s);
}
else
{
pd->authorize_url = s;
}
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg, GNUNET_CONFIGURATION_get_value_string (ps->cfg,
@ -480,19 +520,20 @@ oauth2_load_configuration (void *cls,
* how to begin the OAuth2.0 checking process to * how to begin the OAuth2.0 checking process to
* the client. * the client.
* *
* @param cls a `struct TALER_KYCLOGIC_InitiateHandle *` * @param ih process to redirect for
* @param authorize_url authorization URL to use
*/ */
static void static void
initiate_task (void *cls) initiate_with_url (struct TALER_KYCLOGIC_InitiateHandle *ih,
const char *authorize_url)
{ {
struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
struct PluginState *ps = pd->ps; struct PluginState *ps = pd->ps;
char *hps; char *hps;
char *url; char *url;
char legi_s[42]; char legi_s[42];
ih->task = NULL;
GNUNET_snprintf (legi_s, GNUNET_snprintf (legi_s,
sizeof (legi_s), sizeof (legi_s),
"%llu", "%llu",
@ -515,16 +556,11 @@ initiate_task (void *cls)
} }
GNUNET_asprintf (&url, GNUNET_asprintf (&url,
"%s?response_type=code&client_id=%s&redirect_uri=%s", "%s?response_type=code&client_id=%s&redirect_uri=%s",
pd->login_url, authorize_url,
pd->client_id, pd->client_id,
redirect_uri_encoded); redirect_uri_encoded);
GNUNET_free (redirect_uri_encoded); GNUNET_free (redirect_uri_encoded);
} }
/* FIXME-API: why do we *redirect* the client here,
instead of making the HTTP request *ourselves*
and forwarding the response? This prevents us
from using authentication on initiation,
(which is desirable for challenger!) */
ih->cb (ih->cb_cls, ih->cb (ih->cb_cls,
TALER_EC_NONE, TALER_EC_NONE,
url, url,
@ -537,6 +573,142 @@ initiate_task (void *cls)
} }
/**
* After we are done with the CURL interaction we
* need to update our database state with the information
* retrieved.
*
* @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
* @param response_code HTTP response code from server, 0 on hard error
* @param response in JSON, NULL if response was not in JSON format
*/
static void
handle_curl_setup_finished (void *cls,
long response_code,
const void *response)
{
struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
const json_t *j = response;
ih->job = NULL;
switch (response_code)
{
case MHD_HTTP_OK:
{
const char *nonce;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("nonce",
&nonce),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
const char *emsg;
unsigned int line;
char *url;
res = GNUNET_JSON_parse (j,
spec,
&emsg,
&line);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
json_dumpf (j,
stderr,
JSON_INDENT (2));
ih->cb (ih->cb_cls,
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
NULL,
NULL,
NULL,
"Unexpected response from KYC gateway: setup must return a nonce");
GNUNET_free (ih);
return;
}
GNUNET_asprintf (&url,
"%s/%s",
pd->setup_url,
nonce);
initiate_with_url (ih,
url);
GNUNET_free (url);
return;
}
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"/setup URL returned HTTP status %u\n",
(unsigned int) response_code);
ih->cb (ih->cb_cls,
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
NULL,
NULL,
NULL,
"/setup request to OAuth 2.0 backend returned unexpected HTTP status code");
GNUNET_free (ih);
return;
}
}
/**
* Logic to asynchronously return the response for how to begin the OAuth2.0
* checking process to the client. May first request a dynamic URL via
* ``/setup`` if configured to use a client-authenticated setup process.
*
* @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
*/
static void
initiate_task (void *cls)
{
struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
struct PluginState *ps = pd->ps;
char *hdr;
struct curl_slist *slist;
CURL *eh;
ih->task = NULL;
if (NULL == pd->setup_url)
{
initiate_with_url (ih,
pd->authorize_url);
return;
}
eh = curl_easy_init ();
if (NULL == eh)
{
GNUNET_break (0);
ih->cb (ih->cb_cls,
TALER_EC_GENERIC_ALLOCATION_FAILURE,
NULL,
NULL,
NULL,
"curl_easy_init() failed");
GNUNET_free (ih);
return;
}
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_URL,
pd->setup_url));
GNUNET_asprintf (&hdr,
"%s: Bearer %s",
MHD_HTTP_HEADER_AUTHORIZATION,
pd->client_secret);
slist = curl_slist_append (NULL,
hdr);
ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
eh,
slist,
&handle_curl_setup_finished,
ih);
curl_slist_free_all (slist);
GNUNET_free (hdr);
}
/** /**
* Initiate KYC check. * Initiate KYC check.
* *
@ -584,6 +756,11 @@ oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
GNUNET_SCHEDULER_cancel (ih->task); GNUNET_SCHEDULER_cancel (ih->task);
ih->task = NULL; ih->task = NULL;
} }
if (NULL != ih->job)
{
GNUNET_CURL_job_cancel (ih->job);
ih->job = NULL;
}
GNUNET_free (ih); GNUNET_free (ih);
} }
@ -1002,7 +1179,7 @@ handle_curl_login_finished (void *cls,
eh = curl_easy_init (); eh = curl_easy_init ();
if (NULL == eh) if (NULL == eh)
{ {
GNUNET_break_op (0); GNUNET_break (0);
ph->response ph->response
= TALER_MHD_make_error ( = TALER_MHD_make_error (
TALER_EC_GENERIC_ALLOCATION_FAILURE, TALER_EC_GENERIC_ALLOCATION_FAILURE,
@ -1129,7 +1306,7 @@ oauth2_proof (void *cls,
GNUNET_assert (CURLE_OK == GNUNET_assert (CURLE_OK ==
curl_easy_setopt (ph->eh, curl_easy_setopt (ph->eh,
CURLOPT_URL, CURLOPT_URL,
pd->auth_url)); pd->token_url));
GNUNET_assert (CURLE_OK == GNUNET_assert (CURLE_OK ==
curl_easy_setopt (ph->eh, curl_easy_setopt (ph->eh,
CURLOPT_POST, CURLOPT_POST,

View File

@ -0,0 +1,86 @@
#!/bin/bash
# This file is in the public domain.
#
# This code converts (some of) the JSON output from KYCAID into the GNU Taler
# specific KYC attribute data (again in JSON format). We may need to download
# and inline file data in the process, for authorization pass "-a" with the
# respective bearer token.
#
# Die if anything goes wrong.
set -eu
# Parse command-line options
while getopts ':a:' OPTION; do
case "$OPTION" in
a)
TOKEN="$OPTARG"
;;
?)
echo "Unrecognized command line option"
exit 1
;;
esac
done
# First, extract everything from stdin.
J=$(jq '{"type":.type,"email":.email,"phone":.phone,"first_name":.first_name,"name-middle":.middle_name,"last_name":.last_name,"dob":.dob,"residence_country":.residence_country,"gender":.gender,"pep":.pep,"addresses":.addresses,"documents":.documents,"company_name":.company_name,"business_activity_id":.business_activity_id,"registration_country":.registration_country,"documents":.documents,"decline_reasons":.decline_reasons}')
# TODO:
# log_failure (json_object_get (j, "decline_reasons"));
TYPE=$(echo "$J" | jq -r '.person')
N=0
DOCS_RAW=""
DOCS_JSON=""
for ID in $(jq -r '.documents[]|select(.status=="valid")|.id')
do
TYPE=$(jq -r ".documents[]|select(.id==\"$ID\")|.type")
EXPIRY=$(jq -r ".documents[]|select(.id==\"$ID\")|.expiry_date")
DOCUMENT_FILE=$(mktemp -t tmp.XXXXXXXXXX)
# Authoriazation: Token $TOKEN
DOCUMENT_URL="https://api.kycaid.com/documents/$ID"
if [ -z "${TOKEN:-}" ]
then
wget -q --output-document=- "$DOCUMENT_URL" \
| gnunet-base32 > ${DOCUMENT_FILE}
else
wget -q --output-document=- "$DOCUMENT_URL" \
--header "Authorization: Token $TOKEN" \
| gnunet-base32 > ${DOCUMENT_FILE}
fi
DOCS_RAW="$DOCS_RAW --rawfile photo$N \"${DOCUMENT_FILE}\""
if [ "$N" = 0 ]
then
DOCS_JSON="{\"type\":\"$TYPE\",\"image\":\$photo$N}"
else
DOCS_JSON="{\"type\":\"$TYPE\",\"image\":\$photo$N},$DOCS_JSON"
fi
N=$(expr $N + 1)
done
if [ "person" = "${TYPE}" ]
then
# Next, combine some fields into larger values.
FULLNAME=$(echo "$J" | jq -r '[.first_name,.middle_name,.last_name]|join(" ")')
# STREET=$(echo $J | jq -r '[."street-1",."street-2"]|join(" ")')
# CITY=$(echo $J | jq -r '[.postcode,.city,."address-subdivision,.cc"]|join(" ")')
# Combine into final result for individual.
# FIXME: does jq tolerate 'pep = NULL' here?
echo "$J" | jq \
--arg full_name "${FULLNAME}" \
'{$full_name,"birthdate":.dob,"pep":.pep,"phone":."phone","email",.email,"residences":.residence_country}'
else
# Combine into final result for business.
echo "$J" | jq \
--arg full_name "${FULLNAME}" \
$DOCS_RAW \
"{\"company_name\":.company_name,\"phone\":.phone,\"email\":.email,\"registration_country\":.registration_country,\"documents\":[${DOCS_JSON}]}"
fi
exit 0

View File

@ -733,6 +733,7 @@ decode_keys_json (const json_t *resp_obj,
struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangePublicKeyP pub;
const char *currency; const char *currency;
const char *asset_type; const char *asset_type;
bool tipping_allowed = true;
json_t *wblwk = NULL; json_t *wblwk = NULL;
struct GNUNET_JSON_Specification mspec[] = { struct GNUNET_JSON_Specification mspec[] = {
GNUNET_JSON_spec_fixed_auto ("denominations_sig", GNUNET_JSON_spec_fixed_auto ("denominations_sig",
@ -749,6 +750,10 @@ decode_keys_json (const json_t *resp_obj,
&currency), &currency),
GNUNET_JSON_spec_string ("asset_type", GNUNET_JSON_spec_string ("asset_type",
&asset_type), &asset_type),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_bool ("tipping_allowed",
&tipping_allowed),
NULL),
GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("wallet_balance_limit_without_kyc", GNUNET_JSON_spec_json ("wallet_balance_limit_without_kyc",
&wblwk), &wblwk),
@ -819,6 +824,7 @@ decode_keys_json (const json_t *resp_obj,
NULL, NULL)); NULL, NULL));
key_data->currency = GNUNET_strdup (currency); key_data->currency = GNUNET_strdup (currency);
key_data->asset_type = GNUNET_strdup (asset_type); key_data->asset_type = GNUNET_strdup (asset_type);
key_data->tipping_allowed = tipping_allowed;
/* parse the global fees */ /* parse the global fees */
{ {

View File

@ -78,7 +78,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (cfg, GNUNET_CONFIGURATION_get_value_number (cfg,
section, section,
"port", "PORT",
&port)) &port))
{ {
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,

View File

@ -150,7 +150,7 @@ qconv_json (void *cls,
if (SQLITE_OK != sqlite3_bind_text (stmt, if (SQLITE_OK != sqlite3_bind_text (stmt,
(int) off, (int) off,
str, str,
strlen (str) + 1, strlen (str),
SQLITE_TRANSIENT)) SQLITE_TRANSIENT))
return GNUNET_SYSERR; return GNUNET_SYSERR;
GNUNET_free (str); GNUNET_free (str);

View File

@ -15,6 +15,8 @@ taler_mustach_tool_SOURCES = \
taler_mustach_tool_LDADD = \ taler_mustach_tool_LDADD = \
libmustach.la \ libmustach.la \
-ljansson -ljansson
taler_mustach_tool_CFLAGS = \
-DTOOL=MUSTACH_TOOL_JANSSON
lib_LTLIBRARIES = \ lib_LTLIBRARIES = \
libtalertemplating.la libtalertemplating.la

View File

@ -245,7 +245,7 @@ basic-tests: mustach
@$(MAKE) -C test3 test @$(MAKE) -C test3 test
@$(MAKE) -C test4 test @$(MAKE) -C test4 test
@$(MAKE) -C test5 test @$(MAKE) -C test5 test
@$(MAKE) -C test6 test # @$(MAKE) -C test6 test
spec-tests: $(TESTSPECS) spec-tests: $(TESTSPECS)
@ -298,4 +298,3 @@ manuals: mustach.1.gz
mustach.1.gz: mustach.1.scd mustach.1.gz: mustach.1.scd
if which scdoc >/dev/null 2>&1; then scdoc < mustach.1.scd | gzip > mustach.1.gz; fi if which scdoc >/dev/null 2>&1; then scdoc < mustach.1.scd | gzip > mustach.1.gz; fi

View File

@ -5,7 +5,7 @@ template specification.
The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach). The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach).
The simpliest way to use mustach is to copy the files **mustach.h** and **mustach.c** The simplest way to use mustach is to copy the files **mustach.h** and **mustach.c**
directly into your project and use it. directly into your project and use it.
If you are using one of the JSON libraries listed below, you can get extended feature If you are using one of the JSON libraries listed below, you can get extended feature
@ -85,7 +85,7 @@ It then outputs the result of applying the templates files to the JSON file.
### Portability ### Portability
Some system does not provide *open_memstream*. In that case, tell your Some system does not provide *open_memstream*. In that case, tell your
prefered compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**. preferred compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**.
Example: Example:
CFLAGS=-DNO_OPEN_MEMSTREAM make CFLAGS=-DNO_OPEN_MEMSTREAM make
@ -163,7 +163,7 @@ Here is the summary.
Flag name | Description Flag name | Description
-------------------------------+------------------------------------------------ -------------------------------+------------------------------------------------
Mustach_With_Colon | Explicit tag substition with colon Mustach_With_Colon | Explicit tag substitution with colon
Mustach_With_EmptyTag | Empty Tag Allowed Mustach_With_EmptyTag | Empty Tag Allowed
-------------------------------+------------------------------------------------ -------------------------------+------------------------------------------------
Mustach_With_Equal | Value Testing Equality Mustach_With_Equal | Value Testing Equality
@ -180,7 +180,7 @@ For the details, see below.
### Explicit Tag Substitution With Colon (Mustach_With_Colon) ### Explicit Tag Substitution With Colon (Mustach_With_Colon)
In somecases the name of the key used for substition begins with a In somecases the name of the key used for substitution begins with a
character reserved for mustach: one of `#`, `^`, `/`, `&`, `{`, `>` and `=`. character reserved for mustach: one of `#`, `^`, `/`, `&`, `{`, `>` and `=`.
This extension introduces the special character `:` to explicitly This extension introduces the special character `:` to explicitly
@ -311,4 +311,3 @@ The table below summarize the changes.
fdmustach_json_c | mustach_json_c_fd fdmustach_json_c | mustach_json_c_fd
mustach_json_c | mustach_json_c_mem mustach_json_c | mustach_json_c_mem
mustach_json_c | mustach_json_c_write mustach_json_c | mustach_json_c_write

View File

@ -174,8 +174,6 @@ int main(int ac, char **av)
#define MUSTACH_TOOL_JANSSON 2 #define MUSTACH_TOOL_JANSSON 2
#define MUSTACH_TOOL_CJSON 3 #define MUSTACH_TOOL_CJSON 3
#define TOOL MUSTACH_TOOL_JANSSON
#if TOOL == MUSTACH_TOOL_JSON_C #if TOOL == MUSTACH_TOOL_JSON_C
#include "mustach-json-c.h" #include "mustach-json-c.h"

View File

@ -5,6 +5,9 @@ set -eu
# even bother testing for it in configure.ac. # even bother testing for it in configure.ac.
# However, in that case, skip the test suite. # However, in that case, skip the test suite.
export CFLAGS="-g"
make -f Makefile.orig mustach || exit 77 make -f Makefile.orig mustach || exit 77
make -f Makefile.orig test make -f Makefile.orig clean || true
make -f Makefile.orig basic-tests
make -f Makefile.orig clean || true make -f Makefile.orig clean || true

View File

@ -183,7 +183,7 @@ TALER_TEMPLATING_fill (const char *tmpl,
(eno = mustach_jansson_mem (tmpl, (eno = mustach_jansson_mem (tmpl,
0, /* length of tmpl */ 0, /* length of tmpl */
(json_t *) root, (json_t *) root,
Mustach_With_NoExtensions, Mustach_With_AllExtensions,
(char **) result, (char **) result,
result_size))) result_size)))
{ {

View File

@ -5,12 +5,12 @@
"in_ca": true, "in_ca": true,
"person": false, "person": false,
"repo": [ "repo": [
{ "name": "resque", "who": [ { "committer": "joe" }, { "reviewer": "avrel" }, { "committer": "william" } ] }, { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] },
{ "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" }, { "committer": "greg" } ] }, { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] },
{ "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "committer": "greg" } ] } { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] }
], ],
"person?": { "name": "Jon" }, "person?": { "name": "Jon" },
"special": "----{{extra}}----", "special": "----{{extra}}----\n",
"extra": 3.14159, "extra": 3.14159,
"#sharp": "#", "#sharp": "#",
"!bang": "!", "!bang": "!",

View File

@ -12,7 +12,7 @@ Shown.
{{/person}} {{/person}}
{{#repo}} {{#repo}}
<b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}} <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}}
{{/repo}} {{/repo}}
{{#person?}} {{#person?}}
@ -23,7 +23,7 @@ Shown.
===================================== =====================================
%(%! gros commentaire %)% %(%! gros commentaire %)%
%(%#repo%)% %(%#repo%)%
<b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)% <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)%
%(%/repo%)% %(%/repo%)%
===================================== =====================================
%(%={{ }}=%)% %(%={{ }}=%)%

View File

@ -1,49 +0,0 @@
Hello Chris
You have just won 10000 dollars!
Well, 6000 dollars, after taxes.
Shown.
No person
<b>resque</b> reviewers: avrel committers: joe william
<b>hub</b> reviewers: avrel committers: jack greg
<b>rip</b> reviewers: joe jack committers: greg
Hi Jon!
=====================================
<b>resque</b> reviewers: avrel committers: joe william
<b>hub</b> reviewers: avrel committers: jack greg
<b>rip</b> reviewers: joe jack committers: greg
=====================================
ggggggggg
----3.14159----
jjjjjjjjj
end
#
!
~
~
/ see json pointers IETF RFC 6901
^
=
:
&gt;

View File

@ -1,22 +0,0 @@
<h1>Colors</h1>
<li><strong>red</strong></li>
<li><a href="#Green">green</a></li>
<li><a href="#Blue">blue</a></li>

View File

@ -1,15 +0,0 @@
* Chris
* 18
* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
* <b>GitHub & Co</b>
* <b>GitHub & Co</b>
* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
* <b>GitHub & Co</b>
* <b>GitHub & Co</b>
* <ul><li>Chris</li><li>Kross</li></ul>
* skills: <ul><li>JavaScript</li><li>PHP</li><li>Java</li></ul>
* age: 18

View File

@ -1,100 +0,0 @@
This are extensions!!
Jon
25
Fred
The other Fred.
Hello Jon
No Harry? Hey Calahan...
Hello Fred
Hello Fred#2
Hello Jon, 25 years
Hello Henry, 27 years
Salut Amed, 24 ans
Jon: /25/25
Henry: /27/
Amed: 24/24/24
Jon: /25/25
Henry: /27/
Amed: 24/24/24
(1) person: { "name": "Jon", "age": 25 }
(2) name: Jon
(2) age: 25
(1) person.name: Fred
(1) person.name=Fred: The other Fred.
(1) persons: [ { "name": "Jon", "age": 25, "lang": "en" }, { "name": "Henry", "age": 27, "lang": "en" }, { "name": "Amed", "age": 24, "lang": "fr" } ]
(1) fellows: { "Jon": { "age": 25, "lang": "en" }, "Henry": { "age": 27, "lang": "en" }, "Amed": { "age": 24, "lang": "fr" } }
(2) Jon: { "age": 25, "lang": "en" }
(3) age: 25
(3) lang: en
(2) Henry: { "age": 27, "lang": "en" }
(3) age: 27
(3) lang: en
(2) Amed: { "age": 24, "lang": "fr" }
(3) age: 24
(3) lang: fr

View File

@ -5,12 +5,12 @@
"in_ca": true, "in_ca": true,
"person": false, "person": false,
"repo": [ "repo": [
{ "name": "resque", "who": [ { "committer": "joe" }, { "reviewer": "avrel" }, { "committer": "william" } ] }, { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] },
{ "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" }, { "committer": "greg" } ] }, { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] },
{ "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "committer": "greg" } ] } { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] }
], ],
"person?": { "name": "Jon" }, "person?": { "name": "Jon" },
"special": "----{{extra}}----", "special": "----{{extra}}----\n",
"extra": 3.14159, "extra": 3.14159,
"#sharp": "#", "#sharp": "#",
"!bang": "!", "!bang": "!",

View File

@ -1,6 +1,6 @@
must3.mustache == BEGIN must3.mustache == BEGIN
{{#repo}} {{#repo}}
<b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}} <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}}
{{/repo}} {{/repo}}
{{#person?}} {{#person?}}
@ -11,7 +11,7 @@ must3.mustache == BEGIN
===================================== =====================================
%(%! big comment %)% %(%! big comment %)%
%(%#repo%)% %(%#repo%)%
<b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)% <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)%
%(%/repo%)% %(%/repo%)%
===================================== =====================================
must3.mustache == END must3.mustache == END

View File

@ -1,60 +0,0 @@
=====================================
from json
----3.14159----
=====================================
not found
=====================================
without extension first
must2 == BEGIN
Hello Chris
You have just won 10000 dollars!
Well, 6000 dollars, after taxes.
Shown.
No person
must2 == END
=====================================
last with extension
must3.mustache == BEGIN
<b>resque</b> reviewers: avrel committers: joe william
<b>hub</b> reviewers: avrel committers: jack greg
<b>rip</b> reviewers: joe jack committers: greg
Hi Jon!
=====================================
<b>resque</b> reviewers: avrel committers: joe william
<b>hub</b> reviewers: avrel committers: jack greg
<b>rip</b> reviewers: joe jack committers: greg
=====================================
must3.mustache == END
=====================================
Ensure must3 didn't change specials
Hi Jon!
%(%#person?%)%
Hi %(%name%)%!
%(%/person?%)%

View File

@ -1 +0,0 @@
special ==SHOULD NOT BE SEEN==

View File

@ -1 +0,0 @@
special.mustache ==SHOULD NOT BE SEEN==

Some files were not shown because too many files have changed in this diff Show More