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 }