Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file exchangedb/iterate_aml_decisions.c
18 : * @brief Implementation of the iterate_aml_decisions function for Postgres
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/taler_pq_lib.h"
22 : #include "taler/taler_kyclogic_lib.h"
23 : #include "exchange-database/iterate_aml_decisions.h"
24 : #include "helper.h"
25 :
26 :
27 : /**
28 : * Hard upper bound on the number of records returned by a single
29 : * call, regardless of the limit requested by the client.
30 : */
31 : #define MAX_RECORDS 50000
32 :
33 :
34 : /**
35 : * Closure for #handle_aml_result.
36 : */
37 : struct AmlProcessResultContext
38 : {
39 : /**
40 : * Function to call on each result.
41 : */
42 : TALER_EXCHANGEDB_AmlDecisionCallback cb;
43 :
44 : /**
45 : * Closure for @e cb.
46 : */
47 : void *cb_cls;
48 :
49 : /**
50 : * Plugin context.
51 : */
52 : struct TALER_EXCHANGEDB_PostgresContext *pg;
53 :
54 : /**
55 : * Set to #GNUNET_SYSERR on serious errors.
56 : */
57 : enum GNUNET_GenericReturnValue status;
58 : };
59 :
60 :
61 : /**
62 : * Function to be called with the results of a SELECT statement
63 : * that has returned @a num_results results. Helper function
64 : * for #TALER_EXCHANGEDB_iterate_aml_decisions().
65 : *
66 : * @param cls closure of type `struct AmlProcessResultContext *`
67 : * @param result the postgres result
68 : * @param num_results the number of results in @a result
69 : */
70 : static void
71 9 : handle_aml_result (void *cls,
72 : PGresult *result,
73 : unsigned int num_results)
74 : {
75 9 : struct AmlProcessResultContext *ctx = cls;
76 :
77 20 : for (unsigned int i = 0; i<num_results; i++)
78 : {
79 : struct TALER_NormalizedPaytoHashP h_payto;
80 : uint64_t rowid;
81 : uint64_t kyc_attributes_rowid;
82 11 : char *justification = NULL;
83 11 : char *new_measures = NULL;
84 : struct TALER_AmlOfficerPublicKeyP decider_pub;
85 11 : char *decider_name = NULL;
86 : bool no_decider_pub;
87 : bool no_kyc_attributes_rowid;
88 : struct GNUNET_TIME_Timestamp decision_time;
89 : struct GNUNET_TIME_Absolute expiration_time;
90 11 : json_t *jproperties = NULL;
91 : bool is_wallet;
92 : bool to_investigate;
93 : bool is_active;
94 11 : json_t *account_rules = NULL;
95 11 : json_t *default_rules = NULL;
96 : struct TALER_FullPayto payto;
97 11 : struct GNUNET_PQ_ResultSpec rs[] = {
98 11 : GNUNET_PQ_result_spec_uint64 ("outcome_serial_id",
99 : &rowid),
100 11 : GNUNET_PQ_result_spec_auto_from_type ("h_payto",
101 : &h_payto),
102 11 : GNUNET_PQ_result_spec_bool ("is_wallet",
103 : &is_wallet),
104 11 : GNUNET_PQ_result_spec_allow_null (
105 : GNUNET_PQ_result_spec_string ("justification",
106 : &justification),
107 : NULL),
108 11 : GNUNET_PQ_result_spec_allow_null (
109 : GNUNET_PQ_result_spec_string ("new_measure_name",
110 : &new_measures),
111 : NULL),
112 11 : GNUNET_PQ_result_spec_allow_null (
113 : GNUNET_PQ_result_spec_auto_from_type ("decider_pub",
114 : &decider_pub),
115 : &no_decider_pub),
116 11 : GNUNET_PQ_result_spec_allow_null (
117 : GNUNET_PQ_result_spec_string ("decider_name",
118 : &decider_name),
119 : NULL),
120 11 : GNUNET_PQ_result_spec_allow_null (
121 : GNUNET_PQ_result_spec_uint64 ("kyc_attributes_serial_id",
122 : &kyc_attributes_rowid),
123 : &no_kyc_attributes_rowid),
124 11 : GNUNET_PQ_result_spec_timestamp ("decision_time",
125 : &decision_time),
126 11 : GNUNET_PQ_result_spec_absolute_time ("expiration_time",
127 : &expiration_time),
128 11 : GNUNET_PQ_result_spec_allow_null (
129 : TALER_PQ_result_spec_json ("jproperties",
130 : &jproperties),
131 : NULL),
132 11 : GNUNET_PQ_result_spec_allow_null (
133 : TALER_PQ_result_spec_json ("jnew_rules",
134 : &account_rules),
135 : NULL),
136 11 : GNUNET_PQ_result_spec_bool ("to_investigate",
137 : &to_investigate),
138 11 : GNUNET_PQ_result_spec_bool ("is_active",
139 : &is_active),
140 11 : GNUNET_PQ_result_spec_string ("payto_uri",
141 : &payto.full_payto),
142 : GNUNET_PQ_result_spec_end
143 : };
144 :
145 11 : if (GNUNET_OK !=
146 11 : GNUNET_PQ_extract_result (result,
147 : rs,
148 : i))
149 : {
150 0 : GNUNET_break (0);
151 0 : ctx->status = GNUNET_SYSERR;
152 0 : return;
153 : }
154 11 : if (GNUNET_TIME_absolute_is_past (expiration_time))
155 0 : is_active = false;
156 11 : if (NULL == account_rules)
157 : {
158 : /* A NULL rule set means the account is on the exchange's default
159 : rules (see exchange_do_insert_successor_measure), not that it has
160 : no limits. Report the defaults so the caller always gets a valid
161 : rule set. */
162 0 : default_rules = TALER_KYCLOGIC_get_default_legi_rules (is_wallet);
163 0 : account_rules = default_rules;
164 : }
165 11 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
166 : "Returning AML decisions for `%s' (%s)\n",
167 : TALER_B2S (&h_payto),
168 : is_wallet
169 : ? "wallet"
170 : : "account");
171 11 : ctx->cb (ctx->cb_cls,
172 : rowid,
173 : justification,
174 : new_measures,
175 : no_decider_pub ? NULL : &decider_pub,
176 : decider_name,
177 : no_kyc_attributes_rowid ? NULL : &kyc_attributes_rowid,
178 : &h_payto,
179 : decision_time,
180 : expiration_time,
181 : jproperties,
182 : to_investigate,
183 : is_active,
184 : is_wallet,
185 : payto,
186 : account_rules);
187 11 : json_decref (default_rules);
188 11 : GNUNET_PQ_cleanup_result (rs);
189 : }
190 : }
191 :
192 :
193 : enum GNUNET_DB_QueryStatus
194 9 : TALER_EXCHANGEDB_iterate_aml_decisions (
195 : struct TALER_EXCHANGEDB_PostgresContext *pg,
196 : const struct TALER_NormalizedPaytoHashP *h_payto,
197 : enum TALER_EXCHANGE_YesNoAll investigation_only,
198 : enum TALER_EXCHANGE_YesNoAll active_only,
199 : uint64_t offset,
200 : int64_t limit,
201 : TALER_EXCHANGEDB_AmlDecisionCallback cb,
202 : void *cb_cls)
203 : {
204 9 : uint64_t ulimit = GNUNET_MIN ((uint64_t) MAX_RECORDS,
205 : TALER_EXCHANGEDB_abs_limit (limit));
206 9 : struct GNUNET_PQ_QueryParam params[] = {
207 9 : GNUNET_PQ_query_param_bool (NULL == h_payto),
208 : NULL == h_payto
209 1 : ? GNUNET_PQ_query_param_null ()
210 9 : : GNUNET_PQ_query_param_auto_from_type (h_payto),
211 9 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL ==
212 : investigation_only)),
213 9 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES ==
214 : investigation_only)),
215 9 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL ==
216 : active_only)),
217 9 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES ==
218 : active_only)),
219 9 : GNUNET_PQ_query_param_uint64 (&offset),
220 9 : GNUNET_PQ_query_param_uint64 (&ulimit),
221 : GNUNET_PQ_query_param_end
222 : };
223 9 : struct AmlProcessResultContext ctx = {
224 : .cb = cb,
225 : .cb_cls = cb_cls,
226 : .pg = pg,
227 : .status = GNUNET_OK
228 : };
229 : enum GNUNET_DB_QueryStatus qs;
230 9 : const char *stmt = (limit > 0)
231 : ? "iterate_aml_decisions_inc"
232 : : "iterate_aml_decisions_dec";
233 :
234 9 : PREPARE (pg,
235 : "iterate_aml_decisions_inc",
236 : "SELECT"
237 : " lo.outcome_serial_id"
238 : ",lo.h_payto"
239 : ",ah.justification"
240 : ",lo.new_measure_name"
241 : ",ah.decider_pub"
242 : ",astaff.decider_name"
243 : ",ah.kyc_attributes_serial_id"
244 : ",lo.decision_time"
245 : ",lo.expiration_time"
246 : ",lo.jproperties::TEXT"
247 : ",lo.to_investigate"
248 : ",lo.is_active"
249 : ",lo.jnew_rules::TEXT"
250 : ",kt.is_wallet"
251 : ",wt.payto_uri"
252 : " FROM legitimization_outcomes lo"
253 : " JOIN kyc_targets kt"
254 : " ON (lo.h_payto = kt.h_normalized_payto)"
255 : " JOIN wire_targets wt"
256 : " ON (lo.h_payto = wt.h_normalized_payto)"
257 : " LEFT JOIN aml_history ah"
258 : " USING (outcome_serial_id)"
259 : " LEFT JOIN LATERAL"
260 : " (SELECT s.decider_name"
261 : " FROM aml_staff s"
262 : " WHERE (s.decider_pub = ah.decider_pub)"
263 : " AND (s.last_change <= lo.decision_time)"
264 : " ORDER BY s.last_change DESC"
265 : " LIMIT 1) astaff ON TRUE"
266 : " WHERE (outcome_serial_id > $7)"
267 : " AND ($1 OR (lo.h_payto = $2))"
268 : " AND ($3 OR (lo.to_investigate = $4))"
269 : " AND ($5 OR (lo.is_active = $6))"
270 : " ORDER BY lo.outcome_serial_id ASC"
271 : " LIMIT $8");
272 9 : PREPARE (pg,
273 : "iterate_aml_decisions_dec",
274 : "SELECT"
275 : " lo.outcome_serial_id"
276 : ",lo.h_payto"
277 : ",ah.justification"
278 : ",lo.new_measure_name"
279 : ",ah.decider_pub"
280 : ",astaff.decider_name"
281 : ",ah.kyc_attributes_serial_id"
282 : ",lo.decision_time"
283 : ",lo.expiration_time"
284 : ",lo.jproperties::TEXT"
285 : ",lo.to_investigate"
286 : ",lo.is_active"
287 : ",lo.jnew_rules::TEXT"
288 : ",kt.is_wallet"
289 : ",wt.payto_uri"
290 : " FROM legitimization_outcomes lo"
291 : " JOIN kyc_targets kt"
292 : " ON (lo.h_payto = kt.h_normalized_payto)"
293 : " JOIN wire_targets wt"
294 : " ON (lo.h_payto = wt.h_normalized_payto)"
295 : " LEFT JOIN aml_history ah"
296 : " USING (outcome_serial_id)"
297 : " LEFT JOIN LATERAL"
298 : " (SELECT s.decider_name"
299 : " FROM aml_staff s"
300 : " WHERE (s.decider_pub = ah.decider_pub)"
301 : " AND (s.last_change <= lo.decision_time)"
302 : " ORDER BY s.last_change DESC"
303 : " LIMIT 1) astaff ON TRUE"
304 : " WHERE lo.outcome_serial_id < $7"
305 : " AND ($1 OR (lo.h_payto = $2))"
306 : " AND ($3 OR (lo.to_investigate = $4))"
307 : " AND ($5 OR (lo.is_active = $6))"
308 : " ORDER BY lo.outcome_serial_id DESC"
309 : " LIMIT $8");
310 9 : qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
311 : stmt,
312 : params,
313 : &handle_aml_result,
314 : &ctx);
315 9 : if (GNUNET_OK != ctx.status)
316 0 : return GNUNET_DB_STATUS_HARD_ERROR;
317 9 : return qs;
318 : }
|