challenger

OAuth 2.0-based authentication service that validates user can receive messages at a certain address
Log | Files | Refs | Submodules | README | LICENSE

challenger-httpd_common.c (10467B)


      1 /*
      2   This file is part of Challenger
      3   Copyright (C) 2023, 2024 Taler Systems SA
      4 
      5   Challenger is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   Challenger 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file challenger-httpd_common.c
     18  * @brief common helper functions
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "challenger-httpd_common.h"
     23 #include <gnunet/gnunet_pq_lib.h>
     24 #include "challenger-database/validation_get.h"
     25 
     26 
     27 /**
     28  * Prefix required for all Bearer tokens.
     29  */
     30 #define RFC_8959_PREFIX "secret-token:"
     31 
     32 
     33 // FIXME: enums would be nicer...
     34 int
     35 CH_get_output_type (struct MHD_Connection *connection)
     36 {
     37   const char *mime;
     38   double q_html;
     39   double q_json;
     40 
     41   mime = MHD_lookup_connection_value (connection,
     42                                       MHD_HEADER_KIND,
     43                                       MHD_HTTP_HEADER_ACCEPT);
     44   if (NULL == mime)
     45     return 0; /* default to HTML */
     46   q_html = TALER_pattern_matches (mime,
     47                                   "text/html");
     48   q_json = TALER_pattern_matches (mime,
     49                                   "application/json");
     50   return (q_html > q_json) ? 0 : 1;
     51 }
     52 
     53 
     54 const char *
     55 CH_get_client_secret (struct MHD_Connection *connection)
     56 {
     57   const char *bearer = "Bearer ";
     58   const char *auth;
     59   const char *tok;
     60 
     61   auth = MHD_lookup_connection_value (connection,
     62                                       MHD_HEADER_KIND,
     63                                       MHD_HTTP_HEADER_AUTHORIZATION);
     64   if (NULL == auth)
     65     return NULL;
     66   if (0 != strncmp (auth,
     67                     bearer,
     68                     strlen (bearer)))
     69   {
     70     return NULL;
     71   }
     72   tok = auth + strlen (bearer);
     73   while (' ' == *tok)
     74     tok++;
     75   if (0 != strncasecmp (tok,
     76                         RFC_8959_PREFIX,
     77                         strlen (RFC_8959_PREFIX)))
     78   {
     79     return NULL;
     80   }
     81   return tok;
     82 }
     83 
     84 
     85 char *
     86 CH_compute_code (const struct CHALLENGER_ValidationNonceP *nonce,
     87                  const char *client_secret,
     88                  const char *client_scope,
     89                  const json_t *address,
     90                  const char *client_redirect_uri)
     91 {
     92   char *code;
     93   char *ns;
     94   char *hs;
     95   struct GNUNET_ShortHashCode h;
     96   char *astr;
     97 
     98   astr = json_dumps (address,
     99                      JSON_COMPACT);
    100   GNUNET_assert (GNUNET_YES ==
    101                  GNUNET_CRYPTO_hkdf_gnunet (
    102                    &h,
    103                    sizeof (h),
    104                    nonce,
    105                    sizeof (*nonce),
    106                    client_secret,
    107                    strlen (client_secret),
    108                    GNUNET_CRYPTO_kdf_arg_string (astr),
    109                    GNUNET_CRYPTO_kdf_arg_string (client_redirect_uri),
    110                    GNUNET_CRYPTO_kdf_arg_string (NULL != client_scope
    111                                                  ? client_scope
    112                                                  : "")));
    113   free (astr);
    114   ns = GNUNET_STRINGS_data_to_string_alloc (nonce,
    115                                             sizeof (*nonce));
    116   hs = GNUNET_STRINGS_data_to_string_alloc (&h,
    117                                             sizeof (h));
    118   GNUNET_asprintf (&code,
    119                    "%s-%s",
    120                    ns,
    121                    hs);
    122   GNUNET_free (ns);
    123   GNUNET_free (hs);
    124   return code;
    125 }
    126 
    127 
    128 enum GNUNET_GenericReturnValue
    129 CH_code_to_nonce (const char *code,
    130                   struct CHALLENGER_ValidationNonceP *nonce)
    131 {
    132   const char *dash = strchr (code, '-');
    133 
    134   if (NULL == dash)
    135   {
    136     GNUNET_break_op (0);
    137     return GNUNET_SYSERR;
    138   }
    139   if (GNUNET_OK !=
    140       GNUNET_STRINGS_string_to_data (code,
    141                                      (size_t) (dash - code),
    142                                      nonce,
    143                                      sizeof (*nonce)))
    144   {
    145     GNUNET_break_op (0);
    146     return GNUNET_SYSERR;
    147   }
    148   return GNUNET_OK;
    149 }
    150 
    151 
    152 enum MHD_Result
    153 CH_reply_with_oauth_error (
    154   struct MHD_Connection *connection,
    155   unsigned int http_status,
    156   const char *oauth_error,
    157   enum TALER_ErrorCode ec,
    158   const char *detail)
    159 {
    160   struct MHD_Response *resp;
    161   enum MHD_Result mret;
    162 
    163   resp = TALER_MHD_make_json_steal (
    164     GNUNET_JSON_PACK (
    165       TALER_MHD_PACK_EC (ec),
    166       GNUNET_JSON_pack_string ("error",
    167                                oauth_error),
    168       GNUNET_JSON_pack_allow_null (
    169         GNUNET_JSON_pack_string ("detail",
    170                                  detail))));
    171   if (MHD_HTTP_UNAUTHORIZED == http_status)
    172     GNUNET_break (MHD_YES ==
    173                   MHD_add_response_header (
    174                     resp,
    175                     "WWW-Authenticate",
    176                     "Bearer, error=\"invalid_token\""));
    177   mret = MHD_queue_response (connection,
    178                              http_status,
    179                              resp);
    180   MHD_destroy_response (resp);
    181   return mret;
    182 }
    183 
    184 
    185 enum MHD_Result
    186 TALER_MHD_redirect_with_oauth_status (
    187   struct MHD_Connection *connection,
    188   const char *client_redirect_uri,
    189   const char *state,
    190   const char *oauth_error,
    191   const char *oauth_error_description,
    192   const char *oauth_error_uri)
    193 {
    194   struct MHD_Response *response;
    195   unsigned int http_status;
    196 
    197   if (0 == CH_get_output_type (connection))
    198   {
    199     char *url;
    200     char *enc_err;
    201     char *enc_state;
    202     char *enc_desc = NULL;
    203     char *enc_uri = NULL;
    204 
    205     response = MHD_create_response_from_buffer (strlen (oauth_error),
    206                                                 (void *) oauth_error,
    207                                                 MHD_RESPMEM_PERSISTENT);
    208     if (NULL == response)
    209     {
    210       GNUNET_break (0);
    211       return MHD_NO;
    212     }
    213     TALER_MHD_add_global_headers (response,
    214                                   false);
    215     GNUNET_break (MHD_YES ==
    216                   MHD_add_response_header (response,
    217                                            MHD_HTTP_HEADER_CONTENT_TYPE,
    218                                            "text/plain"));
    219     enc_err = TALER_urlencode (oauth_error);
    220     enc_state = TALER_urlencode (state);
    221     if (NULL != oauth_error_description)
    222       enc_desc = TALER_urlencode (oauth_error_description);
    223     if (NULL != oauth_error_uri)
    224       enc_uri = TALER_urlencode (oauth_error_uri);
    225     url = TALER_url_join (
    226       client_redirect_uri,
    227       "",
    228       "state", enc_state,
    229       "error", enc_err,
    230       "error_description", enc_desc,
    231       "error_uri", enc_uri,
    232       NULL);
    233     GNUNET_free (enc_err);
    234     GNUNET_free (enc_state);
    235     GNUNET_free (enc_desc);
    236     GNUNET_free (enc_uri);
    237     if (MHD_NO ==
    238         MHD_add_response_header (response,
    239                                  MHD_HTTP_HEADER_LOCATION,
    240                                  url))
    241     {
    242       GNUNET_break (0);
    243       MHD_destroy_response (response);
    244       GNUNET_free (url);
    245       return MHD_NO;
    246     }
    247     http_status = MHD_HTTP_FOUND;
    248     GNUNET_free (url);
    249   }
    250   else
    251   {
    252     json_t *args;
    253 
    254     args = GNUNET_JSON_PACK (
    255       GNUNET_JSON_pack_string ("state",
    256                                state),
    257       GNUNET_JSON_pack_string ("error",
    258                                oauth_error),
    259       GNUNET_JSON_pack_allow_null (
    260         GNUNET_JSON_pack_string ("description",
    261                                  oauth_error_description)),
    262       GNUNET_JSON_pack_allow_null (
    263         GNUNET_JSON_pack_string ("uri",
    264                                  oauth_error_uri)));
    265 
    266     response = TALER_MHD_make_json_steal (args);
    267     TALER_MHD_add_global_headers (response,
    268                                   false);
    269     http_status = MHD_HTTP_TOO_MANY_REQUESTS;
    270   }
    271 
    272   {
    273     enum MHD_Result ret;
    274 
    275     ret = MHD_queue_response (connection,
    276                               http_status,
    277                               response);
    278     MHD_destroy_response (response);
    279     return ret;
    280   }
    281 }
    282 
    283 
    284 enum GNUNET_GenericReturnValue
    285 CH_build_full_redirect_url (const struct CHALLENGER_ValidationNonceP *nonce,
    286                             struct MHD_Connection *connection,
    287                             char **url)
    288 {
    289   char *client_secret;
    290   json_t *address;
    291   char *client_scope;
    292   char *client_state;
    293   char *client_redirect_uri;
    294   enum GNUNET_DB_QueryStatus qs;
    295   enum MHD_Result ret;
    296 
    297   qs = CHALLENGERDB_validation_get (CH_context,
    298                                     nonce,
    299                                     &client_secret,
    300                                     &address,
    301                                     &client_scope,
    302                                     &client_state,
    303                                     &client_redirect_uri);
    304   switch (qs)
    305   {
    306   case GNUNET_DB_STATUS_HARD_ERROR:
    307   case GNUNET_DB_STATUS_SOFT_ERROR:
    308     GNUNET_break (0);
    309     ret = TALER_MHD_reply_with_error (
    310       connection,
    311       MHD_HTTP_INTERNAL_SERVER_ERROR,
    312       TALER_EC_GENERIC_DB_FETCH_FAILED,
    313       "validation_get");
    314     return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO;
    315   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    316     GNUNET_break (0);
    317     ret = TALER_MHD_reply_with_error (
    318       connection,
    319       MHD_HTTP_NOT_FOUND,
    320       TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN,
    321       NULL);
    322     return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO;
    323   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    324     break;
    325   }
    326   {
    327     char *code;
    328 
    329     code = CH_compute_code (nonce,
    330                             client_secret,
    331                             client_scope,
    332                             address,
    333                             client_redirect_uri);
    334     if (NULL == client_state)
    335     {
    336       GNUNET_asprintf (url,
    337                        "%s?code=%s",
    338                        client_redirect_uri,
    339                        code);
    340     }
    341     else
    342     {
    343       char *url_encoded;
    344 
    345       url_encoded = TALER_urlencode (client_state);
    346       GNUNET_asprintf (url,
    347                        "%s?code=%s&state=%s",
    348                        client_redirect_uri,
    349                        code,
    350                        url_encoded);
    351       GNUNET_free (url_encoded);
    352     }
    353     GNUNET_free (code);
    354   }
    355   json_decref (address);
    356   GNUNET_free (client_scope);
    357   GNUNET_free (client_secret);
    358   GNUNET_free (client_redirect_uri);
    359   GNUNET_free (client_state);
    360   return GNUNET_OK;
    361 }