|
11 | 11 | from pytest_mh.utils.fs import LinuxFileSystem |
12 | 12 |
|
13 | 13 | from ..misc.errors import ExpectScriptError |
| 14 | +from ..misc.globals import test_venv_bin |
14 | 15 | from .idp import IdpAuthenticationUtils |
15 | 16 |
|
16 | 17 | __all__ = [ |
@@ -639,6 +640,189 @@ def passkey( |
639 | 640 | ) |
640 | 641 | return rc == 0 |
641 | 642 |
|
| 643 | + def vfido_passkey_with_output( |
| 644 | + self, |
| 645 | + username: str, |
| 646 | + *, |
| 647 | + pin: str | int | None = None, |
| 648 | + interactive_prompt: str = "Insert your passkey device, then press ENTER.", |
| 649 | + touch_prompt: str = "Please touch the device.", |
| 650 | + command: str = "exit 0", |
| 651 | + auth_method: PasskeyAuthenticationUseCases = PasskeyAuthenticationUseCases.PASSKEY_PIN, |
| 652 | + ) -> tuple[int, int, str, str]: |
| 653 | + """ |
| 654 | + Call ``su - $username`` and authenticate the user with vfido passkey |
| 655 | +
|
| 656 | + :param username: Username |
| 657 | + :type username: str |
| 658 | + :param pin: Passkey PIN, defaults to None |
| 659 | + :type pin: str | int | None |
| 660 | + :param interactive_prompt: Interactive prompt, defaults to "Insert your passkey device, then press ENTER." |
| 661 | + :type interactive_prompt: str |
| 662 | + :param touch_prompt: Touch prompt, defaults to "Please touch the device." |
| 663 | + :type touch_prompt: str |
| 664 | + :param command: Command executed after user is authenticated, defaults to "exit 0" |
| 665 | + :type command: str |
| 666 | + :param auth_method: Authentication method, defaults to PasskeyAuthenticationUseCases.PASSKEY_PIN |
| 667 | + :type auth_method: PasskeyAuthenticationUseCases |
| 668 | + :return: Tuple containing [return code, command code, stdout, stderr]. |
| 669 | + :rtype: Tuple[int, int, str, str] |
| 670 | + """ |
| 671 | + |
| 672 | + match auth_method: |
| 673 | + case PasskeyAuthenticationUseCases.PASSKEY_PIN | \ |
| 674 | + PasskeyAuthenticationUseCases.PASSKEY_PIN_AND_PROMPTS: |
| 675 | + if pin is None: |
| 676 | + raise ValueError(f"PIN is required for {str(auth_method)}") |
| 677 | + case PasskeyAuthenticationUseCases.PASSKEY_PROMPTS_NO_PIN | \ |
| 678 | + PasskeyAuthenticationUseCases.PASSKEY_FALLBACK_TO_PASSWORD | \ |
| 679 | + PasskeyAuthenticationUseCases.PASSKEY_NO_PIN_NO_PROMPTS: |
| 680 | + if pin is not None: |
| 681 | + raise ValueError(f"PIN is not required for {str(auth_method)}") |
| 682 | + |
| 683 | + run_su = self.fs.mktmp( |
| 684 | + rf""" |
| 685 | + #!/bin/bash |
| 686 | + set -ex |
| 687 | + su --shell /bin/sh nobody -c "su - '{username}' -c '{command}'" |
| 688 | + """, |
| 689 | + mode="a=rx", |
| 690 | + ) |
| 691 | + |
| 692 | + result = self.host.conn.expect( |
| 693 | + rf""" |
| 694 | + # Disable debug output |
| 695 | + # exp_internal 0 |
| 696 | +
|
| 697 | + proc exitmsg {{ msg code }} {{ |
| 698 | + # Close spawned program, if we are in the prompt |
| 699 | + catch close |
| 700 | +
|
| 701 | + # Wait for the exit code |
| 702 | + lassign [wait] pid spawnid os_error_flag rc |
| 703 | +
|
| 704 | + puts "" |
| 705 | + puts "expect result: $msg" |
| 706 | + puts "expect exit code: $code" |
| 707 | + puts "expect spawn exit code: $rc" |
| 708 | + exit $code |
| 709 | + }} |
| 710 | +
|
| 711 | + # It takes some time to get authentication failure |
| 712 | + set timeout {DEFAULT_AUTHENTICATION_TIMEOUT} |
| 713 | + set prompt "\n.*\[#\$>\] $" |
| 714 | + set command "{command}" |
| 715 | + set auth_method "{auth_method}" |
| 716 | +
|
| 717 | + spawn "{run_su}" |
| 718 | + set ID_su $spawn_id |
| 719 | +
|
| 720 | + # If the authentication method set without entering the PIN, it will directly ask |
| 721 | + # prompt, if we set prompting options in sssd.conf it will ask interactive and touch prompt. |
| 722 | +
|
| 723 | + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_NO_PIN_NO_PROMPTS}") |
| 724 | + || ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PROMPTS_NO_PIN}") }} {{ |
| 725 | + expect {{ |
| 726 | + -i $ID_su -re "{interactive_prompt}*" {{ send -i $ID_su "\n" }} |
| 727 | + -i $ID_su timeout {{exitmsg "Unexpected output" 201 }} |
| 728 | + -i $ID_su eof {{exitmsg "Unexpected end of file" 202 }} |
| 729 | + }} |
| 730 | + # If prompt options are set |
| 731 | + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PROMPTS_NO_PIN}") }} {{ |
| 732 | + expect {{ |
| 733 | + -i $ID_su -re "{touch_prompt}*" {{ }} |
| 734 | + -i $ID_su timeout {{exitmsg "Unexpected output" 203 }} |
| 735 | + -i $ID_su eof {{exitmsg "Unexpected end of file" 204 }} |
| 736 | + }} |
| 737 | + }} |
| 738 | + }} |
| 739 | +
|
| 740 | + # If authentication method set with PIN, after interactive prompt always ask to Enter the PIN. |
| 741 | + # If PIN is correct with prompt options in sssd.conf it will ask interactive and touch prompt. |
| 742 | + # If we press Enter key for PIN, sssd will fallback to next auth method, here it will ask |
| 743 | + # for Password. |
| 744 | +
|
| 745 | + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PIN}") |
| 746 | + || ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PIN_AND_PROMPTS}") |
| 747 | + || ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_FALLBACK_TO_PASSWORD}")}} {{ |
| 748 | + expect {{ |
| 749 | + -i $ID_su -re "{interactive_prompt}*" {{ send -i $ID_su "\n" }} |
| 750 | + -i $ID_su timeout {{exitmsg "Unexpected output" 205 }} |
| 751 | + -i $ID_su eof {{exitmsg "Unexpected end of file" 206 }} |
| 752 | + }} |
| 753 | + expect {{ |
| 754 | + -i $ID_su -re "Enter PIN:*" {{send -i $ID_su "{pin}\r"}} |
| 755 | + -i $ID_su timeout {{exitmsg "Unexpected output" 207}} |
| 756 | + -i $ID_su eof {{exitmsg "Unexpected end of file" 208}} |
| 757 | + }} |
| 758 | + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_FALLBACK_TO_PASSWORD}") }} {{ |
| 759 | + expect {{ |
| 760 | + -i $ID_su -re "Password:*" {{send -i $ID_su "Secret123\r"}} |
| 761 | + -i $ID_su timeout {{exitmsg "Unexpected output" 209}} |
| 762 | + -i $ID_su eof {{exitmsg "Unexpected end of file" 210}} |
| 763 | + }} |
| 764 | + }} |
| 765 | + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PIN_AND_PROMPTS}") }} {{ |
| 766 | + expect {{ |
| 767 | + -i $ID_su -re "{touch_prompt}*" {{ }} |
| 768 | + -i $ID_su timeout {{exitmsg "Unexpected output" 211 }} |
| 769 | + -i $ID_su eof {{exitmsg "Unexpected end of file" 212 }} |
| 770 | + }} |
| 771 | + }} |
| 772 | + }} |
| 773 | +
|
| 774 | + # Now simulate touch on vfido device |
| 775 | + spawn {test_venv_bin}/vfido_touch |
| 776 | + set ID_touch $spawn_id |
| 777 | +
|
| 778 | + expect {{ |
| 779 | + -i $ID_su -re "Authentication failure" {{exitmsg "Authentication failure" 1}} |
| 780 | + -i $ID_su eof {{exitmsg "Passkey authentication successful" 0}} |
| 781 | + -i $ID_su timeout {{exitmsg "Unexpected output" 213}} |
| 782 | + }} |
| 783 | +
|
| 784 | + expect -i $ID_touch eof |
| 785 | +
|
| 786 | + exitmsg "Unexpected code path" 220 |
| 787 | + """, |
| 788 | + verbose=False, |
| 789 | + ) |
| 790 | + |
| 791 | + if result.rc > 200: |
| 792 | + raise ExpectScriptError(result.rc) |
| 793 | + |
| 794 | + expect_data = result.stdout_lines[-3:] |
| 795 | + |
| 796 | + # Get command exit code. |
| 797 | + cmdrc = int(expect_data[2].split(":")[1].strip()) |
| 798 | + |
| 799 | + # Alter stdout, first line is spawned command, the last three are our expect output. |
| 800 | + stdout = "\n".join(result.stdout_lines[1:-3]) |
| 801 | + |
| 802 | + return result.rc, cmdrc, stdout, result.stderr |
| 803 | + |
| 804 | + def vfido_passkey( |
| 805 | + self, |
| 806 | + username: str, |
| 807 | + *, |
| 808 | + pin: str | int | None = None, |
| 809 | + command: str = "exit 0", |
| 810 | + ) -> bool: |
| 811 | + """ |
| 812 | + Call ``su - $username`` and authenticate the user with passkey. |
| 813 | +
|
| 814 | + :param username: Username |
| 815 | + :type username: str |
| 816 | + :param pin: Passkey PIN. |
| 817 | + :type pin: str | int | None |
| 818 | + :param command: Command executed after user is authenticated, defaults to "exit 0" |
| 819 | + :type command: str |
| 820 | + :return: True if authentication was successful, False otherwise. |
| 821 | + :rtype: bool |
| 822 | + """ |
| 823 | + rc, _, _, _ = self.vfido_passkey_with_output(username=username, pin=pin, command=command) |
| 824 | + return rc == 0 |
| 825 | + |
642 | 826 |
|
643 | 827 | class SSHAuthenticationUtils(MultihostUtility[MultihostHost]): |
644 | 828 | """ |
|
0 commit comments