Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2021 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 : * Port to listen on.
44 : */
45 : uint16_t port;
46 : };
47 :
48 :
49 : struct RequestCtx
50 : {
51 : struct MHD_PostProcessor *pp;
52 : char *code;
53 : char *client_id;
54 : char *redirect_uri;
55 : char *client_secret;
56 : };
57 :
58 :
59 : static void
60 0 : append (char **target,
61 : const char *data,
62 : size_t size)
63 : {
64 : char *tmp;
65 :
66 0 : if (NULL == *target)
67 : {
68 0 : *target = GNUNET_strndup (data,
69 : size);
70 0 : return;
71 : }
72 0 : GNUNET_asprintf (&tmp,
73 : "%s%.*s",
74 : *target,
75 : (int) size,
76 : data);
77 0 : GNUNET_free (*target);
78 0 : *target = tmp;
79 : }
80 :
81 :
82 : static MHD_RESULT
83 0 : handle_post (void *cls,
84 : enum MHD_ValueKind kind,
85 : const char *key,
86 : const char *filename,
87 : const char *content_type,
88 : const char *transfer_encoding,
89 : const char *data,
90 : uint64_t off,
91 : size_t size)
92 : {
93 0 : struct RequestCtx *rc = cls;
94 :
95 : (void) kind;
96 : (void) filename;
97 : (void) content_type;
98 : (void) transfer_encoding;
99 : (void) off;
100 0 : if (0 == strcmp (key,
101 : "code"))
102 0 : append (&rc->code,
103 : data,
104 : size);
105 0 : if (0 == strcmp (key,
106 : "client_id"))
107 0 : append (&rc->client_id,
108 : data,
109 : size);
110 0 : if (0 == strcmp (key,
111 : "redirect_uri"))
112 0 : append (&rc->redirect_uri,
113 : data,
114 : size);
115 0 : if (0 == strcmp (key,
116 : "client_secret"))
117 0 : append (&rc->client_secret,
118 : data,
119 : size);
120 0 : return MHD_YES;
121 : }
122 :
123 :
124 : /**
125 : * A client has requested the given url using the given method
126 : * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
127 : * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
128 : * must call MHD callbacks to provide content to give back to the
129 : * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
130 : * #MHD_HTTP_NOT_FOUND, etc.).
131 : *
132 : * @param cls argument given together with the function
133 : * pointer when the handler was registered with MHD
134 : * @param connection the connection being handled
135 : * @param url the requested url
136 : * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
137 : * #MHD_HTTP_METHOD_PUT, etc.)
138 : * @param version the HTTP version string (i.e.
139 : * MHD_HTTP_VERSION_1_1)
140 : * @param upload_data the data being uploaded (excluding HEADERS,
141 : * for a POST that fits into memory and that is encoded
142 : * with a supported encoding, the POST data will NOT be
143 : * given in upload_data and is instead available as
144 : * part of MHD_get_connection_values(); very large POST
145 : * data *will* be made available incrementally in
146 : * @a upload_data)
147 : * @param[in,out] upload_data_size set initially to the size of the
148 : * @a upload_data provided; the method must update this
149 : * value to the number of bytes NOT processed;
150 : * @param[in,out] con_cls pointer that the callback can set to some
151 : * address and that will be preserved by MHD for future
152 : * calls for this request; since the access handler may
153 : * be called many times (i.e., for a PUT/POST operation
154 : * with plenty of upload data) this allows the application
155 : * to easily associate some request-specific state.
156 : * If necessary, this state can be cleaned up in the
157 : * global MHD_RequestCompletedCallback (which
158 : * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
159 : * Initially, `*con_cls` will be NULL.
160 : * @return #MHD_YES if the connection was handled successfully,
161 : * #MHD_NO if the socket must be closed due to a serious
162 : * error while handling the request
163 : */
164 : static MHD_RESULT
165 0 : handler_cb (void *cls,
166 : struct MHD_Connection *connection,
167 : const char *url,
168 : const char *method,
169 : const char *version,
170 : const char *upload_data,
171 : size_t *upload_data_size,
172 : void **con_cls)
173 : {
174 0 : struct RequestCtx *rc = *con_cls;
175 : unsigned int hc;
176 : json_t *body;
177 :
178 : (void) cls;
179 : (void) version;
180 0 : if (0 == strcasecmp (method,
181 : MHD_HTTP_METHOD_GET))
182 : {
183 0 : body = GNUNET_JSON_PACK (
184 : GNUNET_JSON_pack_string (
185 : "status",
186 : "success"),
187 : GNUNET_JSON_pack_object_steal (
188 : "data",
189 : GNUNET_JSON_PACK (
190 : GNUNET_JSON_pack_string ("id",
191 : "XXXID12345678"))));
192 0 : return TALER_MHD_reply_json_steal (connection,
193 : body,
194 : MHD_HTTP_OK);
195 : }
196 0 : if (0 != strcasecmp (method,
197 : MHD_HTTP_METHOD_POST))
198 : {
199 0 : GNUNET_break (0);
200 0 : return MHD_NO;
201 : }
202 0 : if (NULL == rc)
203 : {
204 0 : rc = GNUNET_new (struct RequestCtx);
205 0 : *con_cls = rc;
206 0 : rc->pp = MHD_create_post_processor (connection,
207 : 4092,
208 : &handle_post,
209 : rc);
210 0 : return MHD_YES;
211 : }
212 0 : if (0 != *upload_data_size)
213 : {
214 : MHD_RESULT ret;
215 :
216 0 : ret = MHD_post_process (rc->pp,
217 : upload_data,
218 : *upload_data_size);
219 0 : *upload_data_size = 0;
220 0 : return ret;
221 : }
222 :
223 :
224 : /* NOTE: In the future, we MAY want to distinguish between
225 : the different URLs and possibly return more information.
226 : For now, just do the minimum: implement the main handler
227 : that checks the code. */
228 0 : if ( (NULL == rc->code) ||
229 0 : (NULL == rc->client_id) ||
230 0 : (NULL == rc->redirect_uri) ||
231 0 : (NULL == rc->client_secret) )
232 : {
233 0 : GNUNET_break (0);
234 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
235 : "Bad request to Oauth faker: `%s' with %s/%s/%s/%s\n",
236 : url,
237 : rc->code,
238 : rc->client_id,
239 : rc->redirect_uri,
240 : rc->client_secret);
241 0 : return MHD_NO;
242 : }
243 0 : if (0 != strcmp (rc->client_id,
244 : "taler-exchange"))
245 : {
246 0 : body = GNUNET_JSON_PACK (
247 : GNUNET_JSON_pack_string ("error",
248 : "unknown_client"),
249 : GNUNET_JSON_pack_string ("error_description",
250 : "only 'taler-exchange' is allowed"));
251 0 : hc = MHD_HTTP_NOT_FOUND;
252 : }
253 0 : else if (0 != strcmp (rc->client_secret,
254 : "exchange-secret"))
255 : {
256 0 : body = GNUNET_JSON_PACK (
257 : GNUNET_JSON_pack_string ("error",
258 : "invalid_client_secret"),
259 : GNUNET_JSON_pack_string ("error_description",
260 : "only 'exchange-secret' is valid"));
261 0 : hc = MHD_HTTP_FORBIDDEN;
262 : }
263 : else
264 : {
265 0 : if (0 != strcmp (rc->code,
266 : "pass"))
267 : {
268 0 : body = GNUNET_JSON_PACK (
269 : GNUNET_JSON_pack_string ("error",
270 : "invalid_grant"),
271 : GNUNET_JSON_pack_string ("error_description",
272 : "only 'pass' shall pass"));
273 0 : hc = MHD_HTTP_FORBIDDEN;
274 : }
275 : else
276 : {
277 0 : body = GNUNET_JSON_PACK (
278 : GNUNET_JSON_pack_string ("access_token",
279 : "good"),
280 : GNUNET_JSON_pack_string ("token_type",
281 : "bearer"),
282 : GNUNET_JSON_pack_uint64 ("expires_in",
283 : 3600),
284 : GNUNET_JSON_pack_string ("refresh_token",
285 : "better"));
286 0 : hc = MHD_HTTP_OK;
287 : }
288 : }
289 0 : return TALER_MHD_reply_json_steal (connection,
290 : body,
291 : hc);
292 : }
293 :
294 :
295 : static void
296 0 : cleanup (void *cls,
297 : struct MHD_Connection *connection,
298 : void **con_cls,
299 : enum MHD_RequestTerminationCode toe)
300 : {
301 0 : struct RequestCtx *rc = *con_cls;
302 :
303 : (void) cls;
304 : (void) connection;
305 : (void) toe;
306 0 : if (NULL == rc)
307 0 : return;
308 0 : GNUNET_free (rc->code);
309 0 : GNUNET_free (rc->client_id);
310 0 : GNUNET_free (rc->redirect_uri);
311 0 : GNUNET_free (rc->client_secret);
312 0 : GNUNET_free (rc);
313 : }
314 :
315 :
316 : /**
317 : * Run the command.
318 : *
319 : * @param cls closure.
320 : * @param cmd the command to execute.
321 : * @param is the interpreter state.
322 : */
323 : static void
324 0 : oauth_run (void *cls,
325 : const struct TALER_TESTING_Command *cmd,
326 : struct TALER_TESTING_Interpreter *is)
327 : {
328 0 : struct OAuthState *oas = cls;
329 :
330 : (void) cmd;
331 0 : oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD,
332 0 : oas->port,
333 : NULL, NULL,
334 : &handler_cb, oas,
335 : MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL,
336 : NULL);
337 0 : TALER_TESTING_interpreter_next (is);
338 0 : }
339 :
340 :
341 : /**
342 : * Cleanup the state from a "oauth" CMD, and possibly cancel a operation
343 : * thereof.
344 : *
345 : * @param cls closure.
346 : * @param cmd the command which is being cleaned up.
347 : */
348 : static void
349 0 : oauth_cleanup (void *cls,
350 : const struct TALER_TESTING_Command *cmd)
351 : {
352 0 : struct OAuthState *oas = cls;
353 :
354 : (void) cmd;
355 0 : if (NULL != oas->mhd)
356 : {
357 0 : MHD_stop_daemon (oas->mhd);
358 0 : oas->mhd = NULL;
359 : }
360 0 : GNUNET_free (oas);
361 0 : }
362 :
363 :
364 : struct TALER_TESTING_Command
365 0 : TALER_TESTING_cmd_oauth (const char *label,
366 : uint16_t port)
367 : {
368 : struct OAuthState *oas;
369 :
370 0 : oas = GNUNET_new (struct OAuthState);
371 0 : oas->port = port;
372 : {
373 0 : struct TALER_TESTING_Command cmd = {
374 : .cls = oas,
375 : .label = label,
376 : .run = &oauth_run,
377 : .cleanup = &oauth_cleanup,
378 : };
379 :
380 0 : return cmd;
381 : }
382 : }
383 :
384 :
385 : /* end of testing_api_cmd_oauth.c */
|