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