Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2020-2025 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Affero General Public License as
7 : published by the Free Software Foundation; either version 3,
8 : or (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,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 :
20 : /**
21 : * @file taler-merchant-httpd_private-post-instances.c
22 : * @brief implementing POST /instances request handling
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include "taler-merchant-httpd_private-post-instances.h"
27 : #include "taler-merchant-httpd_helper.h"
28 : #include "taler-merchant-httpd.h"
29 : #include "taler-merchant-httpd_mfa.h"
30 : #include "taler_merchant_bank_lib.h"
31 : #include <taler/taler_dbevents.h>
32 : #include <taler/taler_json_lib.h>
33 : #include <regex.h>
34 :
35 : /**
36 : * How often do we retry the simple INSERT database transaction?
37 : */
38 : #define MAX_RETRIES 3
39 :
40 :
41 : /**
42 : * Generate an instance, given its configuration.
43 : *
44 : * @param rh context of the handler
45 : * @param connection the MHD connection to handle
46 : * @param[in,out] hc context with further information about the request
47 : * @param login_token_expiration set to how long a login token validity
48 : * should be, use zero if no login token should be created
49 : * @param validation_needed true if self-provisioned and
50 : * email/phone registration is required before the
51 : * instance can become fully active
52 : * @return MHD result code
53 : */
54 : static MHD_RESULT
55 33 : post_instances (const struct TMH_RequestHandler *rh,
56 : struct MHD_Connection *connection,
57 : struct TMH_HandlerContext *hc,
58 : struct GNUNET_TIME_Relative login_token_expiration,
59 : bool validation_needed)
60 : {
61 33 : struct TALER_MERCHANTDB_InstanceSettings is = { 0 };
62 : struct TALER_MERCHANTDB_InstanceAuthSettings ias;
63 33 : const char *auth_password = NULL;
64 33 : struct TMH_WireMethod *wm_head = NULL;
65 33 : struct TMH_WireMethod *wm_tail = NULL;
66 : const json_t *jauth;
67 : bool no_pay_delay;
68 : bool no_refund_delay;
69 : bool no_transfer_delay;
70 : bool no_rounding_interval;
71 : struct GNUNET_JSON_Specification spec[] = {
72 33 : GNUNET_JSON_spec_string ("id",
73 : (const char **) &is.id),
74 33 : GNUNET_JSON_spec_string ("name",
75 : (const char **) &is.name),
76 33 : GNUNET_JSON_spec_mark_optional (
77 : GNUNET_JSON_spec_string ("email",
78 : (const char **) &is.email),
79 : NULL),
80 33 : GNUNET_JSON_spec_mark_optional (
81 : GNUNET_JSON_spec_string ("phone_number",
82 : (const char **) &is.phone),
83 : NULL),
84 33 : GNUNET_JSON_spec_mark_optional (
85 : GNUNET_JSON_spec_string ("website",
86 : (const char **) &is.website),
87 : NULL),
88 33 : GNUNET_JSON_spec_mark_optional (
89 : GNUNET_JSON_spec_string ("logo",
90 : (const char **) &is.logo),
91 : NULL),
92 33 : GNUNET_JSON_spec_object_const ("auth",
93 : &jauth),
94 33 : GNUNET_JSON_spec_json ("address",
95 : &is.address),
96 33 : GNUNET_JSON_spec_json ("jurisdiction",
97 : &is.jurisdiction),
98 33 : GNUNET_JSON_spec_bool ("use_stefan",
99 : &is.use_stefan),
100 33 : GNUNET_JSON_spec_mark_optional (
101 : GNUNET_JSON_spec_relative_time ("default_pay_delay",
102 : &is.default_pay_delay),
103 : &no_pay_delay),
104 33 : GNUNET_JSON_spec_mark_optional (
105 : GNUNET_JSON_spec_relative_time ("default_refund_delay",
106 : &is.default_refund_delay),
107 : &no_refund_delay),
108 33 : GNUNET_JSON_spec_mark_optional (
109 : GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
110 : &is.default_wire_transfer_delay),
111 : &no_transfer_delay),
112 33 : GNUNET_JSON_spec_mark_optional (
113 : GNUNET_JSON_spec_time_rounder_interval (
114 : "default_wire_transfer_rounding_interval",
115 : &is.default_wire_transfer_rounding_interval),
116 : &no_rounding_interval),
117 33 : GNUNET_JSON_spec_end ()
118 : };
119 :
120 : {
121 : enum GNUNET_GenericReturnValue res;
122 :
123 33 : res = TALER_MHD_parse_json_data (connection,
124 33 : hc->request_body,
125 : spec);
126 33 : if (GNUNET_OK != res)
127 : return (GNUNET_NO == res)
128 : ? MHD_YES
129 0 : : MHD_NO;
130 : }
131 33 : if (no_pay_delay)
132 0 : is.default_pay_delay = TMH_default_pay_delay;
133 33 : if (no_refund_delay)
134 14 : is.default_refund_delay = TMH_default_refund_delay;
135 33 : if (no_transfer_delay)
136 0 : is.default_wire_transfer_delay = TMH_default_wire_transfer_delay;
137 33 : if (no_rounding_interval)
138 : is.default_wire_transfer_rounding_interval
139 14 : = TMH_default_wire_transfer_rounding_interval;
140 : {
141 : enum GNUNET_GenericReturnValue ret;
142 :
143 33 : ret = TMH_check_auth_config (connection,
144 : jauth,
145 : &auth_password);
146 33 : if (GNUNET_OK != ret)
147 : {
148 0 : GNUNET_JSON_parse_free (spec);
149 0 : return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
150 : }
151 : }
152 :
153 : /* check 'id' well-formed */
154 : {
155 : static bool once;
156 : static regex_t reg;
157 33 : bool id_wellformed = true;
158 :
159 33 : if (! once)
160 : {
161 15 : once = true;
162 15 : GNUNET_assert (0 ==
163 : regcomp (®,
164 : "^[A-Za-z0-9][A-Za-z0-9_.@-]+$",
165 : REG_EXTENDED));
166 : }
167 :
168 33 : if (0 != regexec (®,
169 33 : is.id,
170 : 0, NULL, 0))
171 0 : id_wellformed = false;
172 33 : if (! id_wellformed)
173 : {
174 0 : GNUNET_JSON_parse_free (spec);
175 0 : return TALER_MHD_reply_with_error (connection,
176 : MHD_HTTP_BAD_REQUEST,
177 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
178 : "id");
179 : }
180 : }
181 :
182 33 : if (! TMH_location_object_valid (is.address))
183 : {
184 0 : GNUNET_break_op (0);
185 0 : GNUNET_JSON_parse_free (spec);
186 0 : return TALER_MHD_reply_with_error (connection,
187 : MHD_HTTP_BAD_REQUEST,
188 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
189 : "address");
190 : }
191 :
192 33 : if (! TMH_location_object_valid (is.jurisdiction))
193 : {
194 0 : GNUNET_break_op (0);
195 0 : GNUNET_JSON_parse_free (spec);
196 0 : return TALER_MHD_reply_with_error (connection,
197 : MHD_HTTP_BAD_REQUEST,
198 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
199 : "jurisdiction");
200 : }
201 :
202 33 : if ( (NULL != is.logo) &&
203 0 : (! TMH_image_data_url_valid (is.logo)) )
204 : {
205 0 : GNUNET_break_op (0);
206 0 : GNUNET_JSON_parse_free (spec);
207 0 : return TALER_MHD_reply_with_error (connection,
208 : MHD_HTTP_BAD_REQUEST,
209 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
210 : "logo");
211 : }
212 :
213 : {
214 : /* Test if an instance of this id is known */
215 : struct TMH_MerchantInstance *mi;
216 :
217 33 : mi = TMH_lookup_instance (is.id);
218 33 : if (NULL != mi)
219 : {
220 2 : if (mi->deleted)
221 : {
222 0 : GNUNET_JSON_parse_free (spec);
223 0 : return TALER_MHD_reply_with_error (connection,
224 : MHD_HTTP_CONFLICT,
225 : TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED,
226 0 : is.id);
227 : }
228 : /* Check for idempotency */
229 2 : if ( (0 == strcmp (mi->settings.id,
230 2 : is.id)) &&
231 2 : (0 == strcmp (mi->settings.name,
232 2 : is.name)) &&
233 2 : ((mi->settings.email == is.email) ||
234 0 : (NULL != is.email && NULL != mi->settings.email &&
235 0 : 0 == strcmp (mi->settings.email,
236 0 : is.email))) &&
237 2 : ((mi->settings.website == is.website) ||
238 0 : (NULL != is.website && NULL != mi->settings.website &&
239 0 : 0 == strcmp (mi->settings.website,
240 0 : is.website))) &&
241 2 : ((mi->settings.logo == is.logo) ||
242 0 : (NULL != is.logo && NULL != mi->settings.logo &&
243 0 : 0 == strcmp (mi->settings.logo,
244 0 : is.logo))) &&
245 2 : ( ( (NULL != auth_password) &&
246 : (GNUNET_OK ==
247 0 : TMH_check_auth (auth_password,
248 : &mi->auth.auth_salt,
249 2 : &mi->auth.auth_hash)) ) ||
250 4 : ( (NULL == auth_password) &&
251 : (GNUNET_YES ==
252 4 : GNUNET_is_zero (&mi->auth.auth_hash))) ) &&
253 2 : (1 == json_equal (mi->settings.address,
254 4 : is.address)) &&
255 2 : (1 == json_equal (mi->settings.jurisdiction,
256 2 : is.jurisdiction)) &&
257 2 : (mi->settings.use_stefan == is.use_stefan) &&
258 2 : (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
259 : ==,
260 2 : is.default_wire_transfer_delay)) &&
261 2 : (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
262 : ==,
263 2 : is.default_pay_delay)) &&
264 2 : (GNUNET_TIME_relative_cmp (mi->settings.default_refund_delay,
265 : ==,
266 : is.default_refund_delay)) )
267 : {
268 2 : GNUNET_JSON_parse_free (spec);
269 2 : return TALER_MHD_reply_static (connection,
270 : MHD_HTTP_NO_CONTENT,
271 : NULL,
272 : NULL,
273 : 0);
274 : }
275 0 : GNUNET_JSON_parse_free (spec);
276 0 : return TALER_MHD_reply_with_error (connection,
277 : MHD_HTTP_CONFLICT,
278 : TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
279 0 : is.id);
280 : }
281 : }
282 :
283 : /* Check MFA is satisfied */
284 31 : if (validation_needed)
285 : {
286 0 : enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
287 :
288 0 : if ( (0 != (TEH_TCS_SMS & TEH_mandatory_tan_channels)) &&
289 0 : (NULL == is.phone) )
290 : {
291 0 : GNUNET_break_op (0);
292 0 : GNUNET_JSON_parse_free (spec);
293 0 : return TALER_MHD_reply_with_error (connection,
294 : MHD_HTTP_BAD_REQUEST,
295 : TALER_EC_GENERIC_PARAMETER_MISSING,
296 : "phone_number");
297 :
298 : }
299 0 : if ( (0 != (TEH_TCS_EMAIL & TEH_mandatory_tan_channels)) &&
300 0 : (NULL == is.email) )
301 : {
302 0 : GNUNET_break_op (0);
303 0 : GNUNET_JSON_parse_free (spec);
304 0 : return TALER_MHD_reply_with_error (connection,
305 : MHD_HTTP_BAD_REQUEST,
306 : TALER_EC_GENERIC_PARAMETER_MISSING,
307 : "email");
308 : }
309 0 : switch (TEH_mandatory_tan_channels)
310 : {
311 0 : case TEH_TCS_NONE:
312 0 : GNUNET_assert (0);
313 : ret = GNUNET_OK;
314 : break;
315 0 : case TEH_TCS_SMS:
316 0 : is.phone_validated = true;
317 0 : ret = TMH_mfa_challenges_do (hc,
318 : TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
319 : true,
320 : TALER_MERCHANT_MFA_CHANNEL_SMS,
321 : is.phone,
322 : TALER_MERCHANT_MFA_CHANNEL_NONE);
323 0 : break;
324 0 : case TEH_TCS_EMAIL:
325 0 : is.email_validated = true;
326 0 : ret = TMH_mfa_challenges_do (hc,
327 : TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
328 : true,
329 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
330 : is.email,
331 : TALER_MERCHANT_MFA_CHANNEL_NONE);
332 0 : break;
333 0 : case TEH_TCS_EMAIL_AND_SMS:
334 0 : is.phone_validated = true;
335 0 : is.email_validated = true;
336 0 : ret = TMH_mfa_challenges_do (hc,
337 : TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
338 : true,
339 : TALER_MERCHANT_MFA_CHANNEL_SMS,
340 : is.phone,
341 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
342 : is.email,
343 : TALER_MERCHANT_MFA_CHANNEL_NONE);
344 0 : break;
345 : }
346 0 : if (GNUNET_OK != ret)
347 : {
348 0 : GNUNET_JSON_parse_free (spec);
349 : return (GNUNET_NO == ret)
350 : ? MHD_YES
351 0 : : MHD_NO;
352 : }
353 : }
354 :
355 : /* handle authentication token setup */
356 31 : if (NULL == auth_password)
357 : {
358 25 : memset (&ias.auth_salt,
359 : 0,
360 : sizeof (ias.auth_salt));
361 25 : memset (&ias.auth_hash,
362 : 0,
363 : sizeof (ias.auth_hash));
364 : }
365 : else
366 : {
367 : /* Sets 'auth_salt' and 'auth_hash' */
368 6 : TMH_compute_auth (auth_password,
369 : &ias.auth_salt,
370 : &ias.auth_hash);
371 : }
372 :
373 : /* create in-memory data structure */
374 : {
375 : struct TMH_MerchantInstance *mi;
376 : enum GNUNET_DB_QueryStatus qs;
377 :
378 31 : mi = GNUNET_new (struct TMH_MerchantInstance);
379 31 : mi->wm_head = wm_head;
380 31 : mi->wm_tail = wm_tail;
381 31 : mi->settings = is;
382 31 : mi->settings.address = json_incref (mi->settings.address);
383 31 : mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
384 31 : mi->settings.id = GNUNET_strdup (is.id);
385 31 : mi->settings.name = GNUNET_strdup (is.name);
386 31 : if (NULL != is.email)
387 0 : mi->settings.email = GNUNET_strdup (is.email);
388 31 : if (NULL != is.phone)
389 0 : mi->settings.phone = GNUNET_strdup (is.phone);
390 31 : if (NULL != is.website)
391 0 : mi->settings.website = GNUNET_strdup (is.website);
392 31 : if (NULL != is.logo)
393 0 : mi->settings.logo = GNUNET_strdup (is.logo);
394 31 : mi->auth = ias;
395 31 : mi->validation_needed = validation_needed;
396 31 : GNUNET_CRYPTO_eddsa_key_create (&mi->merchant_priv.eddsa_priv);
397 31 : GNUNET_CRYPTO_eddsa_key_get_public (&mi->merchant_priv.eddsa_priv,
398 : &mi->merchant_pub.eddsa_pub);
399 :
400 31 : for (unsigned int i = 0; i<MAX_RETRIES; i++)
401 : {
402 31 : if (GNUNET_OK !=
403 31 : TMH_db->start (TMH_db->cls,
404 : "post /instances"))
405 : {
406 0 : mi->rc = 1;
407 0 : TMH_instance_decref (mi);
408 0 : GNUNET_JSON_parse_free (spec);
409 0 : return TALER_MHD_reply_with_error (connection,
410 : MHD_HTTP_INTERNAL_SERVER_ERROR,
411 : TALER_EC_GENERIC_DB_START_FAILED,
412 : NULL);
413 : }
414 31 : qs = TMH_db->insert_instance (TMH_db->cls,
415 31 : &mi->merchant_pub,
416 31 : &mi->merchant_priv,
417 31 : &mi->settings,
418 31 : &mi->auth,
419 : validation_needed);
420 31 : switch (qs)
421 : {
422 0 : case GNUNET_DB_STATUS_HARD_ERROR:
423 : {
424 : MHD_RESULT ret;
425 :
426 0 : TMH_db->rollback (TMH_db->cls);
427 0 : GNUNET_break (0);
428 0 : ret = TALER_MHD_reply_with_error (connection,
429 : MHD_HTTP_INTERNAL_SERVER_ERROR,
430 : TALER_EC_GENERIC_DB_STORE_FAILED,
431 0 : is.id);
432 0 : mi->rc = 1;
433 0 : TMH_instance_decref (mi);
434 0 : GNUNET_JSON_parse_free (spec);
435 0 : return ret;
436 : }
437 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
438 0 : goto retry;
439 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
440 : {
441 : MHD_RESULT ret;
442 :
443 0 : TMH_db->rollback (TMH_db->cls);
444 0 : GNUNET_break (0);
445 0 : ret = TALER_MHD_reply_with_error (connection,
446 : MHD_HTTP_CONFLICT,
447 : TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
448 0 : is.id);
449 0 : mi->rc = 1;
450 0 : TMH_instance_decref (mi);
451 0 : GNUNET_JSON_parse_free (spec);
452 0 : return ret;
453 : }
454 31 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
455 : /* handled below */
456 31 : break;
457 : }
458 31 : qs = TMH_db->commit (TMH_db->cls);
459 31 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
460 31 : qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
461 0 : retry:
462 31 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
463 31 : break; /* success! -- or hard failure */
464 : } /* for .. MAX_RETRIES */
465 31 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
466 : {
467 0 : mi->rc = 1;
468 0 : TMH_instance_decref (mi);
469 0 : GNUNET_JSON_parse_free (spec);
470 0 : return TALER_MHD_reply_with_error (connection,
471 : MHD_HTTP_INTERNAL_SERVER_ERROR,
472 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
473 : NULL);
474 : }
475 : /* Finally, also update our running process */
476 31 : GNUNET_assert (GNUNET_OK ==
477 : TMH_add_instance (mi));
478 31 : TMH_reload_instances (mi->settings.id);
479 : }
480 31 : GNUNET_JSON_parse_free (spec);
481 31 : if (GNUNET_TIME_relative_is_zero (login_token_expiration))
482 : {
483 31 : return TALER_MHD_reply_static (connection,
484 : MHD_HTTP_NO_CONTENT,
485 : NULL,
486 : NULL,
487 : 0);
488 : }
489 :
490 : {
491 : struct TALER_MERCHANTDB_LoginTokenP btoken;
492 0 : enum TMH_AuthScope iscope = TMH_AS_REFRESHABLE | TMH_AS_SPA;
493 : enum GNUNET_DB_QueryStatus qs;
494 : struct GNUNET_TIME_Timestamp expiration_time;
495 0 : bool refreshable = true;
496 :
497 0 : GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
498 : &btoken,
499 : sizeof (btoken));
500 : expiration_time
501 0 : = GNUNET_TIME_relative_to_timestamp (login_token_expiration);
502 0 : qs = TMH_db->insert_login_token (TMH_db->cls,
503 0 : is.id,
504 : &btoken,
505 : GNUNET_TIME_timestamp_get (),
506 : expiration_time,
507 : iscope,
508 : "login token from instance creation");
509 0 : switch (qs)
510 : {
511 0 : case GNUNET_DB_STATUS_HARD_ERROR:
512 : case GNUNET_DB_STATUS_SOFT_ERROR:
513 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
514 0 : GNUNET_break (0);
515 0 : return TALER_MHD_reply_with_ec (connection,
516 : TALER_EC_GENERIC_DB_STORE_FAILED,
517 : "insert_login_token");
518 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
519 0 : break;
520 : }
521 :
522 : {
523 : char *tok;
524 : MHD_RESULT ret;
525 : char *val;
526 :
527 0 : val = GNUNET_STRINGS_data_to_string_alloc (&btoken,
528 : sizeof (btoken));
529 0 : GNUNET_asprintf (&tok,
530 : RFC_8959_PREFIX "%s",
531 : val);
532 0 : GNUNET_free (val);
533 0 : ret = TALER_MHD_REPLY_JSON_PACK (
534 : connection,
535 : MHD_HTTP_OK,
536 : GNUNET_JSON_pack_string ("access_token",
537 : tok),
538 : GNUNET_JSON_pack_string ("token",
539 : tok),
540 : GNUNET_JSON_pack_string ("scope",
541 : TMH_get_name_by_scope (iscope,
542 : &refreshable)),
543 : GNUNET_JSON_pack_bool ("refreshable",
544 : refreshable),
545 : GNUNET_JSON_pack_timestamp ("expiration",
546 : expiration_time));
547 0 : GNUNET_free (tok);
548 0 : return ret;
549 : }
550 : }
551 : }
552 :
553 :
554 : /**
555 : * Generate an instance, given its configuration.
556 : *
557 : * @param rh context of the handler
558 : * @param connection the MHD connection to handle
559 : * @param[in,out] hc context with further information about the request
560 : * @return MHD result code
561 : */
562 : MHD_RESULT
563 33 : TMH_private_post_instances (const struct TMH_RequestHandler *rh,
564 : struct MHD_Connection *connection,
565 : struct TMH_HandlerContext *hc)
566 : {
567 66 : return post_instances (rh,
568 : connection,
569 : hc,
570 33 : GNUNET_TIME_UNIT_ZERO,
571 : false);
572 : }
573 :
574 :
575 : /**
576 : * Generate an instance, given its configuration.
577 : * Public handler to be used when self-provisioning.
578 : *
579 : * @param rh context of the handler
580 : * @param connection the MHD connection to handle
581 : * @param[in,out] hc context with further information about the request
582 : * @return MHD result code
583 : */
584 : MHD_RESULT
585 0 : TMH_public_post_instances (const struct TMH_RequestHandler *rh,
586 : struct MHD_Connection *connection,
587 : struct TMH_HandlerContext *hc)
588 : {
589 : struct GNUNET_TIME_Relative expiration;
590 :
591 0 : TALER_MHD_parse_request_rel_time (connection,
592 : "token_validity_ms",
593 : &expiration);
594 0 : if (GNUNET_YES !=
595 : TMH_have_self_provisioning)
596 : {
597 0 : GNUNET_break_op (0);
598 0 : return TALER_MHD_reply_with_error (connection,
599 : MHD_HTTP_FORBIDDEN,
600 : TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
601 : "Self-provisioning is not enabled");
602 : }
603 :
604 0 : return post_instances (rh,
605 : connection,
606 : hc,
607 : expiration,
608 : TEH_TCS_NONE !=
609 : TEH_mandatory_tan_channels);
610 : }
611 :
612 :
613 : /* end of taler-merchant-httpd_private-post-instances.c */
|