LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_oauth.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 75 90 83.3 %
Date: 2025-06-05 21:03:14 Functions: 7 7 100.0 %

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

Generated by: LCOV version 1.16