From: Iker Pedrosa Date: Mon, 3 Mar 2025 08:29:57 +0000 (+0100) Subject: tests/: implement binding for `getent shadow $name` X-Git-Tag: 4.18.0-rc1~95 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fe33ae50f7777d9477184ee3f03ba1a35308b482;p=thirdparty%2Fshadow.git tests/: implement binding for `getent shadow $name` Provide a way for the system framework to run `getent shadow $name` and check its output in a meaningful way. Signed-off-by: Iker Pedrosa Reviewed-by: Dan Lavu --- diff --git a/tests/system/framework/utils/tools.py b/tests/system/framework/utils/tools.py index 64a298c20..0d7a3f53f 100644 --- a/tests/system/framework/utils/tools.py +++ b/tests/system/framework/utils/tools.py @@ -14,6 +14,7 @@ __all__ = [ "UnixGroup", "IdEntry", "PasswdEntry", + "ShadowEntry", "GroupEntry", "InitgroupsEntry", "LinuxToolsUtils", @@ -222,6 +223,98 @@ class PasswdEntry(object): return cls.FromDict(result[0]) +class ShadowEntry(object): + """ + Result of ``getent shadow`` + """ + + def __init__( + self, + name: str, + password: str, + last_changed: int, + min_days: int, + max_days: int, + warn_days: int, + inactivity_days: int, + expiration_date: int, + ) -> None: + self.name: str | None = name + """ + User name. + """ + + self.password: str | None = password + """ + User password. + """ + + self.last_changed: int = last_changed + """ + Last password change. + """ + + self.min_days: int = min_days + """ + Minimum number of days before a password change is allowed. + """ + + self.max_days: int = max_days + """ + Maximum number of days a password is valid. + """ + + self.warn_days: int = warn_days + """ + Number of days to warn the user before the password expires. + """ + + self.inactivity_days: int | None = inactivity_days + """ + Number of days after a password expires before the account is disabled. + """ + + self.expiration_date: int | None = expiration_date + """ + The account expiration date, expressed as the number of days since 1970-01-01 00:00:00 UTC. + """ + + def __str__(self) -> str: + return ( + f"({self.name}:{self.password}:{self.last_changed}:" + f"{self.min_days}:{self.max_days}:{self.warn_days}:" + f"{self.inactivity_days}:{self.expiration_date}:)" + ) + + def __repr__(self) -> str: + return str(self) + + @classmethod + def FromDict(cls, d: dict[str, Any]) -> ShadowEntry: + return cls( + name=d.get("username", None), + password=d.get("password", None), + last_changed=d.get("last_changed", None), + min_days=d.get("minimum", None), + max_days=d.get("maximum", None), + warn_days=d.get("warn", None), + inactivity_days=d.get("inactive", None), + expiration_date=d.get("expire", None), + ) + + @classmethod + def FromOutput(cls, stdout: str) -> ShadowEntry: + result = jc.parse("shadow", stdout) + + if not isinstance(result, list): + raise TypeError(f"Unexpected type: {type(result)}, expecting list") + + if len(result) != 1: + raise ValueError("More then one entry was returned") + + return cls.FromDict(result[0]) + + class GroupEntry(object): """ Result of ``getent group`` @@ -435,6 +528,19 @@ class GetentUtils(MultihostUtility[MultihostHost]): """ return self.__exec(PasswdEntry, "passwd", name, service) + def shadow(self, name: str | int, *, service: str | None = None) -> ShadowEntry | None: + """ + Call ``getent shadow $name`` + + :param name: User name or id. + :type name: str | int + :param service: Service used, defaults to None + :type service: str | None + :return: shadow data, None if not found + :rtype: ShadowEntry | None + """ + return self.__exec(ShadowEntry, "shadow", name, service) + def group(self, name: str | int, *, service: str | None = None) -> GroupEntry | None: """ Call ``getent group $name``