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.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. | |||
425 | 425 | ||
426 | // For how many years from now would the client like us to | 426 | // For how many years from now would the client like us to |
427 | // store the truth? | 427 | // store the truth? |
428 | storage_duration_years: Integer; | 428 | storage_duration_years: number; |
429 | 429 | ||
430 | } | 430 | } |
431 | 431 | ||
@@ -482,6 +482,10 @@ charge per truth operation using GNU Taler. | |||
482 | The decrypted ``truth`` does not match the expectations of the authentication | 482 | The decrypted ``truth`` does not match the expectations of the authentication |
483 | backend, i.e. a phone number for sending an SMS is not a number, or | 483 | backend, i.e. a phone number for sending an SMS is not a number, or |
484 | an e-mail address for sending an E-mail is not a valid e-mail address. | 484 | an e-mail address for sending an E-mail is not a valid e-mail address. |
485 | :http:statuscode:`429 Too Many Requests`: | ||
486 | The client exceeded the number of allowed attempts at providing | ||
487 | a valid response for the given time interval. | ||
488 | The response format is given by `RateLimitedMessage`_. | ||
485 | :http:statuscode:`503 Service Unavailable`: | 489 | :http:statuscode:`503 Service Unavailable`: |
486 | Server is out of Service. | 490 | Server is out of Service. |
487 | 491 | ||
@@ -543,9 +547,29 @@ charge per truth operation using GNU Taler. | |||
543 | business_name: string; | 547 | business_name: string; |
544 | 548 | ||
545 | // What is the expected wire transfer subject? | 549 | // What is the expected wire transfer subject? |
546 | wire_transfer_subject: Integer; | 550 | wire_transfer_subject: number; |
547 | 551 | ||
548 | // Hint about the origin account that must be used. | 552 | // Hint about the origin account that must be used. |
549 | debit_account_hint: string; | 553 | debit_account_hint: string; |
550 | 554 | ||
551 | } | 555 | } |
556 | |||
557 | |||
558 | .. _RateLimitedMessage: | ||
559 | .. ts:def:: RateLimitedMessage | ||
560 | |||
561 | interface RateLimitedMessage { | ||
562 | |||
563 | // Taler error code, TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED. | ||
564 | code: number; | ||
565 | |||
566 | // How many attempts are allowed per challenge? | ||
567 | request_limit: number; | ||
568 | |||
569 | // At what frequency are new challenges issued? | ||
570 | request_frequency: RelativeTime; | ||
571 | |||
572 | // The error message. | ||
573 | hint: string; | ||
574 | |||
575 | } | ||
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; | |||
237 | 237 | ||
238 | 238 | ||
239 | /** | 239 | /** |
240 | * Generate a response telling the client that answering this | ||
241 | * challenge failed because the rate limit has been exceeded. | ||
242 | * | ||
243 | * @param gc request to answer for | ||
244 | * @return MHD status code | ||
245 | */ | ||
246 | static MHD_RESULT | ||
247 | reply_rate_limited (const struct GetContext *gc) | ||
248 | { | ||
249 | return TALER_MHD_REPLY_JSON_PACK ( | ||
250 | gc->connection, | ||
251 | MHD_HTTP_TOO_MANY_REQUESTS, | ||
252 | TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED), | ||
253 | GNUNET_JSON_pack_uint64 ("request_limit", | ||
254 | gc->authorization->retry_counter), | ||
255 | GNUNET_JSON_pack_time_rel ("request_frequency", | ||
256 | gc->authorization->code_rotation_period)); | ||
257 | } | ||
258 | |||
259 | |||
260 | /** | ||
240 | * Timeout requests that are past their due date. | 261 | * Timeout requests that are past their due date. |
241 | * | 262 | * |
242 | * @param cls NULL | 263 | * @param cls NULL |
@@ -991,9 +1012,8 @@ run_authorization_process (struct MHD_Connection *connection, | |||
991 | 1012 | ||
992 | 1013 | ||
993 | /** | 1014 | /** |
994 | * Use the database to rate-limit queries to the | 1015 | * Use the database to rate-limit queries to the authentication |
995 | * authentication procedure, but without actually | 1016 | * procedure, but without actually storing 'real' challenge codes. |
996 | * storing 'real' challenge codes. | ||
997 | * | 1017 | * |
998 | * @param[in,out] gc context to rate limit requests for | 1018 | * @param[in,out] gc context to rate limit requests for |
999 | * @return #GNUNET_OK if rate-limiting passes, | 1019 | * @return #GNUNET_OK if rate-limiting passes, |
@@ -1034,10 +1054,7 @@ rate_limit (struct GetContext *gc) | |||
1034 | if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) | 1054 | if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) |
1035 | { | 1055 | { |
1036 | return (MHD_YES == | 1056 | return (MHD_YES == |
1037 | TALER_MHD_reply_with_error (gc->connection, | 1057 | reply_rate_limited (gc)) |
1038 | MHD_HTTP_TOO_MANY_REQUESTS, | ||
1039 | TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, | ||
1040 | NULL)) | ||
1041 | ? GNUNET_NO | 1058 | ? GNUNET_NO |
1042 | : GNUNET_SYSERR; | 1059 | : GNUNET_SYSERR; |
1043 | } | 1060 | } |
@@ -1066,10 +1083,7 @@ rate_limit (struct GetContext *gc) | |||
1066 | : GNUNET_SYSERR; | 1083 | : GNUNET_SYSERR; |
1067 | case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: | 1084 | case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: |
1068 | return (MHD_YES == | 1085 | return (MHD_YES == |
1069 | TALER_MHD_reply_with_error (gc->connection, | 1086 | reply_rate_limited (gc)) |
1070 | MHD_HTTP_TOO_MANY_REQUESTS, | ||
1071 | TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, | ||
1072 | NULL)) | ||
1073 | ? GNUNET_NO | 1087 | ? GNUNET_NO |
1074 | : GNUNET_SYSERR; | 1088 | : GNUNET_SYSERR; |
1075 | case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: | 1089 | case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: |
@@ -1640,10 +1654,7 @@ AH_handler_truth_get ( | |||
1640 | case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: | 1654 | case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: |
1641 | /* 0 == retry_counter of existing challenge => rate limit exceeded */ | 1655 | /* 0 == retry_counter of existing challenge => rate limit exceeded */ |
1642 | GNUNET_free (decrypted_truth); | 1656 | GNUNET_free (decrypted_truth); |
1643 | return TALER_MHD_reply_with_error (connection, | 1657 | return reply_rate_limited (gc); |
1644 | MHD_HTTP_TOO_MANY_REQUESTS, | ||
1645 | TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, | ||
1646 | NULL); | ||
1647 | case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: | 1658 | case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: |
1648 | /* challenge code was stored successfully*/ | 1659 | /* challenge code was stored successfully*/ |
1649 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | 1660 | 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 | |||
207 | unsigned int http_status; | 207 | unsigned int http_status; |
208 | } open_challenge; | 208 | } open_challenge; |
209 | 209 | ||
210 | /** | ||
211 | * Details for #ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED. | ||
212 | */ | ||
213 | struct | ||
214 | { | ||
215 | |||
216 | /** | ||
217 | * How many requests are allowed at most per @e request_frequency? | ||
218 | */ | ||
219 | uint32_t request_limit; | ||
220 | |||
221 | /** | ||
222 | * Frequency at which requests are allowed / new challenges are | ||
223 | * created. | ||
224 | */ | ||
225 | struct GNUNET_TIME_Relative request_frequency; | ||
226 | |||
227 | } rate_limit_exceeded; | ||
210 | 228 | ||
211 | /** | 229 | /** |
212 | * Response with details if | 230 | * 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 | |||
570 | } payment_required; | 570 | } payment_required; |
571 | 571 | ||
572 | 572 | ||
573 | struct | ||
574 | { | ||
575 | |||
576 | /** | ||
577 | * How many requests are allowed at most per @e request_frequency? | ||
578 | */ | ||
579 | uint32_t request_limit; | ||
580 | |||
581 | /** | ||
582 | * Frequency at which requests are allowed / new challenges are | ||
583 | * created. | ||
584 | */ | ||
585 | struct GNUNET_TIME_Relative request_frequency; | ||
586 | |||
587 | } rate_limit_exceeded; | ||
588 | |||
589 | |||
573 | /** | 590 | /** |
574 | * Response with details about a server-side failure, if | 591 | * Response with details about a server-side failure, if |
575 | * @e status is #ANASTASIS_KSD_SERVER_ERROR, | 592 | * @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, | |||
319 | { | 319 | { |
320 | struct ANASTASIS_ChallengeStartResponse csr = { | 320 | struct ANASTASIS_ChallengeStartResponse csr = { |
321 | .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED, | 321 | .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED, |
322 | .challenge = c | 322 | .challenge = c, |
323 | .details.rate_limit_exceeded.request_limit | ||
324 | = dd->details.rate_limit_exceeded.request_limit, | ||
325 | .details.rate_limit_exceeded.request_frequency | ||
326 | = dd->details.rate_limit_exceeded.request_frequency | ||
323 | }; | 327 | }; |
324 | 328 | ||
325 | c->af (c->af_cls, | 329 | 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 ( | |||
765 | json_t *err; | 765 | json_t *err; |
766 | 766 | ||
767 | err = GNUNET_JSON_PACK ( | 767 | err = GNUNET_JSON_PACK ( |
768 | GNUNET_JSON_pack_string ("state", | 768 | GNUNET_JSON_pack_string ( |
769 | "rate-limit-exceeded"), | 769 | "state", |
770 | GNUNET_JSON_pack_uint64 ("error_code", | 770 | "rate-limit-exceeded"), |
771 | TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); | 771 | GNUNET_JSON_pack_uint64 ( |
772 | "request_limit", | ||
773 | csr->details.rate_limit_exceeded.request_limit), | ||
774 | GNUNET_JSON_pack_time_rel ( | ||
775 | "request_frequency", | ||
776 | csr->details.rate_limit_exceeded.request_frequency), | ||
777 | GNUNET_JSON_pack_uint64 ( | ||
778 | "error_code", | ||
779 | TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); | ||
772 | GNUNET_assert (0 == | 780 | GNUNET_assert (0 == |
773 | json_object_set_new (feedback, | 781 | json_object_set_new (feedback, |
774 | uuid, | 782 | 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, | |||
258 | break; | 258 | break; |
259 | case MHD_HTTP_TOO_MANY_REQUESTS: | 259 | case MHD_HTTP_TOO_MANY_REQUESTS: |
260 | kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED; | 260 | kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED; |
261 | { | ||
262 | struct GNUNET_JSON_Specification spec[] = { | ||
263 | GNUNET_JSON_spec_uint32 ( | ||
264 | "request_limit", | ||
265 | &kdd.details.rate_limit_exceeded.request_limit), | ||
266 | GNUNET_JSON_spec_relative_time ( | ||
267 | "request_frequency", | ||
268 | &kdd.details.rate_limit_exceeded.request_frequency), | ||
269 | GNUNET_JSON_spec_end () | ||
270 | }; | ||
271 | json_t *reply; | ||
272 | |||
273 | reply = json_loadb (data, | ||
274 | data_size, | ||
275 | JSON_REJECT_DUPLICATES, | ||
276 | NULL); | ||
277 | if (NULL == reply) | ||
278 | { | ||
279 | GNUNET_break_op (0); | ||
280 | kdd.status = ANASTASIS_KSD_SERVER_ERROR; | ||
281 | kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE; | ||
282 | kdd.details.server_failure.http_status = response_code; | ||
283 | break; | ||
284 | } | ||
285 | if (GNUNET_OK != | ||
286 | GNUNET_JSON_parse (reply, | ||
287 | spec, | ||
288 | NULL, NULL)) | ||
289 | { | ||
290 | GNUNET_break_op (0); | ||
291 | kdd.status = ANASTASIS_KSD_SERVER_ERROR; | ||
292 | kdd.details.server_failure.ec = TALER_JSON_get_error_code (reply); | ||
293 | kdd.details.server_failure.http_status = response_code; | ||
294 | json_decref (reply); | ||
295 | break; | ||
296 | } | ||
297 | json_decref (reply); | ||
298 | } | ||
261 | break; | 299 | break; |
262 | case MHD_HTTP_INTERNAL_SERVER_ERROR: | 300 | case MHD_HTTP_INTERNAL_SERVER_ERROR: |
263 | /* Server had an internal issue; we should retry, but this API | 301 | /* Server had an internal issue; we should retry, but this API |