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