LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_oauth.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 83.3 % 90 75
Test Date: 2026-04-14 15:39:31 Functions: 100.0 % 7 7

            Line data    Source code
       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           40 : append (char **target,
      65              :         const char *data,
      66              :         size_t size)
      67              : {
      68              :   char *tmp;
      69              : 
      70           40 :   if (NULL == *target)
      71              :   {
      72           40 :     *target = GNUNET_strndup (data,
      73              :                               size);
      74           40 :     return;
      75              :   }
      76            0 :   GNUNET_asprintf (&tmp,
      77              :                    "%s%.*s",
      78              :                    *target,
      79              :                    (int) size,
      80              :                    data);
      81            0 :   GNUNET_free (*target);
      82            0 :   *target = tmp;
      83              : }
      84              : 
      85              : 
      86              : static MHD_RESULT
      87           60 : 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           60 :   struct RequestCtx *rc = cls;
      98              : 
      99              :   (void) kind;
     100              :   (void) filename;
     101              :   (void) content_type;
     102              :   (void) transfer_encoding;
     103              :   (void) off;
     104           60 :   if (0 == strcmp (key,
     105              :                    "code"))
     106           10 :     append (&rc->code,
     107              :             data,
     108              :             size);
     109           60 :   if (0 == strcmp (key,
     110              :                    "client_id"))
     111           10 :     append (&rc->client_id,
     112              :             data,
     113              :             size);
     114           60 :   if (0 == strcmp (key,
     115              :                    "redirect_uri"))
     116           10 :     append (&rc->redirect_uri,
     117              :             data,
     118              :             size);
     119           60 :   if (0 == strcmp (key,
     120              :                    "client_secret"))
     121           10 :     append (&rc->client_secret,
     122              :             data,
     123              :             size);
     124           60 :   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           39 : 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           39 :   struct RequestCtx *rc = *con_cls;
     179           39 :   struct OAuthState *oas = cls;
     180              :   unsigned int hc;
     181              :   json_t *body;
     182              : 
     183              :   (void) version;
     184           39 :   if (0 == strcasecmp (method,
     185              :                        MHD_HTTP_METHOD_GET))
     186              :   {
     187              :     json_t *data =
     188            9 :       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            9 :     if (NULL != oas->birthdate)
     197            9 :       GNUNET_assert (0 ==
     198              :                      json_object_set_new (data,
     199              :                                           "birthdate",
     200              :                                           json_string_nocheck (
     201              :                                             oas->birthdate)));
     202              : 
     203            9 :     body = GNUNET_JSON_PACK (
     204              :       GNUNET_JSON_pack_string (
     205              :         "status",
     206              :         "success"),
     207              :       GNUNET_JSON_pack_object_steal (
     208              :         "data", data));
     209            9 :     return TALER_MHD_reply_json_steal (connection,
     210              :                                        body,
     211              :                                        MHD_HTTP_OK);
     212              :   }
     213           30 :   if (0 != strcasecmp (method,
     214              :                        MHD_HTTP_METHOD_POST))
     215              :   {
     216            0 :     GNUNET_break (0);
     217            0 :     return MHD_NO;
     218              :   }
     219           30 :   if (NULL == rc)
     220              :   {
     221           10 :     rc = GNUNET_new (struct RequestCtx);
     222           10 :     *con_cls = rc;
     223           10 :     rc->pp = MHD_create_post_processor (connection,
     224              :                                         4092,
     225              :                                         &handle_post,
     226              :                                         rc);
     227           10 :     return MHD_YES;
     228              :   }
     229           20 :   if (0 != *upload_data_size)
     230              :   {
     231              :     MHD_RESULT ret;
     232              : 
     233           10 :     ret = MHD_post_process (rc->pp,
     234              :                             upload_data,
     235              :                             *upload_data_size);
     236           10 :     *upload_data_size = 0;
     237           10 :     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           10 :   if ( (NULL == rc->code) ||
     246           10 :        (NULL == rc->client_id) ||
     247           10 :        (NULL == rc->redirect_uri) ||
     248           10 :        (NULL == rc->client_secret) )
     249              :   {
     250            0 :     GNUNET_break (0);
     251            0 :     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            0 :     return MHD_NO;
     259              :   }
     260           10 :   if (0 != strcmp (rc->client_id,
     261              :                    "taler-exchange"))
     262              :   {
     263            0 :     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            0 :     hc = MHD_HTTP_NOT_FOUND;
     269              :   }
     270           10 :   else if (0 != strcmp (rc->client_secret,
     271              :                         "exchange-secret"))
     272              :   {
     273            0 :     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            0 :     hc = MHD_HTTP_FORBIDDEN;
     279              :   }
     280              :   else
     281              :   {
     282           10 :     if (0 != strcmp (rc->code,
     283              :                      "pass"))
     284              :     {
     285            1 :       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            1 :       hc = MHD_HTTP_FORBIDDEN;
     291              :     }
     292              :     else
     293              :     {
     294            9 :       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            9 :       hc = MHD_HTTP_OK;
     304              :     }
     305              :   }
     306           10 :   return TALER_MHD_reply_json_steal (connection,
     307              :                                      body,
     308              :                                      hc);
     309              : }
     310              : 
     311              : 
     312              : static void
     313           19 : cleanup (void *cls,
     314              :          struct MHD_Connection *connection,
     315              :          void **con_cls,
     316              :          enum MHD_RequestTerminationCode toe)
     317              : {
     318           19 :   struct RequestCtx *rc = *con_cls;
     319              : 
     320              :   (void) cls;
     321              :   (void) connection;
     322              :   (void) toe;
     323           19 :   if (NULL == rc)
     324            9 :     return;
     325           10 :   MHD_destroy_post_processor (rc->pp);
     326           10 :   GNUNET_free (rc->code);
     327           10 :   GNUNET_free (rc->client_id);
     328           10 :   GNUNET_free (rc->redirect_uri);
     329           10 :   GNUNET_free (rc->client_secret);
     330           10 :   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            5 : oauth_run (void *cls,
     343              :            const struct TALER_TESTING_Command *cmd,
     344              :            struct TALER_TESTING_Interpreter *is)
     345              : {
     346            5 :   struct OAuthState *oas = cls;
     347              : 
     348              :   (void) cmd;
     349           10 :   oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD | MHD_USE_DEBUG,
     350            5 :                                oas->port,
     351              :                                NULL, NULL,
     352              :                                &handler_cb, oas,
     353              :                                MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL,
     354              :                                NULL);
     355            5 :   if (NULL == oas->mhd)
     356              :   {
     357            0 :     GNUNET_break (0);
     358            0 :     TALER_TESTING_interpreter_fail (is);
     359            0 :     return;
     360              :   }
     361            5 :   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            5 : oauth_cleanup (void *cls,
     374              :                const struct TALER_TESTING_Command *cmd)
     375              : {
     376            5 :   struct OAuthState *oas = cls;
     377              : 
     378              :   (void) cmd;
     379            5 :   if (NULL != oas->mhd)
     380              :   {
     381            5 :     MHD_stop_daemon (oas->mhd);
     382            5 :     oas->mhd = NULL;
     383              :   }
     384            5 :   GNUNET_free (oas);
     385            5 : }
     386              : 
     387              : 
     388              : struct TALER_TESTING_Command
     389            5 : TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
     390              :                                         const char *birthdate,
     391              :                                         uint16_t port)
     392              : {
     393              :   struct OAuthState *oas;
     394              : 
     395            5 :   oas = GNUNET_new (struct OAuthState);
     396            5 :   oas->port = port;
     397            5 :   oas->birthdate = birthdate;
     398              :   {
     399            5 :     struct TALER_TESTING_Command cmd = {
     400              :       .cls = oas,
     401              :       .label = label,
     402              :       .run = &oauth_run,
     403              :       .cleanup = &oauth_cleanup,
     404              :     };
     405              : 
     406            5 :     return cmd;
     407              :   }
     408              : }
     409              : 
     410              : 
     411              : /* end of testing_api_cmd_oauth.c */
        

Generated by: LCOV version 2.0-1