Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2026 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
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file bank-lib/bank_api_registration.c
19 : * @brief Implementation of the POST /registration request of the bank's HTTP API
20 : * @author Christian Grothoff
21 : */
22 : #include "bank_api_common.h"
23 : #include <microhttpd.h> /* just for HTTP status codes */
24 : #include "taler/taler_json_lib.h"
25 : #include "taler/taler_signatures.h"
26 : #include "taler/taler_curl_lib.h"
27 :
28 :
29 : /**
30 : * @brief A /registration Handle
31 : */
32 : struct TALER_BANK_RegistrationHandle
33 : {
34 :
35 : /**
36 : * The URL for this request.
37 : */
38 : char *request_url;
39 :
40 : /**
41 : * POST context.
42 : */
43 : struct TALER_CURL_PostContext post_ctx;
44 :
45 : /**
46 : * Handle for the request.
47 : */
48 : struct GNUNET_CURL_Job *job;
49 :
50 : /**
51 : * Function to call with the result.
52 : */
53 : TALER_BANK_RegistrationCallback cb;
54 :
55 : /**
56 : * Closure for @a cb.
57 : */
58 : void *cb_cls;
59 :
60 : };
61 :
62 :
63 : /**
64 : * Parse the "subject" field of a successful /registration response.
65 : * The field is a JSON object discriminated by "type".
66 : *
67 : * @param subject_json the JSON object to parse (the inner "subject" value)
68 : * @param[out] ts set to the parsed transfer subject on success
69 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
70 : */
71 : static enum GNUNET_GenericReturnValue
72 0 : parse_transfer_subject (const json_t *subject_json,
73 : struct TALER_BANK_TransferSubject *ts)
74 : {
75 : const char *type_str;
76 : struct GNUNET_JSON_Specification type_spec[] = {
77 0 : GNUNET_JSON_spec_string ("type",
78 : &type_str),
79 0 : GNUNET_JSON_spec_end ()
80 : };
81 :
82 0 : if (GNUNET_OK !=
83 0 : GNUNET_JSON_parse (subject_json,
84 : type_spec,
85 : NULL,
86 : NULL))
87 : {
88 0 : GNUNET_break_op (0);
89 0 : return GNUNET_SYSERR;
90 : }
91 :
92 0 : if (0 == strcasecmp (type_str,
93 : "SIMPLE"))
94 : {
95 : struct GNUNET_JSON_Specification spec[] = {
96 0 : TALER_JSON_spec_amount_any (
97 : "credit_amount",
98 : &ts->details.simple.credit_amount),
99 0 : GNUNET_JSON_spec_string (
100 : "subject",
101 0 : (const char **) &ts->details.simple.subject),
102 0 : GNUNET_JSON_spec_end ()
103 : };
104 :
105 0 : if (GNUNET_OK !=
106 0 : GNUNET_JSON_parse (subject_json,
107 : spec,
108 : NULL, NULL))
109 : {
110 0 : GNUNET_break_op (0);
111 0 : return GNUNET_SYSERR;
112 : }
113 0 : ts->format = TALER_BANK_SUBJECT_FORMAT_SIMPLE;
114 0 : return GNUNET_OK;
115 : }
116 0 : if (0 == strcasecmp (type_str,
117 : "URI"))
118 : {
119 : struct GNUNET_JSON_Specification spec[] = {
120 0 : TALER_JSON_spec_amount_any (
121 : "credit_amount",
122 : &ts->details.uri.credit_amount),
123 0 : GNUNET_JSON_spec_string (
124 : "uri",
125 0 : (const char **) &ts->details.uri.uri),
126 0 : GNUNET_JSON_spec_end ()
127 : };
128 :
129 0 : if (GNUNET_OK !=
130 0 : GNUNET_JSON_parse (subject_json,
131 : spec,
132 : NULL, NULL))
133 : {
134 0 : GNUNET_break_op (0);
135 0 : return GNUNET_SYSERR;
136 : }
137 0 : ts->format = TALER_BANK_SUBJECT_FORMAT_URI;
138 0 : return GNUNET_OK;
139 : }
140 0 : if (0 == strcasecmp (type_str,
141 : "CH_QR_BILL"))
142 : {
143 : struct GNUNET_JSON_Specification spec[] = {
144 0 : TALER_JSON_spec_amount_any (
145 : "credit_amount",
146 : &ts->details.ch_qr_bill.credit_amount),
147 0 : GNUNET_JSON_spec_string (
148 : "qr_reference_number",
149 0 : (const char **) &ts->details.ch_qr_bill.qr_reference_number),
150 0 : GNUNET_JSON_spec_end ()
151 : };
152 :
153 0 : if (GNUNET_OK !=
154 0 : GNUNET_JSON_parse (subject_json,
155 : spec,
156 : NULL, NULL))
157 : {
158 0 : GNUNET_break_op (0);
159 0 : return GNUNET_SYSERR;
160 : }
161 0 : ts->format = TALER_BANK_SUBJECT_FORMAT_CH_QR_BILL;
162 0 : return GNUNET_OK;
163 : }
164 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
165 : "Unknown transfer subject type `%s'\n",
166 : type_str);
167 0 : GNUNET_break_op (0);
168 0 : return GNUNET_SYSERR;
169 : }
170 :
171 :
172 : /**
173 : * Function called when we're done processing the HTTP POST /registration
174 : * request.
175 : *
176 : * @param cls the `struct TALER_BANK_RegistrationHandle`
177 : * @param response_code HTTP response code, 0 on error
178 : * @param response parsed JSON result, NULL on error
179 : */
180 : static void
181 0 : handle_registration_finished (void *cls,
182 : long response_code,
183 : const void *response)
184 : {
185 0 : struct TALER_BANK_RegistrationHandle *rh = cls;
186 0 : const json_t *j = response;
187 0 : struct TALER_BANK_RegistrationResponse rr = {
188 : .http_status = response_code,
189 : .response = response
190 : };
191 :
192 0 : rh->job = NULL;
193 0 : switch (response_code)
194 : {
195 0 : case 0:
196 0 : rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
197 0 : break;
198 0 : case MHD_HTTP_OK:
199 : {
200 : const json_t *subjects;
201 : struct GNUNET_JSON_Specification spec[] = {
202 0 : GNUNET_JSON_spec_array_const ("subjects",
203 : &subjects),
204 0 : GNUNET_JSON_spec_timestamp ("expiration",
205 : &rr.details.ok.expiration),
206 0 : GNUNET_JSON_spec_end ()
207 : };
208 :
209 0 : if (GNUNET_OK !=
210 0 : GNUNET_JSON_parse (j,
211 : spec,
212 : NULL, NULL))
213 : {
214 0 : GNUNET_break_op (0);
215 0 : rr.http_status = 0;
216 0 : rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
217 0 : break;
218 : }
219 :
220 : {
221 0 : size_t n = json_array_size (subjects);
222 : struct TALER_BANK_TransferSubject *ts;
223 : size_t i;
224 : const json_t *subject;
225 :
226 0 : ts = GNUNET_new_array (GNUNET_NZL (n),
227 : struct TALER_BANK_TransferSubject);
228 0 : json_array_foreach ((json_t *) subjects, i, subject)
229 : {
230 0 : if (GNUNET_OK !=
231 0 : parse_transfer_subject (subject,
232 0 : &ts[i]))
233 : {
234 0 : GNUNET_break_op (0);
235 0 : rr.http_status = 0;
236 0 : rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
237 0 : break;
238 : }
239 : }
240 0 : if (MHD_HTTP_OK == rr.http_status)
241 : {
242 0 : rr.details.ok.num_subjects = n;
243 0 : rr.details.ok.subjects = ts;
244 0 : rh->cb (rh->cb_cls,
245 : &rr);
246 0 : GNUNET_free (ts);
247 0 : TALER_BANK_registration_cancel (rh);
248 0 : return;
249 : }
250 0 : GNUNET_free (ts);
251 : }
252 : }
253 0 : break;
254 0 : case MHD_HTTP_BAD_REQUEST:
255 : /* Either we or the service is buggy, or there is an API version conflict. */
256 0 : GNUNET_break_op (0);
257 0 : rr.ec = TALER_JSON_get_error_code (j);
258 0 : break;
259 0 : case MHD_HTTP_CONFLICT:
260 : /* Covers TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
261 : TALER_EC_BANK_UNSUPPORTED_SUBJECT_FORMAT,
262 : TALER_EC_BANK_DERIVATION_REUSE, and
263 : TALER_EC_BANK_BAD_SIGNATURE. */
264 0 : rr.ec = TALER_JSON_get_error_code (j);
265 0 : break;
266 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
267 : /* Server had an internal issue; we should retry, but this API
268 : leaves that to the application. */
269 0 : rr.ec = TALER_JSON_get_error_code (j);
270 0 : break;
271 0 : default:
272 : /* unexpected response code */
273 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
274 : "Unexpected response code %u\n",
275 : (unsigned int) response_code);
276 0 : GNUNET_break (0);
277 0 : rr.ec = TALER_JSON_get_error_code (j);
278 0 : break;
279 : }
280 0 : rh->cb (rh->cb_cls,
281 : &rr);
282 0 : TALER_BANK_registration_cancel (rh);
283 : }
284 :
285 :
286 : struct TALER_BANK_RegistrationHandle *
287 0 : TALER_BANK_registration (
288 : struct GNUNET_CURL_Context *ctx,
289 : const char *base_url,
290 : const struct TALER_FullPayto *credit_account,
291 : const struct TALER_Amount *credit_amount,
292 : enum TALER_BankRegistrationType type,
293 : const union TALER_AccountPublicKeyP *account_pub,
294 : const struct TALER_PreparedTransferAuthorizationPrivateKeyP *
295 : authorization_priv,
296 : bool recurrent,
297 : TALER_BANK_RegistrationCallback res_cb,
298 : void *res_cb_cls)
299 : {
300 : struct TALER_PreparedTransferAuthorizationPublicKeyP authorization_pub;
301 : struct TALER_PreparedTransferAuthorizationSignatureP authorization_sig;
302 : struct TALER_BANK_RegistrationHandle *rh;
303 : const char *type_str;
304 : json_t *reg_obj;
305 : CURL *eh;
306 :
307 0 : TALER_wallet_prepared_transfer_registration_sign (
308 : *credit_account,
309 : credit_amount,
310 : type,
311 : recurrent,
312 : account_pub,
313 : authorization_priv,
314 : &authorization_sig);
315 0 : GNUNET_CRYPTO_eddsa_key_get_public (&authorization_priv->eddsa_priv,
316 : &authorization_pub.eddsa_pub);
317 :
318 0 : switch (type)
319 : {
320 0 : case TALER_BANK_REGISTRATION_TYPE_RESERVE:
321 0 : type_str = "reserve";
322 0 : break;
323 0 : case TALER_BANK_REGISTRATION_TYPE_KYC:
324 0 : type_str = "kyc";
325 0 : break;
326 0 : default:
327 0 : GNUNET_break (0);
328 0 : return NULL;
329 : }
330 :
331 0 : reg_obj = GNUNET_JSON_PACK (
332 : TALER_JSON_pack_full_payto ("credit_account",
333 : *credit_account),
334 : TALER_JSON_pack_amount ("credit_amount",
335 : credit_amount),
336 : GNUNET_JSON_pack_string ("type",
337 : type_str),
338 : GNUNET_JSON_pack_string ("alg",
339 : "EdDSA"),
340 : GNUNET_JSON_pack_data_auto ("account_pub",
341 : account_pub),
342 : GNUNET_JSON_pack_data_auto ("authorization_pub",
343 : &authorization_pub),
344 : GNUNET_JSON_pack_data_auto ("authorization_sig",
345 : &authorization_sig),
346 : GNUNET_JSON_pack_bool ("recurrent",
347 : recurrent));
348 0 : rh = GNUNET_new (struct TALER_BANK_RegistrationHandle);
349 0 : rh->cb = res_cb;
350 0 : rh->cb_cls = res_cb_cls;
351 0 : rh->request_url = TALER_url_join (base_url,
352 : "registration",
353 : NULL);
354 0 : if (NULL == rh->request_url)
355 : {
356 0 : GNUNET_free (rh);
357 0 : json_decref (reg_obj);
358 0 : return NULL;
359 : }
360 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
361 : "Requesting wire transfer subject registration at `%s'\n",
362 : rh->request_url);
363 0 : eh = curl_easy_init ();
364 0 : if ( (NULL == eh) ||
365 : (CURLE_OK !=
366 0 : curl_easy_setopt (eh,
367 : CURLOPT_URL,
368 0 : rh->request_url)) ||
369 : (GNUNET_OK !=
370 0 : TALER_curl_easy_post (&rh->post_ctx,
371 : eh,
372 : reg_obj)) )
373 : {
374 0 : GNUNET_break (0);
375 0 : TALER_BANK_registration_cancel (rh);
376 0 : if (NULL != eh)
377 0 : curl_easy_cleanup (eh);
378 0 : json_decref (reg_obj);
379 0 : return NULL;
380 : }
381 0 : json_decref (reg_obj);
382 0 : rh->job = GNUNET_CURL_job_add2 (ctx,
383 : eh,
384 0 : rh->post_ctx.headers,
385 : &handle_registration_finished,
386 : rh);
387 0 : if (NULL == rh->job)
388 : {
389 0 : GNUNET_break (0);
390 0 : curl_easy_cleanup (eh);
391 0 : TALER_BANK_registration_cancel (rh);
392 0 : return NULL;
393 : }
394 0 : return rh;
395 : }
396 :
397 :
398 : void
399 0 : TALER_BANK_registration_cancel (
400 : struct TALER_BANK_RegistrationHandle *rh)
401 : {
402 0 : if (NULL != rh->job)
403 : {
404 0 : GNUNET_CURL_job_cancel (rh->job);
405 0 : rh->job = NULL;
406 : }
407 0 : TALER_curl_easy_post_finished (&rh->post_ctx);
408 0 : GNUNET_free (rh->request_url);
409 0 : GNUNET_free (rh);
410 0 : }
411 :
412 :
413 : /* end of bank_api_registration.c */
|