bank_api_registration.c (11014B)
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 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 GNUNET_JSON_spec_string ("type", 77 &type_str), 78 GNUNET_JSON_spec_end () 79 }; 80 81 if (GNUNET_OK != 82 GNUNET_JSON_parse (subject_json, 83 type_spec, 84 NULL, 85 NULL)) 86 { 87 GNUNET_break_op (0); 88 return GNUNET_SYSERR; 89 } 90 91 if (0 == strcasecmp (type_str, 92 "SIMPLE")) 93 { 94 struct GNUNET_JSON_Specification spec[] = { 95 TALER_JSON_spec_amount_any ( 96 "credit_amount", 97 &ts->details.simple.credit_amount), 98 GNUNET_JSON_spec_string ( 99 "subject", 100 (const char **) &ts->details.simple.subject), 101 GNUNET_JSON_spec_end () 102 }; 103 104 if (GNUNET_OK != 105 GNUNET_JSON_parse (subject_json, 106 spec, 107 NULL, NULL)) 108 { 109 GNUNET_break_op (0); 110 return GNUNET_SYSERR; 111 } 112 ts->format = TALER_BANK_SUBJECT_FORMAT_SIMPLE; 113 return GNUNET_OK; 114 } 115 if (0 == strcasecmp (type_str, 116 "URI")) 117 { 118 struct GNUNET_JSON_Specification spec[] = { 119 GNUNET_JSON_spec_string ( 120 "uri", 121 (const char **) &ts->details.uri.uri), 122 GNUNET_JSON_spec_end () 123 }; 124 125 if (GNUNET_OK != 126 GNUNET_JSON_parse (subject_json, 127 spec, 128 NULL, NULL)) 129 { 130 GNUNET_break_op (0); 131 return GNUNET_SYSERR; 132 } 133 ts->format = TALER_BANK_SUBJECT_FORMAT_URI; 134 return GNUNET_OK; 135 } 136 if (0 == strcasecmp (type_str, 137 "CH_QR_BILL")) 138 { 139 struct GNUNET_JSON_Specification spec[] = { 140 TALER_JSON_spec_amount_any ( 141 "credit_amount", 142 &ts->details.ch_qr_bill.credit_amount), 143 GNUNET_JSON_spec_string ( 144 "qr_reference_number", 145 (const char **) &ts->details.ch_qr_bill.qr_reference_number), 146 GNUNET_JSON_spec_end () 147 }; 148 149 if (GNUNET_OK != 150 GNUNET_JSON_parse (subject_json, 151 spec, 152 NULL, NULL)) 153 { 154 GNUNET_break_op (0); 155 return GNUNET_SYSERR; 156 } 157 ts->format = TALER_BANK_SUBJECT_FORMAT_CH_QR_BILL; 158 return GNUNET_OK; 159 } 160 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 161 "Unknown transfer subject type `%s'\n", 162 type_str); 163 GNUNET_break_op (0); 164 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 handle_registration_finished (void *cls, 178 long response_code, 179 const void *response) 180 { 181 struct TALER_BANK_RegistrationHandle *rh = cls; 182 const json_t *j = response; 183 struct TALER_BANK_RegistrationResponse rr = { 184 .http_status = response_code, 185 .response = response 186 }; 187 188 rh->job = NULL; 189 switch (response_code) 190 { 191 case 0: 192 rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 193 break; 194 case MHD_HTTP_OK: 195 { 196 const json_t *subjects; 197 struct GNUNET_JSON_Specification spec[] = { 198 GNUNET_JSON_spec_array_const ("subjects", 199 &subjects), 200 GNUNET_JSON_spec_timestamp ("expiration", 201 &rr.details.ok.expiration), 202 GNUNET_JSON_spec_end () 203 }; 204 205 if (GNUNET_OK != 206 GNUNET_JSON_parse (j, 207 spec, 208 NULL, NULL)) 209 { 210 GNUNET_break_op (0); 211 rr.http_status = 0; 212 rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 213 break; 214 } 215 216 { 217 size_t n = json_array_size (subjects); 218 struct TALER_BANK_TransferSubject ts[GNUNET_NZL (n)]; 219 size_t i; 220 const json_t *subject; 221 222 json_array_foreach ((json_t *) subjects, i, subject) 223 { 224 if (GNUNET_OK != 225 parse_transfer_subject (subject, 226 &ts[i])) 227 { 228 GNUNET_break_op (0); 229 rr.http_status = 0; 230 rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 231 break; 232 } 233 } 234 if (MHD_HTTP_OK == rr.http_status) 235 { 236 rr.details.ok.num_subjects = n; 237 rr.details.ok.subjects = ts; 238 rh->cb (rh->cb_cls, 239 &rr); 240 TALER_BANK_registration_cancel (rh); 241 return; 242 } 243 } 244 } 245 break; 246 case MHD_HTTP_BAD_REQUEST: 247 /* Either we or the service is buggy, or there is an API version conflict. */ 248 GNUNET_break_op (0); 249 rr.ec = TALER_JSON_get_error_code (j); 250 break; 251 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 rr.ec = TALER_JSON_get_error_code (j); 257 break; 258 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 rr.ec = TALER_JSON_get_error_code (j); 262 break; 263 default: 264 /* unexpected response code */ 265 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 266 "Unexpected response code %u\n", 267 (unsigned int) response_code); 268 GNUNET_break (0); 269 rr.ec = TALER_JSON_get_error_code (j); 270 break; 271 } 272 rh->cb (rh->cb_cls, 273 &rr); 274 TALER_BANK_registration_cancel (rh); 275 } 276 277 278 struct TALER_BANK_RegistrationHandle * 279 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 TALER_wallet_reserve_map_authorization_sign (account_pub, 298 authorization_priv, 299 &authorization_sig); 300 GNUNET_CRYPTO_eddsa_key_get_public (&authorization_priv->eddsa_priv, 301 &authorization_pub.eddsa_pub); 302 303 switch (type) 304 { 305 case TALER_BANK_REGISTRATION_TYPE_RESERVE: 306 type_str = "reserve"; 307 break; 308 case TALER_BANK_REGISTRATION_TYPE_KYC: 309 type_str = "kyc"; 310 break; 311 default: 312 GNUNET_break (0); 313 return NULL; 314 } 315 316 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 rh = GNUNET_new (struct TALER_BANK_RegistrationHandle); 332 rh->cb = res_cb; 333 rh->cb_cls = res_cb_cls; 334 rh->request_url = TALER_url_join (base_url, 335 "registration", 336 NULL); 337 if (NULL == rh->request_url) 338 { 339 GNUNET_free (rh); 340 json_decref (reg_obj); 341 return NULL; 342 } 343 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 344 "Requesting wire transfer subject registration at `%s'\n", 345 rh->request_url); 346 eh = curl_easy_init (); 347 if ( (NULL == eh) || 348 (CURLE_OK != 349 curl_easy_setopt (eh, 350 CURLOPT_URL, 351 rh->request_url)) || 352 (GNUNET_OK != 353 TALER_curl_easy_post (&rh->post_ctx, 354 eh, 355 reg_obj)) ) 356 { 357 GNUNET_break (0); 358 TALER_BANK_registration_cancel (rh); 359 if (NULL != eh) 360 curl_easy_cleanup (eh); 361 json_decref (reg_obj); 362 return NULL; 363 } 364 json_decref (reg_obj); 365 rh->job = GNUNET_CURL_job_add2 (ctx, 366 eh, 367 rh->post_ctx.headers, 368 &handle_registration_finished, 369 rh); 370 return rh; 371 } 372 373 374 void 375 TALER_BANK_registration_cancel ( 376 struct TALER_BANK_RegistrationHandle *rh) 377 { 378 if (NULL != rh->job) 379 { 380 GNUNET_CURL_job_cancel (rh->job); 381 rh->job = NULL; 382 } 383 TALER_curl_easy_post_finished (&rh->post_ctx); 384 GNUNET_free (rh->request_url); 385 GNUNET_free (rh); 386 } 387 388 389 /* end of bank_api_registration.c */