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