merchant

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

taler-merchant-httpd_auth.c (21007B)


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