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 "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 40 : append (char **target,
65 : const char *data,
66 : size_t size)
67 : {
68 : char *tmp;
69 :
70 40 : if (NULL == *target)
71 : {
72 40 : *target = GNUNET_strndup (data,
73 : size);
74 40 : return;
75 : }
76 0 : GNUNET_asprintf (&tmp,
77 : "%s%.*s",
78 : *target,
79 : (int) size,
80 : data);
81 0 : GNUNET_free (*target);
82 0 : *target = tmp;
83 : }
84 :
85 :
86 : static MHD_RESULT
87 60 : 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 60 : struct RequestCtx *rc = cls;
98 :
99 : (void) kind;
100 : (void) filename;
101 : (void) content_type;
102 : (void) transfer_encoding;
103 : (void) off;
104 60 : if (0 == strcmp (key,
105 : "code"))
106 10 : append (&rc->code,
107 : data,
108 : size);
109 60 : if (0 == strcmp (key,
110 : "client_id"))
111 10 : append (&rc->client_id,
112 : data,
113 : size);
114 60 : if (0 == strcmp (key,
115 : "redirect_uri"))
116 10 : append (&rc->redirect_uri,
117 : data,
118 : size);
119 60 : if (0 == strcmp (key,
120 : "client_secret"))
121 10 : append (&rc->client_secret,
122 : data,
123 : size);
124 60 : 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 39 : 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 39 : struct RequestCtx *rc = *con_cls;
179 39 : struct OAuthState *oas = cls;
180 : unsigned int hc;
181 : json_t *body;
182 :
183 : (void) version;
184 39 : if (0 == strcasecmp (method,
185 : MHD_HTTP_METHOD_GET))
186 : {
187 : json_t *data =
188 9 : 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 9 : if (NULL != oas->birthdate)
197 9 : GNUNET_assert (0 ==
198 : json_object_set_new (data,
199 : "birthdate",
200 : json_string_nocheck (
201 : oas->birthdate)));
202 :
203 9 : body = GNUNET_JSON_PACK (
204 : GNUNET_JSON_pack_string (
205 : "status",
206 : "success"),
207 : GNUNET_JSON_pack_object_steal (
208 : "data", data));
209 9 : return TALER_MHD_reply_json_steal (connection,
210 : body,
211 : MHD_HTTP_OK);
212 : }
213 30 : if (0 != strcasecmp (method,
214 : MHD_HTTP_METHOD_POST))
215 : {
216 0 : GNUNET_break (0);
217 0 : return MHD_NO;
218 : }
219 30 : if (NULL == rc)
220 : {
221 10 : rc = GNUNET_new (struct RequestCtx);
222 10 : *con_cls = rc;
223 10 : rc->pp = MHD_create_post_processor (connection,
224 : 4092,
225 : &handle_post,
226 : rc);
227 10 : return MHD_YES;
228 : }
229 20 : if (0 != *upload_data_size)
230 : {
231 : MHD_RESULT ret;
232 :
233 10 : ret = MHD_post_process (rc->pp,
234 : upload_data,
235 : *upload_data_size);
236 10 : *upload_data_size = 0;
237 10 : 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 10 : if ( (NULL == rc->code) ||
246 10 : (NULL == rc->client_id) ||
247 10 : (NULL == rc->redirect_uri) ||
248 10 : (NULL == rc->client_secret) )
249 : {
250 0 : GNUNET_break (0);
251 0 : 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 0 : return MHD_NO;
259 : }
260 10 : if (0 != strcmp (rc->client_id,
261 : "taler-exchange"))
262 : {
263 0 : 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 0 : hc = MHD_HTTP_NOT_FOUND;
269 : }
270 10 : else if (0 != strcmp (rc->client_secret,
271 : "exchange-secret"))
272 : {
273 0 : 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 0 : hc = MHD_HTTP_FORBIDDEN;
279 : }
280 : else
281 : {
282 10 : if (0 != strcmp (rc->code,
283 : "pass"))
284 : {
285 1 : 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 1 : hc = MHD_HTTP_FORBIDDEN;
291 : }
292 : else
293 : {
294 9 : 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 9 : hc = MHD_HTTP_OK;
304 : }
305 : }
306 10 : return TALER_MHD_reply_json_steal (connection,
307 : body,
308 : hc);
309 : }
310 :
311 :
312 : static void
313 19 : cleanup (void *cls,
314 : struct MHD_Connection *connection,
315 : void **con_cls,
316 : enum MHD_RequestTerminationCode toe)
317 : {
318 19 : struct RequestCtx *rc = *con_cls;
319 :
320 : (void) cls;
321 : (void) connection;
322 : (void) toe;
323 19 : if (NULL == rc)
324 9 : return;
325 10 : MHD_destroy_post_processor (rc->pp);
326 10 : GNUNET_free (rc->code);
327 10 : GNUNET_free (rc->client_id);
328 10 : GNUNET_free (rc->redirect_uri);
329 10 : GNUNET_free (rc->client_secret);
330 10 : 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 5 : oauth_run (void *cls,
343 : const struct TALER_TESTING_Command *cmd,
344 : struct TALER_TESTING_Interpreter *is)
345 : {
346 5 : struct OAuthState *oas = cls;
347 :
348 : (void) cmd;
349 10 : oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD | MHD_USE_DEBUG,
350 5 : oas->port,
351 : NULL, NULL,
352 : &handler_cb, oas,
353 : MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL,
354 : NULL);
355 5 : if (NULL == oas->mhd)
356 : {
357 0 : GNUNET_break (0);
358 0 : TALER_TESTING_interpreter_fail (is);
359 0 : return;
360 : }
361 5 : 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 5 : oauth_cleanup (void *cls,
374 : const struct TALER_TESTING_Command *cmd)
375 : {
376 5 : struct OAuthState *oas = cls;
377 :
378 : (void) cmd;
379 5 : if (NULL != oas->mhd)
380 : {
381 5 : MHD_stop_daemon (oas->mhd);
382 5 : oas->mhd = NULL;
383 : }
384 5 : GNUNET_free (oas);
385 5 : }
386 :
387 :
388 : struct TALER_TESTING_Command
389 5 : TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
390 : const char *birthdate,
391 : uint16_t port)
392 : {
393 : struct OAuthState *oas;
394 :
395 5 : oas = GNUNET_new (struct OAuthState);
396 5 : oas->port = port;
397 5 : oas->birthdate = birthdate;
398 : {
399 5 : struct TALER_TESTING_Command cmd = {
400 : .cls = oas,
401 : .label = label,
402 : .run = &oauth_run,
403 : .cleanup = &oauth_cleanup,
404 : };
405 :
406 5 : return cmd;
407 : }
408 : }
409 :
410 :
411 : /* end of testing_api_cmd_oauth.c */
|