exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

testing_api_cmd_oauth.c (11477B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2021-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU General Public License as
      7   published by the Free Software Foundation; either version 3, or
      8   (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not, see
     17   <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file testing/testing_api_cmd_oauth.c
     22  * @brief Implement a CMD to run an OAuth service for faking the legitimation service
     23  * @author Christian Grothoff
     24  */
     25 #include "taler/taler_json_lib.h"
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "taler/taler_testing_lib.h"
     28 #include "taler/taler_mhd_lib.h"
     29 
     30 /**
     31  * State for the oauth CMD.
     32  */
     33 struct OAuthState
     34 {
     35 
     36   /**
     37    * Handle to the "oauth" service.
     38    */
     39   struct MHD_Daemon *mhd;
     40 
     41   /**
     42    * Birthdate that the oauth server should return in a response, may be NULL
     43    */
     44   const char *birthdate;
     45 
     46   /**
     47    * Port to listen on.
     48    */
     49   uint16_t port;
     50 };
     51 
     52 
     53 struct RequestCtx
     54 {
     55   struct MHD_PostProcessor *pp;
     56   char *code;
     57   char *client_id;
     58   char *redirect_uri;
     59   char *client_secret;
     60 };
     61 
     62 
     63 static void
     64 append (char **target,
     65         const char *data,
     66         size_t size)
     67 {
     68   char *tmp;
     69 
     70   if (NULL == *target)
     71   {
     72     *target = GNUNET_strndup (data,
     73                               size);
     74     return;
     75   }
     76   GNUNET_asprintf (&tmp,
     77                    "%s%.*s",
     78                    *target,
     79                    (int) size,
     80                    data);
     81   GNUNET_free (*target);
     82   *target = tmp;
     83 }
     84 
     85 
     86 static MHD_RESULT
     87 handle_post (void *cls,
     88              enum MHD_ValueKind kind,
     89              const char *key,
     90              const char *filename,
     91              const char *content_type,
     92              const char *transfer_encoding,
     93              const char *data,
     94              uint64_t off,
     95              size_t size)
     96 {
     97   struct RequestCtx *rc = cls;
     98 
     99   (void) kind;
    100   (void) filename;
    101   (void) content_type;
    102   (void) transfer_encoding;
    103   (void) off;
    104   if (0 == strcmp (key,
    105                    "code"))
    106     append (&rc->code,
    107             data,
    108             size);
    109   if (0 == strcmp (key,
    110                    "client_id"))
    111     append (&rc->client_id,
    112             data,
    113             size);
    114   if (0 == strcmp (key,
    115                    "redirect_uri"))
    116     append (&rc->redirect_uri,
    117             data,
    118             size);
    119   if (0 == strcmp (key,
    120                    "client_secret"))
    121     append (&rc->client_secret,
    122             data,
    123             size);
    124   return MHD_YES;
    125 }
    126 
    127 
    128 /**
    129  * A client has requested the given url using the given method
    130  * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
    131  * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).  The callback
    132  * must call MHD callbacks to provide content to give back to the
    133  * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
    134  * #MHD_HTTP_NOT_FOUND, etc.).
    135  *
    136  * @param cls argument given together with the function
    137  *        pointer when the handler was registered with MHD
    138  * @param connection the connection being handled
    139  * @param url the requested url
    140  * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
    141  *        #MHD_HTTP_METHOD_PUT, etc.)
    142  * @param version the HTTP version string (i.e.
    143  *        MHD_HTTP_VERSION_1_1)
    144  * @param upload_data the data being uploaded (excluding HEADERS,
    145  *        for a POST that fits into memory and that is encoded
    146  *        with a supported encoding, the POST data will NOT be
    147  *        given in upload_data and is instead available as
    148  *        part of MHD_get_connection_values(); very large POST
    149  *        data *will* be made available incrementally in
    150  *        @a upload_data)
    151  * @param[in,out] upload_data_size set initially to the size of the
    152  *        @a upload_data provided; the method must update this
    153  *        value to the number of bytes NOT processed;
    154  * @param[in,out] con_cls pointer that the callback can set to some
    155  *        address and that will be preserved by MHD for future
    156  *        calls for this request; since the access handler may
    157  *        be called many times (i.e., for a PUT/POST operation
    158  *        with plenty of upload data) this allows the application
    159  *        to easily associate some request-specific state.
    160  *        If necessary, this state can be cleaned up in the
    161  *        global MHD_RequestCompletedCallback (which
    162  *        can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
    163  *        Initially, `*con_cls` will be NULL.
    164  * @return #MHD_YES if the connection was handled successfully,
    165  *         #MHD_NO if the socket must be closed due to a serious
    166  *         error while handling the request
    167  */
    168 static MHD_RESULT
    169 handler_cb (void *cls,
    170             struct MHD_Connection *connection,
    171             const char *url,
    172             const char *method,
    173             const char *version,
    174             const char *upload_data,
    175             size_t *upload_data_size,
    176             void **con_cls)
    177 {
    178   struct RequestCtx *rc = *con_cls;
    179   struct OAuthState *oas = cls;
    180   unsigned int hc;
    181   json_t *body;
    182 
    183   (void) version;
    184   if (0 == strcasecmp (method,
    185                        MHD_HTTP_METHOD_GET))
    186   {
    187     json_t *data =
    188       GNUNET_JSON_PACK (
    189         GNUNET_JSON_pack_string ("id",
    190                                  "XXXID12345678"),
    191         GNUNET_JSON_pack_string ("first_name",
    192                                  "Bob"),
    193         GNUNET_JSON_pack_string ("last_name",
    194                                  "Builder"));
    195 
    196     if (NULL != oas->birthdate)
    197       GNUNET_assert (0 ==
    198                      json_object_set_new (data,
    199                                           "birthdate",
    200                                           json_string_nocheck (
    201                                             oas->birthdate)));
    202 
    203     body = GNUNET_JSON_PACK (
    204       GNUNET_JSON_pack_string (
    205         "status",
    206         "success"),
    207       GNUNET_JSON_pack_object_steal (
    208         "data", data));
    209     return TALER_MHD_reply_json_steal (connection,
    210                                        body,
    211                                        MHD_HTTP_OK);
    212   }
    213   if (0 != strcasecmp (method,
    214                        MHD_HTTP_METHOD_POST))
    215   {
    216     GNUNET_break (0);
    217     return MHD_NO;
    218   }
    219   if (NULL == rc)
    220   {
    221     rc = GNUNET_new (struct RequestCtx);
    222     *con_cls = rc;
    223     rc->pp = MHD_create_post_processor (connection,
    224                                         4092,
    225                                         &handle_post,
    226                                         rc);
    227     return MHD_YES;
    228   }
    229   if (0 != *upload_data_size)
    230   {
    231     MHD_RESULT ret;
    232 
    233     ret = MHD_post_process (rc->pp,
    234                             upload_data,
    235                             *upload_data_size);
    236     *upload_data_size = 0;
    237     return ret;
    238   }
    239 
    240 
    241   /* NOTE: In the future, we MAY want to distinguish between
    242      the different URLs and possibly return more information.
    243      For now, just do the minimum: implement the main handler
    244      that checks the code. */
    245   if ( (NULL == rc->code) ||
    246        (NULL == rc->client_id) ||
    247        (NULL == rc->redirect_uri) ||
    248        (NULL == rc->client_secret) )
    249   {
    250     GNUNET_break (0);
    251     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    252                 "Bad request to Oauth faker: `%s' with %s/%s/%s/%s\n",
    253                 url,
    254                 rc->code,
    255                 rc->client_id,
    256                 rc->redirect_uri,
    257                 rc->client_secret);
    258     return MHD_NO;
    259   }
    260   if (0 != strcmp (rc->client_id,
    261                    "taler-exchange"))
    262   {
    263     body = GNUNET_JSON_PACK (
    264       GNUNET_JSON_pack_string ("error",
    265                                "unknown_client"),
    266       GNUNET_JSON_pack_string ("error_description",
    267                                "only 'taler-exchange' is allowed"));
    268     hc = MHD_HTTP_NOT_FOUND;
    269   }
    270   else if (0 != strcmp (rc->client_secret,
    271                         "exchange-secret"))
    272   {
    273     body = GNUNET_JSON_PACK (
    274       GNUNET_JSON_pack_string ("error",
    275                                "invalid_client_secret"),
    276       GNUNET_JSON_pack_string ("error_description",
    277                                "only 'exchange-secret' is valid"));
    278     hc = MHD_HTTP_FORBIDDEN;
    279   }
    280   else
    281   {
    282     if (0 != strcmp (rc->code,
    283                      "pass"))
    284     {
    285       body = GNUNET_JSON_PACK (
    286         GNUNET_JSON_pack_string ("error",
    287                                  "invalid_grant"),
    288         GNUNET_JSON_pack_string ("error_description",
    289                                  "only 'pass' shall pass"));
    290       hc = MHD_HTTP_FORBIDDEN;
    291     }
    292     else
    293     {
    294       body = GNUNET_JSON_PACK (
    295         GNUNET_JSON_pack_string ("access_token",
    296                                  "good"),
    297         GNUNET_JSON_pack_string ("token_type",
    298                                  "bearer"),
    299         GNUNET_JSON_pack_uint64 ("expires_in",
    300                                  3600),
    301         GNUNET_JSON_pack_string ("refresh_token",
    302                                  "better"));
    303       hc = MHD_HTTP_OK;
    304     }
    305   }
    306   return TALER_MHD_reply_json_steal (connection,
    307                                      body,
    308                                      hc);
    309 }
    310 
    311 
    312 static void
    313 cleanup (void *cls,
    314          struct MHD_Connection *connection,
    315          void **con_cls,
    316          enum MHD_RequestTerminationCode toe)
    317 {
    318   struct RequestCtx *rc = *con_cls;
    319 
    320   (void) cls;
    321   (void) connection;
    322   (void) toe;
    323   if (NULL == rc)
    324     return;
    325   MHD_destroy_post_processor (rc->pp);
    326   GNUNET_free (rc->code);
    327   GNUNET_free (rc->client_id);
    328   GNUNET_free (rc->redirect_uri);
    329   GNUNET_free (rc->client_secret);
    330   GNUNET_free (rc);
    331 }
    332 
    333 
    334 /**
    335  * Run the command.
    336  *
    337  * @param cls closure.
    338  * @param cmd the command to execute.
    339  * @param is the interpreter state.
    340  */
    341 static void
    342 oauth_run (void *cls,
    343            const struct TALER_TESTING_Command *cmd,
    344            struct TALER_TESTING_Interpreter *is)
    345 {
    346   struct OAuthState *oas = cls;
    347 
    348   (void) cmd;
    349   oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD | MHD_USE_DEBUG,
    350                                oas->port,
    351                                NULL, NULL,
    352                                &handler_cb, oas,
    353                                MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL,
    354                                NULL);
    355   if (NULL == oas->mhd)
    356   {
    357     GNUNET_break (0);
    358     TALER_TESTING_interpreter_fail (is);
    359     return;
    360   }
    361   TALER_TESTING_interpreter_next (is);
    362 }
    363 
    364 
    365 /**
    366  * Cleanup the state from a "oauth" CMD, and possibly cancel a operation
    367  * thereof.
    368  *
    369  * @param cls closure.
    370  * @param cmd the command which is being cleaned up.
    371  */
    372 static void
    373 oauth_cleanup (void *cls,
    374                const struct TALER_TESTING_Command *cmd)
    375 {
    376   struct OAuthState *oas = cls;
    377 
    378   (void) cmd;
    379   if (NULL != oas->mhd)
    380   {
    381     MHD_stop_daemon (oas->mhd);
    382     oas->mhd = NULL;
    383   }
    384   GNUNET_free (oas);
    385 }
    386 
    387 
    388 struct TALER_TESTING_Command
    389 TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
    390                                         const char *birthdate,
    391                                         uint16_t port)
    392 {
    393   struct OAuthState *oas;
    394 
    395   oas = GNUNET_new (struct OAuthState);
    396   oas->port = port;
    397   oas->birthdate = birthdate;
    398   {
    399     struct TALER_TESTING_Command cmd = {
    400       .cls = oas,
    401       .label = label,
    402       .run = &oauth_run,
    403       .cleanup = &oauth_cleanup,
    404     };
    405 
    406     return cmd;
    407   }
    408 }
    409 
    410 
    411 /* end of testing_api_cmd_oauth.c */