test_challenger_db.c (34798B)
1 /* 2 This file is part of Challenger 3 (C) 2023 Taler Systems SA 4 5 Challenger is free software; you can redistribute it and/or modify it under the 6 terms of the GNU 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 Challenger; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file challengerdb/test_challenger_db.c 18 * @brief testcase for challenger postgres db plugin 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <gnunet/gnunet_pq_lib.h> 24 #include <taler/taler_util.h> 25 #include "challenger_database_lib.h" 26 #include "challenger-database/drop_tables.h" 27 #include "challenger-database/create_tables.h" 28 #include "challenger-database/preflight.h" 29 #include "challenger-database/gc.h" 30 #include "challenger-database/insert_client.h" 31 #include "challenger-database/do_insert_validation.h" 32 #include "challenger-database/do_challenge_address.h" 33 #include "challenger-database/do_solve_challenge.h" 34 #include "challenger-database/do_insert_token.h" 35 #include "challenger-database/get_token.h" 36 #include "challenger-database/get_validation_pkce.h" 37 #include "challenger-database/update_validation.h" 38 #include "challenger_util.h" 39 #include "pg_helper.h" 40 #include "challenger-database/delete_client.h" 41 #include "challenger-database/do_insert_validation.h" 42 43 44 #define FAILIF(cond) \ 45 do { \ 46 if (! (cond)) { break;} \ 47 GNUNET_break (0); \ 48 goto drop; \ 49 } while (0) 50 51 /** 52 * Secret of the client the tests register and act as. 53 */ 54 #define CLIENT_SECRET "secret-token:test-secret" 55 56 /** 57 * Redirect URI of the client the tests register. 58 */ 59 #define CLIENT_URI "http://client.example.com/" 60 61 /** 62 * Global return value for the test. Initially -1, set to 0 upon 63 * completion. Other values indicate some kind of error. 64 */ 65 static int result; 66 67 /** 68 * Handle to the database we are testing. 69 */ 70 static struct CHALLENGERDB_PostgresContext *pg; 71 72 /** 73 * Serial ID of the client registered by #setup_client(). 74 */ 75 static uint64_t client_id; 76 77 78 /** 79 * Register the OAuth client all tests act as. 80 * 81 * @return #GNUNET_OK on success 82 */ 83 static enum GNUNET_GenericReturnValue 84 setup_client (void) 85 { 86 bool uri_taken = false; 87 88 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 89 CHALLENGERDB_insert_client (pg, 90 CLIENT_URI, 91 CLIENT_SECRET, 92 &client_id, 93 &uri_taken)) 94 { 95 GNUNET_break (0); 96 return GNUNET_SYSERR; 97 } 98 return GNUNET_OK; 99 } 100 101 102 /** 103 * Create a fresh validation for #client_id and have a TAN transmitted 104 * for it, leaving the validation in the state a user reaches by posting 105 * an address to ``/challenge`` and then walking away. 106 * 107 * @param[out] nonce set to the nonce identifying the new validation 108 * @param[out] pin set to the TAN that was "transmitted" 109 * @return #GNUNET_OK on success 110 */ 111 static enum GNUNET_GenericReturnValue 112 challenge_validation (struct CHALLENGER_ValidationNonceP *nonce, 113 uint32_t *pin) 114 { 115 struct GNUNET_TIME_Absolute expiration 116 = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS); 117 json_t *address; 118 char *state = NULL; 119 char *redirect_uri = NULL; 120 struct GNUNET_TIME_Absolute last_tx_time; 121 uint32_t auth_attempts_left; 122 uint32_t pin_transmissions_left; 123 bool pin_transmit; 124 bool address_refused; 125 bool solved; 126 bool failed; 127 enum GNUNET_DB_QueryStatus qs; 128 129 GNUNET_CRYPTO_random_block (nonce, 130 sizeof (*nonce)); 131 qs = CHALLENGERDB_do_insert_validation (pg, 132 client_id, 133 CLIENT_SECRET, 134 nonce, 135 expiration, 136 NULL); 137 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 138 { 139 GNUNET_break (0); 140 return GNUNET_SYSERR; 141 } 142 address = json_pack ("{s:s}", 143 "filename", 144 "test-challenger-db.txt"); 145 GNUNET_assert (NULL != address); 146 *pin = 424242; 147 qs = CHALLENGERDB_do_challenge_address (pg, 148 nonce, 149 address, 150 GNUNET_TIME_UNIT_ZERO, 151 pin, 152 &state, 153 &last_tx_time, 154 &auth_attempts_left, 155 &pin_transmissions_left, 156 &pin_transmit, 157 &redirect_uri, 158 &address_refused, 159 &solved, 160 &failed); 161 json_decref (address); 162 /* The TAN is only pending until its transmission is confirmed; this 163 stands in for a successful AUTH_COMMAND run. */ 164 qs = CHALLENGERDB_do_challenge_address_confirm_pin (pg, 165 nonce, 166 &auth_attempts_left); 167 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 168 { 169 GNUNET_break (0); 170 return GNUNET_SYSERR; 171 } 172 GNUNET_free (state); 173 GNUNET_free (redirect_uri); 174 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) || 175 (! pin_transmit) || 176 address_refused || 177 solved || 178 failed) 179 { 180 GNUNET_break (0); 181 return GNUNET_SYSERR; 182 } 183 return GNUNET_OK; 184 } 185 186 187 /** 188 * Enter @a pin for the validation under @a nonce and check it was accepted. 189 * 190 * @param nonce validation to solve 191 * @param pin TAN to enter 192 * @return #GNUNET_OK on success 193 */ 194 static enum GNUNET_GenericReturnValue 195 solve_validation (const struct CHALLENGER_ValidationNonceP *nonce, 196 uint32_t pin) 197 { 198 char *state = NULL; 199 char *redirect_uri = NULL; 200 uint32_t addr_left; 201 uint32_t auth_attempts_left; 202 uint32_t pin_transmissions_left; 203 bool solved; 204 bool exhausted; 205 bool no_challenge; 206 bool failed; 207 enum GNUNET_DB_QueryStatus qs; 208 209 qs = CHALLENGERDB_do_solve_challenge (pg, 210 nonce, 211 pin, 212 &solved, 213 &exhausted, 214 &no_challenge, 215 &failed, 216 &state, 217 &addr_left, 218 &auth_attempts_left, 219 &pin_transmissions_left, 220 &redirect_uri); 221 GNUNET_free (state); 222 GNUNET_free (redirect_uri); 223 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) || 224 (! solved) || 225 exhausted || 226 no_challenge || 227 failed) 228 { 229 GNUNET_break (0); 230 return GNUNET_SYSERR; 231 } 232 return GNUNET_OK; 233 } 234 235 236 /** 237 * Mint an access token for the validation under @a nonce. 238 * 239 * @param client client redeeming the authorization code 240 * @param nonce validation to redeem 241 * @param[out] token set to the (random) access token we tried to store 242 * @return transaction status of CHALLENGERDB_do_insert_token() 243 */ 244 static enum GNUNET_DB_QueryStatus 245 insert_token (uint64_t client, 246 const struct CHALLENGER_ValidationNonceP *nonce, 247 struct CHALLENGER_AccessTokenP *token) 248 { 249 GNUNET_CRYPTO_random_block (token, 250 sizeof (*token)); 251 return CHALLENGERDB_do_insert_token (pg, 252 nonce, 253 client, 254 token, 255 GNUNET_TIME_UNIT_HOURS, 256 GNUNET_TIME_UNIT_DAYS); 257 } 258 259 260 /** 261 * Test that a validation for which the user never entered the correct 262 * TAN cannot be redeemed for an access token, and is not even visible to 263 * the ``/token`` lookup. Without the ``auth_attempts_left < 0`` guard 264 * anyone able to compute the authorization code could mint an 265 * attestation for an address nobody ever proved control over. 266 * 267 * @return #GNUNET_OK on success 268 */ 269 static enum GNUNET_GenericReturnValue 270 test_unsolved_not_redeemable (void) 271 { 272 struct CHALLENGER_ValidationNonceP nonce; 273 struct CHALLENGER_AccessTokenP token; 274 uint32_t pin; 275 276 if (GNUNET_OK != 277 challenge_validation (&nonce, 278 &pin)) 279 return GNUNET_SYSERR; 280 { 281 char *client_secret; 282 json_t *address; 283 char *client_scope; 284 char *client_state; 285 char *client_redirect_uri; 286 char *code_challenge; 287 uint32_t code_challenge_method; 288 289 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 290 CHALLENGERDB_get_validation_pkce (pg, 291 &nonce, 292 client_id, 293 &client_secret, 294 &address, 295 &client_scope, 296 &client_state, 297 &client_redirect_uri, 298 &code_challenge, 299 &code_challenge_method)) 300 { 301 GNUNET_break (0); 302 return GNUNET_SYSERR; 303 } 304 } 305 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 306 insert_token (client_id, 307 &nonce, 308 &token)) 309 { 310 GNUNET_break (0); 311 return GNUNET_SYSERR; 312 } 313 return GNUNET_OK; 314 } 315 316 317 /** 318 * Create a fresh validation for #client_id whose address was set by the 319 * client and marked read-only, so the user cannot change it. 320 * 321 * @param[out] nonce set to the nonce identifying the new validation 322 * @return #GNUNET_OK on success 323 */ 324 static enum GNUNET_GenericReturnValue 325 setup_read_only_validation (struct CHALLENGER_ValidationNonceP *nonce) 326 { 327 struct GNUNET_TIME_Absolute expiration 328 = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS); 329 json_t *address; 330 enum GNUNET_DB_QueryStatus qs; 331 332 GNUNET_CRYPTO_random_block (nonce, 333 sizeof (*nonce)); 334 address = json_pack ("{s:s, s:b}", 335 "filename", 336 "test-challenger-db.txt", 337 "read_only", 338 true); 339 GNUNET_assert (NULL != address); 340 qs = CHALLENGERDB_do_insert_validation (pg, 341 client_id, 342 CLIENT_SECRET, 343 nonce, 344 expiration, 345 address); 346 json_decref (address); 347 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 348 { 349 GNUNET_break (0); 350 return GNUNET_SYSERR; 351 } 352 return GNUNET_OK; 353 } 354 355 356 /** 357 * Post the read-only address of the validation under @a nonce to 358 * ``/challenge`` again, as the user would to get a (new) TAN. 359 * 360 * @param nonce validation to use 361 * @param confirm true to confirm the transmission of a new TAN, false to 362 * act as if the transmission helper failed 363 * @param[out] pin set to the TAN that was transmitted, if any 364 * @param[out] pin_transmit set to true if a TAN was to be transmitted 365 * @param[out] failed set to true if the validation failed permanently 366 * @return #GNUNET_OK on success 367 */ 368 static enum GNUNET_GenericReturnValue 369 challenge_read_only (const struct CHALLENGER_ValidationNonceP *nonce, 370 bool confirm, 371 uint32_t *pin, 372 bool *pin_transmit, 373 bool *failed) 374 { 375 json_t *address; 376 char *state = NULL; 377 char *redirect_uri = NULL; 378 struct GNUNET_TIME_Absolute last_tx_time; 379 uint32_t auth_attempts_left; 380 uint32_t pin_transmissions_left; 381 bool address_refused; 382 bool solved; 383 enum GNUNET_DB_QueryStatus qs; 384 385 address = json_pack ("{s:s, s:b}", 386 "filename", 387 "test-challenger-db.txt", 388 "read_only", 389 true); 390 GNUNET_assert (NULL != address); 391 *pin = GNUNET_CRYPTO_random_u32 (100000000); 392 qs = CHALLENGERDB_do_challenge_address (pg, 393 nonce, 394 address, 395 GNUNET_TIME_UNIT_ZERO, 396 pin, 397 &state, 398 &last_tx_time, 399 &auth_attempts_left, 400 &pin_transmissions_left, 401 pin_transmit, 402 &redirect_uri, 403 &address_refused, 404 &solved, 405 failed); 406 json_decref (address); 407 GNUNET_free (state); 408 GNUNET_free (redirect_uri); 409 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) || 410 solved) 411 { 412 GNUNET_break (0); 413 return GNUNET_SYSERR; 414 } 415 if (confirm && *pin_transmit) 416 { 417 qs = CHALLENGERDB_do_challenge_address_confirm_pin (pg, 418 nonce, 419 &auth_attempts_left); 420 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 421 { 422 GNUNET_break (0); 423 return GNUNET_SYSERR; 424 } 425 } 426 return GNUNET_OK; 427 } 428 429 430 /** 431 * Enter the wrong TAN @a pin for the validation under @a nonce. 432 * 433 * @param nonce validation to use 434 * @param pin (wrong) TAN to enter 435 * @param[out] failed set to true if the validation failed permanently 436 * @return #GNUNET_OK on success 437 */ 438 static enum GNUNET_GenericReturnValue 439 guess_wrong (const struct CHALLENGER_ValidationNonceP *nonce, 440 uint32_t pin, 441 bool *failed) 442 { 443 char *state = NULL; 444 char *redirect_uri = NULL; 445 uint32_t addr_left; 446 uint32_t auth_attempts_left; 447 uint32_t pin_transmissions_left; 448 bool solved; 449 bool exhausted; 450 bool no_challenge; 451 enum GNUNET_DB_QueryStatus qs; 452 453 qs = CHALLENGERDB_do_solve_challenge (pg, 454 nonce, 455 pin, 456 &solved, 457 &exhausted, 458 &no_challenge, 459 failed, 460 &state, 461 &addr_left, 462 &auth_attempts_left, 463 &pin_transmissions_left, 464 &redirect_uri); 465 GNUNET_free (state); 466 GNUNET_free (redirect_uri); 467 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) || 468 solved) 469 { 470 GNUNET_break (0); 471 return GNUNET_SYSERR; 472 } 473 return GNUNET_OK; 474 } 475 476 477 /** 478 * Test that a validation with a read-only address fails once all TAN 479 * transmissions and guesses are spent. The user cannot change a 480 * read-only address, so ``address_attempts_left`` (still at its initial 481 * value) must not keep the validation alive; the guess that uses up the 482 * last chance already reports the failure, and a failed validation stays 483 * failed. 484 * 485 * @return #GNUNET_OK on success 486 */ 487 static enum GNUNET_GenericReturnValue 488 test_read_only_exhaustion_fails (void) 489 { 490 struct CHALLENGER_ValidationNonceP nonce; 491 uint32_t pin; 492 bool pin_transmit; 493 bool failed; 494 495 if (GNUNET_OK != 496 setup_read_only_validation (&nonce)) 497 return GNUNET_SYSERR; 498 for (unsigned int tx = 0; tx < 3; tx++) 499 { 500 if (GNUNET_OK != 501 challenge_read_only (&nonce, 502 true, 503 &pin, 504 &pin_transmit, 505 &failed)) 506 return GNUNET_SYSERR; 507 if ( (! pin_transmit) || 508 failed) 509 { 510 GNUNET_break (0); 511 return GNUNET_SYSERR; 512 } 513 for (unsigned int guess = 0; guess < 3; guess++) 514 { 515 if (GNUNET_OK != 516 guess_wrong (&nonce, 517 (pin + 1) % 100000000, 518 &failed)) 519 return GNUNET_SYSERR; 520 if (failed != ( (2 == tx) && (2 == guess) )) 521 { 522 GNUNET_break (0); 523 return GNUNET_SYSERR; 524 } 525 } 526 } 527 /* failed is final, whatever the user tries next */ 528 if (GNUNET_OK != 529 guess_wrong (&nonce, 530 pin, 531 &failed)) 532 return GNUNET_SYSERR; 533 if (! failed) 534 { 535 GNUNET_break (0); 536 return GNUNET_SYSERR; 537 } 538 if (GNUNET_OK != 539 challenge_read_only (&nonce, 540 true, 541 &pin, 542 &pin_transmit, 543 &failed)) 544 return GNUNET_SYSERR; 545 if ( (! failed) || 546 pin_transmit) 547 { 548 GNUNET_break (0); 549 return GNUNET_SYSERR; 550 } 551 return GNUNET_OK; 552 } 553 554 555 /** 556 * Test that a validation fails if the transmission helper fails on the 557 * last TAN after the guesses on the previous TAN were spent: the user 558 * then never got anything left to try, even though no ``/solve`` spent 559 * the last chance. Also checks that ``/authorize`` reports the failure. 560 * 561 * @return #GNUNET_OK on success 562 */ 563 static enum GNUNET_GenericReturnValue 564 test_failed_last_transmission_fails (void) 565 { 566 struct CHALLENGER_ValidationNonceP nonce; 567 uint32_t pin; 568 bool pin_transmit; 569 bool failed; 570 571 if (GNUNET_OK != 572 setup_read_only_validation (&nonce)) 573 return GNUNET_SYSERR; 574 if (GNUNET_OK != 575 challenge_read_only (&nonce, 576 true, 577 &pin, 578 &pin_transmit, 579 &failed)) 580 return GNUNET_SYSERR; 581 for (unsigned int guess = 0; guess < 3; guess++) 582 if (GNUNET_OK != 583 guess_wrong (&nonce, 584 (pin + 1) % 100000000, 585 &failed)) 586 return GNUNET_SYSERR; 587 if (failed) 588 { 589 GNUNET_break (0); 590 return GNUNET_SYSERR; 591 } 592 /* The remaining two transmissions fail. */ 593 for (unsigned int tx = 0; tx < 2; tx++) 594 { 595 if (GNUNET_OK != 596 challenge_read_only (&nonce, 597 false, 598 &pin, 599 &pin_transmit, 600 &failed)) 601 return GNUNET_SYSERR; 602 if ( (! pin_transmit) || 603 failed) 604 { 605 GNUNET_break (0); 606 return GNUNET_SYSERR; 607 } 608 } 609 { 610 json_t *last_address; 611 uint32_t address_attempts_left; 612 uint32_t pin_transmissions_left; 613 uint32_t auth_attempts_left; 614 bool solved; 615 char *redirect_uri; 616 struct GNUNET_TIME_Absolute last_tx_time; 617 618 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 619 CHALLENGERDB_update_validation (pg, 620 &nonce, 621 client_id, 622 NULL, 623 "the-state", 624 NULL, 625 NULL, 626 0, 627 &last_address, 628 &address_attempts_left, 629 &pin_transmissions_left, 630 &auth_attempts_left, 631 &solved, 632 &failed, 633 &redirect_uri, 634 &last_tx_time)) 635 { 636 GNUNET_break (0); 637 return GNUNET_SYSERR; 638 } 639 json_decref (last_address); 640 if ( (! failed) || 641 (NULL == redirect_uri) || 642 (0 != strcmp (redirect_uri, 643 CLIENT_URI)) ) 644 { 645 GNUNET_break (0); 646 GNUNET_free (redirect_uri); 647 return GNUNET_SYSERR; 648 } 649 GNUNET_free (redirect_uri); 650 } 651 if (GNUNET_OK != 652 guess_wrong (&nonce, 653 pin, 654 &failed)) 655 return GNUNET_SYSERR; 656 if (! failed) 657 { 658 GNUNET_break (0); 659 return GNUNET_SYSERR; 660 } 661 return GNUNET_OK; 662 } 663 664 665 /** 666 * Test that a solved validation is redeemable, and redeemable exactly 667 * once (the authorization code must not be replayable). 668 * 669 * @return #GNUNET_OK on success 670 */ 671 static enum GNUNET_GenericReturnValue 672 test_solved_redeemable (void) 673 { 674 struct CHALLENGER_ValidationNonceP nonce; 675 struct CHALLENGER_AccessTokenP token; 676 uint32_t pin; 677 678 if ( (GNUNET_OK != 679 challenge_validation (&nonce, 680 &pin)) || 681 (GNUNET_OK != 682 solve_validation (&nonce, 683 pin)) ) 684 return GNUNET_SYSERR; 685 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 686 insert_token (client_id, 687 &nonce, 688 &token)) 689 { 690 GNUNET_break (0); 691 return GNUNET_SYSERR; 692 } 693 /* replay: the validation was consumed, so this must not mint a token */ 694 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 695 insert_token (client_id, 696 &nonce, 697 &token)) 698 { 699 GNUNET_break (0); 700 return GNUNET_SYSERR; 701 } 702 return GNUNET_OK; 703 } 704 705 706 /** 707 * Test that the address expiration stored with a freshly minted token is 708 * in the future, and in particular that it does *not* depend on the 709 * validation's ``last_tx_time``. The regression this guards against 710 * computed the expiration as ``address_expiration + last_tx_time``, which 711 * lands in 1970 whenever that column was never written. We force the 712 * column back to 0 to pin the property down without relying on any 713 * particular code path leaving it there. 714 * 715 * @return #GNUNET_OK on success 716 */ 717 static enum GNUNET_GenericReturnValue 718 test_address_expiry_is_in_the_future (void) 719 { 720 struct GNUNET_PQ_ExecuteStatement es[] = { 721 GNUNET_PQ_make_execute ("UPDATE validations SET last_tx_time=0;"), 722 GNUNET_PQ_EXECUTE_STATEMENT_END 723 }; 724 struct CHALLENGER_ValidationNonceP nonce; 725 struct CHALLENGER_AccessTokenP token; 726 struct GNUNET_TIME_Timestamp address_expiration; 727 struct GNUNET_TIME_Absolute now; 728 json_t *address = NULL; 729 uint64_t rowid; 730 uint32_t pin; 731 enum GNUNET_GenericReturnValue ret; 732 733 if ( (GNUNET_OK != 734 challenge_validation (&nonce, 735 &pin)) || 736 (GNUNET_OK != 737 solve_validation (&nonce, 738 pin)) ) 739 return GNUNET_SYSERR; 740 if (GNUNET_OK != 741 GNUNET_PQ_exec_statements (pg->conn, 742 es)) 743 { 744 GNUNET_break (0); 745 return GNUNET_SYSERR; 746 } 747 now = GNUNET_TIME_absolute_get (); 748 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 749 insert_token (client_id, 750 &nonce, 751 &token)) 752 { 753 GNUNET_break (0); 754 return GNUNET_SYSERR; 755 } 756 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 757 CHALLENGERDB_get_token (pg, 758 &token, 759 &rowid, 760 &address, 761 &address_expiration)) 762 { 763 GNUNET_break (0); 764 return GNUNET_SYSERR; 765 } 766 ret = GNUNET_OK; 767 if (address_expiration.abs_time.abs_value_us <= now.abs_value_us) 768 { 769 GNUNET_break (0); 770 ret = GNUNET_SYSERR; 771 } 772 json_decref (address); 773 return ret; 774 } 775 776 777 /** 778 * Test that an expired validation is not consumable, even though the 779 * caller (``/token``) checked the expiration in an earlier, separate 780 * transaction. Everything get_validation_pkce() filters on has to be 781 * repeated by do_insert_token(), or the gap between the two statements is 782 * an unguarded TOCTOU window. 783 * 784 * @return #GNUNET_OK on success 785 */ 786 static enum GNUNET_GenericReturnValue 787 test_expired_not_redeemable (void) 788 { 789 struct GNUNET_PQ_ExecuteStatement es[] = { 790 GNUNET_PQ_make_execute ("UPDATE validations SET expiration_time=1;"), 791 GNUNET_PQ_EXECUTE_STATEMENT_END 792 }; 793 struct CHALLENGER_ValidationNonceP nonce; 794 struct CHALLENGER_AccessTokenP token; 795 uint32_t pin; 796 797 if ( (GNUNET_OK != 798 challenge_validation (&nonce, 799 &pin)) || 800 (GNUNET_OK != 801 solve_validation (&nonce, 802 pin)) ) 803 return GNUNET_SYSERR; 804 if (GNUNET_OK != 805 GNUNET_PQ_exec_statements (pg->conn, 806 es)) 807 { 808 GNUNET_break (0); 809 return GNUNET_SYSERR; 810 } 811 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 812 insert_token (client_id, 813 &nonce, 814 &token)) 815 { 816 GNUNET_break (0); 817 return GNUNET_SYSERR; 818 } 819 return GNUNET_OK; 820 } 821 822 823 /** 824 * Test that a validation cannot be consumed by a client it does not 825 * belong to. 826 * 827 * @return #GNUNET_OK on success 828 */ 829 static enum GNUNET_GenericReturnValue 830 test_foreign_client_not_redeemable (void) 831 { 832 struct CHALLENGER_ValidationNonceP nonce; 833 struct CHALLENGER_AccessTokenP token; 834 uint32_t pin; 835 836 if ( (GNUNET_OK != 837 challenge_validation (&nonce, 838 &pin)) || 839 (GNUNET_OK != 840 solve_validation (&nonce, 841 pin)) ) 842 return GNUNET_SYSERR; 843 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 844 insert_token (client_id + 1, 845 &nonce, 846 &token)) 847 { 848 GNUNET_break (0); 849 return GNUNET_SYSERR; 850 } 851 /* ... and the rightful client still can */ 852 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 853 insert_token (client_id, 854 &nonce, 855 &token)) 856 { 857 GNUNET_break (0); 858 return GNUNET_SYSERR; 859 } 860 return GNUNET_OK; 861 } 862 863 864 /** 865 * Run @a sql directly on the database, bypassing the CHALLENGERDB API. 866 * Used to create states (and to make assertions about the schema) that 867 * the API deliberately cannot produce. 868 * 869 * @param sql SQL statement to execute 870 * @return #GNUNET_OK on success 871 */ 872 static enum GNUNET_GenericReturnValue 873 exec_sql (const char *sql) 874 { 875 struct GNUNET_PQ_ExecuteStatement es[] = { 876 GNUNET_PQ_make_execute (sql), 877 GNUNET_PQ_EXECUTE_STATEMENT_END 878 }; 879 880 return GNUNET_PQ_exec_statements (pg->conn, 881 es); 882 } 883 884 885 /** 886 * Test that a client cannot be inserted silently: a URI that is already 887 * registered must be reported as such, and any *other* unique violation 888 * (notably one on the primary key, which happens once the identity sequence 889 * of 'clients' lags behind max(client_serial_id)) must not be misreported as 890 * a duplicate URI. 891 * 892 * @return #GNUNET_OK on success 893 */ 894 static enum GNUNET_GenericReturnValue 895 test_insert_client (void) 896 { 897 uint64_t nclient_id; 898 bool uri_taken; 899 900 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 901 CHALLENGERDB_insert_client (pg, 902 "https://example.com/a", 903 "secret-token:a", 904 &nclient_id, 905 &uri_taken)) 906 { 907 GNUNET_break (0); 908 return GNUNET_SYSERR; 909 } 910 if (uri_taken) 911 { 912 GNUNET_break (0); 913 return GNUNET_SYSERR; 914 } 915 /* Same URI again: this one really is a duplicate. */ 916 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 917 CHALLENGERDB_insert_client (pg, 918 "https://example.com/a", 919 "secret-token:b", 920 &nclient_id, 921 &uri_taken)) || 922 (! uri_taken) ) 923 { 924 GNUNET_break (0); 925 return GNUNET_SYSERR; 926 } 927 /* Rewind the identity sequence, as a restore that did not restore the 928 sequences leaves it: the next INSERT then collides on the primary key, 929 not on the URI. */ 930 if (GNUNET_OK != 931 exec_sql ("DO $$ BEGIN" 932 " PERFORM setval(" 933 " pg_get_serial_sequence('challenger.clients'," 934 " 'client_serial_id')," 935 " (SELECT MIN(client_serial_id) FROM challenger.clients)," 936 " false);" 937 "END $$;")) 938 { 939 GNUNET_break (0); 940 return GNUNET_SYSERR; 941 } 942 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 943 CHALLENGERDB_insert_client (pg, 944 "https://example.com/b", 945 "secret-token:b", 946 &nclient_id, 947 &uri_taken)) 948 { 949 /* The PK collision must not go unnoticed. */ 950 GNUNET_break (0); 951 return GNUNET_SYSERR; 952 } 953 if (uri_taken) 954 { 955 /* This is the regression: the failure has nothing to do with the URI, 956 and the operator must not be told that the URI already exists. */ 957 GNUNET_break (0); 958 return GNUNET_SYSERR; 959 } 960 /* Repair the sequence, then the very same insert must work. */ 961 if (GNUNET_OK != 962 exec_sql ("DO $$ BEGIN" 963 " PERFORM setval(" 964 " pg_get_serial_sequence('challenger.clients'," 965 " 'client_serial_id')," 966 " (SELECT MAX(client_serial_id) FROM challenger.clients)," 967 " true);" 968 "END $$;")) 969 { 970 GNUNET_break (0); 971 return GNUNET_SYSERR; 972 } 973 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 974 CHALLENGERDB_insert_client (pg, 975 "https://example.com/b", 976 "secret-token:b", 977 &nclient_id, 978 &uri_taken)) || 979 (uri_taken) ) 980 { 981 GNUNET_break (0); 982 return GNUNET_SYSERR; 983 } 984 return GNUNET_OK; 985 } 986 987 988 /** 989 * Test that deleting a client reports how many validations it took with it: 990 * validations_client_serial_id_fkey cascades, so an operator would otherwise 991 * abort in-flight KYC processes without being told. 992 * 993 * @return #GNUNET_OK on success 994 */ 995 static enum GNUNET_GenericReturnValue 996 test_delete_client (void) 997 { 998 const char *uri = "https://example.com/del"; 999 const char *secret = "secret-token:del"; 1000 struct GNUNET_TIME_Absolute expiration 1001 = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS); 1002 uint64_t nclient_id; 1003 uint64_t validations_deleted; 1004 bool uri_taken; 1005 1006 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1007 CHALLENGERDB_insert_client (pg, 1008 uri, 1009 secret, 1010 &nclient_id, 1011 &uri_taken)) 1012 { 1013 GNUNET_break (0); 1014 return GNUNET_SYSERR; 1015 } 1016 for (unsigned int i = 0; i < 2; i++) 1017 { 1018 struct CHALLENGER_ValidationNonceP nonce; 1019 1020 GNUNET_CRYPTO_random_block (&nonce, 1021 sizeof (nonce)); 1022 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1023 CHALLENGERDB_do_insert_validation (pg, 1024 nclient_id, 1025 secret, 1026 &nonce, 1027 expiration, 1028 NULL)) 1029 { 1030 GNUNET_break (0); 1031 return GNUNET_SYSERR; 1032 } 1033 } 1034 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1035 CHALLENGERDB_delete_client (pg, 1036 uri, 1037 &validations_deleted)) || 1038 (2 != validations_deleted) ) 1039 { 1040 GNUNET_break (0); 1041 return GNUNET_SYSERR; 1042 } 1043 /* Deleting it again finds nothing and discards nothing. */ 1044 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 1045 CHALLENGERDB_delete_client (pg, 1046 uri, 1047 &validations_deleted)) || 1048 (0 != validations_deleted) ) 1049 { 1050 GNUNET_break (0); 1051 return GNUNET_SYSERR; 1052 } 1053 return GNUNET_OK; 1054 } 1055 1056 1057 /** 1058 * Main function that will be run by the scheduler. 1059 * 1060 * @param cls closure with config 1061 */ 1062 static void 1063 run (void *cls) 1064 { 1065 struct GNUNET_CONFIGURATION_Handle *cfg = cls; 1066 1067 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1068 "Connecting\n"); 1069 if (NULL == (pg = CHALLENGERDB_connect_admin (cfg))) 1070 { 1071 result = 77; 1072 return; 1073 } 1074 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1075 "Connected\n"); 1076 if (GNUNET_OK != 1077 CHALLENGERDB_drop_tables (pg)) 1078 { 1079 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1080 "Dropping tables failed\n"); 1081 } 1082 if (GNUNET_OK != 1083 CHALLENGERDB_create_tables (pg)) 1084 { 1085 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1086 "Creating tables failed\n"); 1087 goto drop; 1088 } 1089 GNUNET_assert (GNUNET_OK == 1090 CHALLENGERDB_preflight (pg)); 1091 { 1092 struct GNUNET_TIME_Absolute ts = GNUNET_TIME_absolute_get (); 1093 1094 FAILIF (0 > 1095 CHALLENGERDB_gc (pg, 1096 ts)); 1097 } 1098 FAILIF (GNUNET_OK != 1099 setup_client ()); 1100 FAILIF (GNUNET_OK != 1101 test_unsolved_not_redeemable ()); 1102 FAILIF (GNUNET_OK != 1103 test_solved_redeemable ()); 1104 FAILIF (GNUNET_OK != 1105 test_address_expiry_is_in_the_future ()); 1106 FAILIF (GNUNET_OK != 1107 test_expired_not_redeemable ()); 1108 FAILIF (GNUNET_OK != 1109 test_foreign_client_not_redeemable ()); 1110 FAILIF (GNUNET_OK != 1111 test_read_only_exhaustion_fails ()); 1112 FAILIF (GNUNET_OK != 1113 test_failed_last_transmission_fails ()); 1114 FAILIF (GNUNET_OK != 1115 test_insert_client ()); 1116 FAILIF (GNUNET_OK != 1117 test_delete_client ()); 1118 result = 0; 1119 drop: 1120 GNUNET_break (GNUNET_OK == 1121 CHALLENGERDB_drop_tables (pg)); 1122 CHALLENGERDB_disconnect (pg); 1123 pg = NULL; 1124 } 1125 1126 1127 int 1128 main (int argc, 1129 char *const argv[]) 1130 { 1131 struct GNUNET_CONFIGURATION_Handle *cfg; 1132 1133 (void) argc; 1134 result = EXIT_FAILURE; 1135 GNUNET_log_setup (argv[0], 1136 "DEBUG", 1137 NULL); 1138 cfg = GNUNET_CONFIGURATION_create (CHALLENGER_project_data ()); 1139 if (GNUNET_OK != 1140 GNUNET_CONFIGURATION_parse (cfg, 1141 "test_challenger_db_postgres.conf")) 1142 { 1143 GNUNET_break (0); 1144 return EXIT_NOTCONFIGURED; 1145 } 1146 GNUNET_SCHEDULER_run (&run, cfg); 1147 GNUNET_CONFIGURATION_destroy (cfg); 1148 return result; 1149 } 1150 1151 1152 /* end of test_challenger_db.c */