test_merchant_mfa.sh (21787B)
1 #!/usr/bin/env bash 2 # This file is part of TALER 3 # Copyright (C) 2025 Taler Systems SA 4 # 5 # TALER is free software; you can redistribute it and/or modify 6 # it under the terms of the GNU General Public License as 7 # published by the Free Software Foundation; either version 3, or 8 # (at your option) any later version. 9 # 10 # TALER is distributed in the hope that it will be useful, but 11 # WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public 16 # License along with TALER; see the file COPYING. If not, see 17 # <http://www.gnu.org/licenses/> 18 # 19 set -eu 20 21 . setup.sh 22 23 24 echo -n "Reject password-change MFA without mandatory TAN channels " 25 INVALID_CONFIG=$(mktemp -p "${TMPDIR:-/tmp}" test_merchant_mfa.conf-XXXXXX) 26 INVALID_LOG=$(mktemp -p "${TMPDIR:-/tmp}" test_merchant_mfa.log-XXXXXX) 27 cp test_merchant_mfa.conf "$INVALID_CONFIG" 28 sed -i '/^MANDATORY_TAN_CHANNELS[[:space:]]*=/d' "$INVALID_CONFIG" 29 STATUS=0 30 timeout 10 taler-merchant-httpd -L INFO -c "$INVALID_CONFIG" \ 31 > "$INVALID_LOG" 2>&1 || STATUS=$? 32 if [ "$STATUS" = "0" ] || 33 [ "$STATUS" = "124" ] || 34 ! grep -q \ 35 'requires at least one MANDATORY_TAN_CHANNELS entry' \ 36 "$INVALID_LOG" 37 then 38 cat "$INVALID_LOG" 39 rm -f "$INVALID_CONFIG" "$INVALID_LOG" 40 exit_fail "Expected invalid password-change MFA configuration to fail" 41 fi 42 rm -f "$INVALID_CONFIG" "$INVALID_LOG" 43 echo "OK" 44 45 46 # Launch system. 47 setup \ 48 -c "test_merchant_mfa.conf" \ 49 -m 50 LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX) 51 52 solve_challenge() 53 { 54 CHALLENGE_ID=$1 55 CHANNEL=$2 56 EXPECTED_ADDRESS=$3 57 58 rm -f "/tmp/test-merchant-$CHANNEL-tan.txt" \ 59 "/tmp/test-merchant-$CHANNEL-address.txt" 60 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 61 "http://localhost:9966/challenge/$CHALLENGE_ID" \ 62 -d '{}' \ 63 -w "%{http_code}" -s \ 64 -o "$LAST_RESPONSE") 65 if [ "$STATUS" != "200" ] 66 then 67 jq < "$LAST_RESPONSE" 68 exit_fail "Expected challenge transmission to return 200 OK. Got: $STATUS" 69 fi 70 71 TAN=$(head -n1 "/tmp/test-merchant-$CHANNEL-tan.txt" | awk '{print $1}') 72 ADDRESS=$(cat "/tmp/test-merchant-$CHANNEL-address.txt") 73 if [ "$ADDRESS" != "$EXPECTED_ADDRESS" ] 74 then 75 exit_fail "Expected $CHANNEL address '$EXPECTED_ADDRESS'. Got: $ADDRESS" 76 fi 77 78 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 79 "http://localhost:9966/challenge/$CHALLENGE_ID/confirm" \ 80 -d '{"tan":"'"$TAN"'"}' \ 81 -w "%{http_code}" -s \ 82 -o "$LAST_RESPONSE") 83 if [ "$STATUS" != "204" ] 84 then 85 jq < "$LAST_RESPONSE" 86 exit_fail "Expected challenge confirmation to return 204 No Content. Got: $STATUS" 87 fi 88 } 89 90 91 solve_response_challenges() 92 { 93 RESPONSE=$1 94 while IFS=$'\t' read -r CHALLENGE_ID CHANNEL 95 do 96 case "$CHANNEL" in 97 email) 98 solve_challenge "$CHALLENGE_ID" "$CHANNEL" "self@example.com" 99 ;; 100 sms) 101 solve_challenge "$CHALLENGE_ID" "$CHANNEL" "+4171234" 102 ;; 103 *) 104 exit_fail "Unexpected TAN channel: $CHANNEL" 105 ;; 106 esac 107 done < <(jq -r '.challenges[] | [.challenge_id, .tan_channel] | @tsv' "$RESPONSE") 108 } 109 110 echo -n "Configuring a merchant admin instance ..." 111 112 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 113 -H 'Authorization: Bearer secret-token:super_secret' \ 114 http://localhost:9966/management/instances \ 115 -d '{"auth":{"method":"external"},"id":"admin","name":"default","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \ 116 -w "%{http_code}" -s \ 117 -o "$LAST_RESPONSE") 118 119 if [ "$STATUS" != "204" ] 120 then 121 jq < "$LAST_RESPONSE" 122 exit_fail "Expected 204 ok, instance created. got: $STATUS" 123 fi 124 125 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 126 -H 'Authorization: Bearer secret-token:super_secret' \ 127 http://localhost:9966/private/accounts \ 128 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/43?receiver-name=user43"}' \ 129 -w "%{http_code}" -s \ 130 -o "$LAST_RESPONSE") 131 132 133 if [ "$STATUS" != "200" ] 134 then 135 jq < "$LAST_RESPONSE" 136 exit_fail "Expected 200 OK. Got: $STATUS" 137 fi 138 139 echo " OK" 140 echo -n "Self-provision instance ..." 141 142 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 143 http://localhost:9966/instances \ 144 -d '{"auth":{"method":"token", "password":"pass1234"},"id":"self","name":"default","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \ 145 -w "%{http_code}" -s \ 146 -o "$LAST_RESPONSE") 147 148 if [ "$STATUS" != "202" ] 149 then 150 jq < "$LAST_RESPONSE" 151 exit_fail "Expected 202 Accepted. Got: $STATUS" 152 fi 153 echo " OK" 154 155 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE") 156 C2=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE") 157 158 echo -n "Requesting challenge $C1 " 159 160 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 161 "http://localhost:9966/challenge/$C1" \ 162 -d '{}' \ 163 -w "%{http_code}" -s \ 164 -o "$LAST_RESPONSE") 165 166 if [ "$STATUS" != "200" ] 167 then 168 jq < "$LAST_RESPONSE" 169 exit_fail "Expected 200 OK. Got: $STATUS" 170 fi 171 echo "OK" 172 173 TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1 | awk '{print $1}') 174 ADDR=$(cat /tmp/test-merchant-email-address.txt) 175 176 if [ "$ADDR" != "self@example.com" ] 177 then 178 exit_fail "Expected address 'self@example.com'. Got: $ADDR" 179 fi 180 181 echo -n "Sending challenge $C1 solution " 182 183 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 184 "http://localhost:9966/challenge/$C1/confirm" \ 185 -d '{"tan":"'"$TAN"'"}' \ 186 -w "%{http_code}" -s \ 187 -o "$LAST_RESPONSE") 188 189 if [ "$STATUS" != "204" ] 190 then 191 jq < "$LAST_RESPONSE" 192 exit_fail "Expected 204 OK. Got: $STATUS" 193 fi 194 echo "OK" 195 196 echo -n "Requesting challenge $C2 " 197 198 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 199 "http://localhost:9966/challenge/$C2" \ 200 -d '{}' \ 201 -w "%{http_code}" -s \ 202 -o "$LAST_RESPONSE") 203 204 if [ "$STATUS" != "200" ] 205 then 206 jq < "$LAST_RESPONSE" 207 exit_fail "Expected 200 OK. Got: $STATUS" 208 fi 209 echo "OK" 210 211 TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1 | awk '{print $1}') 212 213 echo -n "Sending challenge $C2 solution " 214 215 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 216 "http://localhost:9966/challenge/$C2/confirm" \ 217 -d '{"tan":"'"$TAN"'"}' \ 218 -w "%{http_code}" -s \ 219 -o "$LAST_RESPONSE") 220 221 if [ "$STATUS" != "204" ] 222 then 223 jq < "$LAST_RESPONSE" 224 exit_fail "Expected 204 OK. Got: $STATUS" 225 fi 226 echo "OK" 227 228 229 echo -n "Retrying instance creation with other body " 230 231 STATUS=$(curl \ 232 -H "Content-Type: application/json" \ 233 -H "Taler-Challenge-Ids: $C1,$C2" \ 234 -X POST \ 235 http://localhost:9966/instances \ 236 -d '{"auth":{"method":"external"},"id":"self","name":"change","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \ 237 -w "%{http_code}" -s \ 238 -o "$LAST_RESPONSE") 239 240 if [ "$STATUS" != "202" ] 241 then 242 jq < "$LAST_RESPONSE" 243 exit_fail "Expected 202 Accepted. Got: $STATUS" 244 fi 245 echo "OK" 246 247 248 249 echo -n "Retrying instance creation with original body " 250 251 STATUS=$(curl \ 252 -H "Content-Type: application/json" \ 253 -H "Taler-Challenge-Ids: $C1,$C2" \ 254 -X POST \ 255 http://localhost:9966/instances \ 256 -d '{"auth":{"method":"token", "password":"pass1234"},"id":"self","name":"default","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \ 257 -w "%{http_code}" -s \ 258 -o "$LAST_RESPONSE") 259 260 if [ "$STATUS" != "204" ] 261 then 262 jq < "$LAST_RESPONSE" 263 exit_fail "Expected 204 OK. Got: $STATUS" 264 fi 265 266 echo "OK" 267 268 269 echo -n "Unauthorized trigger MFA to add bank account " 270 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 271 -H 'Authorization: Bearer secret-token:bad_password' \ 272 http://localhost:9966/instances/self/private/accounts \ 273 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \ 274 -w "%{http_code}" -s \ 275 -o "$LAST_RESPONSE") 276 277 if [ "$STATUS" != "401" ] 278 then 279 jq < "$LAST_RESPONSE" 280 exit_fail "Expected 401 Forbidden. Got: $STATUS" 281 fi 282 283 echo " OK" 284 285 286 echo -n "Do NOT Trigger MFA to add first bank account " 287 STATUS=$(curl \ 288 -H "Content-Type: application/json" \ 289 -X POST \ 290 -H 'Authorization: Bearer secret-token:pass1234' \ 291 http://localhost:9966/instances/self/private/accounts \ 292 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \ 293 -w "%{http_code}" -s \ 294 -o "$LAST_RESPONSE") 295 296 if [ "$STATUS" != "200" ] 297 then 298 jq < "$LAST_RESPONSE" 299 exit_fail "Expected 200 OK. Got: $STATUS" 300 fi 301 302 echo " OK" 303 304 305 echo -n "Trigger MFA to add 2nd bank account with 2-FA authorization " 306 STATUS=$(curl \ 307 -H "Content-Type: application/json" \ 308 -X POST \ 309 -H 'Authorization: Bearer secret-token:pass1234' \ 310 http://localhost:9966/instances/self/private/accounts \ 311 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/45?receiver-name=user45"}' \ 312 -w "%{http_code}" -s \ 313 -o "$LAST_RESPONSE") 314 315 if [ "$STATUS" != "202" ] 316 then 317 jq < "$LAST_RESPONSE" 318 exit_fail "Expected 202 Accepted. Got: $STATUS" 319 fi 320 321 echo " OK" 322 323 324 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE") 325 326 # Delete old TANs. 327 rm /tmp/test-merchant-*-tan.txt 328 echo -n "Requesting challenge $C1 " 329 330 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 331 "http://localhost:9966/challenge/$C1" \ 332 -d '{}' \ 333 -w "%{http_code}" -s \ 334 -o "$LAST_RESPONSE") 335 336 if [ "$STATUS" != "200" ] 337 then 338 jq < "$LAST_RESPONSE" 339 exit_fail "Expected 200 OK. Got: $STATUS" 340 fi 341 echo "OK" 342 343 TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1 | awk '{print $1}') 344 ADDR=$(cat /tmp/test-merchant-email-address.txt) 345 346 if [ "$ADDR" != "self@example.com" ] 347 then 348 exit_fail "Expected address 'self@example.com'. Got: $ADDR" 349 fi 350 351 echo -n "Sending challenge $C1 solution " 352 353 STATUS=$(curl \ 354 -H "Content-Type: application/json" \ 355 -X POST \ 356 "http://localhost:9966/challenge/$C1/confirm" \ 357 -d '{"tan":"'"$TAN"'"}' \ 358 -w "%{http_code}" -s \ 359 -o "$LAST_RESPONSE") 360 361 if [ "$STATUS" != "204" ] 362 then 363 jq < "$LAST_RESPONSE" 364 exit_fail "Expected 204 OK. Got: $STATUS" 365 fi 366 echo "OK" 367 368 echo -n "Finally, add 2nd bank account " 369 STATUS=$(curl \ 370 -X POST \ 371 -H "Content-Type: application/json" \ 372 -H "Taler-Challenge-Ids: $C1" \ 373 -H 'Authorization: Bearer secret-token:pass1234' \ 374 http://localhost:9966/instances/self/private/accounts \ 375 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/45?receiver-name=user45"}' \ 376 -w "%{http_code}" -s \ 377 -o "$LAST_RESPONSE") 378 379 if [ "$STATUS" != "200" ] 380 then 381 jq < "$LAST_RESPONSE" 382 exit_fail "Expected 200 OK. Got: $STATUS" 383 fi 384 385 echo " OK" 386 387 388 echo -n "Begin forgotten password reset " 389 STATUS=$(curl \ 390 -X POST \ 391 -H "Content-Type: application/json" \ 392 http://localhost:9966/instances/self/forgot-password \ 393 -d '{"method":"token","password":"amnesia"}' \ 394 -w "%{http_code}" -s \ 395 -o "$LAST_RESPONSE") 396 397 if [ "$STATUS" != "202" ] 398 then 399 jq < "$LAST_RESPONSE" 400 exit_fail "Expected 202 Accepted. Got: $STATUS" 401 fi 402 403 echo " OK" 404 405 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE") 406 C2=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE") 407 408 echo -n "Requesting challenge $C1 " 409 410 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 411 "http://localhost:9966/challenge/$C1" \ 412 -d '{}' \ 413 -w "%{http_code}" -s \ 414 -o "$LAST_RESPONSE") 415 416 if [ "$STATUS" != "200" ] 417 then 418 jq < "$LAST_RESPONSE" 419 exit_fail "Expected 200 OK. Got: $STATUS" 420 fi 421 echo "OK" 422 423 TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1 | awk '{print $1}') 424 ADDR=$(cat /tmp/test-merchant-email-address.txt) 425 426 if [ "$ADDR" != "self@example.com" ] 427 then 428 exit_fail "Expected address 'self@example.com'. Got: $ADDR" 429 fi 430 431 echo -n "Sending challenge $C1 solution " 432 433 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 434 "http://localhost:9966/challenge/$C1/confirm" \ 435 -d '{"tan":"'"$TAN"'"}' \ 436 -w "%{http_code}" -s \ 437 -o "$LAST_RESPONSE") 438 439 if [ "$STATUS" != "204" ] 440 then 441 jq < "$LAST_RESPONSE" 442 exit_fail "Expected 204 OK. Got: $STATUS" 443 fi 444 echo "OK" 445 446 echo -n "Reject password reset with only one mandatory challenge solved " 447 STATUS=$(curl \ 448 -X POST \ 449 -H "Content-Type: application/json" \ 450 -H "Taler-Challenge-Ids: $C1" \ 451 http://localhost:9966/instances/self/forgot-password \ 452 -d '{"method":"token","password":"amnesia"}' \ 453 -w "%{http_code}" -s \ 454 -o "$LAST_RESPONSE") 455 456 if [ "$STATUS" != "202" ] || 457 [ "$(jq -r .combi_and < "$LAST_RESPONSE")" != "true" ] 458 then 459 jq < "$LAST_RESPONSE" 460 exit_fail "Expected both mandatory channels to remain required. Got: $STATUS" 461 fi 462 echo "OK" 463 464 465 echo -n "Requesting challenge $C2 " 466 467 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 468 "http://localhost:9966/challenge/$C2" \ 469 -d '{}' \ 470 -w "%{http_code}" -s \ 471 -o "$LAST_RESPONSE") 472 473 if [ "$STATUS" != "200" ] 474 then 475 jq < "$LAST_RESPONSE" 476 exit_fail "Expected 200 OK. Got: $STATUS" 477 fi 478 echo "OK" 479 480 TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1 | awk '{print $1}') 481 482 echo -n "Sending challenge $C2 solution " 483 484 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 485 "http://localhost:9966/challenge/$C2/confirm" \ 486 -d '{"tan":"'"$TAN"'"}' \ 487 -w "%{http_code}" -s \ 488 -o "$LAST_RESPONSE") 489 490 if [ "$STATUS" != "204" ] 491 then 492 jq < "$LAST_RESPONSE" 493 exit_fail "Expected 204 OK. Got: $STATUS" 494 fi 495 echo "OK" 496 497 echo -n "Complete password reset " 498 STATUS=$(curl \ 499 -X POST \ 500 -H "Content-Type: application/json" \ 501 -H "Taler-Challenge-Ids: $C1,$C2" \ 502 http://localhost:9966/instances/self/forgot-password \ 503 -d '{"method":"token","password":"amnesia"}' \ 504 -w "%{http_code}" -s \ 505 -o "$LAST_RESPONSE") 506 507 if [ "$STATUS" != "204" ] 508 then 509 jq < "$LAST_RESPONSE" 510 exit_fail "Expected 204 No content. Got: $STATUS" 511 fi 512 513 echo " OK" 514 515 516 517 518 519 echo -n "Begin password reset with login token issuance " 520 TOKEN_RESET_BODY='{"method":"token","password":"recovered","token_duration":{"d_us":600000000}}' 521 STATUS=$(curl \ 522 -X POST \ 523 -H "Content-Type: application/json" \ 524 http://localhost:9966/instances/self/forgot-password \ 525 -d "$TOKEN_RESET_BODY" \ 526 -w "%{http_code}" -s \ 527 -o "$LAST_RESPONSE") 528 529 if [ "$STATUS" != "202" ] 530 then 531 jq < "$LAST_RESPONSE" 532 exit_fail "Expected 202 Accepted. Got: $STATUS" 533 fi 534 cp "$LAST_RESPONSE" "$LAST_RESPONSE.challenges" 535 RESET_CHALLENGE_IDS=$(jq -r '[.challenges[].challenge_id] | join(",")' \ 536 < "$LAST_RESPONSE") 537 solve_response_challenges "$LAST_RESPONSE.challenges" 538 echo "OK" 539 540 541 echo -n "Reject solved challenges for a modified token duration " 542 STATUS=$(curl \ 543 -X POST \ 544 -H "Content-Type: application/json" \ 545 -H "Taler-Challenge-Ids: $RESET_CHALLENGE_IDS" \ 546 http://localhost:9966/instances/self/forgot-password \ 547 -d '{"method":"token","password":"recovered","token_duration":{"d_us":300000000}}' \ 548 -w "%{http_code}" -s \ 549 -o "$LAST_RESPONSE") 550 551 if [ "$STATUS" != "202" ] 552 then 553 jq < "$LAST_RESPONSE" 554 exit_fail "Expected changed request body to require new challenges. Got: $STATUS" 555 fi 556 echo "OK" 557 558 559 echo -n "Complete password reset and receive login token " 560 STATUS=$(curl \ 561 -X POST \ 562 -H "Content-Type: application/json" \ 563 -H "Taler-Challenge-Ids: $RESET_CHALLENGE_IDS" \ 564 http://localhost:9966/instances/self/forgot-password \ 565 -d "$TOKEN_RESET_BODY" \ 566 -w "%{http_code}" -s \ 567 -o "$LAST_RESPONSE") 568 569 if [ "$STATUS" != "200" ] 570 then 571 jq < "$LAST_RESPONSE" 572 exit_fail "Expected 200 OK with login token. Got: $STATUS" 573 fi 574 RESET_TOKEN=$(jq -er \ 575 'select(.scope == "spa" and .refreshable == true) | .access_token' \ 576 < "$LAST_RESPONSE") 577 if [ "$(jq -r .token < "$LAST_RESPONSE")" != "$RESET_TOKEN" ] 578 then 579 exit_fail "Expected token and access_token response fields to match" 580 fi 581 echo "OK" 582 583 584 echo -n "Use login token returned by password reset " 585 STATUS=$(curl \ 586 -X GET \ 587 -H "Authorization: Bearer $RESET_TOKEN" \ 588 http://localhost:9966/instances/self/private/products \ 589 -w "%{http_code}" -s \ 590 -o "$LAST_RESPONSE") 591 592 if [ "$STATUS" != "200" ] 593 then 594 jq < "$LAST_RESPONSE" 595 exit_fail "Expected reset login token to authorize SPA access. Got: $STATUS" 596 fi 597 echo "OK" 598 599 600 echo -n "Require one additional factor for a password change " 601 PASSWORD_CHANGE_BODY='{"method":"token","password":"recovered","old_password":"recovered"}' 602 STATUS=$(curl \ 603 -X POST \ 604 -H "Content-Type: application/json" \ 605 -H "Authorization: Bearer $RESET_TOKEN" \ 606 http://localhost:9966/instances/self/private/auth \ 607 -d "$PASSWORD_CHANGE_BODY" \ 608 -w "%{http_code}" -s \ 609 -o "$LAST_RESPONSE") 610 611 if [ "$STATUS" != "202" ] || 612 [ "$(jq -r .combi_and < "$LAST_RESPONSE")" != "false" ] || 613 [ "$(jq -r '.challenges | length' < "$LAST_RESPONSE")" != "2" ] 614 then 615 jq < "$LAST_RESPONSE" 616 exit_fail "Expected password change to offer either MFA channel. Got: $STATUS" 617 fi 618 619 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE") 620 CHANNEL=$(jq -r .challenges[0].tan_channel < "$LAST_RESPONSE") 621 case "$CHANNEL" in 622 email) 623 EXPECTED_ADDRESS="self@example.com" 624 ;; 625 sms) 626 EXPECTED_ADDRESS="+4171234" 627 ;; 628 *) 629 exit_fail "Unexpected TAN channel: $CHANNEL" 630 ;; 631 esac 632 solve_challenge "$C1" "$CHANNEL" "$EXPECTED_ADDRESS" 633 634 STATUS=$(curl \ 635 -X POST \ 636 -H "Content-Type: application/json" \ 637 -H "Taler-Challenge-Ids: $C1" \ 638 -H "Authorization: Bearer $RESET_TOKEN" \ 639 http://localhost:9966/instances/self/private/auth \ 640 -d "$PASSWORD_CHANGE_BODY" \ 641 -w "%{http_code}" -s \ 642 -o "$LAST_RESPONSE") 643 644 if [ "$STATUS" != "204" ] 645 then 646 jq < "$LAST_RESPONSE" 647 exit_fail "Expected one solved challenge to authorize password change. Got: $STATUS" 648 fi 649 echo "OK" 650 651 652 echo -n "Self-provision second instance for instance-binding test " 653 OTHER_INSTANCE_BODY='{"auth":{"method":"token","password":"recovered"},"id":"other","name":"other","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us":50000000},"default_pay_delay":{"d_us":60000000}}' 654 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 655 http://localhost:9966/instances \ 656 -d "$OTHER_INSTANCE_BODY" \ 657 -w "%{http_code}" -s \ 658 -o "$LAST_RESPONSE") 659 660 if [ "$STATUS" != "202" ] 661 then 662 jq < "$LAST_RESPONSE" 663 exit_fail "Expected 202 Accepted. Got: $STATUS" 664 fi 665 cp "$LAST_RESPONSE" "$LAST_RESPONSE.challenges" 666 OTHER_CREATE_CHALLENGE_IDS=$(jq -r \ 667 '[.challenges[].challenge_id] | join(",")' < "$LAST_RESPONSE") 668 solve_response_challenges "$LAST_RESPONSE.challenges" 669 670 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 671 -H "Taler-Challenge-Ids: $OTHER_CREATE_CHALLENGE_IDS" \ 672 http://localhost:9966/instances \ 673 -d "$OTHER_INSTANCE_BODY" \ 674 -w "%{http_code}" -s \ 675 -o "$LAST_RESPONSE") 676 if [ "$STATUS" != "204" ] 677 then 678 jq < "$LAST_RESPONSE" 679 exit_fail "Expected 204 No Content. Got: $STATUS" 680 fi 681 echo "OK" 682 683 684 echo -n "Solve token-creation challenge for first instance " 685 TOKEN_BODY='{"scope":"spa","duration":{"d_us":600000000},"refreshable":true}' 686 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 687 -H 'Authorization: Bearer secret-token:recovered' \ 688 http://localhost:9966/instances/self/private/token \ 689 -d "$TOKEN_BODY" \ 690 -w "%{http_code}" -s \ 691 -o "$LAST_RESPONSE") 692 if [ "$STATUS" != "202" ] 693 then 694 jq < "$LAST_RESPONSE" 695 exit_fail "Expected 202 Accepted. Got: $STATUS" 696 fi 697 INSTANCE_CHALLENGE_ID=$(jq -r '.challenges[0].challenge_id' < "$LAST_RESPONSE") 698 INSTANCE_CHALLENGE_CHANNEL=$(jq -r '.challenges[0].tan_channel' < "$LAST_RESPONSE") 699 case "$INSTANCE_CHALLENGE_CHANNEL" in 700 email) 701 INSTANCE_CHALLENGE_ADDRESS=self@example.com 702 ;; 703 sms) 704 INSTANCE_CHALLENGE_ADDRESS=+4171234 705 ;; 706 *) 707 exit_fail "Unexpected TAN channel: $INSTANCE_CHALLENGE_CHANNEL" 708 ;; 709 esac 710 solve_challenge "$INSTANCE_CHALLENGE_ID" \ 711 "$INSTANCE_CHALLENGE_CHANNEL" \ 712 "$INSTANCE_CHALLENGE_ADDRESS" 713 echo "OK" 714 715 716 echo -n "Reject solved challenge at a different instance " 717 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 718 -H 'Authorization: Bearer secret-token:recovered' \ 719 -H "Taler-Challenge-Ids: $INSTANCE_CHALLENGE_ID" \ 720 http://localhost:9966/instances/other/private/token \ 721 -d "$TOKEN_BODY" \ 722 -w "%{http_code}" -s \ 723 -o "$LAST_RESPONSE") 724 if [ "$STATUS" != "202" ] 725 then 726 jq < "$LAST_RESPONSE" 727 exit_fail "Expected foreign challenge to be rejected with 202. Got: $STATUS" 728 fi 729 if jq -e --arg cid "$INSTANCE_CHALLENGE_ID" \ 730 '.challenges[] | select(.challenge_id == $cid)' "$LAST_RESPONSE" > /dev/null 731 then 732 exit_fail "Foreign challenge was returned as applicable to the other instance" 733 fi 734 echo "OK" 735 736 737 echo -n "Accept solved challenge at its original instance " 738 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 739 -H 'Authorization: Bearer secret-token:recovered' \ 740 -H "Taler-Challenge-Ids: $INSTANCE_CHALLENGE_ID" \ 741 http://localhost:9966/instances/self/private/token \ 742 -d "$TOKEN_BODY" \ 743 -w "%{http_code}" -s \ 744 -o "$LAST_RESPONSE") 745 if [ "$STATUS" != "200" ] 746 then 747 jq < "$LAST_RESPONSE" 748 exit_fail "Expected same-instance challenge to authorize request. Got: $STATUS" 749 fi 750 echo "OK" 751 752 753 echo "TEST PASSED" 754 755 exit 0