1# -*- test-case-name: twisted.python.test.test_fakepwd -*-
2# Copyright (c) Twisted Matrix Laboratories.
3# See LICENSE for details.
4
5"""
6L{twisted.python.fakepwd} provides a fake implementation of the L{pwd} API.
7"""
8
9from typing import List
10
11__all__ = ["UserDatabase", "ShadowDatabase"]
12
13
14class _UserRecord:
15    """
16    L{_UserRecord} holds the user data for a single user in L{UserDatabase}.
17    It corresponds to the C{passwd} structure from the L{pwd} module.
18    See that module for attribute documentation.
19    """
20
21    def __init__(
22        self,
23        name: str,
24        password: str,
25        uid: int,
26        gid: int,
27        gecos: str,
28        home: str,
29        shell: str,
30    ) -> None:
31        self.pw_name = name
32        self.pw_passwd = password
33        self.pw_uid = uid
34        self.pw_gid = gid
35        self.pw_gecos = gecos
36        self.pw_dir = home
37        self.pw_shell = shell
38
39    def __len__(self) -> int:
40        return 7
41
42    def __getitem__(self, index):
43        return (
44            self.pw_name,
45            self.pw_passwd,
46            self.pw_uid,
47            self.pw_gid,
48            self.pw_gecos,
49            self.pw_dir,
50            self.pw_shell,
51        )[index]
52
53
54class UserDatabase:
55    """
56    L{UserDatabase} holds a traditional POSIX user data in memory and makes it
57    available via the same API as L{pwd}.
58
59    @ivar _users: A C{list} of L{_UserRecord} instances holding all user data
60        added to this database.
61    """
62
63    _users: List[_UserRecord]
64
65    def __init__(self) -> None:
66        self._users = []
67
68    def addUser(
69        self,
70        username: str,
71        password: str,
72        uid: int,
73        gid: int,
74        gecos: str,
75        home: str,
76        shell: str,
77    ) -> None:
78        """
79        Add a new user record to this database.
80
81        @param username: The value for the C{pw_name} field of the user
82            record to add.
83
84        @param password: The value for the C{pw_passwd} field of the user
85            record to add.
86
87        @param uid: The value for the C{pw_uid} field of the user record to
88            add.
89
90        @param gid: The value for the C{pw_gid} field of the user record to
91            add.
92
93        @param gecos: The value for the C{pw_gecos} field of the user record
94            to add.
95
96        @param home: The value for the C{pw_dir} field of the user record to
97            add.
98
99        @param shell: The value for the C{pw_shell} field of the user record to
100            add.
101        """
102        self._users.append(
103            _UserRecord(username, password, uid, gid, gecos, home, shell)
104        )
105
106    def getpwuid(self, uid: int) -> _UserRecord:
107        """
108        Return the user record corresponding to the given uid.
109        """
110        for entry in self._users:
111            if entry.pw_uid == uid:
112                return entry
113        raise KeyError()
114
115    def getpwnam(self, name: str) -> _UserRecord:
116        """
117        Return the user record corresponding to the given username.
118        """
119        if not isinstance(name, str):
120            raise TypeError(f"getpwuam() argument must be str, not {type(name)}")
121        for entry in self._users:
122            if entry.pw_name == name:
123                return entry
124        raise KeyError()
125
126    def getpwall(self) -> List[_UserRecord]:
127        """
128        Return a list of all user records.
129        """
130        return self._users
131
132
133class _ShadowRecord:
134    """
135    L{_ShadowRecord} holds the shadow user data for a single user in
136    L{ShadowDatabase}.  It corresponds to C{spwd.struct_spwd}.  See that class
137    for attribute documentation.
138    """
139
140    def __init__(
141        self,
142        username: str,
143        password: str,
144        lastChange: int,
145        min: int,
146        max: int,
147        warn: int,
148        inact: int,
149        expire: int,
150        flag: int,
151    ) -> None:
152        self.sp_nam = username
153        self.sp_pwd = password
154        self.sp_lstchg = lastChange
155        self.sp_min = min
156        self.sp_max = max
157        self.sp_warn = warn
158        self.sp_inact = inact
159        self.sp_expire = expire
160        self.sp_flag = flag
161
162    def __len__(self) -> int:
163        return 9
164
165    def __getitem__(self, index):
166        return (
167            self.sp_nam,
168            self.sp_pwd,
169            self.sp_lstchg,
170            self.sp_min,
171            self.sp_max,
172            self.sp_warn,
173            self.sp_inact,
174            self.sp_expire,
175            self.sp_flag,
176        )[index]
177
178
179class ShadowDatabase:
180    """
181    L{ShadowDatabase} holds a shadow user database in memory and makes it
182    available via the same API as C{spwd}.
183
184    @ivar _users: A C{list} of L{_ShadowRecord} instances holding all user data
185        added to this database.
186
187    @since: 12.0
188    """
189
190    _users: List[_ShadowRecord]
191
192    def __init__(self) -> None:
193        self._users = []
194
195    def addUser(
196        self,
197        username: str,
198        password: str,
199        lastChange: int,
200        min: int,
201        max: int,
202        warn: int,
203        inact: int,
204        expire: int,
205        flag: int,
206    ) -> None:
207        """
208        Add a new user record to this database.
209
210        @param username: The value for the C{sp_nam} field of the user record to
211            add.
212
213        @param password: The value for the C{sp_pwd} field of the user record to
214            add.
215
216        @param lastChange: The value for the C{sp_lstchg} field of the user
217            record to add.
218
219        @param min: The value for the C{sp_min} field of the user record to add.
220
221        @param max: The value for the C{sp_max} field of the user record to add.
222
223        @param warn: The value for the C{sp_warn} field of the user record to
224            add.
225
226        @param inact: The value for the C{sp_inact} field of the user record to
227            add.
228
229        @param expire: The value for the C{sp_expire} field of the user record
230            to add.
231
232        @param flag: The value for the C{sp_flag} field of the user record to
233            add.
234        """
235        self._users.append(
236            _ShadowRecord(
237                username, password, lastChange, min, max, warn, inact, expire, flag
238            )
239        )
240
241    def getspnam(self, username: str) -> _ShadowRecord:
242        """
243        Return the shadow user record corresponding to the given username.
244        """
245        if not isinstance(username, str):
246            raise TypeError(f"getspnam() argument must be str, not {type(username)}")
247        for entry in self._users:
248            if entry.sp_nam == username:
249                return entry
250        raise KeyError(username)
251
252    def getspall(self):
253        """
254        Return a list of all shadow user records.
255        """
256        return self._users
257