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_authorize.c (11794B)


      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_authorize.c
     18  * @brief functions to handle incoming requests for authorizations
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "challenger-httpd.h"
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <gnunet/gnunet_db_lib.h>
     25 #include <taler/taler_templating_lib.h>
     26 #include "challenger-httpd_authorize.h"
     27 #include "challenger-httpd_common.h"
     28 #include "challenger-httpd_spa.h"
     29 #include "challenger-database/authorize_start.h"
     30 #include "challenger_cm_enums.h"
     31 
     32 /**
     33  * Maximum number of retries for the database interaction.
     34  */
     35 #define MAX_RETRIES 3
     36 
     37 /**
     38  * Generate error reply in the format requested by
     39  * the client.
     40  *
     41  * @param hc our context
     42  * @param template error template to use
     43  * @param http_status HTTP status to return
     44  * @param ec error code to return
     45  * @param hint human-readable hint to give
     46  */
     47 static enum MHD_Result
     48 reply_error (struct CH_HandlerContext *hc,
     49              const char *template,
     50              unsigned int http_status,
     51              enum TALER_ErrorCode ec,
     52              const char *hint)
     53 {
     54   return TALER_MHD_reply_with_error (
     55     hc->connection,
     56     http_status,
     57     ec,
     58     hint);
     59 }
     60 
     61 
     62 enum MHD_Result
     63 CH_handler_authorize (struct CH_HandlerContext *hc,
     64                       const char *upload_data,
     65                       size_t *upload_data_size)
     66 {
     67   const char *response_type;
     68   unsigned long long client_id;
     69   const char *redirect_uri;
     70   const char *state;
     71   const char *scope;
     72   const char *code_challenge;
     73   enum CHALLENGER_CM code_challenge_method_enum;
     74   struct CHALLENGER_ValidationNonceP nonce;
     75 
     76   (void) upload_data;
     77   (void) upload_data_size;
     78   if (GNUNET_OK !=
     79       GNUNET_STRINGS_string_to_data (hc->path,
     80                                      strlen (hc->path),
     81                                      &nonce,
     82                                      sizeof (nonce)))
     83   {
     84     GNUNET_break_op (0);
     85     return reply_error (
     86       hc,
     87       "invalid-request",
     88       MHD_HTTP_NOT_FOUND,
     89       TALER_EC_GENERIC_PARAMETER_MISSING,
     90       hc->path);
     91   }
     92   response_type
     93     = MHD_lookup_connection_value (hc->connection,
     94                                    MHD_GET_ARGUMENT_KIND,
     95                                    "response_type");
     96   if (NULL == response_type)
     97   {
     98     GNUNET_break_op (0);
     99     return reply_error (
    100       hc,
    101       "invalid-request",
    102       MHD_HTTP_BAD_REQUEST,
    103       TALER_EC_GENERIC_PARAMETER_MISSING,
    104       "response_type");
    105   }
    106   if (0 != strcmp (response_type,
    107                    "code"))
    108   {
    109     GNUNET_break_op (0);
    110     return reply_error (
    111       hc,
    112       "invalid-request",
    113       MHD_HTTP_BAD_REQUEST,
    114       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    115       "response_type (must be 'code')");
    116   }
    117 
    118   {
    119     const char *client_id_str;
    120     char dummy;
    121 
    122     client_id_str
    123       = MHD_lookup_connection_value (hc->connection,
    124                                      MHD_GET_ARGUMENT_KIND,
    125                                      "client_id");
    126     if (NULL == client_id_str)
    127     {
    128       GNUNET_break_op (0);
    129       return reply_error (
    130         hc,
    131         "invalid_request",
    132         MHD_HTTP_BAD_REQUEST,
    133         TALER_EC_GENERIC_PARAMETER_MISSING,
    134         "client_id");
    135     }
    136     if (1 != sscanf (client_id_str,
    137                      "%llu%c",
    138                      &client_id,
    139                      &dummy))
    140     {
    141       GNUNET_break_op (0);
    142       return reply_error (
    143         hc,
    144         "invalid-request",
    145         MHD_HTTP_BAD_REQUEST,
    146         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    147         "client_id");
    148     }
    149   }
    150   redirect_uri
    151     = MHD_lookup_connection_value (hc->connection,
    152                                    MHD_GET_ARGUMENT_KIND,
    153                                    "redirect_uri");
    154 
    155   {
    156     const char *code_challenge_method;
    157 
    158     code_challenge_method
    159       = MHD_lookup_connection_value (hc->connection,
    160                                      MHD_GET_ARGUMENT_KIND,
    161                                      "code_challenge_method");
    162     code_challenge_method_enum
    163       = CHALLENGER_cm_from_string (
    164           code_challenge_method);
    165   }
    166   if (CHALLENGER_CM_UNKNOWN == code_challenge_method_enum)
    167   {
    168     GNUNET_break_op (0);
    169     return reply_error (hc,
    170                         "invalid-request",
    171                         MHD_HTTP_BAD_REQUEST,
    172                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    173                         "Unsupported code_challenge_method, supported only \"plain\", \"S256\".");
    174   }
    175   code_challenge = MHD_lookup_connection_value (hc->connection,
    176                                                 MHD_GET_ARGUMENT_KIND,
    177                                                 "code_challenge");
    178   if ( (NULL == code_challenge) &&
    179        (CHALLENGER_CM_PLAIN == code_challenge_method_enum) )
    180   {
    181     /* Client specified code challenge method but then did not give
    182        the code_challenge. Reject bad request. */
    183     GNUNET_break_op (0);
    184     return reply_error (
    185       hc,
    186       "invalid-request",
    187       MHD_HTTP_BAD_REQUEST,
    188       TALER_EC_GENERIC_PARAMETER_MISSING,
    189       "code_challenge");
    190   }
    191   if ( (NULL != code_challenge) &&
    192        (CHALLENGER_CM_EMPTY == code_challenge_method_enum) )
    193     code_challenge_method_enum = CHALLENGER_CM_PLAIN;
    194 
    195   /**
    196    * Safety check to not allow public clients without s256 code_challenge
    197    */
    198   if ( (NULL != redirect_uri) &&
    199        (! TALER_is_web_url (redirect_uri)) &&
    200        ( (CHALLENGER_CM_EMPTY == code_challenge_method_enum) ||
    201          (CHALLENGER_CM_PLAIN == code_challenge_method_enum) ) )
    202   {
    203     GNUNET_break_op (0);
    204     return reply_error (
    205       hc,
    206       "invalid-request",
    207       MHD_HTTP_BAD_REQUEST,
    208       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    209       "redirect_uri (has to start with 'http://' or 'https://' or not use 'plain'/NULL as code_challenge)");
    210   }
    211 
    212   state
    213     = MHD_lookup_connection_value (hc->connection,
    214                                    MHD_GET_ARGUMENT_KIND,
    215                                    "state");
    216   if (NULL == state)
    217     state = "";
    218 
    219   scope
    220     = MHD_lookup_connection_value (hc->connection,
    221                                    MHD_GET_ARGUMENT_KIND,
    222                                    "scope");
    223   {
    224     json_t *last_address = NULL;
    225     uint32_t address_attempts_left;
    226     uint32_t pin_transmissions_left;
    227     uint32_t auth_attempts_left;
    228     struct GNUNET_TIME_Absolute last_tx_time;
    229     bool solved;
    230     enum GNUNET_DB_QueryStatus qs;
    231 
    232     /* authorize_start will return 0 if a 'redirect_uri' was
    233        configured for the client and this one differs. */
    234     for (unsigned int r = 0; r<MAX_RETRIES; r++)
    235     {
    236       qs = CHALLENGERDB_authorize_start (CH_context,
    237                                          &nonce,
    238                                          client_id,
    239                                          scope,
    240                                          state,
    241                                          redirect_uri,
    242                                          code_challenge,
    243                                          (uint32_t) code_challenge_method_enum,
    244                                          &last_address,
    245                                          &address_attempts_left,
    246                                          &pin_transmissions_left,
    247                                          &auth_attempts_left,
    248                                          &solved,
    249                                          &last_tx_time);
    250       switch (qs)
    251       {
    252       case GNUNET_DB_STATUS_HARD_ERROR:
    253         GNUNET_break (0);
    254         return reply_error (
    255           hc,
    256           "internal-error",
    257           MHD_HTTP_INTERNAL_SERVER_ERROR,
    258           TALER_EC_GENERIC_DB_STORE_FAILED,
    259           "authorize_start");
    260       case GNUNET_DB_STATUS_SOFT_ERROR:
    261         if (r < MAX_RETRIES - 1)
    262           continue;
    263         GNUNET_break (0);
    264         return reply_error (
    265           hc,
    266           "internal-error",
    267           MHD_HTTP_INTERNAL_SERVER_ERROR,
    268           TALER_EC_GENERIC_DB_STORE_FAILED,
    269           "authorize_start");
    270       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    271         GNUNET_break_op (0);
    272         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    273                     "Failed to find authorization process of client %llu for nonce `%s'\n",
    274                     client_id,
    275                     hc->path);
    276         return reply_error (
    277           hc,
    278           "validation-unknown",
    279           MHD_HTTP_NOT_FOUND,
    280           TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN,
    281           NULL);
    282       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    283         break;
    284       }
    285       break;
    286     }
    287     if (0 == CH_get_output_type (hc->connection))
    288     {
    289       char *prev_full_url = hc->full_url;
    290       const char *rparams = strchr (hc->full_url, '?');
    291 
    292       if (NULL == rparams)
    293         GNUNET_asprintf (&hc->full_url,
    294                          "%s?nonce=%s",
    295                          prev_full_url,
    296                          hc->path);
    297 
    298       else
    299         GNUNET_asprintf (&hc->full_url,
    300                          "%s&nonce=%s",
    301                          prev_full_url,
    302                          hc->path);
    303 
    304       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    305                   "Redirect before %s now `%s'\n",
    306                   prev_full_url,
    307                   hc->full_url);
    308       GNUNET_free (prev_full_url);
    309       json_decref (last_address);
    310       return CH_spa_redirect (hc,
    311                               NULL,
    312                               0);
    313     }
    314     {
    315       json_t *args;
    316       struct MHD_Response *resp;
    317       enum MHD_Result res;
    318       json_t *ro;
    319 
    320       ro = json_object_get (last_address,
    321                             "read_only");
    322       if ( (NULL != ro) &&
    323            (json_boolean_value (ro)) )
    324         address_attempts_left = 0;
    325 
    326       args = GNUNET_JSON_PACK (
    327         GNUNET_JSON_pack_bool ("fix_address",
    328                                0 == address_attempts_left),
    329         GNUNET_JSON_pack_allow_null (
    330           GNUNET_JSON_pack_object_steal ("last_address",
    331                                          last_address)),
    332         GNUNET_JSON_pack_bool ("solved",
    333                                solved),
    334         GNUNET_JSON_pack_uint64 ("pin_transmissions_left",
    335                                  pin_transmissions_left),
    336         GNUNET_JSON_pack_uint64 ("auth_attempts_left",
    337                                  auth_attempts_left),
    338         GNUNET_JSON_pack_timestamp ("retransmission_time",
    339                                     GNUNET_TIME_absolute_to_timestamp (
    340                                       GNUNET_TIME_absolute_add (
    341                                         last_tx_time,
    342                                         CH_validation_duration))),
    343         GNUNET_JSON_pack_uint64 ("changes_left",
    344                                  address_attempts_left)
    345         );
    346       resp = TALER_MHD_make_json_steal (args);
    347       GNUNET_break (MHD_YES ==
    348                     MHD_add_response_header (resp,
    349                                              MHD_HTTP_HEADER_CACHE_CONTROL,
    350                                              "no-store,no-cache"));
    351       res = MHD_queue_response (hc->connection,
    352                                 MHD_HTTP_OK,
    353                                 resp);
    354       MHD_destroy_response (resp);
    355       return res;
    356     }
    357   }
    358 }