merchant

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

taler-merchant-httpd_auth.c (20605B)


      1 /*
      2   This file is part of TALER
      3   (C) 2014--2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Lesser General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file src/backend/taler-merchant-httpd_auth.c
     18  * @brief client authentication logic
     19  * @author Martin Schanzenbach
     20  * @author Christian Grothoff
     21  */
     22 #include "platform.h"
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <gnunet/gnunet_db_lib.h>
     25 #include <taler/taler_json_lib.h>
     26 #include "taler-merchant-httpd_auth.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 
     29 /**
     30  * Maximum length of a permissions string of a scope
     31  */
     32 #define TMH_MAX_SCOPE_PERMISSIONS_LEN 4096
     33 
     34 /**
     35  * Maximum length of a name of a scope
     36  */
     37 #define TMH_MAX_NAME_LEN 255
     38 
     39 /**
     40  * Represents a hard-coded set of default scopes with their
     41  * permissions and names
     42  */
     43 struct ScopePermissionMap
     44 {
     45   /**
     46    * The scope enum value
     47    */
     48   enum TMH_AuthScope as;
     49 
     50   /**
     51    * The scope name
     52    */
     53   char name[TMH_MAX_NAME_LEN];
     54 
     55   /**
     56    * The scope permissions string.
     57    * Comma-separated.
     58    */
     59   char permissions[TMH_MAX_SCOPE_PERMISSIONS_LEN];
     60 };
     61 
     62 /**
     63  * The default scopes array for merchant
     64  */
     65 static struct ScopePermissionMap scope_permissions[] = {
     66   /* Deprecated since v19 */
     67   {
     68     .as = TMH_AS_ALL,
     69     .name = "write",
     70     .permissions = "*"
     71   },
     72   /* Full access for SPA */
     73   {
     74     .as = TMH_AS_ALL,
     75     .name = "all",
     76     .permissions = "*"
     77   },
     78   /* Full access for SPA */
     79   {
     80     .as = TMH_AS_SPA,
     81     .name = "spa",
     82     .permissions = "*"
     83   },
     84   /* Read-only access */
     85   {
     86     .as = TMH_AS_READ_ONLY,
     87     .name = "readonly",
     88     .permissions = "*-read"
     89   },
     90   /* Simple order management */
     91   {
     92     .as = TMH_AS_ORDER_SIMPLE,
     93     .name = "order-simple",
     94     .permissions = "orders-read,orders-write"
     95   },
     96   /* Simple order management for PoS, also allows inventory locking */
     97   {
     98     .as = TMH_AS_ORDER_POS,
     99     .name = "order-pos",
    100     .permissions = "orders-read,orders-write,inventory-lock"
    101   },
    102   /* Simple order management, also allows refunding */
    103   {
    104     .as = TMH_AS_ORDER_MGMT,
    105     .name = "order-mgmt",
    106     .permissions = "orders-read,orders-write,orders-refund"
    107   },
    108   /* Full order management, allows inventory locking and refunds */
    109   {
    110     .as = TMH_AS_ORDER_FULL,
    111     .name = "order-full",
    112     .permissions = "orders-read,orders-write,inventory-lock,orders-refund"
    113   },
    114   /* No permissions, dummy scope */
    115   {
    116     .as = TMH_AS_NONE,
    117   }
    118 };
    119 
    120 
    121 /**
    122  * Get permissions string for scope.
    123  * Also extracts the leftmost bit into the @a refreshable
    124  * output parameter.
    125  *
    126  * @param as the scope to get the permissions string from
    127  * @param[out] refreshable true if the token associated with this scope is refreshable.
    128  * @return the permissions string, or NULL if no such scope found
    129  */
    130 static const char*
    131 get_scope_permissions (enum TMH_AuthScope as,
    132                        bool *refreshable)
    133 {
    134   *refreshable = as & TMH_AS_REFRESHABLE;
    135   for (unsigned int i = 0; TMH_AS_NONE != scope_permissions[i].as; i++)
    136   {
    137     /* We ignore the TMH_AS_REFRESHABLE bit */
    138     if ( (as & ~TMH_AS_REFRESHABLE)  ==
    139          (scope_permissions[i].as & ~TMH_AS_REFRESHABLE) )
    140       return scope_permissions[i].permissions;
    141   }
    142   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    143               "Failed to find required permissions for scope %d\n",
    144               as);
    145   return NULL;
    146 }
    147 
    148 
    149 /**
    150  * Extract the token from authorization header value @a auth.
    151  * The @a auth value can be a bearer token or a Basic
    152  * authentication header. In both cases, this function
    153  * updates @a auth to point to the actual credential,
    154  * skipping spaces.
    155  *
    156  * NOTE: We probably want to replace this function with MHD2
    157  * API calls in the future that are more robust.
    158  *
    159  * @param[in,out] auth pointer to authorization header value,
    160  *        will be updated to point to the start of the token
    161  *        or set to NULL if header value is invalid
    162  * @param[out] is_basic_auth will be set to true if the
    163  *        authorization header uses basic authentication,
    164  *        otherwise to false
    165  */
    166 static void
    167 extract_auth (const char **auth,
    168               bool *is_basic_auth)
    169 {
    170   const char *bearer = "Bearer ";
    171   const char *basic = "Basic ";
    172   const char *tok = *auth;
    173   size_t offset = 0;
    174   bool is_bearer = false;
    175 
    176   *is_basic_auth = false;
    177   if (0 == strncmp (tok,
    178                     bearer,
    179                     strlen (bearer)))
    180   {
    181     offset = strlen (bearer);
    182     is_bearer = true;
    183   }
    184   else if (0 == strncmp (tok,
    185                          basic,
    186                          strlen (basic)))
    187   {
    188     offset = strlen (basic);
    189     *is_basic_auth = true;
    190   }
    191   else
    192   {
    193     *auth = NULL;
    194     return;
    195   }
    196   tok += offset;
    197   while (' ' == *tok)
    198     tok++;
    199   if ( (is_bearer) &&
    200        (0 != strncasecmp (tok,
    201                           RFC_8959_PREFIX,
    202                           strlen (RFC_8959_PREFIX))) )
    203   {
    204     *auth = NULL;
    205     return;
    206   }
    207   *auth = tok;
    208 }
    209 
    210 
    211 /**
    212  * Check if @a userpass grants access to @a instance.
    213  *
    214  * @param userpass base64 encoded "$USERNAME:$PASSWORD" value
    215  *        from HTTP Basic "Authentication" header
    216  * @param instance the access controlled instance
    217  */
    218 static enum GNUNET_GenericReturnValue
    219 check_auth_instance (const char *userpass,
    220                      struct TMH_MerchantInstance *instance)
    221 {
    222   char *tmp;
    223   char *colon;
    224   const char *instance_name;
    225   const char *password;
    226   const char *target_instance = "admin";
    227   enum GNUNET_GenericReturnValue ret;
    228 
    229   /* implicitly a zeroed out hash means no authentication */
    230   if (GNUNET_is_zero (&instance->auth.auth_hash))
    231     return GNUNET_OK;
    232   if (NULL == userpass)
    233   {
    234     GNUNET_break_op (0);
    235     return GNUNET_SYSERR;
    236   }
    237   if (0 ==
    238       GNUNET_STRINGS_base64_decode (userpass,
    239                                     strlen (userpass),
    240                                     (void**) &tmp))
    241   {
    242     GNUNET_break_op (0);
    243     return GNUNET_SYSERR;
    244   }
    245   colon = strchr (tmp,
    246                   ':');
    247   if (NULL == colon)
    248   {
    249     GNUNET_break_op (0);
    250     GNUNET_free (tmp);
    251     return GNUNET_SYSERR;
    252   }
    253   *colon = '\0';
    254   instance_name = tmp;
    255   password = colon + 1;
    256   /* instance->settings.id can be NULL if there is no instance yet */
    257   if (NULL != instance->settings.id)
    258     target_instance = instance->settings.id;
    259   if (0 != strcmp (instance_name,
    260                    target_instance))
    261   {
    262     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    263                 "Somebody tried to login to instance %s with username %s (login failed).\n",
    264                 target_instance,
    265                 instance_name);
    266     GNUNET_free (tmp);
    267     return GNUNET_SYSERR;
    268   }
    269   ret = TMH_check_auth (password,
    270                         &instance->auth.auth_salt,
    271                         &instance->auth.auth_hash);
    272   GNUNET_free (tmp);
    273   if (GNUNET_OK != ret)
    274   {
    275     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    276                 "Password provided does not match credentials for %s\n",
    277                 target_instance);
    278   }
    279   return ret;
    280 }
    281 
    282 
    283 void
    284 TMH_compute_auth (const char *token,
    285                   struct TALER_MerchantAuthenticationSaltP *salt,
    286                   struct TALER_MerchantAuthenticationHashP *hash)
    287 {
    288   GNUNET_CRYPTO_random_block (salt,
    289                               sizeof (*salt));
    290   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    291               "Computing initial auth using token with salt %s\n",
    292               TALER_B2S (salt));
    293   TALER_merchant_instance_auth_hash_with_salt (hash,
    294                                                salt,
    295                                                token);
    296 }
    297 
    298 
    299 /**
    300  * Function used to process Basic authorization header value.
    301  * Sets correct scope in the auth_scope parameter of the
    302  * #TMH_HandlerContext.
    303  *
    304  * @param hc the handler context
    305  * @param authn_s the value of the authorization header
    306  */
    307 static void
    308 process_basic_auth (struct TMH_HandlerContext *hc,
    309                     const char *authn_s)
    310 {
    311   /* Handle token endpoint slightly differently: Only allow
    312    * instance password (Basic auth) to retrieve access token.
    313    * We need to handle authorization with Basic auth here first
    314    * The only time we need to handle authentication like this is
    315    * for the token endpoint!
    316    */
    317   if ( (0 != strncmp (hc->rh->url_prefix,
    318                       "/token",
    319                       strlen ("/token"))) ||
    320        (0 != strncmp (MHD_HTTP_METHOD_POST,
    321                       hc->rh->method,
    322                       strlen (MHD_HTTP_METHOD_POST))) ||
    323        (NULL == hc->instance))
    324   {
    325     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    326                 "Called endpoint `%s' with Basic authentication. Rejecting...\n",
    327                 hc->rh->url_prefix);
    328     hc->auth_scope = TMH_AS_NONE;
    329     return;
    330   }
    331   if (GNUNET_OK ==
    332       check_auth_instance (authn_s,
    333                            hc->instance))
    334   {
    335     hc->auth_scope = TMH_AS_ALL;
    336   }
    337   else
    338   {
    339     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    340                 "Basic authentication failed!\n");
    341     hc->auth_scope = TMH_AS_NONE;
    342   }
    343 }
    344 
    345 
    346 /**
    347  * Function used to process Bearer authorization header value.
    348  * Sets correct scope in the auth_scope parameter of the
    349  * #TMH_HandlerContext..
    350  *
    351  * @param hc the handler context
    352  * @param authn_s the value of the authorization header
    353  * @return TALER_EC_NONE on success.
    354  */
    355 static enum TALER_ErrorCode
    356 process_bearer_auth (struct TMH_HandlerContext *hc,
    357                      const char *authn_s)
    358 {
    359   if (NULL == hc->instance)
    360   {
    361     hc->auth_scope = TMH_AS_NONE;
    362     return TALER_EC_NONE;
    363   }
    364   if (GNUNET_is_zero (&hc->instance->auth.auth_hash))
    365   {
    366     /* hash zero means no authentication for instance */
    367     hc->auth_scope = TMH_AS_ALL;
    368     return TALER_EC_NONE;
    369   }
    370   {
    371     enum TALER_ErrorCode ec;
    372 
    373     ec = TMH_check_token (authn_s,
    374                           hc->instance->settings.id,
    375                           &hc->auth_scope);
    376     if (TALER_EC_NONE != ec)
    377     {
    378       char *dec;
    379       size_t dec_len;
    380       const char *token;
    381 
    382       /* NOTE: Deprecated, remove sometime after v1.1 */
    383       if (0 != strncasecmp (authn_s,
    384                             RFC_8959_PREFIX,
    385                             strlen (RFC_8959_PREFIX)))
    386       {
    387         GNUNET_break_op (0);
    388         hc->auth_scope = TMH_AS_NONE;
    389         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    390                     "Authentication token invalid: %d\n",
    391                     (int) ec);
    392         return ec;
    393       }
    394       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    395                   "Trying deprecated secret-token:password API authN\n");
    396       token = authn_s + strlen (RFC_8959_PREFIX);
    397       dec_len = GNUNET_STRINGS_urldecode (token,
    398                                           strlen (token),
    399                                           &dec);
    400       if ( (0 == dec_len) ||
    401            (GNUNET_OK !=
    402             TMH_check_auth (dec,
    403                             &hc->instance->auth.auth_salt,
    404                             &hc->instance->auth.auth_hash)) )
    405       {
    406         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    407                     "Login failed\n");
    408         hc->auth_scope = TMH_AS_NONE;
    409         GNUNET_free (dec);
    410         return TALER_EC_NONE;
    411       }
    412       hc->auth_scope = TMH_AS_ALL;
    413       GNUNET_free (dec);
    414     }
    415   }
    416   return TALER_EC_NONE;
    417 }
    418 
    419 
    420 /**
    421  * Checks if @a permission_required is in permissions of
    422  * @a scope.
    423  *
    424  * @param permission_required the permission to check.
    425  * @param scope the scope to check.
    426  * @return true if @a permission_required is in the permissions set of @a scope.
    427  */
    428 static bool
    429 permission_in_scope (const char *permission_required,
    430                      enum TMH_AuthScope scope)
    431 {
    432   char *permissions;
    433   const char *perms_tmp;
    434   bool is_read_perm = false;
    435   bool is_write_perm = false;
    436   bool refreshable;
    437   const char *last_dash;
    438 
    439   perms_tmp = get_scope_permissions (scope,
    440                                      &refreshable);
    441   if (NULL == perms_tmp)
    442   {
    443     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    444                 "Permission check failed: scope %d not understood\n",
    445                 (int) scope);
    446     return false;
    447   }
    448   last_dash = strrchr (permission_required,
    449                        '-');
    450   if (NULL != last_dash)
    451   {
    452     is_write_perm = (0 == strcmp (last_dash,
    453                                   "-write"));
    454     is_read_perm = (0 == strcmp (last_dash,
    455                                  "-read"));
    456   }
    457 
    458   if (0 == strcmp ("token-refresh",
    459                    permission_required))
    460   {
    461     if (! refreshable)
    462     {
    463       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    464                   "Permission check failed: token not refreshable\n");
    465     }
    466     return refreshable;
    467   }
    468   permissions = GNUNET_strdup (perms_tmp);
    469   {
    470     const char *perm = strtok (permissions,
    471                                ",");
    472 
    473     if (NULL == perm)
    474     {
    475       GNUNET_free (permissions);
    476       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    477                   "Permission check failed: empty permission set\n");
    478       return false;
    479     }
    480     while (NULL != perm)
    481     {
    482       if (0 == strcmp ("*",
    483                        perm))
    484       {
    485         GNUNET_free (permissions);
    486         return true;
    487       }
    488       if ( (0 == strcmp ("*-write",
    489                          perm)) &&
    490            (is_write_perm) )
    491       {
    492         GNUNET_free (permissions);
    493         return true;
    494       }
    495       if ( (0 == strcmp ("*-read",
    496                          perm)) &&
    497            (is_read_perm) )
    498       {
    499         GNUNET_free (permissions);
    500         return true;
    501       }
    502       if (0 == strcmp (permission_required,
    503                        perm))
    504       {
    505         GNUNET_free (permissions);
    506         return true;
    507       }
    508       perm = strtok (NULL,
    509                      ",");
    510     }
    511   }
    512   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    513               "Permission check failed: %s not found in %s\n",
    514               permission_required,
    515               permissions);
    516   GNUNET_free (permissions);
    517   return false;
    518 }
    519 
    520 
    521 bool
    522 TMH_scope_is_subset (enum TMH_AuthScope as,
    523                      enum TMH_AuthScope candidate)
    524 {
    525   const char *as_perms;
    526   const char *candidate_perms;
    527   char *permissions;
    528   bool as_refreshable;
    529   bool cand_refreshable;
    530 
    531   as_perms = get_scope_permissions (as,
    532                                     &as_refreshable);
    533   candidate_perms = get_scope_permissions (candidate,
    534                                            &cand_refreshable);
    535   if (! as_refreshable && cand_refreshable)
    536     return false;
    537   if ( (NULL == as_perms) &&
    538        (NULL != candidate_perms) )
    539     return false;
    540   if ( (NULL == candidate_perms) ||
    541        (0 == strcmp ("*",
    542                      as_perms)))
    543     return true;
    544   permissions = GNUNET_strdup (candidate_perms);
    545   {
    546     const char *perm;
    547 
    548     perm = strtok (permissions,
    549                    ",");
    550     if (NULL == perm)
    551     {
    552       GNUNET_free (permissions);
    553       return true;
    554     }
    555     while (NULL != perm)
    556     {
    557       if (! permission_in_scope (perm,
    558                                  as))
    559       {
    560         GNUNET_free (permissions);
    561         return false;
    562       }
    563       perm = strtok (NULL,
    564                      ",");
    565     }
    566   }
    567   GNUNET_free (permissions);
    568   return true;
    569 }
    570 
    571 
    572 enum TMH_AuthScope
    573 TMH_get_scope_by_name (const char *name)
    574 {
    575   if (NULL == name)
    576     return TMH_AS_NONE;
    577   for (unsigned int i = 0; TMH_AS_NONE != scope_permissions[i].as; i++)
    578   {
    579     if (0 == strcasecmp (scope_permissions[i].name,
    580                          name))
    581       return scope_permissions[i].as;
    582   }
    583   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    584               "Name `%s' does not match any scope we understand\n",
    585               name);
    586   return TMH_AS_NONE;
    587 }
    588 
    589 
    590 const char*
    591 TMH_get_name_by_scope (enum TMH_AuthScope scope,
    592                        bool *refreshable)
    593 {
    594   *refreshable = scope & TMH_AS_REFRESHABLE;
    595   for (unsigned int i = 0; TMH_AS_NONE != scope_permissions[i].as; i++)
    596   {
    597     /* We ignore the TMH_AS_REFRESHABLE bit */
    598     if ( (scope & ~TMH_AS_REFRESHABLE)  ==
    599          (scope_permissions[i].as & ~TMH_AS_REFRESHABLE) )
    600       return scope_permissions[i].name;
    601   }
    602   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    603               "Scope #%d does not match any scope we understand\n",
    604               (int) scope);
    605   return NULL;
    606 }
    607 
    608 
    609 enum GNUNET_GenericReturnValue
    610 TMH_check_auth (const char *password,
    611                 struct TALER_MerchantAuthenticationSaltP *salt,
    612                 struct TALER_MerchantAuthenticationHashP *hash)
    613 {
    614   struct TALER_MerchantAuthenticationHashP val;
    615 
    616   if (GNUNET_is_zero (hash))
    617     return GNUNET_OK;
    618   if (NULL == password)
    619   {
    620     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    621                 "Denying access: empty password provided\n");
    622     return GNUNET_SYSERR;
    623   }
    624   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    625               "Checking against token with salt %s\n",
    626               TALER_B2S (salt));
    627   TALER_merchant_instance_auth_hash_with_salt (&val,
    628                                                salt,
    629                                                password);
    630   if (0 !=
    631       GNUNET_memcmp (&val,
    632                      hash))
    633   {
    634     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    635                 "Access denied: password does not match\n");
    636     return GNUNET_SYSERR;
    637   }
    638   return GNUNET_OK;
    639 }
    640 
    641 
    642 /**
    643  * Check if the client has provided the necessary credentials
    644  * to access the selected endpoint of the selected instance.
    645  *
    646  * @param[in,out] hc handler context
    647  * @return #GNUNET_OK on success,
    648  *         #GNUNET_NO if an error was queued (return #MHD_YES)
    649  *         #GNUNET_SYSERR to close the connection (return #MHD_NO)
    650  */
    651 enum GNUNET_GenericReturnValue
    652 TMH_perform_access_control (struct TMH_HandlerContext *hc)
    653 {
    654   const char *auth;
    655   bool is_basic_auth = false;
    656   bool auth_malformed = false;
    657 
    658   auth = MHD_lookup_connection_value (hc->connection,
    659                                       MHD_HEADER_KIND,
    660                                       MHD_HTTP_HEADER_AUTHORIZATION);
    661 
    662   if (NULL != auth)
    663   {
    664     extract_auth (&auth,
    665                   &is_basic_auth);
    666     if (NULL == auth)
    667       auth_malformed = true;
    668     hc->auth_token = auth;
    669   }
    670 
    671   /* If we have zero configured instances (not even ones that have been
    672      purged) or explicitly disabled authentication, THEN we accept anything
    673      (no access control), as we then also have no data to protect. */
    674   if ((0 == GNUNET_CONTAINER_multihashmap_size (TMH_by_id_map)) ||
    675       (GNUNET_YES == TMH_auth_disabled))
    676   {
    677     hc->auth_scope = TMH_AS_ALL;
    678   }
    679   else if (is_basic_auth)
    680   {
    681     process_basic_auth (hc,
    682                         auth);
    683   }
    684   else   /* Check bearer token */
    685   {
    686     enum TALER_ErrorCode ec;
    687 
    688     ec = process_bearer_auth (hc,
    689                               auth);
    690     if (TALER_EC_NONE != ec)
    691     {
    692       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    693                   "Bearer authentication failed: %d\n",
    694                   (int) ec);
    695       return (MHD_YES ==
    696               TALER_MHD_reply_with_ec (hc->connection,
    697                                        ec,
    698                                        NULL))
    699           ? GNUNET_NO
    700           : GNUNET_SYSERR;
    701     }
    702   }
    703   /* We grant access if:
    704      - Endpoint does not require permissions
    705      - Authorization scope of bearer token contains permissions
    706        required by endpoint.
    707    */
    708   if ( (NULL != hc->rh->permission) &&
    709        (! permission_in_scope (hc->rh->permission,
    710                                hc->auth_scope)))
    711   {
    712     if (auth_malformed &&
    713         (TMH_AS_NONE == hc->auth_scope) )
    714     {
    715       GNUNET_break_op (0);
    716       return (MHD_YES ==
    717               TALER_MHD_reply_with_error (
    718                 hc->connection,
    719                 MHD_HTTP_UNAUTHORIZED,
    720                 TALER_EC_GENERIC_PARAMETER_MALFORMED,
    721                 "'" RFC_8959_PREFIX
    722                 "' prefix or 'Bearer' missing in 'Authorization' header"))
    723           ? GNUNET_NO
    724           : GNUNET_SYSERR;
    725     }
    726     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    727                 "Credentials provided are %d which are insufficient for access to `%s'\n",
    728                 (int) hc->auth_scope,
    729                 hc->rh->permission);
    730     return (MHD_YES ==
    731             TALER_MHD_reply_with_error (
    732               hc->connection,
    733               MHD_HTTP_UNAUTHORIZED,
    734               TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
    735               "Check credentials in 'Authorization' header"))
    736         ? GNUNET_NO
    737         : GNUNET_SYSERR;
    738   }
    739   return GNUNET_OK;
    740 }