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 (11505B)


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