1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """HTTP authentication module.
22
23 """
24
25 import logging
26 import re
27 import base64
28 import binascii
29
30 from ganeti import utils
31 from ganeti import http
32
33 from cStringIO import StringIO
34
35
36
37 HTTP_BASIC_AUTH = "Basic"
38 HTTP_DIGEST_AUTH = "Digest"
39
40
41 _NOQUOTE = re.compile(r"^[-_a-z0-9]+$", re.I)
42
43
72
73
75
76 AUTH_REALM = None
77
79 """Returns the authentication realm for a request.
80
81 MAY be overridden by a subclass, which then can return different realms for
82 different paths. Returning "None" means no authentication is needed for a
83 request.
84
85 @type req: L{http.server._HttpServerRequest}
86 @param req: HTTP request context
87 @rtype: str or None
88 @return: Authentication realm
89
90 """
91 return self.AUTH_REALM
92
128
130 """Checks 'Authorization' header sent by client.
131
132 @type req: L{http.server._HttpServerRequest}
133 @param req: HTTP request context
134 @rtype: bool
135 @return: Whether user is allowed to execute request
136
137 """
138 credentials = req.request_headers.get(http.HTTP_AUTHORIZATION, None)
139 if not credentials:
140 return False
141
142
143 parts = credentials.strip().split(None, 2)
144 if len(parts) < 1:
145
146 return False
147
148
149
150 scheme = parts[0].lower()
151
152 if scheme == HTTP_BASIC_AUTH.lower():
153
154 if len(parts) < 2:
155 raise http.HttpBadRequest(message=("Basic authentication requires"
156 " credentials"))
157 return self._CheckBasicAuthorization(req, parts[1])
158
159 elif scheme == HTTP_DIGEST_AUTH.lower():
160
161
162
163
164 pass
165
166
167 return False
168
170 """Checks credentials sent for basic authentication.
171
172 @type req: L{http.server._HttpServerRequest}
173 @param req: HTTP request context
174 @type in_data: str
175 @param in_data: Username and password encoded as Base64
176 @rtype: bool
177 @return: Whether user is allowed to execute request
178
179 """
180 try:
181 creds = base64.b64decode(in_data.encode('ascii')).decode('ascii')
182 except (TypeError, binascii.Error, UnicodeError):
183 logging.exception("Error when decoding Basic authentication credentials")
184 return False
185
186 if ":" not in creds:
187 return False
188
189 (user, password) = creds.split(":", 1)
190
191 return self.Authenticate(req, user, password)
192
194 """Checks the password for a user.
195
196 This function MUST be overridden by a subclass.
197
198 """
199 raise NotImplementedError()
200
201
203 """Data structure for users from password file.
204
205 """
206 - def __init__(self, name, password, options):
207 self.name = name
208 self.password = password
209 self.options = options
210
211
213 """Reads a password file.
214
215 Lines in the password file are of the following format::
216
217 <username> <password> [options]
218
219 Fields are separated by whitespace. Username and password are mandatory,
220 options are optional and separated by comma (','). Empty lines and comments
221 ('#') are ignored.
222
223 @type file_name: str
224 @param file_name: Path to password file
225 @rtype: dict
226 @return: Dictionary containing L{PasswordFileUser} instances
227
228 """
229 users = {}
230
231 for line in utils.ReadFile(file_name).splitlines():
232 line = line.strip()
233
234
235 if not line or line.startswith("#"):
236 continue
237
238 parts = line.split(None, 2)
239 if len(parts) < 2:
240
241 continue
242
243 name = parts[0]
244 password = parts[1]
245
246
247 options = []
248 if len(parts) >= 3:
249 for part in parts[2].split(","):
250 options.append(part.strip())
251
252 users[name] = PasswordFileUser(name, password, options)
253
254 return users
255