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