merchant

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

taler-merchant-httpd_auth.c (20658B)


      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 taler-merchant-httpd_auth.c
     18  * @brief client authentication logic
     19  * @author Martin Schanzenbach
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/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 (GNUNET_CRYPTO_QUALITY_NONCE,
    289                               salt,
    290                               sizeof (*salt));
    291   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    292               "Computing initial auth using token with salt %s\n",
    293               TALER_B2S (salt));
    294   TALER_merchant_instance_auth_hash_with_salt (hash,
    295                                                salt,
    296                                                token);
    297 }
    298 
    299 
    300 /**
    301  * Function used to process Basic authorization header value.
    302  * Sets correct scope in the auth_scope parameter of the
    303  * #TMH_HandlerContext.
    304  *
    305  * @param hc the handler context
    306  * @param authn_s the value of the authorization header
    307  */
    308 static void
    309 process_basic_auth (struct TMH_HandlerContext *hc,
    310                     const char *authn_s)
    311 {
    312   /* Handle token endpoint slightly differently: Only allow
    313    * instance password (Basic auth) to retrieve access token.
    314    * We need to handle authorization with Basic auth here first
    315    * The only time we need to handle authentication like this is
    316    * for the token endpoint!
    317    */
    318   if ( (0 != strncmp (hc->rh->url_prefix,
    319                       "/token",
    320                       strlen ("/token"))) ||
    321        (0 != strncmp (MHD_HTTP_METHOD_POST,
    322                       hc->rh->method,
    323                       strlen (MHD_HTTP_METHOD_POST))) ||
    324        (NULL == hc->instance))
    325   {
    326     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    327                 "Called endpoint `%s' with Basic authentication. Rejecting...\n",
    328                 hc->rh->url_prefix);
    329     hc->auth_scope = TMH_AS_NONE;
    330     return;
    331   }
    332   if (GNUNET_OK ==
    333       check_auth_instance (authn_s,
    334                            hc->instance))
    335   {
    336     hc->auth_scope = TMH_AS_ALL;
    337   }
    338   else
    339   {
    340     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    341                 "Basic authentication failed!\n");
    342     hc->auth_scope = TMH_AS_NONE;
    343   }
    344 }
    345 
    346 
    347 /**
    348  * Function used to process Bearer authorization header value.
    349  * Sets correct scope in the auth_scope parameter of the
    350  * #TMH_HandlerContext..
    351  *
    352  * @param hc the handler context
    353  * @param authn_s the value of the authorization header
    354  * @return TALER_EC_NONE on success.
    355  */
    356 static enum TALER_ErrorCode
    357 process_bearer_auth (struct TMH_HandlerContext *hc,
    358                      const char *authn_s)
    359 {
    360   if (NULL == hc->instance)
    361   {
    362     hc->auth_scope = TMH_AS_NONE;
    363     return TALER_EC_NONE;
    364   }
    365   if (GNUNET_is_zero (&hc->instance->auth.auth_hash))
    366   {
    367     /* hash zero means no authentication for instance */
    368     hc->auth_scope = TMH_AS_ALL;
    369     return TALER_EC_NONE;
    370   }
    371   {
    372     enum TALER_ErrorCode ec;
    373 
    374     ec = TMH_check_token (authn_s,
    375                           hc->instance->settings.id,
    376                           &hc->auth_scope);
    377     if (TALER_EC_NONE != ec)
    378     {
    379       char *dec;
    380       size_t dec_len;
    381       const char *token;
    382 
    383       /* NOTE: Deprecated, remove sometime after v1.1 */
    384       if (0 != strncasecmp (authn_s,
    385                             RFC_8959_PREFIX,
    386                             strlen (RFC_8959_PREFIX)))
    387       {
    388         GNUNET_break_op (0);
    389         hc->auth_scope = TMH_AS_NONE;
    390         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    391                     "Authentication token invalid: %d\n",
    392                     (int) ec);
    393         return ec;
    394       }
    395       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    396                   "Trying deprecated secret-token:password API authN\n");
    397       token = authn_s + strlen (RFC_8959_PREFIX);
    398       dec_len = GNUNET_STRINGS_urldecode (token,
    399                                           strlen (token),
    400                                           &dec);
    401       if ( (0 == dec_len) ||
    402            (GNUNET_OK !=
    403             TMH_check_auth (dec,
    404                             &hc->instance->auth.auth_salt,
    405                             &hc->instance->auth.auth_hash)) )
    406       {
    407         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    408                     "Login failed\n");
    409         hc->auth_scope = TMH_AS_NONE;
    410         GNUNET_free (dec);
    411         return TALER_EC_NONE;
    412       }
    413       hc->auth_scope = TMH_AS_ALL;
    414       GNUNET_free (dec);
    415     }
    416   }
    417   return TALER_EC_NONE;
    418 }
    419 
    420 
    421 /**
    422  * Checks if @a permission_required is in permissions of
    423  * @a scope.
    424  *
    425  * @param permission_required the permission to check.
    426  * @param scope the scope to check.
    427  * @return true if @a permission_required is in the permissions set of @a scope.
    428  */
    429 static bool
    430 permission_in_scope (const char *permission_required,
    431                      enum TMH_AuthScope scope)
    432 {
    433   char *permissions;
    434   const char *perms_tmp;
    435   bool is_read_perm = false;
    436   bool is_write_perm = false;
    437   bool refreshable;
    438   const char *last_dash;
    439 
    440   perms_tmp = get_scope_permissions (scope,
    441                                      &refreshable);
    442   if (NULL == perms_tmp)
    443   {
    444     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    445                 "Permission check failed: scope %d not understood\n",
    446                 (int) scope);
    447     return false;
    448   }
    449   last_dash = strrchr (permission_required,
    450                        '-');
    451   if (NULL != last_dash)
    452   {
    453     is_write_perm = (0 == strcmp (last_dash,
    454                                   "-write"));
    455     is_read_perm = (0 == strcmp (last_dash,
    456                                  "-read"));
    457   }
    458 
    459   if (0 == strcmp ("token-refresh",
    460                    permission_required))
    461   {
    462     if (! refreshable)
    463     {
    464       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    465                   "Permission check failed: token not refreshable\n");
    466     }
    467     return refreshable;
    468   }
    469   permissions = GNUNET_strdup (perms_tmp);
    470   {
    471     const char *perm = strtok (permissions,
    472                                ",");
    473 
    474     if (NULL == perm)
    475     {
    476       GNUNET_free (permissions);
    477       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    478                   "Permission check failed: empty permission set\n");
    479       return false;
    480     }
    481     while (NULL != perm)
    482     {
    483       if (0 == strcmp ("*",
    484                        perm))
    485       {
    486         GNUNET_free (permissions);
    487         return true;
    488       }
    489       if ( (0 == strcmp ("*-write",
    490                          perm)) &&
    491            (is_write_perm) )
    492       {
    493         GNUNET_free (permissions);
    494         return true;
    495       }
    496       if ( (0 == strcmp ("*-read",
    497                          perm)) &&
    498            (is_read_perm) )
    499       {
    500         GNUNET_free (permissions);
    501         return true;
    502       }
    503       if (0 == strcmp (permission_required,
    504                        perm))
    505       {
    506         GNUNET_free (permissions);
    507         return true;
    508       }
    509       perm = strtok (NULL,
    510                      ",");
    511     }
    512   }
    513   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    514               "Permission check failed: %s not found in %s\n",
    515               permission_required,
    516               permissions);
    517   GNUNET_free (permissions);
    518   return false;
    519 }
    520 
    521 
    522 bool
    523 TMH_scope_is_subset (enum TMH_AuthScope as,
    524                      enum TMH_AuthScope candidate)
    525 {
    526   const char *as_perms;
    527   const char *candidate_perms;
    528   char *permissions;
    529   bool as_refreshable;
    530   bool cand_refreshable;
    531 
    532   as_perms = get_scope_permissions (as,
    533                                     &as_refreshable);
    534   candidate_perms = get_scope_permissions (candidate,
    535                                            &cand_refreshable);
    536   if (! as_refreshable && cand_refreshable)
    537     return false;
    538   if ( (NULL == as_perms) &&
    539        (NULL != candidate_perms) )
    540     return false;
    541   if ( (NULL == candidate_perms) ||
    542        (0 == strcmp ("*",
    543                      as_perms)))
    544     return true;
    545   permissions = GNUNET_strdup (candidate_perms);
    546   {
    547     const char *perm;
    548 
    549     perm = strtok (permissions,
    550                    ",");
    551     if (NULL == perm)
    552     {
    553       GNUNET_free (permissions);
    554       return true;
    555     }
    556     while (NULL != perm)
    557     {
    558       if (! permission_in_scope (perm,
    559                                  as))
    560       {
    561         GNUNET_free (permissions);
    562         return false;
    563       }
    564       perm = strtok (NULL,
    565                      ",");
    566     }
    567   }
    568   GNUNET_free (permissions);
    569   return true;
    570 }
    571 
    572 
    573 enum TMH_AuthScope
    574 TMH_get_scope_by_name (const char *name)
    575 {
    576   if (NULL == name)
    577     return TMH_AS_NONE;
    578   for (unsigned int i = 0; TMH_AS_NONE != scope_permissions[i].as; i++)
    579   {
    580     if (0 == strcasecmp (scope_permissions[i].name,
    581                          name))
    582       return scope_permissions[i].as;
    583   }
    584   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    585               "Name `%s' does not match any scope we understand\n",
    586               name);
    587   return TMH_AS_NONE;
    588 }
    589 
    590 
    591 const char*
    592 TMH_get_name_by_scope (enum TMH_AuthScope scope,
    593                        bool *refreshable)
    594 {
    595   *refreshable = scope & TMH_AS_REFRESHABLE;
    596   for (unsigned int i = 0; TMH_AS_NONE != scope_permissions[i].as; i++)
    597   {
    598     /* We ignore the TMH_AS_REFRESHABLE bit */
    599     if ( (scope & ~TMH_AS_REFRESHABLE)  ==
    600          (scope_permissions[i].as & ~TMH_AS_REFRESHABLE) )
    601       return scope_permissions[i].name;
    602   }
    603   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    604               "Scope #%d does not match any scope we understand\n",
    605               (int) scope);
    606   return NULL;
    607 }
    608 
    609 
    610 enum GNUNET_GenericReturnValue
    611 TMH_check_auth (const char *password,
    612                 struct TALER_MerchantAuthenticationSaltP *salt,
    613                 struct TALER_MerchantAuthenticationHashP *hash)
    614 {
    615   struct TALER_MerchantAuthenticationHashP val;
    616 
    617   if (GNUNET_is_zero (hash))
    618     return GNUNET_OK;
    619   if (NULL == password)
    620   {
    621     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    622                 "Denying access: empty password provided\n");
    623     return GNUNET_SYSERR;
    624   }
    625   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    626               "Checking against token with salt %s\n",
    627               TALER_B2S (salt));
    628   TALER_merchant_instance_auth_hash_with_salt (&val,
    629                                                salt,
    630                                                password);
    631   if (0 !=
    632       GNUNET_memcmp (&val,
    633                      hash))
    634   {
    635     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    636                 "Access denied: password does not match\n");
    637     return GNUNET_SYSERR;
    638   }
    639   return GNUNET_OK;
    640 }
    641 
    642 
    643 /**
    644  * Check if the client has provided the necessary credentials
    645  * to access the selected endpoint of the selected instance.
    646  *
    647  * @param[in,out] hc handler context
    648  * @return #GNUNET_OK on success,
    649  *         #GNUNET_NO if an error was queued (return #MHD_YES)
    650  *         #GNUNET_SYSERR to close the connection (return #MHD_NO)
    651  */
    652 enum GNUNET_GenericReturnValue
    653 TMH_perform_access_control (struct TMH_HandlerContext *hc)
    654 {
    655   const char *auth;
    656   bool is_basic_auth = false;
    657   bool auth_malformed = false;
    658 
    659   auth = MHD_lookup_connection_value (hc->connection,
    660                                       MHD_HEADER_KIND,
    661                                       MHD_HTTP_HEADER_AUTHORIZATION);
    662 
    663   if (NULL != auth)
    664   {
    665     extract_auth (&auth,
    666                   &is_basic_auth);
    667     if (NULL == auth)
    668       auth_malformed = true;
    669     hc->auth_token = auth;
    670   }
    671 
    672   /* If we have zero configured instances (not even ones that have been
    673      purged) or explicitly disabled authentication, THEN we accept anything
    674      (no access control), as we then also have no data to protect. */
    675   if ((0 == GNUNET_CONTAINER_multihashmap_size (TMH_by_id_map)) ||
    676       (GNUNET_YES == TMH_auth_disabled))
    677   {
    678     hc->auth_scope = TMH_AS_ALL;
    679   }
    680   else if (is_basic_auth)
    681   {
    682     process_basic_auth (hc,
    683                         auth);
    684   }
    685   else   /* Check bearer token */
    686   {
    687     enum TALER_ErrorCode ec;
    688 
    689     ec = process_bearer_auth (hc,
    690                               auth);
    691     if (TALER_EC_NONE != ec)
    692     {
    693       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    694                   "Bearer authentication failed: %d\n",
    695                   (int) ec);
    696       return (MHD_YES ==
    697               TALER_MHD_reply_with_ec (hc->connection,
    698                                        ec,
    699                                        NULL))
    700           ? GNUNET_NO
    701           : GNUNET_SYSERR;
    702     }
    703   }
    704   /* We grant access if:
    705      - Endpoint does not require permissions
    706      - Authorization scope of bearer token contains permissions
    707        required by endpoint.
    708    */
    709   if ( (NULL != hc->rh->permission) &&
    710        (! permission_in_scope (hc->rh->permission,
    711                                hc->auth_scope)))
    712   {
    713     if (auth_malformed &&
    714         (TMH_AS_NONE == hc->auth_scope) )
    715     {
    716       GNUNET_break_op (0);
    717       return (MHD_YES ==
    718               TALER_MHD_reply_with_error (
    719                 hc->connection,
    720                 MHD_HTTP_UNAUTHORIZED,
    721                 TALER_EC_GENERIC_PARAMETER_MALFORMED,
    722                 "'" RFC_8959_PREFIX
    723                 "' prefix or 'Bearer' missing in 'Authorization' header"))
    724           ? GNUNET_NO
    725           : GNUNET_SYSERR;
    726     }
    727     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    728                 "Credentials provided are %d which are insufficient for access to `%s'\n",
    729                 (int) hc->auth_scope,
    730                 hc->rh->permission);
    731     return (MHD_YES ==
    732             TALER_MHD_reply_with_error (
    733               hc->connection,
    734               MHD_HTTP_UNAUTHORIZED,
    735               TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
    736               "Check credentials in 'Authorization' header"))
    737         ? GNUNET_NO
    738         : GNUNET_SYSERR;
    739   }
    740   return GNUNET_OK;
    741 }