root / lib / http / auth.py @ 6873a52a
History | View | Annotate | Download (9.3 kB)
1 | be500c29 | Michael Hanselmann | #
|
---|---|---|---|
2 | be500c29 | Michael Hanselmann | #
|
3 | be500c29 | Michael Hanselmann | |
4 | be500c29 | Michael Hanselmann | # Copyright (C) 2007, 2008 Google Inc.
|
5 | be500c29 | Michael Hanselmann | #
|
6 | be500c29 | Michael Hanselmann | # This program is free software; you can redistribute it and/or modify
|
7 | be500c29 | Michael Hanselmann | # it under the terms of the GNU General Public License as published by
|
8 | be500c29 | Michael Hanselmann | # the Free Software Foundation; either version 2 of the License, or
|
9 | be500c29 | Michael Hanselmann | # (at your option) any later version.
|
10 | be500c29 | Michael Hanselmann | #
|
11 | be500c29 | Michael Hanselmann | # This program is distributed in the hope that it will be useful, but
|
12 | be500c29 | Michael Hanselmann | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | be500c29 | Michael Hanselmann | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | be500c29 | Michael Hanselmann | # General Public License for more details.
|
15 | be500c29 | Michael Hanselmann | #
|
16 | be500c29 | Michael Hanselmann | # You should have received a copy of the GNU General Public License
|
17 | be500c29 | Michael Hanselmann | # along with this program; if not, write to the Free Software
|
18 | be500c29 | Michael Hanselmann | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | be500c29 | Michael Hanselmann | # 02110-1301, USA.
|
20 | be500c29 | Michael Hanselmann | |
21 | be500c29 | Michael Hanselmann | """HTTP authentication module.
|
22 | be500c29 | Michael Hanselmann |
|
23 | be500c29 | Michael Hanselmann | """
|
24 | be500c29 | Michael Hanselmann | |
25 | be500c29 | Michael Hanselmann | import logging |
26 | be500c29 | Michael Hanselmann | import re |
27 | be500c29 | Michael Hanselmann | import base64 |
28 | be500c29 | Michael Hanselmann | import binascii |
29 | be500c29 | Michael Hanselmann | |
30 | be500c29 | Michael Hanselmann | from ganeti import utils |
31 | be500c29 | Michael Hanselmann | from ganeti import http |
32 | be500c29 | Michael Hanselmann | |
33 | be500c29 | Michael Hanselmann | from cStringIO import StringIO |
34 | be500c29 | Michael Hanselmann | |
35 | bf9bd8dd | Michael Hanselmann | try:
|
36 | bf9bd8dd | Michael Hanselmann | from hashlib import md5 |
37 | bf9bd8dd | Michael Hanselmann | except ImportError: |
38 | bf9bd8dd | Michael Hanselmann | from md5 import new as md5 |
39 | bf9bd8dd | Michael Hanselmann | |
40 | be500c29 | Michael Hanselmann | |
41 | be500c29 | Michael Hanselmann | # Digest types from RFC2617
|
42 | be500c29 | Michael Hanselmann | HTTP_BASIC_AUTH = "Basic"
|
43 | be500c29 | Michael Hanselmann | HTTP_DIGEST_AUTH = "Digest"
|
44 | be500c29 | Michael Hanselmann | |
45 | be500c29 | Michael Hanselmann | # Not exactly as described in RFC2616, section 2.2, but good enough
|
46 | 8a088b79 | Guido Trotter | _NOQUOTE = re.compile(r"^[-_a-z0-9]+$", re.I)
|
47 | be500c29 | Michael Hanselmann | |
48 | be500c29 | Michael Hanselmann | |
49 | be500c29 | Michael Hanselmann | def _FormatAuthHeader(scheme, params): |
50 | be500c29 | Michael Hanselmann | """Formats WWW-Authentication header value as per RFC2617, section 1.2
|
51 | be500c29 | Michael Hanselmann |
|
52 | be500c29 | Michael Hanselmann | @type scheme: str
|
53 | be500c29 | Michael Hanselmann | @param scheme: Authentication scheme
|
54 | be500c29 | Michael Hanselmann | @type params: dict
|
55 | be500c29 | Michael Hanselmann | @param params: Additional parameters
|
56 | be500c29 | Michael Hanselmann | @rtype: str
|
57 | be500c29 | Michael Hanselmann | @return: Formatted header value
|
58 | be500c29 | Michael Hanselmann |
|
59 | be500c29 | Michael Hanselmann | """
|
60 | be500c29 | Michael Hanselmann | buf = StringIO() |
61 | be500c29 | Michael Hanselmann | |
62 | be500c29 | Michael Hanselmann | buf.write(scheme) |
63 | be500c29 | Michael Hanselmann | |
64 | be500c29 | Michael Hanselmann | for name, value in params.iteritems(): |
65 | be500c29 | Michael Hanselmann | buf.write(" ")
|
66 | be500c29 | Michael Hanselmann | buf.write(name) |
67 | be500c29 | Michael Hanselmann | buf.write("=")
|
68 | be500c29 | Michael Hanselmann | if _NOQUOTE.match(value):
|
69 | be500c29 | Michael Hanselmann | buf.write(value) |
70 | be500c29 | Michael Hanselmann | else:
|
71 | be500c29 | Michael Hanselmann | buf.write("\"")
|
72 | be500c29 | Michael Hanselmann | # TODO: Better quoting
|
73 | be500c29 | Michael Hanselmann | buf.write(value.replace("\"", "\\\"")) |
74 | be500c29 | Michael Hanselmann | buf.write("\"")
|
75 | be500c29 | Michael Hanselmann | |
76 | be500c29 | Michael Hanselmann | return buf.getvalue()
|
77 | be500c29 | Michael Hanselmann | |
78 | be500c29 | Michael Hanselmann | |
79 | be500c29 | Michael Hanselmann | class HttpServerRequestAuthentication(object): |
80 | be500c29 | Michael Hanselmann | # Default authentication realm
|
81 | 23ccba04 | Michael Hanselmann | AUTH_REALM = "Unspecified"
|
82 | be500c29 | Michael Hanselmann | |
83 | bf9bd8dd | Michael Hanselmann | # Schemes for passwords
|
84 | bf9bd8dd | Michael Hanselmann | _CLEARTEXT_SCHEME = "{CLEARTEXT}"
|
85 | bf9bd8dd | Michael Hanselmann | _HA1_SCHEME = "{HA1}"
|
86 | bf9bd8dd | Michael Hanselmann | |
87 | be500c29 | Michael Hanselmann | def GetAuthRealm(self, req): |
88 | be500c29 | Michael Hanselmann | """Returns the authentication realm for a request.
|
89 | be500c29 | Michael Hanselmann |
|
90 | 23ccba04 | Michael Hanselmann | May be overridden by a subclass, which then can return different realms for
|
91 | 23ccba04 | Michael Hanselmann | different paths.
|
92 | be500c29 | Michael Hanselmann |
|
93 | be500c29 | Michael Hanselmann | @type req: L{http.server._HttpServerRequest}
|
94 | be500c29 | Michael Hanselmann | @param req: HTTP request context
|
95 | 23ccba04 | Michael Hanselmann | @rtype: string
|
96 | be500c29 | Michael Hanselmann | @return: Authentication realm
|
97 | be500c29 | Michael Hanselmann |
|
98 | be500c29 | Michael Hanselmann | """
|
99 | 2d54e29c | Iustin Pop | # today we don't have per-request filtering, but we might want to
|
100 | 2d54e29c | Iustin Pop | # add it in the future
|
101 | 2d54e29c | Iustin Pop | # pylint: disable-msg=W0613
|
102 | be500c29 | Michael Hanselmann | return self.AUTH_REALM |
103 | be500c29 | Michael Hanselmann | |
104 | 23ccba04 | Michael Hanselmann | def AuthenticationRequired(self, req): |
105 | 23ccba04 | Michael Hanselmann | """Determines whether authentication is required for a request.
|
106 | 23ccba04 | Michael Hanselmann |
|
107 | 23ccba04 | Michael Hanselmann | To enable authentication, override this function in a subclass and return
|
108 | 23ccba04 | Michael Hanselmann | C{True}. L{AUTH_REALM} must be set.
|
109 | 23ccba04 | Michael Hanselmann |
|
110 | 23ccba04 | Michael Hanselmann | @type req: L{http.server._HttpServerRequest}
|
111 | 23ccba04 | Michael Hanselmann | @param req: HTTP request context
|
112 | 23ccba04 | Michael Hanselmann |
|
113 | 23ccba04 | Michael Hanselmann | """
|
114 | 6873a52a | Michael Hanselmann | # Unused argument, method could be a function
|
115 | 6873a52a | Michael Hanselmann | # pylint: disable-msg=W0613,R0201
|
116 | 23ccba04 | Michael Hanselmann | return False |
117 | 23ccba04 | Michael Hanselmann | |
118 | be500c29 | Michael Hanselmann | def PreHandleRequest(self, req): |
119 | be500c29 | Michael Hanselmann | """Called before a request is handled.
|
120 | be500c29 | Michael Hanselmann |
|
121 | be500c29 | Michael Hanselmann | @type req: L{http.server._HttpServerRequest}
|
122 | be500c29 | Michael Hanselmann | @param req: HTTP request context
|
123 | be500c29 | Michael Hanselmann |
|
124 | be500c29 | Michael Hanselmann | """
|
125 | 81b59aaf | Iustin Pop | # Authentication not required, and no credentials given?
|
126 | 23ccba04 | Michael Hanselmann | if not (self.AuthenticationRequired(req) or |
127 | 23ccba04 | Michael Hanselmann | (req.request_headers and
|
128 | 23ccba04 | Michael Hanselmann | http.HTTP_AUTHORIZATION in req.request_headers)):
|
129 | be500c29 | Michael Hanselmann | return
|
130 | be500c29 | Michael Hanselmann | |
131 | 23ccba04 | Michael Hanselmann | realm = self.GetAuthRealm(req)
|
132 | 23ccba04 | Michael Hanselmann | |
133 | 23ccba04 | Michael Hanselmann | if not realm: |
134 | 23ccba04 | Michael Hanselmann | raise AssertionError("No authentication realm") |
135 | 81b59aaf | Iustin Pop | |
136 | be500c29 | Michael Hanselmann | # Check "Authorization" header
|
137 | be500c29 | Michael Hanselmann | if self._CheckAuthorization(req): |
138 | be500c29 | Michael Hanselmann | # User successfully authenticated
|
139 | be500c29 | Michael Hanselmann | return
|
140 | be500c29 | Michael Hanselmann | |
141 | be500c29 | Michael Hanselmann | # Send 401 Unauthorized response
|
142 | be500c29 | Michael Hanselmann | params = { |
143 | be500c29 | Michael Hanselmann | "realm": realm,
|
144 | be500c29 | Michael Hanselmann | } |
145 | be500c29 | Michael Hanselmann | |
146 | be500c29 | Michael Hanselmann | # TODO: Support for Digest authentication (RFC2617, section 3).
|
147 | be500c29 | Michael Hanselmann | # TODO: Support for more than one WWW-Authenticate header with the same
|
148 | be500c29 | Michael Hanselmann | # response (RFC2617, section 4.6).
|
149 | be500c29 | Michael Hanselmann | headers = { |
150 | be500c29 | Michael Hanselmann | http.HTTP_WWW_AUTHENTICATE: _FormatAuthHeader(HTTP_BASIC_AUTH, params), |
151 | be500c29 | Michael Hanselmann | } |
152 | be500c29 | Michael Hanselmann | |
153 | be500c29 | Michael Hanselmann | raise http.HttpUnauthorized(headers=headers)
|
154 | be500c29 | Michael Hanselmann | |
155 | be500c29 | Michael Hanselmann | def _CheckAuthorization(self, req): |
156 | 25e7b43f | Iustin Pop | """Checks 'Authorization' header sent by client.
|
157 | be500c29 | Michael Hanselmann |
|
158 | be500c29 | Michael Hanselmann | @type req: L{http.server._HttpServerRequest}
|
159 | be500c29 | Michael Hanselmann | @param req: HTTP request context
|
160 | be500c29 | Michael Hanselmann | @rtype: bool
|
161 | be500c29 | Michael Hanselmann | @return: Whether user is allowed to execute request
|
162 | be500c29 | Michael Hanselmann |
|
163 | be500c29 | Michael Hanselmann | """
|
164 | be500c29 | Michael Hanselmann | credentials = req.request_headers.get(http.HTTP_AUTHORIZATION, None)
|
165 | be500c29 | Michael Hanselmann | if not credentials: |
166 | be500c29 | Michael Hanselmann | return False |
167 | be500c29 | Michael Hanselmann | |
168 | be500c29 | Michael Hanselmann | # Extract scheme
|
169 | be500c29 | Michael Hanselmann | parts = credentials.strip().split(None, 2) |
170 | be500c29 | Michael Hanselmann | if len(parts) < 1: |
171 | be500c29 | Michael Hanselmann | # Missing scheme
|
172 | be500c29 | Michael Hanselmann | return False |
173 | be500c29 | Michael Hanselmann | |
174 | be500c29 | Michael Hanselmann | # RFC2617, section 1.2: "[...] It uses an extensible, case-insensitive
|
175 | be500c29 | Michael Hanselmann | # token to identify the authentication scheme [...]"
|
176 | be500c29 | Michael Hanselmann | scheme = parts[0].lower()
|
177 | be500c29 | Michael Hanselmann | |
178 | be500c29 | Michael Hanselmann | if scheme == HTTP_BASIC_AUTH.lower():
|
179 | be500c29 | Michael Hanselmann | # Do basic authentication
|
180 | be500c29 | Michael Hanselmann | if len(parts) < 2: |
181 | be500c29 | Michael Hanselmann | raise http.HttpBadRequest(message=("Basic authentication requires" |
182 | be500c29 | Michael Hanselmann | " credentials"))
|
183 | be500c29 | Michael Hanselmann | return self._CheckBasicAuthorization(req, parts[1]) |
184 | be500c29 | Michael Hanselmann | |
185 | be500c29 | Michael Hanselmann | elif scheme == HTTP_DIGEST_AUTH.lower():
|
186 | be500c29 | Michael Hanselmann | # TODO: Implement digest authentication
|
187 | be500c29 | Michael Hanselmann | # RFC2617, section 3.3: "Note that the HTTP server does not actually need
|
188 | be500c29 | Michael Hanselmann | # to know the user's cleartext password. As long as H(A1) is available to
|
189 | be500c29 | Michael Hanselmann | # the server, the validity of an Authorization header may be verified."
|
190 | be500c29 | Michael Hanselmann | pass
|
191 | be500c29 | Michael Hanselmann | |
192 | be500c29 | Michael Hanselmann | # Unsupported authentication scheme
|
193 | be500c29 | Michael Hanselmann | return False |
194 | be500c29 | Michael Hanselmann | |
195 | e09fdcfa | Iustin Pop | def _CheckBasicAuthorization(self, req, in_data): |
196 | be500c29 | Michael Hanselmann | """Checks credentials sent for basic authentication.
|
197 | be500c29 | Michael Hanselmann |
|
198 | be500c29 | Michael Hanselmann | @type req: L{http.server._HttpServerRequest}
|
199 | be500c29 | Michael Hanselmann | @param req: HTTP request context
|
200 | e09fdcfa | Iustin Pop | @type in_data: str
|
201 | e09fdcfa | Iustin Pop | @param in_data: Username and password encoded as Base64
|
202 | be500c29 | Michael Hanselmann | @rtype: bool
|
203 | be500c29 | Michael Hanselmann | @return: Whether user is allowed to execute request
|
204 | be500c29 | Michael Hanselmann |
|
205 | be500c29 | Michael Hanselmann | """
|
206 | be500c29 | Michael Hanselmann | try:
|
207 | e09fdcfa | Iustin Pop | creds = base64.b64decode(in_data.encode('ascii')).decode('ascii') |
208 | be500c29 | Michael Hanselmann | except (TypeError, binascii.Error, UnicodeError): |
209 | be500c29 | Michael Hanselmann | logging.exception("Error when decoding Basic authentication credentials")
|
210 | be500c29 | Michael Hanselmann | return False |
211 | be500c29 | Michael Hanselmann | |
212 | be500c29 | Michael Hanselmann | if ":" not in creds: |
213 | be500c29 | Michael Hanselmann | return False |
214 | be500c29 | Michael Hanselmann | |
215 | be500c29 | Michael Hanselmann | (user, password) = creds.split(":", 1) |
216 | be500c29 | Michael Hanselmann | |
217 | be500c29 | Michael Hanselmann | return self.Authenticate(req, user, password) |
218 | be500c29 | Michael Hanselmann | |
219 | 85414b69 | Iustin Pop | def Authenticate(self, req, user, password): |
220 | be500c29 | Michael Hanselmann | """Checks the password for a user.
|
221 | be500c29 | Michael Hanselmann |
|
222 | 5bbd3f7f | Michael Hanselmann | This function MUST be overridden by a subclass.
|
223 | be500c29 | Michael Hanselmann |
|
224 | be500c29 | Michael Hanselmann | """
|
225 | be500c29 | Michael Hanselmann | raise NotImplementedError() |
226 | e6e94655 | Michael Hanselmann | |
227 | bf9bd8dd | Michael Hanselmann | def VerifyBasicAuthPassword(self, req, username, password, expected): |
228 | bf9bd8dd | Michael Hanselmann | """Checks the password for basic authentication.
|
229 | bf9bd8dd | Michael Hanselmann |
|
230 | 23057d29 | Michael Hanselmann | As long as they don't start with an opening brace ("E{lb}"), old passwords
|
231 | 23057d29 | Michael Hanselmann | are supported. A new scheme uses H(A1) from RFC2617, where H is MD5 and A1
|
232 | bf9bd8dd | Michael Hanselmann | consists of the username, the authentication realm and the actual password.
|
233 | bf9bd8dd | Michael Hanselmann |
|
234 | bf9bd8dd | Michael Hanselmann | @type req: L{http.server._HttpServerRequest}
|
235 | bf9bd8dd | Michael Hanselmann | @param req: HTTP request context
|
236 | bf9bd8dd | Michael Hanselmann | @type username: string
|
237 | bf9bd8dd | Michael Hanselmann | @param username: Username from HTTP headers
|
238 | bf9bd8dd | Michael Hanselmann | @type password: string
|
239 | bf9bd8dd | Michael Hanselmann | @param password: Password from HTTP headers
|
240 | bf9bd8dd | Michael Hanselmann | @type expected: string
|
241 | bf9bd8dd | Michael Hanselmann | @param expected: Expected password with optional scheme prefix (e.g. from
|
242 | bf9bd8dd | Michael Hanselmann | users file)
|
243 | bf9bd8dd | Michael Hanselmann |
|
244 | bf9bd8dd | Michael Hanselmann | """
|
245 | bf9bd8dd | Michael Hanselmann | # Backwards compatibility for old-style passwords without a scheme
|
246 | bf9bd8dd | Michael Hanselmann | if not expected.startswith("{"): |
247 | bf9bd8dd | Michael Hanselmann | expected = self._CLEARTEXT_SCHEME + expected
|
248 | bf9bd8dd | Michael Hanselmann | |
249 | bf9bd8dd | Michael Hanselmann | # Check again, just to be sure
|
250 | bf9bd8dd | Michael Hanselmann | if not expected.startswith("{"): |
251 | bf9bd8dd | Michael Hanselmann | raise AssertionError("Invalid scheme") |
252 | bf9bd8dd | Michael Hanselmann | |
253 | bf9bd8dd | Michael Hanselmann | scheme_end_idx = expected.find("}", 1) |
254 | bf9bd8dd | Michael Hanselmann | |
255 | bf9bd8dd | Michael Hanselmann | # Ensure scheme has a length of at least one character
|
256 | bf9bd8dd | Michael Hanselmann | if scheme_end_idx <= 1: |
257 | bf9bd8dd | Michael Hanselmann | logging.warning("Invalid scheme in password for user '%s'", username)
|
258 | bf9bd8dd | Michael Hanselmann | return False |
259 | bf9bd8dd | Michael Hanselmann | |
260 | bf9bd8dd | Michael Hanselmann | scheme = expected[:scheme_end_idx + 1].upper()
|
261 | bf9bd8dd | Michael Hanselmann | expected_password = expected[scheme_end_idx + 1:]
|
262 | bf9bd8dd | Michael Hanselmann | |
263 | bf9bd8dd | Michael Hanselmann | # Good old plain text password
|
264 | bf9bd8dd | Michael Hanselmann | if scheme == self._CLEARTEXT_SCHEME: |
265 | bf9bd8dd | Michael Hanselmann | return password == expected_password
|
266 | bf9bd8dd | Michael Hanselmann | |
267 | bf9bd8dd | Michael Hanselmann | # H(A1) as described in RFC2617
|
268 | bf9bd8dd | Michael Hanselmann | if scheme == self._HA1_SCHEME: |
269 | bf9bd8dd | Michael Hanselmann | realm = self.GetAuthRealm(req)
|
270 | bf9bd8dd | Michael Hanselmann | if not realm: |
271 | bf9bd8dd | Michael Hanselmann | # There can not be a valid password for this case
|
272 | 23ccba04 | Michael Hanselmann | raise AssertionError("No authentication realm") |
273 | bf9bd8dd | Michael Hanselmann | |
274 | bf9bd8dd | Michael Hanselmann | expha1 = md5() |
275 | bf9bd8dd | Michael Hanselmann | expha1.update("%s:%s:%s" % (username, realm, password))
|
276 | bf9bd8dd | Michael Hanselmann | |
277 | bf9bd8dd | Michael Hanselmann | return (expected_password.lower() == expha1.hexdigest().lower())
|
278 | bf9bd8dd | Michael Hanselmann | |
279 | bf9bd8dd | Michael Hanselmann | logging.warning("Unknown scheme '%s' in password for user '%s'",
|
280 | bf9bd8dd | Michael Hanselmann | scheme, username) |
281 | bf9bd8dd | Michael Hanselmann | |
282 | bf9bd8dd | Michael Hanselmann | return False |
283 | bf9bd8dd | Michael Hanselmann | |
284 | e6e94655 | Michael Hanselmann | |
285 | e6e94655 | Michael Hanselmann | class PasswordFileUser(object): |
286 | e6e94655 | Michael Hanselmann | """Data structure for users from password file.
|
287 | e6e94655 | Michael Hanselmann |
|
288 | e6e94655 | Michael Hanselmann | """
|
289 | e6e94655 | Michael Hanselmann | def __init__(self, name, password, options): |
290 | e6e94655 | Michael Hanselmann | self.name = name
|
291 | e6e94655 | Michael Hanselmann | self.password = password
|
292 | e6e94655 | Michael Hanselmann | self.options = options
|
293 | e6e94655 | Michael Hanselmann | |
294 | e6e94655 | Michael Hanselmann | |
295 | e6e94655 | Michael Hanselmann | def ReadPasswordFile(file_name): |
296 | e6e94655 | Michael Hanselmann | """Reads a password file.
|
297 | e6e94655 | Michael Hanselmann |
|
298 | 25e7b43f | Iustin Pop | Lines in the password file are of the following format::
|
299 | e6e94655 | Michael Hanselmann |
|
300 | 25e7b43f | Iustin Pop | <username> <password> [options]
|
301 | e6e94655 | Michael Hanselmann |
|
302 | e6e94655 | Michael Hanselmann | Fields are separated by whitespace. Username and password are mandatory,
|
303 | 25e7b43f | Iustin Pop | options are optional and separated by comma (','). Empty lines and comments
|
304 | 25e7b43f | Iustin Pop | ('#') are ignored.
|
305 | e6e94655 | Michael Hanselmann |
|
306 | e6e94655 | Michael Hanselmann | @type file_name: str
|
307 | e6e94655 | Michael Hanselmann | @param file_name: Path to password file
|
308 | e6e94655 | Michael Hanselmann | @rtype: dict
|
309 | e6e94655 | Michael Hanselmann | @return: Dictionary containing L{PasswordFileUser} instances
|
310 | e6e94655 | Michael Hanselmann |
|
311 | e6e94655 | Michael Hanselmann | """
|
312 | e6e94655 | Michael Hanselmann | users = {} |
313 | e6e94655 | Michael Hanselmann | |
314 | e6e94655 | Michael Hanselmann | for line in utils.ReadFile(file_name).splitlines(): |
315 | e6e94655 | Michael Hanselmann | line = line.strip() |
316 | e6e94655 | Michael Hanselmann | |
317 | e6e94655 | Michael Hanselmann | # Ignore empty lines and comments
|
318 | e6e94655 | Michael Hanselmann | if not line or line.startswith("#"): |
319 | e6e94655 | Michael Hanselmann | continue
|
320 | e6e94655 | Michael Hanselmann | |
321 | e6e94655 | Michael Hanselmann | parts = line.split(None, 2) |
322 | e6e94655 | Michael Hanselmann | if len(parts) < 2: |
323 | e6e94655 | Michael Hanselmann | # Invalid line
|
324 | e6e94655 | Michael Hanselmann | continue
|
325 | e6e94655 | Michael Hanselmann | |
326 | e6e94655 | Michael Hanselmann | name = parts[0]
|
327 | e6e94655 | Michael Hanselmann | password = parts[1]
|
328 | e6e94655 | Michael Hanselmann | |
329 | e6e94655 | Michael Hanselmann | # Extract options
|
330 | e6e94655 | Michael Hanselmann | options = [] |
331 | e6e94655 | Michael Hanselmann | if len(parts) >= 3: |
332 | e6e94655 | Michael Hanselmann | for part in parts[2].split(","): |
333 | e6e94655 | Michael Hanselmann | options.append(part.strip()) |
334 | e6e94655 | Michael Hanselmann | |
335 | e6e94655 | Michael Hanselmann | users[name] = PasswordFileUser(name, password, options) |
336 | e6e94655 | Michael Hanselmann | |
337 | e6e94655 | Michael Hanselmann | return users |