merchant

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

taler-merchant-httpd_post-management-instances.c (26261B)


      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 src/backend/taler-merchant-httpd_post-management-instances.c
     22  * @brief implementing POST /instances request handling
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_post-management-instances.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include "taler-merchant-httpd.h"
     29 #include "taler-merchant-httpd_auth.h"
     30 #include "taler-merchant-httpd_mfa.h"
     31 #include "taler/taler_merchant_bank_lib.h"
     32 #include <taler/taler_dbevents.h>
     33 #include <taler/taler_json_lib.h>
     34 #include <regex.h>
     35 #include "merchant-database/insert_instance.h"
     36 #include "merchant-database/set_instance.h"
     37 #include "merchant-database/insert_login_token.h"
     38 #include "merchant-database/start.h"
     39 
     40 /**
     41  * How often do we retry the simple INSERT database transaction?
     42  */
     43 #define MAX_RETRIES 3
     44 
     45 
     46 /**
     47  * Generate an instance, given its configuration.
     48  *
     49  * @param rh context of the handler
     50  * @param connection the MHD connection to handle
     51  * @param[in,out] hc context with further information about the request
     52  * @param login_token_expiration set to how long a login token validity
     53  *   should be, use zero if no login token should be created
     54  * @param validation_needed true if self-provisioned and
     55  *   email/phone registration is required before the
     56  *   instance can become fully active
     57  * @return MHD result code
     58  */
     59 static enum MHD_Result
     60 post_instances (const struct TMH_RequestHandler *rh,
     61                 struct MHD_Connection *connection,
     62                 struct TMH_HandlerContext *hc,
     63                 struct GNUNET_TIME_Relative login_token_expiration,
     64                 bool validation_needed)
     65 {
     66   struct TALER_MERCHANTDB_InstanceSettings is = { 0 };
     67   struct TALER_MERCHANTDB_InstanceAuthSettings ias;
     68   const char *auth_password = NULL;
     69   struct TMH_WireMethod *wm_head = NULL;
     70   struct TMH_WireMethod *wm_tail = NULL;
     71   const json_t *jauth;
     72   const char *iphone = NULL;
     73   char *id_lower;
     74   bool no_pay_delay;
     75   bool no_refund_delay;
     76   bool no_transfer_delay;
     77   bool no_rounding_interval;
     78   struct GNUNET_JSON_Specification spec[] = {
     79     TALER_JSON_spec_slug ("id",
     80                           (const char **) &is.id),
     81     GNUNET_JSON_spec_string ("name",
     82                              (const char **) &is.name),
     83     GNUNET_JSON_spec_mark_optional (
     84       GNUNET_JSON_spec_string ("email",
     85                                (const char **) &is.email),
     86       NULL),
     87     GNUNET_JSON_spec_mark_optional (
     88       GNUNET_JSON_spec_string ("phone_number",
     89                                &iphone),
     90       NULL),
     91     GNUNET_JSON_spec_mark_optional (
     92       TALER_JSON_spec_web_url ("website",
     93                                (const char **) &is.website),
     94       NULL),
     95     GNUNET_JSON_spec_mark_optional (
     96       GNUNET_JSON_spec_string ("logo",
     97                                (const char **) &is.logo),
     98       NULL),
     99     GNUNET_JSON_spec_object_const ("auth",
    100                                    &jauth),
    101     GNUNET_JSON_spec_json ("address",
    102                            &is.address),
    103     GNUNET_JSON_spec_json ("jurisdiction",
    104                            &is.jurisdiction),
    105     GNUNET_JSON_spec_bool ("use_stefan",
    106                            &is.use_stefan),
    107     GNUNET_JSON_spec_mark_optional (
    108       GNUNET_JSON_spec_relative_time ("default_pay_delay",
    109                                       &is.default_pay_delay),
    110       &no_pay_delay),
    111     GNUNET_JSON_spec_mark_optional (
    112       GNUNET_JSON_spec_relative_time ("default_refund_delay",
    113                                       &is.default_refund_delay),
    114       &no_refund_delay),
    115     GNUNET_JSON_spec_mark_optional (
    116       GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
    117                                       &is.default_wire_transfer_delay),
    118       &no_transfer_delay),
    119     GNUNET_JSON_spec_mark_optional (
    120       GNUNET_JSON_spec_time_rounder_interval (
    121         "default_wire_transfer_rounding_interval",
    122         &is.default_wire_transfer_rounding_interval),
    123       &no_rounding_interval),
    124     GNUNET_JSON_spec_end ()
    125   };
    126 
    127   {
    128     enum GNUNET_GenericReturnValue res;
    129 
    130     res = TALER_MHD_parse_json_data (connection,
    131                                      hc->request_body,
    132                                      spec);
    133     if (GNUNET_OK != res)
    134       return (GNUNET_NO == res)
    135              ? MHD_YES
    136              : MHD_NO;
    137   }
    138   if (no_pay_delay)
    139     is.default_pay_delay = TMH_default_pay_delay;
    140   if (no_refund_delay)
    141     is.default_refund_delay = TMH_default_refund_delay;
    142   if (no_transfer_delay)
    143     is.default_wire_transfer_delay = TMH_default_wire_transfer_delay;
    144   if (no_rounding_interval)
    145     is.default_wire_transfer_rounding_interval
    146       = TMH_default_wire_transfer_rounding_interval;
    147   if (GNUNET_TIME_relative_is_forever (is.default_pay_delay))
    148   {
    149     GNUNET_break_op (0);
    150     GNUNET_JSON_parse_free (spec);
    151     return TALER_MHD_reply_with_error (connection,
    152                                        MHD_HTTP_BAD_REQUEST,
    153                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    154                                        "default_pay_delay");
    155   }
    156   if (GNUNET_TIME_relative_is_forever (is.default_refund_delay))
    157   {
    158     GNUNET_break_op (0);
    159     GNUNET_JSON_parse_free (spec);
    160     return TALER_MHD_reply_with_error (connection,
    161                                        MHD_HTTP_BAD_REQUEST,
    162                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    163                                        "default_refund_delay");
    164   }
    165   if (GNUNET_TIME_relative_is_forever (is.default_wire_transfer_delay))
    166   {
    167     GNUNET_break_op (0);
    168     GNUNET_JSON_parse_free (spec);
    169     return TALER_MHD_reply_with_error (connection,
    170                                        MHD_HTTP_BAD_REQUEST,
    171                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    172                                        "default_wire_transfer_delay");
    173   }
    174   if (NULL != iphone)
    175   {
    176     is.phone = TALER_MERCHANT_phone_validate_normalize (iphone,
    177                                                         false);
    178     if (NULL == is.phone)
    179     {
    180       GNUNET_break_op (0);
    181       GNUNET_JSON_parse_free (spec);
    182       return TALER_MHD_reply_with_error (connection,
    183                                          MHD_HTTP_BAD_REQUEST,
    184                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    185                                          "phone_number");
    186     }
    187     if ( (NULL != TMH_phone_regex) &&
    188          (0 !=
    189           regexec (&TMH_phone_rx,
    190                    is.phone,
    191                    0,
    192                    NULL,
    193                    0)) )
    194     {
    195       GNUNET_break_op (0);
    196       GNUNET_JSON_parse_free (spec);
    197       return TALER_MHD_reply_with_error (connection,
    198                                          MHD_HTTP_BAD_REQUEST,
    199                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    200                                          "phone_number");
    201     }
    202   }
    203   if ( (NULL != is.email) &&
    204        (! TALER_MERCHANT_email_valid (is.email)) )
    205   {
    206     GNUNET_break_op (0);
    207     GNUNET_JSON_parse_free (spec);
    208     GNUNET_free (is.phone);
    209     return TALER_MHD_reply_with_error (connection,
    210                                        MHD_HTTP_BAD_REQUEST,
    211                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    212                                        "email");
    213   }
    214 
    215   {
    216     enum GNUNET_GenericReturnValue ret;
    217 
    218     ret = TMH_check_auth_config (connection,
    219                                  jauth,
    220                                  &auth_password);
    221     if (GNUNET_OK != ret)
    222     {
    223       GNUNET_free (is.phone);
    224       GNUNET_JSON_parse_free (spec);
    225       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
    226     }
    227   }
    228 
    229   /* check extra constraints on instance IDs beyond is 'slug' */
    230   {
    231     bool id_wellformed = true;
    232 
    233     if ('\0' == is.id[1])
    234       id_wellformed = false;   /* at least two characters */
    235     if (! ( ( ('a' <= is.id[0]) && ('z' >= is.id[0]) ) ||
    236             ( ('A' <= is.id[0]) && ('Z' >= is.id[0]) ) ||
    237             ( ('0' <= is.id[0]) && ('9' >= is.id[0]) ) ) )
    238       id_wellformed = false;   /* must start alphanumeric */
    239     if (! id_wellformed)
    240     {
    241       GNUNET_break_op (0);
    242       GNUNET_JSON_parse_free (spec);
    243       GNUNET_free (is.phone);
    244       return TALER_MHD_reply_with_error (connection,
    245                                          MHD_HTTP_BAD_REQUEST,
    246                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    247                                          "id");
    248     }
    249   }
    250 
    251   if (! TMH_location_object_valid (is.address))
    252   {
    253     GNUNET_break_op (0);
    254     GNUNET_JSON_parse_free (spec);
    255     GNUNET_free (is.phone);
    256     return TALER_MHD_reply_with_error (connection,
    257                                        MHD_HTTP_BAD_REQUEST,
    258                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    259                                        "address");
    260   }
    261 
    262   if (! TMH_location_object_valid (is.jurisdiction))
    263   {
    264     GNUNET_break_op (0);
    265     GNUNET_JSON_parse_free (spec);
    266     GNUNET_free (is.phone);
    267     return TALER_MHD_reply_with_error (connection,
    268                                        MHD_HTTP_BAD_REQUEST,
    269                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    270                                        "jurisdiction");
    271   }
    272 
    273   if ( (NULL != is.logo) &&
    274        (! TALER_MERCHANT_image_data_url_valid (is.logo)) )
    275   {
    276     GNUNET_break_op (0);
    277     GNUNET_JSON_parse_free (spec);
    278     GNUNET_free (is.phone);
    279     return TALER_MHD_reply_with_error (connection,
    280                                        MHD_HTTP_BAD_REQUEST,
    281                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    282                                        "logo");
    283   }
    284 
    285   id_lower = GNUNET_STRINGS_utf8_tolower (is.id);
    286   {
    287     /* Test if an instance of this id is known */
    288     struct TMH_MerchantInstance *mi;
    289 
    290     mi = TMH_lookup_instance (id_lower);
    291     if (NULL != mi)
    292     {
    293       enum MHD_Result ret;
    294 
    295       if (mi->deleted)
    296       {
    297         GNUNET_JSON_parse_free (spec);
    298         GNUNET_free (is.phone);
    299         GNUNET_free (id_lower);
    300         return TALER_MHD_reply_with_error (
    301           connection,
    302           MHD_HTTP_CONFLICT,
    303           TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED,
    304           is.id);
    305       }
    306       /* Check for idempotency */
    307       if ( (0 == strcmp (mi->settings.id,
    308                          id_lower)) &&
    309            (0 == strcmp (mi->settings.name,
    310                          is.name)) &&
    311            ((mi->settings.email == is.email) ||
    312             (NULL != is.email && NULL != mi->settings.email &&
    313              0 == strcmp (mi->settings.email,
    314                           is.email))) &&
    315            ((mi->settings.website == is.website) ||
    316             (NULL != is.website && NULL != mi->settings.website &&
    317              0 == strcmp (mi->settings.website,
    318                           is.website))) &&
    319            ((mi->settings.logo == is.logo) ||
    320             (NULL != is.logo && NULL != mi->settings.logo &&
    321              0 == strcmp (mi->settings.logo,
    322                           is.logo))) &&
    323            ( ( (NULL != auth_password) &&
    324                (GNUNET_OK ==
    325                 TMH_check_auth (auth_password,
    326                                 &mi->auth.auth_salt,
    327                                 &mi->auth.auth_hash)) ) ||
    328              ( (NULL == auth_password) &&
    329                (GNUNET_YES ==
    330                 GNUNET_is_zero (&mi->auth.auth_hash))) ) &&
    331            (1 == json_equal (mi->settings.address,
    332                              is.address)) &&
    333            (1 == json_equal (mi->settings.jurisdiction,
    334                              is.jurisdiction)) &&
    335            (mi->settings.use_stefan == is.use_stefan) &&
    336            (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
    337                                       ==,
    338                                       is.default_wire_transfer_delay)) &&
    339            (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
    340                                       ==,
    341                                       is.default_pay_delay)) &&
    342            (GNUNET_TIME_relative_cmp (mi->settings.default_refund_delay,
    343                                       ==,
    344                                       is.default_refund_delay)) )
    345       {
    346         GNUNET_JSON_parse_free (spec);
    347         GNUNET_free (is.phone);
    348         GNUNET_free (id_lower);
    349         return TALER_MHD_reply_static (connection,
    350                                        MHD_HTTP_NO_CONTENT,
    351                                        NULL,
    352                                        NULL,
    353                                        0);
    354       }
    355       GNUNET_JSON_parse_free (spec);
    356       GNUNET_free (is.phone);
    357       ret = TALER_MHD_reply_with_error (connection,
    358                                         MHD_HTTP_CONFLICT,
    359                                         TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
    360                                         id_lower);
    361       GNUNET_free (id_lower);
    362       return ret;
    363     }
    364   }
    365 
    366   /* Check MFA is satisfied */
    367   if (validation_needed)
    368   {
    369     enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    370 
    371     if ( (0 != (TMH_TCS_SMS & TEH_mandatory_tan_channels)) &&
    372          (NULL == is.phone) )
    373     {
    374       GNUNET_break_op (0);
    375       GNUNET_JSON_parse_free (spec);
    376       GNUNET_free (is.phone); /* does nothing... */
    377       GNUNET_free (id_lower);
    378       return TALER_MHD_reply_with_error (connection,
    379                                          MHD_HTTP_BAD_REQUEST,
    380                                          TALER_EC_GENERIC_PARAMETER_MISSING,
    381                                          "phone_number");
    382 
    383     }
    384     if ( (0 != (TMH_TCS_EMAIL & TEH_mandatory_tan_channels)) &&
    385          (NULL == is.email) )
    386     {
    387       GNUNET_break_op (0);
    388       GNUNET_JSON_parse_free (spec);
    389       GNUNET_free (is.phone);
    390       GNUNET_free (id_lower);
    391       return TALER_MHD_reply_with_error (connection,
    392                                          MHD_HTTP_BAD_REQUEST,
    393                                          TALER_EC_GENERIC_PARAMETER_MISSING,
    394                                          "email");
    395     }
    396     switch (TEH_mandatory_tan_channels)
    397     {
    398     case TMH_TCS_NONE:
    399       GNUNET_assert (0);
    400       ret = GNUNET_OK;
    401       break;
    402     case TMH_TCS_SMS:
    403       is.phone_validated = true;
    404       ret = TMH_mfa_challenges_do (hc,
    405                                    id_lower,
    406                                    TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
    407                                    true,
    408                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    409                                    is.phone,
    410                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    411       break;
    412     case TMH_TCS_EMAIL:
    413       is.email_validated = true;
    414       ret = TMH_mfa_challenges_do (hc,
    415                                    id_lower,
    416                                    TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
    417                                    true,
    418                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    419                                    is.email,
    420                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    421       break;
    422     case TMH_TCS_EMAIL_AND_SMS:
    423       is.phone_validated = true;
    424       is.email_validated = true;
    425       ret = TMH_mfa_challenges_do (hc,
    426                                    id_lower,
    427                                    TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
    428                                    true,
    429                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    430                                    is.email,
    431                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    432                                    is.phone,
    433                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    434       break;
    435     }
    436     if (GNUNET_OK != ret)
    437     {
    438       GNUNET_JSON_parse_free (spec);
    439       GNUNET_free (is.phone);
    440       GNUNET_free (id_lower);
    441       return (GNUNET_NO == ret)
    442         ? MHD_YES
    443         : MHD_NO;
    444     }
    445   }
    446 
    447   /* handle authentication token setup */
    448   if (NULL == auth_password)
    449   {
    450     memset (&ias.auth_salt,
    451             0,
    452             sizeof (ias.auth_salt));
    453     memset (&ias.auth_hash,
    454             0,
    455             sizeof (ias.auth_hash));
    456   }
    457   else
    458   {
    459     /* Sets 'auth_salt' and 'auth_hash' */
    460     TMH_compute_auth (auth_password,
    461                       &ias.auth_salt,
    462                       &ias.auth_hash);
    463   }
    464 
    465   /* create in-memory data structure */
    466   {
    467     struct TMH_MerchantInstance *mi;
    468     enum GNUNET_DB_QueryStatus qs;
    469 
    470     mi = GNUNET_new (struct TMH_MerchantInstance);
    471     mi->wm_head = wm_head;
    472     mi->wm_tail = wm_tail;
    473     mi->settings = is;
    474     mi->settings.address = json_incref (mi->settings.address);
    475     mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
    476     mi->settings.id = id_lower;
    477     mi->settings.name = GNUNET_strdup (is.name);
    478     if (NULL != is.email)
    479       mi->settings.email = GNUNET_strdup (is.email);
    480     mi->settings.phone = is.phone;
    481     is.phone = NULL;
    482     if (NULL != is.website)
    483       mi->settings.website = GNUNET_strdup (is.website);
    484     if (NULL != is.logo)
    485       mi->settings.logo = GNUNET_strdup (is.logo);
    486     mi->auth = ias;
    487     GNUNET_CRYPTO_eddsa_key_create (&mi->merchant_priv.eddsa_priv);
    488     GNUNET_CRYPTO_eddsa_key_get_public (&mi->merchant_priv.eddsa_priv,
    489                                         &mi->merchant_pub.eddsa_pub);
    490 
    491     for (unsigned int i = 0; i<MAX_RETRIES; i++)
    492     {
    493       if (GNUNET_OK !=
    494           TALER_MERCHANTDB_start (TMH_db,
    495                                   "post /instances"))
    496       {
    497         GNUNET_break (0);
    498         mi->rc = 1;
    499         TMH_instance_decref (mi);
    500         GNUNET_JSON_parse_free (spec);
    501         return TALER_MHD_reply_with_error (
    502           connection,
    503           MHD_HTTP_INTERNAL_SERVER_ERROR,
    504           TALER_EC_GENERIC_DB_START_FAILED,
    505           NULL);
    506       }
    507       qs = TALER_MERCHANTDB_insert_instance (TMH_db,
    508                                              &mi->merchant_pub,
    509                                              &mi->merchant_priv,
    510                                              &mi->settings,
    511                                              &mi->auth);
    512       switch (qs)
    513       {
    514       case GNUNET_DB_STATUS_HARD_ERROR:
    515         {
    516           enum MHD_Result ret;
    517 
    518           TALER_MERCHANTDB_rollback (TMH_db);
    519           GNUNET_break (0);
    520           ret = TALER_MHD_reply_with_error (
    521             connection,
    522             MHD_HTTP_INTERNAL_SERVER_ERROR,
    523             TALER_EC_GENERIC_DB_STORE_FAILED,
    524             "insert_instance");
    525           mi->rc = 1;
    526           TMH_instance_decref (mi);
    527           GNUNET_JSON_parse_free (spec);
    528           return ret;
    529         }
    530       case GNUNET_DB_STATUS_SOFT_ERROR:
    531         goto retry;
    532       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    533         {
    534           enum MHD_Result ret;
    535 
    536           TALER_MERCHANTDB_rollback (TMH_db);
    537           GNUNET_break (0);
    538           ret = TALER_MHD_reply_with_error (
    539             connection,
    540             MHD_HTTP_CONFLICT,
    541             TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
    542             id_lower);
    543           mi->rc = 1;
    544           TMH_instance_decref (mi);
    545           GNUNET_JSON_parse_free (spec);
    546           return ret;
    547         }
    548       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    549         /* handled below */
    550         break;
    551       }
    552       qs = TALER_MERCHANTDB_commit (TMH_db);
    553       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    554         qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    555 retry:
    556       if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    557         break; /* success! -- or hard failure */
    558     } /* for .. MAX_RETRIES */
    559     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    560     {
    561       GNUNET_break (0);
    562       mi->rc = 1;
    563       TMH_instance_decref (mi);
    564       GNUNET_JSON_parse_free (spec);
    565       return TALER_MHD_reply_with_error (connection,
    566                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    567                                          TALER_EC_GENERIC_DB_COMMIT_FAILED,
    568                                          NULL);
    569     }
    570     /* Finally, also update our running process */
    571     GNUNET_assert (GNUNET_OK ==
    572                    TMH_add_instance (mi));
    573     TMH_reload_instances (mi->settings.id);
    574   }
    575   GNUNET_JSON_parse_free (spec);
    576   if (GNUNET_TIME_relative_is_zero (login_token_expiration))
    577   {
    578     return TALER_MHD_reply_static (connection,
    579                                    MHD_HTTP_NO_CONTENT,
    580                                    NULL,
    581                                    NULL,
    582                                    0);
    583   }
    584 
    585   {
    586     /* Narrow DB interaction to new instance */
    587     enum GNUNET_DB_QueryStatus qs;
    588 
    589     qs = TALER_MERCHANTDB_set_instance (TMH_db,
    590                                         id_lower);
    591     switch (qs)
    592     {
    593     case GNUNET_DB_STATUS_HARD_ERROR:
    594       GNUNET_break (0);
    595       return TALER_MHD_reply_with_error (
    596         connection,
    597         MHD_HTTP_INTERNAL_SERVER_ERROR,
    598         TALER_EC_GENERIC_DB_SETUP_FAILED,
    599         "set_instance");
    600     case GNUNET_DB_STATUS_SOFT_ERROR:
    601       GNUNET_break (0);
    602       return TALER_MHD_reply_with_error (
    603         connection,
    604         MHD_HTTP_INTERNAL_SERVER_ERROR,
    605         TALER_EC_GENERIC_DB_SETUP_FAILED,
    606         "set_instance");
    607     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    608       return TALER_MHD_reply_with_error (
    609         connection,
    610         MHD_HTTP_NOT_FOUND,
    611         TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    612         hc->url);
    613     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    614       break;
    615     }
    616   }
    617 
    618   {
    619     struct TALER_MERCHANTDB_LoginTokenP btoken;
    620     enum TMH_AuthScope iscope = TMH_AS_REFRESHABLE | TMH_AS_SPA;
    621     enum GNUNET_DB_QueryStatus qs;
    622     struct GNUNET_TIME_Timestamp expiration_time;
    623     bool refreshable = true;
    624 
    625     GNUNET_CRYPTO_random_block (&btoken,
    626                                 sizeof (btoken));
    627     expiration_time
    628       = GNUNET_TIME_relative_to_timestamp (login_token_expiration);
    629     qs = TALER_MERCHANTDB_insert_login_token (TMH_db,
    630                                               id_lower,
    631                                               &btoken,
    632                                               GNUNET_TIME_timestamp_get (),
    633                                               expiration_time,
    634                                               iscope,
    635                                               "login token from instance creation");
    636     switch (qs)
    637     {
    638     case GNUNET_DB_STATUS_HARD_ERROR:
    639     case GNUNET_DB_STATUS_SOFT_ERROR:
    640     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    641       GNUNET_break (0);
    642       return TALER_MHD_reply_with_error (
    643         connection,
    644         MHD_HTTP_INTERNAL_SERVER_ERROR,
    645         TALER_EC_GENERIC_DB_STORE_FAILED,
    646         "insert_login_token");
    647     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    648       break;
    649     }
    650 
    651     {
    652       char *tok;
    653       enum MHD_Result ret;
    654       char *val;
    655 
    656       val = GNUNET_STRINGS_data_to_string_alloc (&btoken,
    657                                                  sizeof (btoken));
    658       GNUNET_asprintf (&tok,
    659                        RFC_8959_PREFIX "%s",
    660                        val);
    661       GNUNET_free (val);
    662       ret = TALER_MHD_REPLY_JSON_PACK (
    663         connection,
    664         MHD_HTTP_OK,
    665         GNUNET_JSON_pack_string ("access_token",
    666                                  tok),
    667         GNUNET_JSON_pack_string ("token",
    668                                  tok),
    669         GNUNET_JSON_pack_string ("scope",
    670                                  TMH_get_name_by_scope (iscope,
    671                                                         &refreshable)),
    672         GNUNET_JSON_pack_bool ("refreshable",
    673                                refreshable),
    674         GNUNET_JSON_pack_timestamp ("expiration",
    675                                     expiration_time));
    676       GNUNET_free (tok);
    677       return ret;
    678     }
    679   }
    680 }
    681 
    682 
    683 /**
    684  * Generate an instance, given its configuration.
    685  *
    686  * @param rh context of the handler
    687  * @param connection the MHD connection to handle
    688  * @param[in,out] hc context with further information about the request
    689  * @return MHD result code
    690  */
    691 enum MHD_Result
    692 TMH_private_post_instances (const struct TMH_RequestHandler *rh,
    693                             struct MHD_Connection *connection,
    694                             struct TMH_HandlerContext *hc)
    695 {
    696   return post_instances (rh,
    697                          connection,
    698                          hc,
    699                          GNUNET_TIME_UNIT_ZERO,
    700                          false);
    701 }
    702 
    703 
    704 /**
    705  * Generate an instance, given its configuration.
    706  * Public handler to be used when self-provisioning.
    707  *
    708  * @param rh context of the handler
    709  * @param connection the MHD connection to handle
    710  * @param[in,out] hc context with further information about the request
    711  * @return MHD result code
    712  */
    713 enum MHD_Result
    714 TMH_public_post_instances (const struct TMH_RequestHandler *rh,
    715                            struct MHD_Connection *connection,
    716                            struct TMH_HandlerContext *hc)
    717 {
    718   struct GNUNET_TIME_Relative expiration;
    719 
    720   TALER_MHD_parse_request_rel_time (connection,
    721                                     "token_validity_ms",
    722                                     &expiration);
    723   if (GNUNET_YES !=
    724       TMH_have_self_provisioning)
    725   {
    726     GNUNET_break_op (0);
    727     return TALER_MHD_reply_with_error (connection,
    728                                        MHD_HTTP_FORBIDDEN,
    729                                        TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
    730                                        "Self-provisioning is not enabled");
    731   }
    732 
    733   return post_instances (rh,
    734                          connection,
    735                          hc,
    736                          expiration,
    737                          TMH_TCS_NONE !=
    738                          TEH_mandatory_tan_channels);
    739 }
    740 
    741 
    742 /* end of taler-merchant-httpd_post-management-instances.c */