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) \
coverage.sh \
gnunet.tag \
microhttpd.tag
microhttpd.tag \
packages
# Change the set of supported languages here. You should
# 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
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=""
for f in $changed;
@ -28,7 +28,7 @@ done
if [ $RET = 1 ];
then
echo "Run"
echo "uncrustify --no-backup -c uncrustify.cfg ${crustified}"
echo "uncrustify --replace -c uncrustify.cfg ${crustified}"
echo "before committing."
fi
exit $RET

View File

@ -12,6 +12,14 @@
# 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
@ -26,14 +34,17 @@ UNIXPATH_MODE = 666
# Bank accounts used by the exchange should be specified here:
[exchange-account-1]
enable_credit = no
enable_debit = no
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 =
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

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

View File

@ -24,6 +24,7 @@ man_MANS = \
prebuilt/man/taler-exchange-drain.1 \
prebuilt/man/taler-exchange-expire.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-offline.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 add exchange to database: entry already exists\n");
TALER_AUDITORDB_plugin_unload (adb);
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
}
TALER_AUDITORDB_plugin_unload (adb);

View File

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

View File

@ -12,6 +12,7 @@ CURRENCY_ROUND_UNIT = EUR:0.01
[exchange]
AML_THRESHOLD = EUR:99999999
SIGNKEY_LEGAL_DURATION = 2 years
# HTTP port the exchange listens to
@ -50,6 +51,7 @@ HTTP_PORT = 8082
SERVE = http
MAX_DEBT = EUR:100000000000.0
MAX_DEBT_BANK = EUR:1000000000000000.0
DATABASE = bank-db.sqlite3
[benchmark]
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
* @return #GNUNET_OK on success
*/
static int
static enum GNUNET_GenericReturnValue
parallel_benchmark (TALER_TESTING_Main main_cb,
void *main_cb_cls,
const char *config_file)
@ -565,7 +565,7 @@ parallel_benchmark (TALER_TESTING_Main main_cb,
if (GNUNET_OK !=
TALER_TESTING_prepare_bank (cfg_filename,
GNUNET_NO,
"exchange-account-2",
"exchange-account-test",
&bc))
{
return 1;
@ -1061,32 +1061,20 @@ main (int argc,
}
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
will have forgotten everything... */
GNUNET_assert (GNUNET_OK ==
if (GNUNET_OK !=
TALER_TESTING_prepare_exchange (cfg_filename,
(GNUNET_YES == use_fakebank)
? GNUNET_YES
: 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
{

View File

@ -3397,14 +3397,28 @@ do_set_global_fee (char *const *args)
(NULL == args[3]) ||
(NULL == args[4]) ||
(NULL == args[5]) ||
(NULL == args[6]) ||
( (1 != sscanf (args[0],
(NULL == args[6]) )
{
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",
&year,
&dummy)) &&
(0 != strcasecmp ("now",
args[0])) ) ||
(GNUNET_OK !=
args[0])) )
{
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],
&fees.history)) ||
(GNUNET_OK !=
@ -3412,20 +3426,34 @@ do_set_global_fee (char *const *args)
&fees.account)) ||
(GNUNET_OK !=
TALER_string_to_amount (args[3],
&fees.purse)) ||
(GNUNET_OK !=
&fees.purse)) )
{
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],
&purse_timeout)) ||
(GNUNET_OK !=
GNUNET_STRINGS_fancy_time_to_relative (args[5],
&history_expiration)) ||
(1 != sscanf (args[6],
"%u%c",
&purse_account_limit,
&dummy)) )
&history_expiration)) )
{
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 ();
global_ret = EXIT_INVALIDARGUMENT;
return;

View File

@ -15,6 +15,8 @@ pkgcfg_DATA = \
exchange.conf
# Programs
bin_SCRIPTS = \
taler-exchange-kyc-aml-pep-trigger.sh
bin_PROGRAMS = \
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_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_kyc.c taler-exchange-httpd_common_kyc.h \
taler-exchange-httpd_config.c taler-exchange-httpd_config.h \
taler-exchange-httpd_contract.c taler-exchange-httpd_contract.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.post \
exchange.conf \
$(bin_SCRIPTS) \
$(check_SCRIPTS)

View File

@ -6,10 +6,23 @@
# This must be adjusted to your actual installation.
# 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
# in the database. Should be a high-entropy nonce.
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
# limit is the minimum of this value and the first expected
# 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)) )
{
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;
}
if (GNUNET_OK !=
TALER_config_get_amount (cfg,
"taler",
"exchange",
"AML_THRESHOLD",
&aml_threshold))
{
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;
}

View File

@ -153,6 +153,16 @@ struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
*/
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
* merge into a reserve before always triggering
@ -1844,6 +1854,17 @@ exchange_serve_process_config (void)
"valid relative time expected");
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 !=
TALER_config_get_currency (TEH_cfg,
&TEH_currency))
@ -1855,19 +1876,30 @@ exchange_serve_process_config (void)
}
if (GNUNET_OK !=
TALER_config_get_amount (TEH_cfg,
"taler",
"exchange",
"AML_THRESHOLD",
&TEH_aml_threshold))
{
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;
}
if (0 != strcmp (TEH_currency,
TEH_aml_threshold.currency))
{
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;
}
if (GNUNET_OK !=

View File

@ -64,6 +64,11 @@ extern int TEH_check_invariants_flag;
*/
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.
*/
@ -97,6 +102,11 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
*/
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
* 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 h_payto account for which the attribute data is stored
* @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
@ -55,7 +53,6 @@ kyc_attribute_cb (
void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
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),
GNUNET_JSON_pack_string ("asset_type",
asset_type),
GNUNET_JSON_pack_bool ("tipping_allowed",
GNUNET_YES == TEH_enable_tipping),
GNUNET_JSON_pack_data_auto ("master_public_key",
&TEH_master_public_key),
GNUNET_JSON_pack_time_rel ("reserve_closing_delay",

View File

@ -1,6 +1,6 @@
/*
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
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 <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_attributes.h"
#include "taler_json_lib.h"
#include "taler_kyclogic_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_responses.h"
@ -68,6 +68,11 @@ struct KycProofContext
*/
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
* 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.
*
@ -192,74 +219,40 @@ proof_cb (
kpc->ph = NULL;
GNUNET_async_scope_enter (&rc->async_scope_id,
&old_scope);
if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
{
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,
&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->kat = TEH_kyc_finished (&rc->async_scope_id,
kpc->process_row,
kpc->provider_section,
&kpc->h_payto,
kpc->provider_section,
provider_user_id,
provider_legitimization_id,
expiration);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
expiration,
attributes,
http_status,
response,
&proof_finish,
kpc);
if (NULL == kpc->kat)
{
GNUNET_break (0);
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
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,
"set_kyc_ok");
GNUNET_async_scope_restore (&old_scope);
return;
response = TALER_MHD_make_error (
TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
"[exchange] AML_KYC_TRIGGER");
}
}
else
if (NULL == kpc->kat)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC process #%llu failed with status %d\n",
(unsigned long long) kpc->process_row,
status);
proof_finish (kpc,
http_status,
response);
}
kpc->response_code = http_status;
kpc->response = response;
kpc_resume (kpc);
GNUNET_async_scope_restore (&old_scope);
}
@ -279,6 +272,11 @@ clean_kpc (struct TEH_RequestContext *rc)
kpc->logic->proof_cancel (kpc->ph);
kpc->ph = NULL;
}
if (NULL != kpc->kat)
{
TEH_kyc_finished_cancel (kpc->kat);
kpc->kat = NULL;
}
if (NULL != kpc->response)
{
MHD_destroy_response (kpc->response);

View File

@ -1,6 +1,6 @@
/*
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
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_mhd_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_responses.h"
@ -53,6 +54,11 @@ struct KycWebhookContext
*/
struct TEH_RequestContext *rc;
/**
* Handle for the KYC-AML trigger interaction.
*/
struct TEH_KycAmlTrigger *kat;
/**
* 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.
*
@ -178,58 +206,27 @@ webhook_finished_cb (
switch (status)
{
case TALER_KYCLOGIC_STATUS_SUCCESS:
/* _successfully_ resumed case */
{
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,
kwh->kat = TEH_kyc_finished (
&kwh->rc->async_scope_id,
process_row,
provider_section,
account_id,
provider_section,
provider_user_id,
provider_legitimization_id,
expiration);
if (qs < 0)
expiration,
attributes,
http_status,
response,
&kyc_aml_webhook_finished,
kwh);
if (NULL == kwh->kat)
{
GNUNET_break (0);
kwh->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"set_kyc_ok");
kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
kwh_resume (kwh);
return;
}
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
if (NULL != response)
MHD_destroy_response (response);
response = TALER_MHD_make_error (
TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
"[exchange] AML_KYC_TRIGGER");
}
break;
default:
@ -241,9 +238,10 @@ webhook_finished_cb (
status);
break;
}
kwh->response = response;
kwh->response_code = http_status;
kwh_resume (kwh);
if (NULL == kwh->kat)
kyc_aml_webhook_finished (kwh,
http_status,
response);
}
@ -262,6 +260,11 @@ clean_kwh (struct TEH_RequestContext *rc)
kwh->plugin->webhook_cancel (kwh->wh);
kwh->wh = NULL;
}
if (NULL != kwh->kat)
{
TEH_kyc_finished_cancel (kwh->kat);
kwh->kat = NULL;
}
if (NULL != 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 h_payto account for which the attribute data is stored
* @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
@ -169,7 +167,6 @@ static void
kyc_process_cb (void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,

View File

@ -64,8 +64,6 @@ struct ReserveAttestContext
* @param cls our `struct ReserveAttestContext *`
* @param h_payto account for which the attribute data is stored
* @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
@ -75,7 +73,6 @@ static void
kyc_process_cb (void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
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 = EUR
CURRENCY_ROUND_UNIT = EUR:0.01
AML_THRESHOLD = EUR:1000000
[auditor]
TINY_AMOUNT = EUR:0.01
[exchange]
AML_THRESHOLD = EUR:1000000
# Directory with our terms of service.
TERMS_DIR = ../../contrib/tos

View File

@ -31,7 +31,7 @@ BEGIN
',current_balance_frac INT4 NOT NULL DEFAULT(0)'
',purses_active 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'
',gc_date INT8 NOT NULL'
') %s ;'
@ -80,6 +80,12 @@ BEGIN
,table_name
,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
$$;

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_create_aggregation_transient.h pg_create_aggregation_transient.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_kyc_attributes.h pg_select_kyc_attributes.c \
pg_insert_aml_officer.h pg_insert_aml_officer.c \

View File

@ -1,6 +1,6 @@
--
-- 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
-- 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);
SET search_path TO exchange;
#include "0004-kyc_attributes.sql"
#include "0004-wire_accounts.sql"
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;
RETURN;
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
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
terms of the GNU General Public License as published by the Free Software
@ -29,43 +29,72 @@
enum GNUNET_DB_QueryStatus
TEH_PG_insert_kyc_attributes (
void *cls,
uint64_t process_row,
const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section,
const char *birthdate,
uint32_t birthday,
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,
const void *enc_attributes)
const void *enc_attributes,
bool require_aml)
{
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[] = {
GNUNET_PQ_query_param_uint64 (&process_row),
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_uint32 (&birthday),
(NULL == provider_account_id)
? 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 (&expiration_time),
GNUNET_PQ_query_param_absolute_time (&expiration_time),
GNUNET_PQ_query_param_timestamp (&expiration),
GNUNET_PQ_query_param_fixed_size (enc_attributes,
enc_attributes_size),
GNUNET_PQ_query_param_bool (require_aml),
GNUNET_PQ_query_param_string (kyc_completed_notify_s),
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,
"insert_kyc_attributes",
"INSERT INTO kyc_attributes "
"(h_payto"
",kyc_prox"
",provider"
",birthdate"
",collection_time"
",expiration_time"
",encrypted_attributes"
") VALUES "
"($1, $2, $3, $4, $5, $6, $7);");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"SELECT "
" out_ok"
" FROM exchange_do_insert_kyc_attributes "
"($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"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 process_row KYC process row to update
* @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 provider_account_id provider account ID
* @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 expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
* @param require_aml true to trigger AML
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
TEH_PG_insert_kyc_attributes (
void *cls,
uint64_t process_row,
const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section,
const char *birthdate,
uint32_t birthday,
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,
const void *enc_attributes);
const void *enc_attributes,
bool require_aml);
#endif

View File

@ -1995,10 +1995,6 @@ irbt_cb_table_kyc_attributes (struct PostgresClosure *pg,
&td->details.kyc_attributes.kyc_prox),
GNUNET_PQ_query_param_string (
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 (
&td->details.kyc_attributes.collection_time),
GNUNET_PQ_query_param_timestamp (
@ -2016,12 +2012,11 @@ irbt_cb_table_kyc_attributes (struct PostgresClosure *pg,
",h_payto"
",kyc_prox"
",provider"
",birthdate"
",collection_time"
",expiration_time"
",encrypted_attributes"
") 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,
"insert_into_table_kyc_attributes",
params);

View File

@ -2684,11 +2684,6 @@ lrbt_cb_table_kyc_attributes (void *cls,
GNUNET_PQ_result_spec_string (
"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 (
"collection_time",
&td.details.kyc_attributes.collection_time),
@ -3577,7 +3572,6 @@ TEH_PG_lookup_records_by_table (void *cls,
",h_payto"
",kyc_prox"
",provider"
",birthdate"
",collection_time"
",expiration_time"
",encrypted_attributes"

View File

@ -615,3 +615,284 @@ TEH_PG_reserves_in_insert (
GNUNET_free (rrs[i].notify_s);
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;
void *enc_attributes;
char *provider;
char *birthdate = NULL;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("provider",
&provider),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("birthdate",
&birthdate),
NULL),
GNUNET_PQ_result_spec_timestamp ("collection_time",
&collection_time),
GNUNET_PQ_result_spec_timestamp ("expiration_time",
@ -110,7 +105,6 @@ get_attributes_cb (void *cls,
ctx->cb (ctx->cb_cls,
ctx->h_payto,
provider,
birthdate,
collection_time,
expiration_time,
enc_attributes_size,
@ -145,7 +139,6 @@ TEH_PG_select_kyc_attributes (
"select_kyc_attributes",
"SELECT "
" provider"
",birthdate"
",collection_time"
",expiration_time"
",encrypted_attributes"

View File

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

View File

@ -207,7 +207,6 @@
#include "pg_setup_wire_target.h"
#include "pg_compute_shard.h"
#include "pg_insert_kyc_attributes.h"
#include "pg_update_kyc_attributes.h"
#include "pg_select_similar_kyc_attributes.h"
#include "pg_select_kyc_attributes.h"
#include "pg_insert_aml_officer.h"
@ -754,8 +753,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &TEH_PG_set_purse_balance;
plugin->insert_kyc_attributes
= &TEH_PG_insert_kyc_attributes;
plugin->update_kyc_attributes
= &TEH_PG_update_kyc_attributes;
plugin->select_similar_kyc_attributes
= &TEH_PG_select_similar_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_aml_decision.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_batch_reserves_update.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_SYSERR if value was invalid or could not be normalized
*/
int
enum GNUNET_GenericReturnValue
TALER_amount_normalize (struct TALER_Amount *amount);

View File

@ -366,6 +366,10 @@ struct TALER_EXCHANGE_Keys
*/
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 GNUNET_ShortHashCode kyc_prox;
char *provider;
char *birthdate; /* NULL allowed! */
struct GNUNET_TIME_Timestamp collection_time;
struct GNUNET_TIME_Timestamp expiration_time;
void *encrypted_attributes;
@ -2429,8 +2428,6 @@ typedef void
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @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
@ -2441,7 +2438,6 @@ typedef void
void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
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 process_row KYC process row to update
* @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 provider_account_id provider account ID
* @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 expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
* @param require_aml true to trigger AML
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*insert_kyc_attributes)(
void *cls,
uint64_t process_row,
const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section,
const char *birthdate,
uint32_t birthday,
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,
const void *enc_attributes);
/**
* 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);
const void *enc_attributes,
bool require_aml);
/**

View File

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

View File

@ -12,6 +12,10 @@ PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
# How long is the KYC check valid?
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.
KYC_KYCAID_AUTH_TOKEN = XXX

View File

@ -13,11 +13,11 @@ PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
KYC_OAUTH2_VALIDITY = forever
# 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
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.
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?
KYC_OAUTH2_POST_URL = http://example.com/thank-you

View File

@ -87,6 +87,12 @@ struct TALER_KYCLOGIC_ProviderDetails
*/
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.
*/
@ -215,6 +221,12 @@ struct TALER_KYCLOGIC_WebhookHandle
*/
struct PluginState *ps;
/**
* Handle to helper process to extract attributes
* we care about.
*/
struct TALER_JSON_ExternalConversion *econ;
/**
* Our configuration details.
*/
@ -225,6 +237,11 @@ struct TALER_KYCLOGIC_WebhookHandle
*/
struct MHD_Connection *connection;
/**
* JSON response we got back, or NULL for none.
*/
json_t *json_response;
/**
* Verification ID from the service.
*/
@ -261,6 +278,11 @@ struct TALER_KYCLOGIC_WebhookHandle
*/
uint64_t process_row;
/**
* HTTP response code we got from KYCAID.
*/
unsigned int kycaid_response_code;
/**
* HTTP response code to return asynchronously.
*/
@ -277,6 +299,7 @@ static void
kycaid_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
{
curl_slist_free_all (pd->slist);
GNUNET_free (pd->conversion_helper);
GNUNET_free (pd->auth_token);
GNUNET_free (pd->form_id);
GNUNET_free (pd->section);
@ -337,6 +360,18 @@ kycaid_load_configuration (void *cls,
kycaid_unload_configuration (pd);
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;
@ -695,11 +730,21 @@ kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
GNUNET_SCHEDULER_cancel (wh->task);
wh->task = NULL;
}
if (NULL != wh->econ)
{
TALER_JSON_external_conversion_stop (wh->econ);
wh->econ = NULL;
}
if (NULL != wh->job)
{
GNUNET_CURL_job_cancel (wh->job);
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->applicant_id);
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
* HTTP "/applicants/{verification_id}" request.
@ -768,246 +904,20 @@ handle_webhook_finished (void *cls,
struct MHD_Response *resp;
wh->job = NULL;
wh->kycaid_response_code = response_code;
wh->json_response = json_incref ((json_t *) j);
switch (response_code)
{
case MHD_HTTP_OK:
{
const char *type;
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 !=
GNUNET_JSON_parse (j,
spec,
NULL, NULL));
if (! no_parse)
{
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 = json_string_value (
json_object_get (
j,
"profile_status"));
if (0 != strcasecmp ("valid",
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;
@ -1015,6 +925,9 @@ handle_webhook_finished (void *cls,
profile_status))
? TALER_KYCLOGIC_STATUS_PENDING
: TALER_KYCLOGIC_STATUS_USER_ABORTED;
resp = MHD_create_response_from_buffer (0,
"",
MHD_RESPMEM_PERSISTENT);
wh->cb (wh->cb_cls,
wh->process_row,
&wh->h_payto,
@ -1026,9 +939,19 @@ handle_webhook_finished (void *cls,
NULL,
MHD_HTTP_NO_CONTENT,
resp);
break;
}
GNUNET_JSON_parse_free (ispec);
GNUNET_JSON_parse_free (spec);
wh->econ
= 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;
case MHD_HTTP_BAD_REQUEST:

View File

@ -1,6 +1,6 @@
/*
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
terms of the GNU Affero General Public License as published by the Free Software
@ -75,15 +75,21 @@ struct TALER_KYCLOGIC_ProviderDetails
char *section;
/**
* URL of the OAuth2.0 endpoint for KYC checks.
* (token/auth)
* URL of the Challenger ``/setup`` endpoint for
* approving address validations. NULL if not used.
*/
char *auth_url;
char *setup_url;
/**
* 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.
@ -147,6 +153,11 @@ struct TALER_KYCLOGIC_InitiateHandle
*/
struct GNUNET_SCHEDULER_Task *task;
/**
* Handle for the OAuth 2.0 setup request.
*/
struct GNUNET_CURL_Job *job;
/**
* Continuation to call.
*/
@ -283,8 +294,9 @@ static void
oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
{
GNUNET_free (pd->section);
GNUNET_free (pd->auth_url);
GNUNET_free (pd->login_url);
GNUNET_free (pd->token_url);
GNUNET_free (pd->setup_url);
GNUNET_free (pd->authorize_url);
GNUNET_free (pd->info_url);
GNUNET_free (pd->client_id);
GNUNET_free (pd->client_secret);
@ -327,12 +339,12 @@ oauth2_load_configuration (void *cls,
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
"KYC_OAUTH2_AUTH_URL",
"KYC_OAUTH2_TOKEN_URL",
&s))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
"KYC_OAUTH2_AUTH_URL");
"KYC_OAUTH2_TOKEN_URL");
oauth2_unload_configuration (pd);
return NULL;
}
@ -346,23 +358,23 @@ oauth2_load_configuration (void *cls,
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
"KYC_OAUTH2_AUTH_URL",
"KYC_OAUTH2_TOKEN_URL",
"not a valid URL");
GNUNET_free (s);
oauth2_unload_configuration (pd);
return NULL;
}
pd->auth_url = s;
pd->token_url = s;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
"KYC_OAUTH2_LOGIN_URL",
"KYC_OAUTH2_AUTHORIZE_URL",
&s))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
"KYC_OAUTH2_LOGIN_URL");
"KYC_OAUTH2_AUTHORIZE_URL");
oauth2_unload_configuration (pd);
return NULL;
}
@ -376,13 +388,41 @@ oauth2_load_configuration (void *cls,
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
"KYC_OAUTH2_LOGIN_URL",
"KYC_OAUTH2_AUTHORIZE_URL",
"not a valid URL");
oauth2_unload_configuration (pd);
GNUNET_free (s);
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 !=
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
* the client.
*
* @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
* @param ih process to redirect for
* @param authorize_url authorization URL to use
*/
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;
struct PluginState *ps = pd->ps;
char *hps;
char *url;
char legi_s[42];
ih->task = NULL;
GNUNET_snprintf (legi_s,
sizeof (legi_s),
"%llu",
@ -515,16 +556,11 @@ initiate_task (void *cls)
}
GNUNET_asprintf (&url,
"%s?response_type=code&client_id=%s&redirect_uri=%s",
pd->login_url,
authorize_url,
pd->client_id,
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,
TALER_EC_NONE,
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.
*
@ -584,6 +756,11 @@ oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
GNUNET_SCHEDULER_cancel (ih->task);
ih->task = NULL;
}
if (NULL != ih->job)
{
GNUNET_CURL_job_cancel (ih->job);
ih->job = NULL;
}
GNUNET_free (ih);
}
@ -1002,7 +1179,7 @@ handle_curl_login_finished (void *cls,
eh = curl_easy_init ();
if (NULL == eh)
{
GNUNET_break_op (0);
GNUNET_break (0);
ph->response
= TALER_MHD_make_error (
TALER_EC_GENERIC_ALLOCATION_FAILURE,
@ -1129,7 +1306,7 @@ oauth2_proof (void *cls,
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (ph->eh,
CURLOPT_URL,
pd->auth_url));
pd->token_url));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (ph->eh,
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;
const char *currency;
const char *asset_type;
bool tipping_allowed = true;
json_t *wblwk = NULL;
struct GNUNET_JSON_Specification mspec[] = {
GNUNET_JSON_spec_fixed_auto ("denominations_sig",
@ -749,6 +750,10 @@ decode_keys_json (const json_t *resp_obj,
&currency),
GNUNET_JSON_spec_string ("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_json ("wallet_balance_limit_without_kyc",
&wblwk),
@ -819,6 +824,7 @@ decode_keys_json (const json_t *resp_obj,
NULL, NULL));
key_data->currency = GNUNET_strdup (currency);
key_data->asset_type = GNUNET_strdup (asset_type);
key_data->tipping_allowed = tipping_allowed;
/* parse the global fees */
{

View File

@ -78,7 +78,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (cfg,
section,
"port",
"PORT",
&port))
{
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,
(int) off,
str,
strlen (str) + 1,
strlen (str),
SQLITE_TRANSIENT))
return GNUNET_SYSERR;
GNUNET_free (str);

View File

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

View File

@ -245,7 +245,7 @@ basic-tests: mustach
@$(MAKE) -C test3 test
@$(MAKE) -C test4 test
@$(MAKE) -C test5 test
@$(MAKE) -C test6 test
# @$(MAKE) -C test6 test
spec-tests: $(TESTSPECS)
@ -298,4 +298,3 @@ manuals: mustach.1.gz
mustach.1.gz: mustach.1.scd
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 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.
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
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:
CFLAGS=-DNO_OPEN_MEMSTREAM make
@ -163,7 +163,7 @@ Here is the summary.
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_Equal | Value Testing Equality
@ -180,7 +180,7 @@ For the details, see below.
### 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 `=`.
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
mustach_json_c | mustach_json_c_mem
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_CJSON 3
#define TOOL MUSTACH_TOOL_JANSSON
#if TOOL == MUSTACH_TOOL_JSON_C
#include "mustach-json-c.h"

View File

@ -5,6 +5,9 @@ set -eu
# even bother testing for it in configure.ac.
# However, in that case, skip the test suite.
export CFLAGS="-g"
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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ Shown.
{{/person}}
{{#repo}}
<b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}}
<b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}}
{{/repo}}
{{#person?}}
@ -23,7 +23,7 @@ Shown.
=====================================
%(%! gros commentaire %)%
%(%#repo%)%
<b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)%
<b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)%
%(%/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,
"person": false,
"repo": [
{ "name": "resque", "who": [ { "committer": "joe" }, { "reviewer": "avrel" }, { "committer": "william" } ] },
{ "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" }, { "committer": "greg" } ] },
{ "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "committer": "greg" } ] }
{ "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] },
{ "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] },
{ "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] }
],
"person?": { "name": "Jon" },
"special": "----{{extra}}----",
"special": "----{{extra}}----\n",
"extra": 3.14159,
"#sharp": "#",
"!bang": "!",

View File

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