1111from pytest_mh .utils .fs import LinuxFileSystem
1212
1313from ..misc .errors import ExpectScriptError
14+ from ..misc .globals import test_venv_bin
1415from .idp import IdpAuthenticationUtils
1516
1617__all__ = [
@@ -412,8 +413,28 @@ def password_expired(self, username: str, password: str, new_password: str) -> b
412413
413414 def passkey_with_output (
414415 self ,
415- username : str ,
416+ ** kwargs ,
417+ ) -> tuple [int , int , str , str ]:
418+ """wrapper for *_passkey_with_output methods"""
419+ if "virt_type" in kwargs and kwargs ["virt_type" ] == "vfido" :
420+ return self .vfido_passkey_with_output (** kwargs )
421+ else :
422+ return self .umockdev_passkey_with_output (** kwargs )
423+
424+ def passkey (
425+ self ,
426+ ** kwargs ,
427+ ) -> bool :
428+ """wrapper for *_passkey methods"""
429+ if "virt_type" in kwargs and kwargs ["virt_type" ] == "vfido" :
430+ return self .vfido_passkey (** kwargs )
431+ else :
432+ return self .umockdev_passkey (** kwargs )
433+
434+ def umockdev_passkey_with_output (
435+ self ,
416436 * ,
437+ username : str ,
417438 device : str ,
418439 ioctl : str ,
419440 script : str ,
@@ -604,10 +625,10 @@ def passkey_with_output(
604625
605626 return result .rc , cmdrc , stdout , result .stderr
606627
607- def passkey (
628+ def umockdev_passkey (
608629 self ,
609- username : str ,
610630 * ,
631+ username : str ,
611632 device : str ,
612633 ioctl : str ,
613634 script : str ,
@@ -634,11 +655,195 @@ def passkey(
634655 :return: True if authentication was successful, False otherwise.
635656 :rtype: bool
636657 """
637- rc , _ , _ , _ = self .passkey_with_output (
658+ rc , _ , _ , _ = self .umockdev_passkey_with_output (
638659 username = username , pin = pin , device = device , ioctl = ioctl , script = script , command = command
639660 )
640661 return rc == 0
641662
663+ def vfido_passkey_with_output (
664+ self ,
665+ * ,
666+ username : str ,
667+ pin : str | int | None ,
668+ interactive_prompt : str = "Insert your passkey device, then press ENTER." ,
669+ touch_prompt : str = "Please touch the device." ,
670+ command : str = "exit 0" ,
671+ auth_method : PasskeyAuthenticationUseCases = PasskeyAuthenticationUseCases .PASSKEY_PIN ,
672+ ) -> tuple [int , int , str , str ]:
673+ """
674+ Call ``su - $username`` and authenticate the user with vfido passkey
675+
676+ :param username: Username
677+ :type username: str
678+ :param pin: Passkey PIN, defaults to None
679+ :type pin: str | int | None
680+ :param interactive_prompt: Interactive prompt, defaults to "Insert your passkey device, then press ENTER."
681+ :type interactive_prompt: str
682+ :param touch_prompt: Touch prompt, defaults to "Please touch the device."
683+ :type touch_prompt: str
684+ :param command: Command executed after user is authenticated, defaults to "exit 0"
685+ :type command: str
686+ :param auth_method: Authentication method, defaults to PasskeyAuthenticationUseCases.PASSKEY_PIN
687+ :type auth_method: PasskeyAuthenticationUseCases
688+ :return: Tuple containing [return code, command code, stdout, stderr].
689+ :rtype: Tuple[int, int, str, str]
690+ """
691+
692+ match auth_method :
693+ case PasskeyAuthenticationUseCases .PASSKEY_PIN | PasskeyAuthenticationUseCases .PASSKEY_PIN_AND_PROMPTS :
694+ if pin is None :
695+ raise ValueError (f"PIN is required for { str (auth_method )} " )
696+ case (
697+ PasskeyAuthenticationUseCases .PASSKEY_PROMPTS_NO_PIN
698+ | PasskeyAuthenticationUseCases .PASSKEY_FALLBACK_TO_PASSWORD
699+ | PasskeyAuthenticationUseCases .PASSKEY_NO_PIN_NO_PROMPTS
700+ ):
701+ if pin is not None :
702+ raise ValueError (f"PIN is not required for { str (auth_method )} " )
703+
704+ run_su = self .fs .mktmp (
705+ rf"""
706+ #!/bin/bash
707+ set -ex
708+ su --shell /bin/sh nobody -c "su - '{ username } ' -c '{ command } '"
709+ """ ,
710+ mode = "a=rx" ,
711+ )
712+
713+ result = self .host .conn .expect (
714+ rf"""
715+ # Disable debug output
716+ # exp_internal 0
717+
718+ proc exitmsg {{ msg code }} {{
719+ # Close spawned program, if we are in the prompt
720+ catch close
721+
722+ # Wait for the exit code
723+ lassign [wait] pid spawnid os_error_flag rc
724+
725+ puts ""
726+ puts "expect result: $msg"
727+ puts "expect exit code: $code"
728+ puts "expect spawn exit code: $rc"
729+ exit $code
730+ }}
731+
732+ # It takes some time to get authentication failure
733+ set timeout { DEFAULT_AUTHENTICATION_TIMEOUT }
734+ set prompt "\n.*\[#\$>\] $"
735+ set command "{ command } "
736+ set auth_method "{ auth_method } "
737+
738+ spawn "{ run_su } "
739+ set ID_su $spawn_id
740+
741+ # If the authentication method set without entering the PIN, it will directly ask
742+ # prompt, if we set prompting options in sssd.conf it will ask interactive and touch prompt.
743+
744+ if {{ ($auth_method eq "{ PasskeyAuthenticationUseCases .PASSKEY_NO_PIN_NO_PROMPTS } ")
745+ || ($auth_method eq "{ PasskeyAuthenticationUseCases .PASSKEY_PROMPTS_NO_PIN } ") }} {{
746+ expect {{
747+ -i $ID_su -re "{ interactive_prompt } *" {{ send -i $ID_su "\n" }}
748+ -i $ID_su timeout {{exitmsg "Unexpected output" 201 }}
749+ -i $ID_su eof {{exitmsg "Unexpected end of file" 202 }}
750+ }}
751+ # If prompt options are set
752+ if {{ ($auth_method eq "{ PasskeyAuthenticationUseCases .PASSKEY_PROMPTS_NO_PIN } ") }} {{
753+ expect {{
754+ -i $ID_su -re "{ touch_prompt } *" {{ }}
755+ -i $ID_su timeout {{exitmsg "Unexpected output" 203 }}
756+ -i $ID_su eof {{exitmsg "Unexpected end of file" 204 }}
757+ }}
758+ }}
759+ }}
760+
761+ # If authentication method set with PIN, after interactive prompt always ask to Enter the PIN.
762+ # If PIN is correct with prompt options in sssd.conf it will ask interactive and touch prompt.
763+ # If we press Enter key for PIN, sssd will fallback to next auth method, here it will ask
764+ # for Password.
765+
766+ if {{ ($auth_method eq "{ PasskeyAuthenticationUseCases .PASSKEY_PIN } ")
767+ || ($auth_method eq "{ PasskeyAuthenticationUseCases .PASSKEY_PIN_AND_PROMPTS } ")
768+ || ($auth_method eq "{ PasskeyAuthenticationUseCases .PASSKEY_FALLBACK_TO_PASSWORD } ")}} {{
769+ expect {{
770+ -i $ID_su -re "{ interactive_prompt } *" {{ send -i $ID_su "\n" }}
771+ -i $ID_su timeout {{exitmsg "Unexpected output" 205 }}
772+ -i $ID_su eof {{exitmsg "Unexpected end of file" 206 }}
773+ }}
774+ expect {{
775+ -i $ID_su -re "Enter PIN:*" {{send -i $ID_su "{ pin } \r"}}
776+ -i $ID_su timeout {{exitmsg "Unexpected output" 207}}
777+ -i $ID_su eof {{exitmsg "Unexpected end of file" 208}}
778+ }}
779+ if {{ ($auth_method eq "{ PasskeyAuthenticationUseCases .PASSKEY_FALLBACK_TO_PASSWORD } ") }} {{
780+ expect {{
781+ -i $ID_su -re "Password:*" {{send -i $ID_su "Secret123\r"}}
782+ -i $ID_su timeout {{exitmsg "Unexpected output" 209}}
783+ -i $ID_su eof {{exitmsg "Unexpected end of file" 210}}
784+ }}
785+ }}
786+ if {{ ($auth_method eq "{ PasskeyAuthenticationUseCases .PASSKEY_PIN_AND_PROMPTS } ") }} {{
787+ expect {{
788+ -i $ID_su -re "{ touch_prompt } *" {{ }}
789+ -i $ID_su timeout {{exitmsg "Unexpected output" 211 }}
790+ -i $ID_su eof {{exitmsg "Unexpected end of file" 212 }}
791+ }}
792+ }}
793+ }}
794+
795+ # Now simulate touch on vfido device
796+ spawn { test_venv_bin } /vfido_touch
797+ set ID_touch $spawn_id
798+
799+ expect {{
800+ -i $ID_su -re "Authentication failure" {{exitmsg "Authentication failure" 1}}
801+ -i $ID_su eof {{exitmsg "Passkey authentication successful" 0}}
802+ -i $ID_su timeout {{exitmsg "Unexpected output" 213}}
803+ }}
804+
805+ expect -i $ID_touch eof
806+
807+ exitmsg "Unexpected code path" 220
808+ """ ,
809+ verbose = False ,
810+ )
811+
812+ if result .rc > 200 :
813+ raise ExpectScriptError (result .rc )
814+
815+ expect_data = result .stdout_lines [- 3 :]
816+
817+ # Get command exit code.
818+ cmdrc = int (expect_data [2 ].split (":" )[1 ].strip ())
819+
820+ # Alter stdout, first line is spawned command, the last three are our expect output.
821+ stdout = "\n " .join (result .stdout_lines [1 :- 3 ])
822+
823+ return result .rc , cmdrc , stdout , result .stderr
824+
825+ def vfido_passkey (
826+ self ,
827+ * ,
828+ username : str ,
829+ pin : str | int | None = None ,
830+ command : str = "exit 0" ,
831+ ) -> bool :
832+ """
833+ Call ``su - $username`` and authenticate the user with passkey.
834+
835+ :param username: Username
836+ :type username: str
837+ :param pin: Passkey PIN.
838+ :type pin: str | int | None
839+ :param command: Command executed after user is authenticated, defaults to "exit 0"
840+ :type command: str
841+ :return: True if authentication was successful, False otherwise.
842+ :rtype: bool
843+ """
844+ rc , _ , _ , _ = self .vfido_passkey_with_output (username = username , pin = pin , command = command )
845+ return rc == 0
846+
642847
643848class SSHAuthenticationUtils (MultihostUtility [MultihostHost ]):
644849 """
0 commit comments