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