]> git.ipfire.org Git - thirdparty/shadow.git/commitdiff
tests/: implement binding for `getent shadow $name`
authorIker Pedrosa <ipedrosa@redhat.com>
Mon, 3 Mar 2025 08:29:57 +0000 (09:29 +0100)
committerIker Pedrosa <ikerpedrosam@gmail.com>
Wed, 21 May 2025 08:04:42 +0000 (10:04 +0200)
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 <ipedrosa@redhat.com>
Reviewed-by: Dan Lavu <dlavu@redhat.com>
tests/system/framework/utils/tools.py

index 64a298c20e795fe31d9bd48fa431d3b07a050a7d..0d7a3f53f1804fd3fae3703d354fcfcf7f62c923 100644 (file)
@@ -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``