merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

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 */