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