diff options
author | Christian Grothoff <christian@grothoff.org> | 2021-12-31 11:37:42 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2021-12-31 11:37:42 +0100 |
commit | ce443bb4d4815ac79170b81cae74fc8b8030ea54 (patch) | |
tree | 5ad2e67ea4179a4f766c3180bf44b06cc8577f33 | |
parent | 9f7a6d50b4c6a79ab16dfabe2c57510565bc4cf2 (diff) | |
download | anastasis-ce443bb4d4815ac79170b81cae74fc8b8030ea54.tar.gz anastasis-ce443bb4d4815ac79170b81cae74fc8b8030ea54.tar.bz2 anastasis-ce443bb4d4815ac79170b81cae74fc8b8030ea54.zip |
fix #7054: propagate more information on the rate-limiting
-rw-r--r-- | doc/sphinx/rest.rst | 28 | ||||
-rw-r--r-- | src/backend/anastasis-httpd_truth.c | 41 | ||||
-rw-r--r-- | src/include/anastasis.h | 18 | ||||
-rw-r--r-- | src/include/anastasis_service.h | 17 | ||||
-rw-r--r-- | src/lib/anastasis_recovery.c | 6 | ||||
-rw-r--r-- | src/reducer/anastasis_api_recovery_redux.c | 16 | ||||
-rw-r--r-- | src/restclient/anastasis_api_keyshare_lookup.c | 38 |
7 files changed, 142 insertions, 22 deletions
diff --git a/doc/sphinx/rest.rst b/doc/sphinx/rest.rst index a1c5810..9127354 100644 --- a/doc/sphinx/rest.rst +++ b/doc/sphinx/rest.rst @@ -425,7 +425,7 @@ charge per truth operation using GNU Taler. // For how many years from now would the client like us to // store the truth? - storage_duration_years: Integer; + storage_duration_years: number; } @@ -482,6 +482,10 @@ charge per truth operation using GNU Taler. The decrypted ``truth`` does not match the expectations of the authentication backend, i.e. a phone number for sending an SMS is not a number, or an e-mail address for sending an E-mail is not a valid e-mail address. + :http:statuscode:`429 Too Many Requests`: + The client exceeded the number of allowed attempts at providing + a valid response for the given time interval. + The response format is given by `RateLimitedMessage`_. :http:statuscode:`503 Service Unavailable`: Server is out of Service. @@ -543,9 +547,29 @@ charge per truth operation using GNU Taler. business_name: string; // What is the expected wire transfer subject? - wire_transfer_subject: Integer; + wire_transfer_subject: number; // Hint about the origin account that must be used. debit_account_hint: string; } + + + .. _RateLimitedMessage: + .. ts:def:: RateLimitedMessage + + interface RateLimitedMessage { + + // Taler error code, TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED. + code: number; + + // How many attempts are allowed per challenge? + request_limit: number; + + // At what frequency are new challenges issued? + request_frequency: RelativeTime; + + // The error message. + hint: string; + + } diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth.c index 6c05ef8..54969bf 100644 --- a/src/backend/anastasis-httpd_truth.c +++ b/src/backend/anastasis-httpd_truth.c @@ -237,6 +237,27 @@ static struct GNUNET_SCHEDULER_Task *to_task; /** + * Generate a response telling the client that answering this + * challenge failed because the rate limit has been exceeded. + * + * @param gc request to answer for + * @return MHD status code + */ +static MHD_RESULT +reply_rate_limited (const struct GetContext *gc) +{ + return TALER_MHD_REPLY_JSON_PACK ( + gc->connection, + MHD_HTTP_TOO_MANY_REQUESTS, + TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED), + GNUNET_JSON_pack_uint64 ("request_limit", + gc->authorization->retry_counter), + GNUNET_JSON_pack_time_rel ("request_frequency", + gc->authorization->code_rotation_period)); +} + + +/** * Timeout requests that are past their due date. * * @param cls NULL @@ -991,9 +1012,8 @@ run_authorization_process (struct MHD_Connection *connection, /** - * Use the database to rate-limit queries to the - * authentication procedure, but without actually - * storing 'real' challenge codes. + * Use the database to rate-limit queries to the authentication + * procedure, but without actually storing 'real' challenge codes. * * @param[in,out] gc context to rate limit requests for * @return #GNUNET_OK if rate-limiting passes, @@ -1034,10 +1054,7 @@ rate_limit (struct GetContext *gc) if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { return (MHD_YES == - TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, - NULL)) + reply_rate_limited (gc)) ? GNUNET_NO : GNUNET_SYSERR; } @@ -1066,10 +1083,7 @@ rate_limit (struct GetContext *gc) : GNUNET_SYSERR; case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: return (MHD_YES == - TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, - NULL)) + reply_rate_limited (gc)) ? GNUNET_NO : GNUNET_SYSERR; case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: @@ -1640,10 +1654,7 @@ AH_handler_truth_get ( case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* 0 == retry_counter of existing challenge => rate limit exceeded */ GNUNET_free (decrypted_truth); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, - NULL); + return reply_rate_limited (gc); case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* challenge code was stored successfully*/ GNUNET_log (GNUNET_ERROR_TYPE_INFO, diff --git a/src/include/anastasis.h b/src/include/anastasis.h index fd7ed40..b957f18 100644 --- a/src/include/anastasis.h +++ b/src/include/anastasis.h @@ -207,6 +207,24 @@ struct ANASTASIS_ChallengeStartResponse unsigned int http_status; } open_challenge; + /** + * Details for #ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED. + */ + struct + { + + /** + * How many requests are allowed at most per @e request_frequency? + */ + uint32_t request_limit; + + /** + * Frequency at which requests are allowed / new challenges are + * created. + */ + struct GNUNET_TIME_Relative request_frequency; + + } rate_limit_exceeded; /** * Response with details if diff --git a/src/include/anastasis_service.h b/src/include/anastasis_service.h index b6b3d76..0ef31d6 100644 --- a/src/include/anastasis_service.h +++ b/src/include/anastasis_service.h @@ -570,6 +570,23 @@ struct ANASTASIS_KeyShareDownloadDetails } payment_required; + struct + { + + /** + * How many requests are allowed at most per @e request_frequency? + */ + uint32_t request_limit; + + /** + * Frequency at which requests are allowed / new challenges are + * created. + */ + struct GNUNET_TIME_Relative request_frequency; + + } rate_limit_exceeded; + + /** * Response with details about a server-side failure, if * @e status is #ANASTASIS_KSD_SERVER_ERROR, diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c index b85b0f6..3a7943e 100644 --- a/src/lib/anastasis_recovery.c +++ b/src/lib/anastasis_recovery.c @@ -319,7 +319,11 @@ keyshare_lookup_cb (void *cls, { struct ANASTASIS_ChallengeStartResponse csr = { .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED, - .challenge = c + .challenge = c, + .details.rate_limit_exceeded.request_limit + = dd->details.rate_limit_exceeded.request_limit, + .details.rate_limit_exceeded.request_frequency + = dd->details.rate_limit_exceeded.request_frequency }; c->af (c->af_cls, diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c index a26b6ad..088ff7e 100644 --- a/src/reducer/anastasis_api_recovery_redux.c +++ b/src/reducer/anastasis_api_recovery_redux.c @@ -765,10 +765,18 @@ answer_feedback_cb ( json_t *err; err = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "rate-limit-exceeded"), - GNUNET_JSON_pack_uint64 ("error_code", - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); + GNUNET_JSON_pack_string ( + "state", + "rate-limit-exceeded"), + GNUNET_JSON_pack_uint64 ( + "request_limit", + csr->details.rate_limit_exceeded.request_limit), + GNUNET_JSON_pack_time_rel ( + "request_frequency", + csr->details.rate_limit_exceeded.request_frequency), + GNUNET_JSON_pack_uint64 ( + "error_code", + TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); GNUNET_assert (0 == json_object_set_new (feedback, uuid, diff --git a/src/restclient/anastasis_api_keyshare_lookup.c b/src/restclient/anastasis_api_keyshare_lookup.c index 13390c9..99924d1 100644 --- a/src/restclient/anastasis_api_keyshare_lookup.c +++ b/src/restclient/anastasis_api_keyshare_lookup.c @@ -258,6 +258,44 @@ handle_keyshare_lookup_finished (void *cls, break; case MHD_HTTP_TOO_MANY_REQUESTS: kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED; + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ( + "request_limit", + &kdd.details.rate_limit_exceeded.request_limit), + GNUNET_JSON_spec_relative_time ( + "request_frequency", + &kdd.details.rate_limit_exceeded.request_frequency), + GNUNET_JSON_spec_end () + }; + json_t *reply; + + reply = json_loadb (data, + data_size, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == reply) + { + GNUNET_break_op (0); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kdd.details.server_failure.http_status = response_code; + break; + } + if (GNUNET_OK != + GNUNET_JSON_parse (reply, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + kdd.details.server_failure.ec = TALER_JSON_get_error_code (reply); + kdd.details.server_failure.http_status = response_code; + json_decref (reply); + break; + } + json_decref (reply); + } break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API |