From f12db0eccef3afe2aad7f3a3dcf2da6b95f8e44e Mon Sep 17 00:00:00 2001 From: Madhuri Upadhye Date: Sat, 18 May 2024 16:41:56 +0530 Subject: [PATCH] authentication: Add different auth methos for passkey Add following authentication methods 1. Passkey with pin (default) 2. Passkey with pin and interactive and touch prompts 3. Passkey wihout pin with both prompts 4. Passkey auth when we press enter for pin and it goes to next auth method as a password Signed-off-by: Madhuri Upadhye --- sssd_test_framework/utils/authentication.py | 116 +++++++++++++++++--- 1 file changed, 103 insertions(+), 13 deletions(-) diff --git a/sssd_test_framework/utils/authentication.py b/sssd_test_framework/utils/authentication.py index 9007485..c7f1ac0 100644 --- a/sssd_test_framework/utils/authentication.py +++ b/sssd_test_framework/utils/authentication.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import datetime +from enum import Enum from typing import Any from pytest_mh import MultihostHost, MultihostUtility @@ -23,6 +24,18 @@ """Default timeout for authentication failure.""" +class PasskeyAuthenticationUseCases(Enum): + """ + Authentication methods for passkey authentication. + """ + + PASSKEY_PIN = 0 + PASSKEY_PIN_AND_PROMPTS = 1 + PASSKEY_PROMPTS_NO_PIN = 2 + PASSKEY_NO_PIN_NO_PROMPTS = 3 + PASSKEY_FALLBACK_TO_PASSWORD = 4 + + class AuthenticationUtils(MultihostUtility[MultihostHost]): """ Methods for testing various authentication and authorization mechanisms. @@ -341,23 +354,39 @@ def password_expired(self, username: str, password: str, new_password: str) -> b return result.rc == 0 def passkey_with_output( - self, username: str, *, pin: str | int, device: str, ioctl: str, script: str, command: str = "exit 0" + self, + username: str, + *, + device: str, + ioctl: str, + script: str, + pin: str | int | None = None, + interactive_prompt: str = "Insert your passkey device, then press ENTER.", + touch_prompt: str = "Please touch the device.", + command: str = "exit 0", + auth_method: PasskeyAuthenticationUseCases = PasskeyAuthenticationUseCases.PASSKEY_PIN, ) -> tuple[int, int, str, str]: """ Call ``su - $username`` and authenticate the user with passkey. :param username: Username :type username: str - :param pin: Passkey PIN. - :type pin: str | int :param device: Path to local umockdev device file. :type device: str :param ioctl: Path to local umockdev ioctl file. :type ioctl: str :param script: Path to local umockdev script file :type script: str + :param pin: Passkey PIN, defaults to None + :type pin: str | int | None + :param interactive_prompt: Interactive prompt, defaults to "Insert your passkey device, then press ENTER." + :type interactive_prompt: str + :param touch_prompt: Touch prompt, defaults to "Can you touch this device" + :type touch_prompt: str :param command: Command executed after user is authenticated, defaults to "exit 0" :type command: str + :param auth_method: Authentication method, defaults to PasskeyAuthenticationUseCases.PASSKEY_WITH_PIN + :type auth_method: PasskeyAuthenticationUseCases :return: Tuple containing [return code, command code, stdout, stderr]. :rtype: Tuple[int, int, str, str] """ @@ -368,6 +397,18 @@ def passkey_with_output( ioctl_path = self.fs.upload_to_tmp(ioctl, mode="a=r") script_path = self.fs.upload_to_tmp(script, mode="a=r") + match auth_method: + case (PasskeyAuthenticationUseCases.PASSKEY_PIN, PasskeyAuthenticationUseCases.PASSKEY_PIN_AND_PROMPTS): + if pin is None: + raise ValueError(f"PIN is required for {str(auth_method)}") + case ( + PasskeyAuthenticationUseCases.PASSKEY_PROMPTS_NO_PIN, + PasskeyAuthenticationUseCases.PASSKEY_FALLBACK_TO_PASSWORD, + PasskeyAuthenticationUseCases.PASSKEY_NO_PIN_NO_PROMPTS, + ): + if pin is not None: + raise ValueError(f"PIN is not required for {str(auth_method)}") + run_su = self.fs.mktmp( rf""" #!/bin/bash @@ -422,19 +463,62 @@ def passkey_with_output( set timeout {DEFAULT_AUTHENTICATION_TIMEOUT} set prompt "\n.*\[#\$>\] $" set command "{command}" + set auth_method "{auth_method}" spawn "{playback_umockdev}" - expect {{ - "Insert your passkey device, then press ENTER*" {{send -- "\n"}} - timeout {{exitmsg "Unexpected output" 201}} - eof {{exitmsg "Unexpected end of file" 202}} + # If the authentication method set without entering the PIN, it will directly ask + # prompt, if we set prompting options in sssd.conf it will ask interactive and touch prompt. + + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_NO_PIN_NO_PROMPTS}") + || ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PROMPTS_NO_PIN}") }} {{ + expect {{ + "{interactive_prompt}*" {{ send -- "\n" }} + timeout {{exitmsg "Unexpected output" 201 }} + eof {{exitmsg "Unexpected end of file" 202 }} + }} + # If prompt options are set + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PROMPTS_NO_PIN}") }} {{ + expect {{ + "{touch_prompt}*" {{ send -- "\n" }} + timeout {{exitmsg "Unexpected output" 201 }} + eof {{exitmsg "Unexpected end of file" 202 }} + }} + }} }} - expect {{ - "Enter PIN:*" {{send -- "{pin}\r"}} - timeout {{exitmsg "Unexpected output" 201}} - eof {{exitmsg "Unexpected end of file" 202}} + # If authentication method set with PIN, after interactive prompt always ask to Enter the PIN. + # If PIN is correct with prompt options in sssd.conf it will ask interactive and touch prompt. + # If we press Enter key for PIN, sssd will fallback to next auth method, here it will ask + # for Password. + + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PIN}") + || ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PIN_AND_PROMPTS}") + || ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_FALLBACK_TO_PASSWORD}")}} {{ + expect {{ + "{interactive_prompt}*" {{ send -- "\n" }} + timeout {{exitmsg "Unexpected output" 201 }} + eof {{exitmsg "Unexpected end of file" 202 }} + }} + expect {{ + "Enter PIN:*" {{send -- "{pin}\r"}} + timeout {{exitmsg "Unexpected output" 201}} + eof {{exitmsg "Unexpected end of file" 202}} + }} + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_FALLBACK_TO_PASSWORD}") }} {{ + expect {{ + "Password:*" {{send -- "Secret123\r"}} + timeout {{exitmsg "Unexpected output" 201}} + eof {{exitmsg "Unexpected end of file" 202}} + }} + }} + if {{ ($auth_method eq "{PasskeyAuthenticationUseCases.PASSKEY_PIN_AND_PROMPTS}") }} {{ + expect {{ + "{touch_prompt}*" {{ send -- "\n" }} + timeout {{exitmsg "Unexpected output" 201 }} + eof {{exitmsg "Unexpected end of file" 202 }} + }} + }} }} expect {{ @@ -444,7 +528,6 @@ def passkey_with_output( }} exitmsg "Unexpected code path" 203 - """, verbose=False, ) @@ -465,7 +548,14 @@ def passkey_with_output( return result.rc, cmdrc, stdout, result.stderr def passkey( - self, username: str, *, pin: str | int, device: str, ioctl: str, script: str, command: str = "exit 0" + self, + username: str, + *, + device: str, + ioctl: str, + script: str, + pin: str | int | None = None, + command: str = "exit 0", ) -> bool: """ Call ``su - $username`` and authenticate the user with passkey.