merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_private-post-instances.c (23152B)


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