/* This file is part of TALER Copyright (C) 2021 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file testing/testing_api_cmd_oauth.c * @brief Implement a CMD to run an OAuth service for faking the legitimation service * @author Christian Grothoff */ #include "platform.h" #include "taler_json_lib.h" #include #include "taler_testing_lib.h" #include "taler_mhd_lib.h" /** * State for the oauth CMD. */ struct OAuthState { /** * Handle to the "oauth" service. */ struct MHD_Daemon *mhd; /** * Port to listen on. */ uint16_t port; }; /** * A client has requested the given url using the given method * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback * must call MHD callbacks to provide content to give back to the * client and return an HTTP status code (i.e. #MHD_HTTP_OK, * #MHD_HTTP_NOT_FOUND, etc.). * * @param cls argument given together with the function * pointer when the handler was registered with MHD * @param url the requested url * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, * #MHD_HTTP_METHOD_PUT, etc.) * @param version the HTTP version string (i.e. * #MHD_HTTP_VERSION_1_1) * @param upload_data the data being uploaded (excluding HEADERS, * for a POST that fits into memory and that is encoded * with a supported encoding, the POST data will NOT be * given in upload_data and is instead available as * part of #MHD_get_connection_values; very large POST * data *will* be made available incrementally in * @a upload_data) * @param[in,out] upload_data_size set initially to the size of the * @a upload_data provided; the method must update this * value to the number of bytes NOT processed; * @param[in,out] con_cls pointer that the callback can set to some * address and that will be preserved by MHD for future * calls for this request; since the access handler may * be called many times (i.e., for a PUT/POST operation * with plenty of upload data) this allows the application * to easily associate some request-specific state. * If necessary, this state can be cleaned up in the * global #MHD_RequestCompletedCallback (which * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). * Initially, `*con_cls` will be NULL. * @return #MHD_YES if the connection was handled successfully, * #MHD_NO if the socket must be closed due to a serious * error while handling the request */ static enum MHD_Result handler_cb (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { const char *code; const char *client_id; const char *redirect_uri; const char *client_secret; unsigned int hc; json_t *body; /* NOTE: In the future, we MAY want to distinguish between the different URLs and possibly return more information. For now, just do the minimum: implement the main handler that checks the code. */ code = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "code"); client_id = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "client_id"); redirect_uri = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "redirect_uri"); client_secret = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "client_secret"); if ( (NULL == code) || (NULL == client_id) || (NULL == redirect_uri) || (NULL == client_secret) ) { GNUNET_break (0); return MHD_NO; } if (0 != strcmp (client_id, "taler-exchange")) { body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("error", "unknown_client"), GNUNET_JSON_pack_string ("error_description", "only 'taler-exchange' is allowed")); hc = MHD_HTTP_NOT_FOUND; } else if (0 != strcmp (client_secret, "exchange-secret")) { body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("error", "invalid_client_secret"), GNUNET_JSON_pack_string ("error_description", "only 'exchange-secret' is valid")); hc = MHD_HTTP_FORBIDDEN; } else { if (0 != strcmp (code, "pass")) { body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("error", "invalid_grant"), GNUNET_JSON_pack_string ("error_description", "only 'pass' shall pass")); hc = MHD_HTTP_FORBIDDEN; } else { body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("access_token", "good"), GNUNET_JSON_pack_string ("token_type", "bearer"), GNUNET_JSON_pack_uint64 ("expires_in", 3600), GNUNET_JSON_pack_string ("refresh_token", "better")); hc = MHD_HTTP_OK; } } return TALER_MHD_reply_json_steal (connection, body, hc); } /** * Run the command. * * @param cls closure. * @param cmd the command to execute. * @param is the interpreter state. */ static void oauth_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { struct OAuthState *oas = cls; (void) cmd; (void) is; oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD, oas->port, NULL, NULL, &handler_cb, oas, NULL); } /** * Cleanup the state from a "oauth" CMD, and possibly cancel a operation * thereof. * * @param cls closure. * @param cmd the command which is being cleaned up. */ static void oauth_cleanup (void *cls, const struct TALER_TESTING_Command *cmd) { struct OAuthState *oas = cls; if (NULL != oas->mhd) { MHD_stop_daemon (oas->mhd); oas->mhd = NULL; } GNUNET_free (oas); } struct TALER_TESTING_Command TALER_TESTING_cmd_oauth (const char *label, uint16_t port) { struct OAuthState *oas; oas = GNUNET_new (struct OAuthState); oas->port = port; { struct TALER_TESTING_Command cmd = { .cls = oas, .label = label, .run = &oauth_run, .cleanup = &oauth_cleanup, }; return cmd; } } /* end of testing_api_cmd_oauth.c */