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