merchant

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

taler-merchant-httpd_post-management-instances-INSTANCE-auth.c (11940B)


      1 /*
      2   This file is part of GNU Taler
      3   (C) 2021 Taler Systems SA
      4 
      5   GNU 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   GNU 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-INSTANCE-auth.c
     22  * @brief implementing POST /instances/$ID/auth request handling
     23  * @author Christian Grothoff
     24  * @author Florian Dold
     25  */
     26 #include "platform.h"
     27 #include "taler-merchant-httpd_post-management-instances-INSTANCE-auth.h"
     28 #include "taler-merchant-httpd_auth.h"
     29 #include "taler-merchant-httpd_helper.h"
     30 #include "taler-merchant-httpd_mfa.h"
     31 #include <taler/taler_json_lib.h>
     32 #include "merchant-database/lookup_instance_auth.h"
     33 #include "merchant-database/update_instance_auth.h"
     34 #include "merchant-database/start.h"
     35 
     36 
     37 /**
     38  * How often do we retry the simple INSERT database transaction?
     39  */
     40 #define MAX_RETRIES 3
     41 
     42 
     43 /**
     44  * Change the authentication settings of an instance.
     45  *
     46  * @param mi instance to modify settings of
     47  * @param connection the MHD connection to handle
     48  * @param[in,out] hc context with further information about the request
     49  * @param auth_override The authentication settings for this instance
     50  *   do not apply due to administrative action. Do not check
     51  *   against the DB value when updating the auth token.
     52  * @param tcs set of multi-factor authorizations required
     53  * @return MHD result code
     54  */
     55 static enum MHD_Result
     56 post_instances_ID_auth (struct TMH_MerchantInstance *mi,
     57                         struct MHD_Connection *connection,
     58                         struct TMH_HandlerContext *hc,
     59                         bool auth_override,
     60                         enum TEH_TanChannelSet tcs)
     61 {
     62   struct TALER_MERCHANTDB_InstanceAuthSettings ias;
     63   const char *auth_pw = NULL;
     64   json_t *jauth = hc->request_body;
     65 
     66   {
     67     enum GNUNET_GenericReturnValue ret;
     68 
     69     ret = TMH_check_auth_config (connection,
     70                                  jauth,
     71                                  &auth_pw);
     72     if (GNUNET_OK != ret)
     73       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
     74   }
     75 
     76   if ( (0 != (tcs & TMH_TCS_SMS) &&
     77         ( (NULL == mi->settings.phone) ||
     78           (NULL == TMH_helper_sms) ||
     79           (! mi->settings.phone_validated) ) ) )
     80   {
     81     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     82                 "Cannot reset password: SMS factor not available\n");
     83     return TALER_MHD_reply_with_error (
     84       connection,
     85       MHD_HTTP_FORBIDDEN,
     86       TALER_EC_MERCHANT_GENERIC_MFA_MISSING,
     87       "phone_number");
     88   }
     89   if ( (0 != (tcs & TMH_TCS_EMAIL) &&
     90         ( (NULL == mi->settings.email) ||
     91           (NULL == TMH_helper_email) ||
     92           (! mi->settings.email_validated) ) ) )
     93   {
     94     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     95                 "Cannot reset password: E-mail factor not available\n");
     96     return TALER_MHD_reply_with_error (
     97       connection,
     98       MHD_HTTP_FORBIDDEN,
     99       TALER_EC_MERCHANT_GENERIC_MFA_MISSING,
    100       "email");
    101   }
    102   if (! auth_override)
    103   {
    104     enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; // fix -Wmaybe-uninitialized
    105 
    106     switch (tcs)
    107     {
    108     case TMH_TCS_NONE:
    109       ret = GNUNET_OK;
    110       break;
    111     case TMH_TCS_SMS:
    112       ret = TMH_mfa_challenges_do (hc,
    113                                    TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION,
    114                                    true,
    115                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    116                                    mi->settings.phone,
    117                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    118       break;
    119     case TMH_TCS_EMAIL:
    120       ret = TMH_mfa_challenges_do (hc,
    121                                    TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION,
    122                                    true,
    123                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    124                                    mi->settings.email,
    125                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    126       break;
    127     case TMH_TCS_EMAIL_AND_SMS:
    128       ret = TMH_mfa_challenges_do (hc,
    129                                    TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION,
    130                                    true,
    131                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    132                                    mi->settings.email,
    133                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    134                                    mi->settings.phone,
    135                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    136       break;
    137     }
    138     if (GNUNET_OK != ret)
    139     {
    140       return (GNUNET_NO == ret)
    141         ? MHD_YES
    142         : MHD_NO;
    143     }
    144   }
    145 
    146   if (NULL == auth_pw)
    147   {
    148     memset (&ias.auth_salt,
    149             0,
    150             sizeof (ias.auth_salt));
    151     memset (&ias.auth_hash,
    152             0,
    153             sizeof (ias.auth_hash));
    154   }
    155   else
    156   {
    157     TMH_compute_auth (auth_pw,
    158                       &ias.auth_salt,
    159                       &ias.auth_hash);
    160   }
    161 
    162   /* Store the new auth information in the database */
    163   {
    164     enum GNUNET_DB_QueryStatus qs;
    165 
    166     for (unsigned int i = 0; i<MAX_RETRIES; i++)
    167     {
    168       if (GNUNET_OK !=
    169           TALER_MERCHANTDB_start (TMH_db,
    170                                   "post /instances/$ID/auth"))
    171       {
    172         return TALER_MHD_reply_with_error (connection,
    173                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
    174                                            TALER_EC_GENERIC_DB_START_FAILED,
    175                                            NULL);
    176       }
    177 
    178       /* Make the authentication update a serializable operation.
    179          We first check that the authentication information
    180          that the caller's request authenticated with
    181          is still up to date.
    182          Otherwise, we've detected a conflicting update
    183          to the authentication. */
    184       {
    185         struct TALER_MERCHANTDB_InstanceAuthSettings db_ias;
    186         enum TALER_ErrorCode ec;
    187 
    188         qs = TALER_MERCHANTDB_lookup_instance_auth (TMH_db,
    189                                                     mi->settings.id,
    190                                                     &db_ias);
    191 
    192         switch (qs)
    193         {
    194         case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    195           /* Instance got purged. */
    196           TALER_MERCHANTDB_rollback (TMH_db);
    197           return TALER_MHD_reply_with_error (connection,
    198                                              MHD_HTTP_NOT_FOUND,
    199                                              TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    200                                              NULL);
    201         case GNUNET_DB_STATUS_SOFT_ERROR:
    202           TALER_MERCHANTDB_rollback (TMH_db);
    203           goto retry;
    204         case GNUNET_DB_STATUS_HARD_ERROR:
    205           TALER_MERCHANTDB_rollback (TMH_db);
    206           return TALER_MHD_reply_with_error (connection,
    207                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    208                                              TALER_EC_GENERIC_DB_FETCH_FAILED,
    209                                              NULL);
    210         case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    211           /* Success! */
    212           break;
    213         }
    214 
    215         if (! auth_override)
    216         {
    217           // FIXME are we sure what the scope here is?
    218           ec = TMH_check_token (hc->auth_token,
    219                                 mi->settings.id,
    220                                 &hc->auth_scope);
    221           if (TALER_EC_NONE != ec)
    222           {
    223             TALER_MERCHANTDB_rollback (TMH_db);
    224             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    225                         "Refusing auth change: `%s'\n",
    226                         TALER_ErrorCode_get_hint (ec));
    227             return TALER_MHD_reply_with_error (connection,
    228                                                MHD_HTTP_UNAUTHORIZED,
    229                                                TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
    230                                                NULL);
    231           }
    232         }
    233       }
    234 
    235       qs = TALER_MERCHANTDB_update_instance_auth (TMH_db,
    236                                                   mi->settings.id,
    237                                                   &ias);
    238       if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    239       {
    240         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    241         TALER_MERCHANTDB_rollback (TMH_db);
    242         if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    243         {
    244           return TALER_MHD_reply_with_error (connection,
    245                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    246                                              TALER_EC_GENERIC_DB_FETCH_FAILED,
    247                                              NULL);
    248         }
    249         goto retry;
    250       }
    251       qs = TALER_MERCHANTDB_commit (TMH_db);
    252       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    253         qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    254 retry:
    255       if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    256         break; /* success! -- or hard failure */
    257     } /* for .. MAX_RETRIES */
    258     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    259     {
    260       return TALER_MHD_reply_with_error (connection,
    261                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    262                                          TALER_EC_GENERIC_DB_COMMIT_FAILED,
    263                                          NULL);
    264     }
    265     /* Finally, also update our running process */
    266     mi->auth = ias;
    267   }
    268   TMH_reload_instances (mi->settings.id);
    269   return TALER_MHD_reply_static (connection,
    270                                  MHD_HTTP_NO_CONTENT,
    271                                  NULL,
    272                                  NULL,
    273                                  0);
    274 }
    275 
    276 
    277 enum MHD_Result
    278 TMH_private_post_instances_ID_auth (const struct TMH_RequestHandler *rh,
    279                                     struct MHD_Connection *connection,
    280                                     struct TMH_HandlerContext *hc)
    281 {
    282   struct TMH_MerchantInstance *mi = hc->instance;
    283 
    284   return post_instances_ID_auth (mi,
    285                                  connection,
    286                                  hc,
    287                                  false,
    288                                  TMH_TCS_NONE);
    289 }
    290 
    291 
    292 enum MHD_Result
    293 TMH_public_post_instances_ID_auth (const struct TMH_RequestHandler *rh,
    294                                    struct MHD_Connection *connection,
    295                                    struct TMH_HandlerContext *hc)
    296 {
    297   struct TMH_MerchantInstance *mi = hc->instance;
    298 
    299   if (0 == strcmp ("admin",
    300                    mi->settings.id))
    301   {
    302     GNUNET_break_op (0);
    303     return TALER_MHD_reply_with_error (
    304       connection,
    305       MHD_HTTP_FORBIDDEN,
    306       TALER_EC_MERCHANT_GENERIC_MFA_MISSING,
    307       "not allowed for 'admin' account");
    308   }
    309   return post_instances_ID_auth (mi,
    310                                  connection,
    311                                  hc,
    312                                  false,
    313                                  TEH_mandatory_tan_channels);
    314 }
    315 
    316 
    317 enum MHD_Result
    318 TMH_private_post_instances_default_ID_auth (
    319   const struct TMH_RequestHandler *rh,
    320   struct MHD_Connection *connection,
    321   struct TMH_HandlerContext *hc)
    322 {
    323   struct TMH_MerchantInstance *mi;
    324   enum MHD_Result ret;
    325 
    326   mi = TMH_lookup_instance (hc->infix);
    327   if (NULL == mi)
    328   {
    329     return TALER_MHD_reply_with_error (
    330       connection,
    331       MHD_HTTP_NOT_FOUND,
    332       TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    333       hc->infix);
    334   }
    335   ret = post_instances_ID_auth (mi,
    336                                 connection,
    337                                 hc,
    338                                 true,
    339                                 TMH_TCS_NONE);
    340   return ret;
    341 }
    342 
    343 
    344 /* end of taler-merchant-httpd_post-management-instances-INSTANCE-auth.c */