merchant_api_post-private-token.c (9966B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Lesser General Public License as 7 published by the Free Software Foundation; either version 2.1, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General 16 Public License along with TALER; see the file COPYING.LGPL. 17 If not, see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file merchant_api_post-private-token.c 21 * @brief Implementation of the POST /private/token request 22 * @author Christian Grothoff 23 */ 24 #include "taler/platform.h" 25 #include <curl/curl.h> 26 #include <jansson.h> 27 #include <microhttpd.h> /* just for HTTP status codes */ 28 #include <gnunet/gnunet_util_lib.h> 29 #include <gnunet/gnunet_curl_lib.h> 30 #include <taler/taler-merchant/post-private-token.h> 31 #include "merchant_api_curl_defaults.h" 32 #include "merchant_api_common.h" 33 #include <taler/taler_json_lib.h> 34 #include <taler/taler_curl_lib.h> 35 36 37 /** 38 * Handle for a POST /private/token operation. 39 */ 40 struct TALER_MERCHANT_PostPrivateTokenHandle 41 { 42 /** 43 * Base URL of the merchant backend. 44 */ 45 char *base_url; 46 47 /** 48 * The full URL for this request. 49 */ 50 char *url; 51 52 /** 53 * Handle for the request. 54 */ 55 struct GNUNET_CURL_Job *job; 56 57 /** 58 * Function to call with the result. 59 */ 60 TALER_MERCHANT_PostPrivateTokenCallback cb; 61 62 /** 63 * Closure for @a cb. 64 */ 65 TALER_MERCHANT_POST_PRIVATE_TOKEN_RESULT_CLOSURE *cb_cls; 66 67 /** 68 * Reference to the execution context. 69 */ 70 struct GNUNET_CURL_Context *ctx; 71 72 /** 73 * Minor context that holds body and headers. 74 */ 75 struct TALER_CURL_PostContext post_ctx; 76 77 /** 78 * Instance identifier (or NULL). 79 */ 80 char *instance_id; 81 82 /** 83 * Scope for the token. 84 */ 85 char *scope; 86 87 /** 88 * How long the token should be valid. 89 */ 90 struct GNUNET_TIME_Relative duration; 91 92 /** 93 * Whether @e duration was explicitly set via options. 94 */ 95 bool duration_set; 96 97 /** 98 * Whether the token may be refreshed. 99 */ 100 bool refreshable; 101 102 /** 103 * Whether @e refreshable was explicitly set via options. 104 */ 105 bool refreshable_set; 106 107 /** 108 * Description for the token (or NULL). 109 */ 110 const char *description; 111 }; 112 113 114 /** 115 * Function called when we're done processing the 116 * HTTP POST /private/token request. 117 * 118 * @param cls the `struct TALER_MERCHANT_PostPrivateTokenHandle` 119 * @param response_code HTTP response code, 0 on error 120 * @param response response body, NULL if not in JSON 121 */ 122 static void 123 handle_post_token_finished (void *cls, 124 long response_code, 125 const void *response) 126 { 127 struct TALER_MERCHANT_PostPrivateTokenHandle *ppth = cls; 128 const json_t *json = response; 129 struct TALER_MERCHANT_PostPrivateTokenResponse ptr = { 130 .hr.http_status = (unsigned int) response_code, 131 .hr.reply = json 132 }; 133 134 ppth->job = NULL; 135 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 136 "POST /private/token completed with response code %u\n", 137 (unsigned int) response_code); 138 switch (response_code) 139 { 140 case MHD_HTTP_OK: 141 { 142 struct GNUNET_JSON_Specification spec[] = { 143 GNUNET_JSON_spec_string ("access_token", 144 &ptr.details.ok.access_token), 145 GNUNET_JSON_spec_string ("scope", 146 &ptr.details.ok.scope), 147 GNUNET_JSON_spec_bool ("refreshable", 148 &ptr.details.ok.refreshable), 149 GNUNET_JSON_spec_timestamp ("expiration", 150 &ptr.details.ok.expiration), 151 GNUNET_JSON_spec_end () 152 }; 153 154 if (GNUNET_OK != 155 GNUNET_JSON_parse (json, 156 spec, 157 NULL, 158 NULL)) 159 { 160 GNUNET_break_op (0); 161 ptr.hr.http_status = 0; 162 ptr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 163 break; 164 } 165 } 166 break; 167 case MHD_HTTP_BAD_REQUEST: 168 ptr.hr.ec = TALER_JSON_get_error_code (json); 169 ptr.hr.hint = TALER_JSON_get_error_hint (json); 170 break; 171 case MHD_HTTP_ACCEPTED: 172 if (GNUNET_OK != 173 TALER_MERCHANT_parse_mfa_challenge_response_ ( 174 json, 175 &ptr.details.accepted)) 176 { 177 GNUNET_break_op (0); 178 ptr.hr.http_status = 0; 179 ptr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 180 } 181 break; 182 case MHD_HTTP_UNAUTHORIZED: 183 ptr.hr.ec = TALER_JSON_get_error_code (json); 184 ptr.hr.hint = TALER_JSON_get_error_hint (json); 185 break; 186 default: 187 ptr.hr.ec = TALER_JSON_get_error_code (json); 188 ptr.hr.hint = TALER_JSON_get_error_hint (json); 189 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 190 "Unexpected response code %u/%d\n", 191 (unsigned int) response_code, 192 (int) ptr.hr.ec); 193 break; 194 } 195 ppth->cb (ppth->cb_cls, 196 &ptr); 197 if (MHD_HTTP_ACCEPTED == response_code) 198 TALER_MERCHANT_mfa_challenge_response_free ( 199 &ptr.details.accepted); 200 TALER_MERCHANT_post_private_token_cancel (ppth); 201 } 202 203 204 struct TALER_MERCHANT_PostPrivateTokenHandle * 205 TALER_MERCHANT_post_private_token_create ( 206 struct GNUNET_CURL_Context *ctx, 207 const char *url, 208 const char *instance_id, 209 const char *scope) 210 { 211 struct TALER_MERCHANT_PostPrivateTokenHandle *ppth; 212 213 ppth = GNUNET_new (struct TALER_MERCHANT_PostPrivateTokenHandle); 214 ppth->ctx = ctx; 215 ppth->base_url = GNUNET_strdup (url); 216 if (NULL != instance_id) 217 ppth->instance_id = GNUNET_strdup (instance_id); 218 ppth->scope = GNUNET_strdup (scope); 219 return ppth; 220 } 221 222 223 enum GNUNET_GenericReturnValue 224 TALER_MERCHANT_post_private_token_set_options_ ( 225 struct TALER_MERCHANT_PostPrivateTokenHandle *ppth, 226 unsigned int num_options, 227 const struct TALER_MERCHANT_PostPrivateTokenOptionValue *options) 228 { 229 for (unsigned int i = 0; i < num_options; i++) 230 { 231 switch (options[i].option) 232 { 233 case TALER_MERCHANT_POST_PRIVATE_TOKEN_OPTION_END: 234 return GNUNET_OK; 235 case TALER_MERCHANT_POST_PRIVATE_TOKEN_OPTION_DURATION: 236 ppth->duration = options[i].details.duration; 237 ppth->duration_set = true; 238 break; 239 case TALER_MERCHANT_POST_PRIVATE_TOKEN_OPTION_REFRESHABLE: 240 ppth->refreshable = options[i].details.refreshable; 241 ppth->refreshable_set = true; 242 break; 243 case TALER_MERCHANT_POST_PRIVATE_TOKEN_OPTION_DESCRIPTION: 244 ppth->description = options[i].details.description; 245 break; 246 default: 247 GNUNET_break (0); 248 return GNUNET_SYSERR; 249 } 250 } 251 return GNUNET_OK; 252 } 253 254 255 enum TALER_ErrorCode 256 TALER_MERCHANT_post_private_token_start ( 257 struct TALER_MERCHANT_PostPrivateTokenHandle *ppth, 258 TALER_MERCHANT_PostPrivateTokenCallback cb, 259 TALER_MERCHANT_POST_PRIVATE_TOKEN_RESULT_CLOSURE *cb_cls) 260 { 261 json_t *req_obj; 262 CURL *eh; 263 264 ppth->cb = cb; 265 ppth->cb_cls = cb_cls; 266 if (NULL != ppth->instance_id) 267 { 268 char *path; 269 270 GNUNET_asprintf (&path, 271 "instances/%s/private/token", 272 ppth->instance_id); 273 ppth->url = TALER_url_join (ppth->base_url, 274 path, 275 NULL); 276 GNUNET_free (path); 277 } 278 else 279 { 280 ppth->url = TALER_url_join (ppth->base_url, 281 "private/token", 282 NULL); 283 } 284 if (NULL == ppth->url) 285 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 286 req_obj = GNUNET_JSON_PACK ( 287 GNUNET_JSON_pack_string ("scope", 288 ppth->scope)); 289 if (ppth->duration_set) 290 { 291 GNUNET_assert (0 == 292 json_object_set_new (req_obj, 293 "duration", 294 GNUNET_JSON_from_time_rel ( 295 ppth->duration))); 296 } 297 if (ppth->refreshable_set) 298 { 299 GNUNET_assert (0 == 300 json_object_set_new (req_obj, 301 "refreshable", 302 json_boolean (ppth->refreshable))); 303 } 304 if (NULL != ppth->description) 305 { 306 GNUNET_assert (0 == 307 json_object_set_new (req_obj, 308 "description", 309 json_string (ppth->description))); 310 } 311 eh = TALER_MERCHANT_curl_easy_get_ (ppth->url); 312 if ( (NULL == eh) || 313 (GNUNET_OK != 314 TALER_curl_easy_post (&ppth->post_ctx, 315 eh, 316 req_obj)) ) 317 { 318 GNUNET_break (0); 319 json_decref (req_obj); 320 if (NULL != eh) 321 curl_easy_cleanup (eh); 322 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 323 } 324 json_decref (req_obj); 325 GNUNET_assert (CURLE_OK == 326 curl_easy_setopt (eh, 327 CURLOPT_CUSTOMREQUEST, 328 MHD_HTTP_METHOD_POST)); 329 ppth->job = GNUNET_CURL_job_add2 (ppth->ctx, 330 eh, 331 ppth->post_ctx.headers, 332 &handle_post_token_finished, 333 ppth); 334 if (NULL == ppth->job) 335 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 336 return TALER_EC_NONE; 337 } 338 339 340 void 341 TALER_MERCHANT_post_private_token_cancel ( 342 struct TALER_MERCHANT_PostPrivateTokenHandle *ppth) 343 { 344 if (NULL != ppth->job) 345 { 346 GNUNET_CURL_job_cancel (ppth->job); 347 ppth->job = NULL; 348 } 349 TALER_curl_easy_post_finished (&ppth->post_ctx); 350 GNUNET_free (ppth->instance_id); 351 GNUNET_free (ppth->scope); 352 GNUNET_free (ppth->url); 353 GNUNET_free (ppth->base_url); 354 GNUNET_free (ppth); 355 } 356 357 358 /* end of merchant_api_post-private-token.c */