Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2025-2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Lesser General Public License as
7 : published by the Free Software Foundation; either version 2.1,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful,
11 : but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU Lesser General Public License for more details.
14 :
15 : You should have received a copy of the GNU Lesser General
16 : Public License along with TALER; see the file COPYING.LGPL.
17 : If not, see <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file merchant_api_post-private-token.c
21 : * @brief Implementation of the POST /private/token request
22 : * @author Christian Grothoff
23 : */
24 : #include "taler/platform.h"
25 : #include <curl/curl.h>
26 : #include <jansson.h>
27 : #include <microhttpd.h> /* just for HTTP status codes */
28 : #include <gnunet/gnunet_util_lib.h>
29 : #include <gnunet/gnunet_curl_lib.h>
30 : #include <taler/merchant/post-private-token.h>
31 : #include "merchant_api_curl_defaults.h"
32 : #include "merchant_api_common.h"
33 : #include <taler/taler_json_lib.h>
34 : #include <taler/taler_curl_lib.h>
35 :
36 :
37 : /**
38 : * Handle for a POST /private/token operation.
39 : */
40 : struct TALER_MERCHANT_PostPrivateTokenHandle
41 : {
42 : /**
43 : * Base URL of the merchant backend.
44 : */
45 : char *base_url;
46 :
47 : /**
48 : * The full URL for this request.
49 : */
50 : char *url;
51 :
52 : /**
53 : * Handle for the request.
54 : */
55 : struct GNUNET_CURL_Job *job;
56 :
57 : /**
58 : * Function to call with the result.
59 : */
60 : TALER_MERCHANT_PostPrivateTokenCallback cb;
61 :
62 : /**
63 : * Closure for @a cb.
64 : */
65 : TALER_MERCHANT_POST_PRIVATE_TOKEN_RESULT_CLOSURE *cb_cls;
66 :
67 : /**
68 : * Reference to the execution context.
69 : */
70 : struct GNUNET_CURL_Context *ctx;
71 :
72 : /**
73 : * Minor context that holds body and headers.
74 : */
75 : struct TALER_CURL_PostContext post_ctx;
76 :
77 : /**
78 : * Instance identifier (or NULL).
79 : */
80 : char *instance_id;
81 :
82 : /**
83 : * Scope for the token.
84 : */
85 : char *scope;
86 :
87 : /**
88 : * How long the token should be valid.
89 : */
90 : struct GNUNET_TIME_Relative duration;
91 :
92 : /**
93 : * Whether @e duration was explicitly set via options.
94 : */
95 : bool duration_set;
96 :
97 : /**
98 : * Whether the token may be refreshed.
99 : */
100 : bool refreshable;
101 :
102 : /**
103 : * Whether @e refreshable was explicitly set via options.
104 : */
105 : bool refreshable_set;
106 :
107 : /**
108 : * Description for the token (or NULL).
109 : */
110 : const char *description;
111 : };
112 :
113 :
114 : /**
115 : * Function called when we're done processing the
116 : * HTTP POST /private/token request.
117 : *
118 : * @param cls the `struct TALER_MERCHANT_PostPrivateTokenHandle`
119 : * @param response_code HTTP response code, 0 on error
120 : * @param response response body, NULL if not in JSON
121 : */
122 : static void
123 0 : handle_post_token_finished (void *cls,
124 : long response_code,
125 : const void *response)
126 : {
127 0 : struct TALER_MERCHANT_PostPrivateTokenHandle *ppth = cls;
128 0 : const json_t *json = response;
129 0 : struct TALER_MERCHANT_PostPrivateTokenResponse ptr = {
130 0 : .hr.http_status = (unsigned int) response_code,
131 : .hr.reply = json
132 : };
133 :
134 0 : ppth->job = NULL;
135 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
136 : "POST /private/token completed with response code %u\n",
137 : (unsigned int) response_code);
138 0 : switch (response_code)
139 : {
140 0 : case MHD_HTTP_OK:
141 : {
142 : struct GNUNET_JSON_Specification spec[] = {
143 0 : GNUNET_JSON_spec_string ("access_token",
144 : &ptr.details.ok.access_token),
145 0 : GNUNET_JSON_spec_string ("scope",
146 : &ptr.details.ok.scope),
147 0 : GNUNET_JSON_spec_bool ("refreshable",
148 : &ptr.details.ok.refreshable),
149 0 : GNUNET_JSON_spec_timestamp ("expiration",
150 : &ptr.details.ok.expiration),
151 0 : GNUNET_JSON_spec_end ()
152 : };
153 :
154 0 : if (GNUNET_OK !=
155 0 : GNUNET_JSON_parse (json,
156 : spec,
157 : NULL,
158 : NULL))
159 : {
160 0 : GNUNET_break_op (0);
161 0 : ptr.hr.http_status = 0;
162 0 : ptr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
163 0 : break;
164 : }
165 : }
166 0 : break;
167 0 : case MHD_HTTP_BAD_REQUEST:
168 0 : ptr.hr.ec = TALER_JSON_get_error_code (json);
169 0 : ptr.hr.hint = TALER_JSON_get_error_hint (json);
170 0 : break;
171 0 : case MHD_HTTP_ACCEPTED:
172 0 : if (GNUNET_OK !=
173 0 : TALER_MERCHANT_parse_mfa_challenge_response_ (
174 : json,
175 : &ptr.details.accepted))
176 : {
177 0 : GNUNET_break_op (0);
178 0 : ptr.hr.http_status = 0;
179 0 : ptr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
180 : }
181 0 : break;
182 0 : case MHD_HTTP_UNAUTHORIZED:
183 0 : ptr.hr.ec = TALER_JSON_get_error_code (json);
184 0 : ptr.hr.hint = TALER_JSON_get_error_hint (json);
185 0 : break;
186 0 : default:
187 0 : ptr.hr.ec = TALER_JSON_get_error_code (json);
188 0 : ptr.hr.hint = TALER_JSON_get_error_hint (json);
189 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
190 : "Unexpected response code %u/%d\n",
191 : (unsigned int) response_code,
192 : (int) ptr.hr.ec);
193 0 : break;
194 : }
195 0 : ppth->cb (ppth->cb_cls,
196 : &ptr);
197 0 : if (MHD_HTTP_ACCEPTED == response_code)
198 0 : TALER_MERCHANT_mfa_challenge_response_free (
199 : &ptr.details.accepted);
200 0 : TALER_MERCHANT_post_private_token_cancel (ppth);
201 0 : }
202 :
203 :
204 : struct TALER_MERCHANT_PostPrivateTokenHandle *
205 0 : TALER_MERCHANT_post_private_token_create (
206 : struct GNUNET_CURL_Context *ctx,
207 : const char *url,
208 : const char *instance_id,
209 : const char *scope)
210 : {
211 : struct TALER_MERCHANT_PostPrivateTokenHandle *ppth;
212 :
213 0 : ppth = GNUNET_new (struct TALER_MERCHANT_PostPrivateTokenHandle);
214 0 : ppth->ctx = ctx;
215 0 : ppth->base_url = GNUNET_strdup (url);
216 0 : if (NULL != instance_id)
217 0 : ppth->instance_id = GNUNET_strdup (instance_id);
218 0 : ppth->scope = GNUNET_strdup (scope);
219 0 : return ppth;
220 : }
221 :
222 :
223 : enum GNUNET_GenericReturnValue
224 0 : TALER_MERCHANT_post_private_token_set_options_ (
225 : struct TALER_MERCHANT_PostPrivateTokenHandle *ppth,
226 : unsigned int num_options,
227 : const struct TALER_MERCHANT_PostPrivateTokenOptionValue *options)
228 : {
229 0 : for (unsigned int i = 0; i < num_options; i++)
230 : {
231 0 : switch (options[i].option)
232 : {
233 0 : case TALER_MERCHANT_POST_PRIVATE_TOKEN_OPTION_END:
234 0 : return GNUNET_OK;
235 0 : case TALER_MERCHANT_POST_PRIVATE_TOKEN_OPTION_DURATION:
236 0 : ppth->duration = options[i].details.duration;
237 0 : ppth->duration_set = true;
238 0 : break;
239 0 : case TALER_MERCHANT_POST_PRIVATE_TOKEN_OPTION_REFRESHABLE:
240 0 : ppth->refreshable = options[i].details.refreshable;
241 0 : ppth->refreshable_set = true;
242 0 : break;
243 0 : case TALER_MERCHANT_POST_PRIVATE_TOKEN_OPTION_DESCRIPTION:
244 0 : ppth->description = options[i].details.description;
245 0 : break;
246 0 : default:
247 0 : GNUNET_break (0);
248 0 : return GNUNET_SYSERR;
249 : }
250 : }
251 0 : return GNUNET_OK;
252 : }
253 :
254 :
255 : enum TALER_ErrorCode
256 0 : TALER_MERCHANT_post_private_token_start (
257 : struct TALER_MERCHANT_PostPrivateTokenHandle *ppth,
258 : TALER_MERCHANT_PostPrivateTokenCallback cb,
259 : TALER_MERCHANT_POST_PRIVATE_TOKEN_RESULT_CLOSURE *cb_cls)
260 : {
261 : json_t *req_obj;
262 : CURL *eh;
263 :
264 0 : ppth->cb = cb;
265 0 : ppth->cb_cls = cb_cls;
266 0 : if (NULL != ppth->instance_id)
267 : {
268 : char *path;
269 :
270 0 : GNUNET_asprintf (&path,
271 : "instances/%s/private/token",
272 : ppth->instance_id);
273 0 : ppth->url = TALER_url_join (ppth->base_url,
274 : path,
275 : NULL);
276 0 : GNUNET_free (path);
277 : }
278 : else
279 : {
280 0 : ppth->url = TALER_url_join (ppth->base_url,
281 : "private/token",
282 : NULL);
283 : }
284 0 : if (NULL == ppth->url)
285 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
286 0 : req_obj = GNUNET_JSON_PACK (
287 : GNUNET_JSON_pack_string ("scope",
288 : ppth->scope));
289 0 : if (ppth->duration_set)
290 : {
291 0 : GNUNET_assert (0 ==
292 : json_object_set_new (req_obj,
293 : "duration",
294 : GNUNET_JSON_from_time_rel (
295 : ppth->duration)));
296 : }
297 0 : if (ppth->refreshable_set)
298 : {
299 0 : GNUNET_assert (0 ==
300 : json_object_set_new (req_obj,
301 : "refreshable",
302 : json_boolean (ppth->refreshable)));
303 : }
304 0 : if (NULL != ppth->description)
305 : {
306 0 : GNUNET_assert (0 ==
307 : json_object_set_new (req_obj,
308 : "description",
309 : json_string (ppth->description)));
310 : }
311 0 : eh = TALER_MERCHANT_curl_easy_get_ (ppth->url);
312 0 : if ( (NULL == eh) ||
313 : (GNUNET_OK !=
314 0 : TALER_curl_easy_post (&ppth->post_ctx,
315 : eh,
316 : req_obj)) )
317 : {
318 0 : GNUNET_break (0);
319 0 : json_decref (req_obj);
320 0 : if (NULL != eh)
321 0 : curl_easy_cleanup (eh);
322 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
323 : }
324 0 : json_decref (req_obj);
325 0 : GNUNET_assert (CURLE_OK ==
326 : curl_easy_setopt (eh,
327 : CURLOPT_CUSTOMREQUEST,
328 : MHD_HTTP_METHOD_POST));
329 0 : ppth->job = GNUNET_CURL_job_add2 (ppth->ctx,
330 : eh,
331 0 : ppth->post_ctx.headers,
332 : &handle_post_token_finished,
333 : ppth);
334 0 : if (NULL == ppth->job)
335 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
336 0 : return TALER_EC_NONE;
337 : }
338 :
339 :
340 : void
341 0 : TALER_MERCHANT_post_private_token_cancel (
342 : struct TALER_MERCHANT_PostPrivateTokenHandle *ppth)
343 : {
344 0 : if (NULL != ppth->job)
345 : {
346 0 : GNUNET_CURL_job_cancel (ppth->job);
347 0 : ppth->job = NULL;
348 : }
349 0 : TALER_curl_easy_post_finished (&ppth->post_ctx);
350 0 : GNUNET_free (ppth->instance_id);
351 0 : GNUNET_free (ppth->scope);
352 0 : GNUNET_free (ppth->url);
353 0 : GNUNET_free (ppth->base_url);
354 0 : GNUNET_free (ppth);
355 0 : }
356 :
357 :
358 : /* end of merchant_api_post-private-token.c */
|