bank_api_get_accounts.c (10677B)
1 /* 2 This file is part of TALER cash2ecash 3 Copyright (C) 2026 GNUnet e.V. 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as 7 published by the Free Software Foundation, either version 3 of the 8 License, or (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <https://www.gnu.org/licenses/>. 18 */ 19 /** 20 * @file bank-lib/bank_api_get_accounts.c 21 * @brief implements the Taler Bank API "GET accounts/$USERNAME" handler 22 * @author Reto Tellenbach 23 */ 24 25 #include <microhttpd.h> 26 #include "taler/taler_json_lib.h" 27 #include "taler/taler_bank_service.h" 28 #include "bank_api_get_accounts.h" 29 #include "bank_api_curl_defaults.h" 30 31 /** 32 * Log error related to CURL operations. 33 * 34 * @param type log level 35 * @param function which function failed to run 36 * @param code what was the curl error code 37 */ 38 #define CURL_STRERROR(type, function, code) \ 39 GNUNET_log (type, \ 40 "Curl function `%s' has failed at `%s:%d' with error: %s", \ 41 function, __FILE__, __LINE__, curl_easy_strerror (code)); 42 43 /** 44 * Handle for the accounts request. 45 */ 46 struct TALER_BANK_GetAccountsHandle 47 { 48 /** 49 * The context of this handle 50 */ 51 struct GNUNET_CURL_Context *ctx; 52 53 /** 54 * Function to call with the , 55 * NULL if this has already been done. 56 */ 57 TALER_BANK_AccountsCallback accounts_cb; 58 59 /** 60 * Closure to pass to 61 */ 62 void *accounts_cb_cls; 63 64 /** 65 * Data for the request to get the accounts/$USERNAME of a bank, 66 * NULL once we are past stage #MHS_INIT. 67 */ 68 struct GNUNET_CURL_Job *job; 69 70 /** 71 * The whole request line 72 */ 73 char *job_url; 74 }; 75 76 77 /** 78 * Decode the JSON in @a resp_obj from the accounts/$USERNAME response 79 * 80 * @param[in] resp_obj JSON object to parse 81 * @param[in,out] vi where to store the results we decoded 82 * @param[out] vc where to store account info data 83 * @return #TALER_EC_NONE on success 84 */ 85 static enum TALER_ErrorCode 86 decode_config_json (const json_t *resp_obj, 87 struct TALER_BANK_AccountInformation *vi) 88 { 89 const json_t *channels_pack; 90 json_t *cnt; 91 json_t *blc; 92 bool contact_missing; 93 bool tan_channels_missing; 94 95 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 96 "Received body\n`%s'\n", 97 json_dumps(resp_obj, 0)); 98 99 100 struct GNUNET_JSON_Specification spec[] = { 101 GNUNET_JSON_spec_json ("balance", 102 &blc), 103 TALER_JSON_spec_amount_any ("debit_threshold", 104 &vi->debit_threshold), 105 GNUNET_JSON_spec_mark_optional ( 106 GNUNET_JSON_spec_array_const ("tan_channels", 107 &channels_pack), 108 &tan_channels_missing), 109 GNUNET_JSON_spec_mark_optional ( 110 GNUNET_JSON_spec_json ("contact_data", 111 &cnt), 112 &contact_missing), 113 GNUNET_JSON_spec_mark_optional ( 114 GNUNET_JSON_spec_string ("status", 115 &vi->status), 116 NULL), 117 GNUNET_JSON_spec_mark_optional ( 118 GNUNET_JSON_spec_uint32 ("conversion_rate_class_id", 119 &vi->conversion_rate_class_id), 120 NULL), 121 GNUNET_JSON_spec_end () 122 }; 123 124 if (JSON_OBJECT != json_typeof (resp_obj)) 125 { 126 GNUNET_break_op (0); 127 return TALER_EC_GENERIC_JSON_INVALID; 128 } 129 if (GNUNET_OK != 130 GNUNET_JSON_parse (resp_obj, 131 spec, 132 NULL, NULL)) 133 { 134 GNUNET_break_op (0); 135 return TALER_EC_GENERIC_JSON_INVALID; 136 } 137 struct GNUNET_JSON_Specification spec_blc[] = { 138 TALER_JSON_spec_amount_any("amount", 139 &vi->balance.amount), 140 GNUNET_JSON_spec_string("credit_debit_indicator", 141 &vi->balance.credit_debit_indicator), 142 GNUNET_JSON_spec_end () 143 }; 144 if (GNUNET_OK != GNUNET_JSON_parse (blc, 145 spec_blc, 146 NULL, NULL)) 147 { 148 GNUNET_break_op (0); 149 return TALER_EC_GENERIC_JSON_INVALID; 150 } 151 if(!contact_missing) 152 { 153 struct GNUNET_JSON_Specification spec_contact[] = { 154 GNUNET_JSON_spec_mark_optional ( 155 GNUNET_JSON_spec_string("email", 156 &vi->contact_data.email), 157 NULL), 158 GNUNET_JSON_spec_mark_optional ( 159 GNUNET_JSON_spec_string ("phone", 160 &vi->contact_data.phone_number), 161 NULL), 162 GNUNET_JSON_spec_end () 163 }; 164 if (GNUNET_OK != GNUNET_JSON_parse (cnt, 165 spec_contact, 166 NULL, NULL)) 167 { 168 GNUNET_break_op (0); 169 return TALER_EC_GENERIC_JSON_INVALID; 170 } 171 } 172 if (!tan_channels_missing) 173 { 174 size_t idx; 175 json_t *val; 176 json_array_foreach (channels_pack, 177 idx, 178 val) 179 { 180 if (idx < TMH_TCS_OPTIONS_COUNT) 181 { 182 vi->tan_channels[idx] = GNUNET_strdup (json_string_value (val)); 183 } 184 } 185 } 186 187 return TALER_EC_NONE; 188 } 189 190 191 /** 192 * Callback used when http reply arived to a /accounts/$USERNAME request. 193 * 194 * @param cls the `struct TALER_BANK_GetAccountsHandle` 195 * @param response_code HTTP response code or 0 on error 196 * @param gresp_obj JSON result, NULL on error, must be a `const json_t *` 197 */ 198 static void 199 response_cb(void *cls, 200 long response_code, 201 const void *gresp_obj) 202 { 203 struct TALER_BANK_GetAccountsHandle *account = cls; 204 const json_t *resp_obj = gresp_obj; 205 206 struct TALER_BANK_AccountsResponse ar = { 207 .hr.response = resp_obj, 208 .hr.http_status = (unsigned int)response_code 209 }; 210 211 account->job = NULL; //job was successfull, curl job cancel not needed anymore in cleanup 212 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 213 "Received config from URL `%s' with status %ld.\n", 214 account->job_url, 215 response_code); 216 217 switch (response_code) 218 { 219 case 0: 220 GNUNET_break_op (0); 221 ar.hr.ec = TALER_EC_INVALID; 222 break; 223 case MHD_HTTP_OK: 224 if (NULL == resp_obj) 225 { 226 GNUNET_break_op (0); 227 ar.hr.http_status = 0; 228 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 229 break; 230 } 231 ar.hr.ec = decode_config_json (resp_obj, 232 &ar.details.ok.acc); 233 if (TALER_EC_NONE != ar.hr.ec) 234 { 235 GNUNET_break_op (0); 236 ar.hr.http_status = 0; 237 break; 238 } 239 break; 240 case MHD_HTTP_UNAUTHORIZED: 241 ar.hr.ec = TALER_JSON_get_error_code (resp_obj); 242 ar.hr.hint = TALER_JSON_get_error_hint (resp_obj); 243 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 244 "Invalid or missing credentials %u/%d\n", 245 (unsigned int) response_code, 246 (int) ar.hr.ec); 247 break; 248 case MHD_HTTP_FORBIDDEN: 249 ar.hr.ec = TALER_JSON_get_error_code (resp_obj); 250 ar.hr.hint = TALER_JSON_get_error_hint (resp_obj); 251 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 252 "Missing rights %u/%d\n", 253 (unsigned int) response_code, 254 (int) ar.hr.ec); 255 break; 256 case MHD_HTTP_NOT_FOUND: 257 ar.hr.ec = TALER_JSON_get_error_code (resp_obj); 258 ar.hr.hint = TALER_JSON_get_error_hint (resp_obj); 259 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 260 "The account pointed by $USERNAME was not found %u/%d\n", 261 (unsigned int) response_code, 262 (int) ar.hr.ec); 263 break; 264 case MHD_HTTP_INTERNAL_SERVER_ERROR: 265 ar.hr.ec = TALER_JSON_get_error_code (resp_obj); 266 ar.hr.hint = TALER_JSON_get_error_hint (resp_obj); 267 break; 268 default: 269 ar.hr.ec = TALER_JSON_get_error_code (resp_obj); 270 ar.hr.hint = TALER_JSON_get_error_hint (resp_obj); 271 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 272 "Unexpected response code %u/%d\n", 273 (unsigned int) response_code, 274 (int) ar.hr.ec); 275 break; 276 } 277 278 account->accounts_cb (account->accounts_cb_cls, &ar); 279 TALER_BANK_get_accounts_cancel(account); 280 } 281 282 283 struct TALER_BANK_GetAccountsHandle * 284 TALER_BANK_get_accounts ( struct GNUNET_CURL_Context *ctx, 285 const char *url, 286 const char *username, 287 const struct DIGITIZER_BankAuthenticationData *authorization, 288 TALER_BANK_AccountsCallback accounts_cb, 289 void *accounts_cb_cls) 290 { 291 struct TALER_BANK_GetAccountsHandle *account; 292 char *usr; 293 CURL *eh; 294 295 account = GNUNET_new(struct TALER_BANK_GetAccountsHandle); 296 account->accounts_cb = accounts_cb; 297 account->accounts_cb_cls = accounts_cb_cls; 298 account->ctx = ctx; 299 GNUNET_asprintf(&usr, 300 "accounts/%s", 301 username); 302 account->job_url = TALER_url_join(url, 303 usr, 304 NULL); 305 if(NULL == account->job_url) 306 { 307 GNUNET_break(0); 308 GNUNET_free(account); 309 return NULL; 310 } 311 GNUNET_log( GNUNET_ERROR_TYPE_INFO, 312 "Requesting bank account information with URL `%s'.\n", 313 account->job_url); 314 eh = TALER_BANK_curl_easy_get_(account->job_url); 315 if(NULL == eh) 316 { 317 GNUNET_break(0); 318 TALER_BANK_get_accounts_cancel(account); 319 return NULL; 320 } 321 if(GNUNET_OK != DIGITIZER_setup_auth_(eh,authorization)) 322 { 323 GNUNET_break(0); 324 TALER_BANK_get_accounts_cancel(account); 325 return NULL; 326 } 327 account->job = GNUNET_CURL_job_add(account->ctx, 328 eh, 329 &response_cb, 330 account); 331 if(NULL == account->job) 332 { 333 GNUNET_break(0); 334 TALER_BANK_get_accounts_cancel(account); 335 return NULL; 336 } 337 338 return account; 339 } 340 341 342 343 void 344 TALER_BANK_get_accounts_cancel ( struct TALER_BANK_GetAccountsHandle *account) 345 { 346 if(NULL != account->job) 347 { 348 GNUNET_CURL_job_cancel(account->job); 349 account->job = NULL; 350 } 351 GNUNET_free(account->job_url); 352 GNUNET_free(account); 353 }