Merge lp:~khussein/swift/authn into lp:~hudson-openstack/swift/trunk
- authn
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~khussein/swift/authn |
Merge into: | lp:~hudson-openstack/swift/trunk |
Diff against target: |
1008 lines (+442/-250) 7 files modified
.unittests (+1/-1) doc/source/development_saio.rst (+9/-3) setup.py (+4/-2) swift/common/middleware/devauthn.py (+145/-0) swift/common/middleware/devauthz.py (+48/-109) swift/common/middleware/papiauth.py (+62/-0) test/unit/common/middleware/test_auth.py (+173/-135) |
To merge this branch: | bzr merge lp:~khussein/swift/authn |
Related bugs: | |
Related blueprints: |
Authentication in OpenStack
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chuck Thier (community) | Needs Fixing | ||
gholt | Pending | ||
Review via email: mp+45532@code.launchpad.net |
This proposal supersedes a proposal from 2010-12-08.
This proposal has been superseded by a proposal from 2011-01-11.
Commit message
Description of the change
Implemented the proposed protocol to handle authentication in OpenStack services.
Mike Barton (redbo) wrote : Posted in a previous version of this proposal | # |
gholt (gholt) wrote : Posted in a previous version of this proposal | # |
We meant for this to be a work-in-progress; sorry.
gholt (gholt) wrote : Posted in a previous version of this proposal | # |
Oh, and these weren't the droids you were looking for anyhow. Move along; move along.
Khaled Hussein (khussein) wrote : Posted in a previous version of this proposal | # |
The proxy-server config now would be [auth1 devauth papiauth]
Chuck Thier (cthier) wrote : | # |
Initial comments:
1. There is one unit test failure
2. There are 9 functional test failures
3. There are no unit tests for the new pieces of middleware
4. There are no docs that explain how/when to use the different pieces of middleware
5. Do the saio or multiserver install docs need to change?
6. auth1 is a poor choice for the module name and class name
7. Is anything going to need to change when we move to swauth?
8. PEP8:
cthier@
swift/common/
swift/common/
swift/common/
swift/common/
swift/common/
cthier@
swift/common/
swift/common/
cthier@
cthier@
swift/common/
gholt (gholt) wrote : | # |
As I understood things, the DevAuth that we have always used was not going to change except that it was going to set an additional header instead of just setting REMOTE_USER. All the new auth stuff was going to be new files. What happened with that plan?
Khaled Hussein (khussein) wrote : | # |
Following the 'public or anonymous access' use case that you've mentioned, we've discussed adding the delegated mode, which is documented in our blueprint. So, this added a couple of new changes to the source code. It also lead us to refactor the code base a little bit so that we separate the two concerns: authentication and authorization into two separate modules.
- 160. By Khaled Hussein
-
documentation change
- 161. By Khaled Hussein
-
merge trunk
Unmerged revisions
- 161. By Khaled Hussein
-
merge trunk
- 160. By Khaled Hussein
-
documentation change
- 159. By Khaled Hussein
-
saio documentation changes
- 158. By Khaled Hussein
-
PEP8 Stuff :)
- 157. By Khaled Hussein
-
Refactored and Added unit tests
- 156. By Khaled Hussein
-
fixing conflicts
- 155. By Khaled Hussein
-
fixed functional tests
- 154. By Khaled Hussein
-
setup.py config changes
- 153. By Khaled Hussein
-
Merged trunk
- 152. By Khaled Hussein
-
Fixed unit tests
Preview Diff
1 | === modified file '.unittests' |
2 | --- .unittests 2010-07-13 03:34:34 +0000 |
3 | +++ .unittests 2011-01-11 20:54:28 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | #!/bin/bash |
6 | |
7 | -nosetests test/unit --exe --with-coverage --cover-package swift --cover-erase |
8 | +nosetests test/unit/ --exe --with-coverage --cover-package swift --cover-erase |
9 | rm -f .coverage |
10 | |
11 | === modified file 'doc/source/development_saio.rst' |
12 | --- doc/source/development_saio.rst 2010-12-02 01:08:49 +0000 |
13 | +++ doc/source/development_saio.rst 2011-01-11 20:54:28 +0000 |
14 | @@ -240,7 +240,7 @@ |
15 | |
16 | [pipeline:main] |
17 | # For DevAuth: |
18 | - pipeline = healthcheck cache auth proxy-server |
19 | + pipeline = healthcheck cache devauthn devauthz papiauth proxy-server |
20 | # For Swauth: |
21 | # pipeline = healthcheck cache swauth proxy-server |
22 | |
23 | @@ -249,8 +249,14 @@ |
24 | allow_account_management = true |
25 | |
26 | # Only needed for DevAuth |
27 | - [filter:auth] |
28 | - use = egg:swift#auth |
29 | + [filter:devauthn] |
30 | + use = egg:swift#devauthn |
31 | + |
32 | + [filter:devauthz] |
33 | + use = egg:swift#devauthz |
34 | + |
35 | + [filter:papiauth] |
36 | + use = egg:swift#papiauth |
37 | |
38 | # Only needed for Swauth |
39 | [filter:swauth] |
40 | |
41 | === modified file 'setup.py' |
42 | --- setup.py 2011-01-05 15:17:36 +0000 |
43 | +++ setup.py 2011-01-11 20:54:28 +0000 |
44 | @@ -1,5 +1,5 @@ |
45 | #!/usr/bin/python |
46 | -# Copyright (c) 2010-2011 OpenStack, LLC. |
47 | +# Copyright (c) 2010 OpenStack, LLC. |
48 | # |
49 | # Licensed under the Apache License, Version 2.0 (the "License"); |
50 | # you may not use this file except in compliance with the License. |
51 | @@ -94,7 +94,9 @@ |
52 | 'auth=swift.auth.server:app_factory', |
53 | ], |
54 | 'paste.filter_factory': [ |
55 | - 'auth=swift.common.middleware.auth:filter_factory', |
56 | + 'devauthn=swift.common.middleware.devauthn:filter_factory', |
57 | + 'devauthz=swift.common.middleware.devauthz:filter_factory', |
58 | + 'papiauth=swift.common.middleware.papiauth:filter_factory', |
59 | 'swauth=swift.common.middleware.swauth:filter_factory', |
60 | 'healthcheck=swift.common.middleware.healthcheck:filter_factory', |
61 | 'memcache=swift.common.middleware.memcache:filter_factory', |
62 | |
63 | === added file 'swift/common/middleware/devauthn.py' |
64 | --- swift/common/middleware/devauthn.py 1970-01-01 00:00:00 +0000 |
65 | +++ swift/common/middleware/devauthn.py 2011-01-11 20:54:28 +0000 |
66 | @@ -0,0 +1,145 @@ |
67 | +# Copyright (c) 2010 OpenStack, LLC. |
68 | +# |
69 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
70 | +# you may not use this file except in compliance with the License. |
71 | +# You may obtain a copy of the License at |
72 | +# |
73 | +# http://www.apache.org/licenses/LICENSE-2.0 |
74 | +# |
75 | +# Unless required by applicable law or agreed to in writing, software |
76 | +# distributed under the License is distributed on an "AS IS" BASIS, |
77 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
78 | +# implied. |
79 | +# See the License for the specific language governing permissions and |
80 | +# limitations under the License. |
81 | + |
82 | +from time import time |
83 | + |
84 | +from eventlet.timeout import Timeout |
85 | +from webob.exc import HTTPUnauthorized |
86 | + |
87 | +from swift.common.bufferedhttp import http_connect_raw as http_connect |
88 | +from swift.common.utils import cache_from_env, TRUE_VALUES |
89 | + |
90 | + |
91 | +class DevAuthN(object): |
92 | + """Auth Middleware that uses the dev auth server.""" |
93 | + |
94 | + def __init__(self, app, conf): |
95 | + self.app = app |
96 | + self.conf = conf |
97 | + self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip() |
98 | + if self.reseller_prefix and self.reseller_prefix[-1] != '_': |
99 | + self.reseller_prefix += '_' |
100 | + self.auth_host = conf.get('ip', '127.0.0.1') |
101 | + self.auth_prefix = conf.get('prefix', '/') |
102 | + self.auth_port = int(conf.get('port', 11000)) |
103 | + self.ssl = conf.get('ssl', 'false').lower() in TRUE_VALUES |
104 | + self.timeout = int(conf.get('node_timeout', 10)) |
105 | + self.delegated = int(conf.get('delegated', 0)) |
106 | + |
107 | + def __call__(self, env, start_response): |
108 | + """ |
109 | + Accepts a standard WSGI application call and authenticates the request. |
110 | + For an authenticated request, SWIFT_GROUPS will be set to a comma |
111 | + separated list of the user's groups. It'll also set X-Authorization |
112 | + header to 'Proxy [Username]'. If it is running in a delegated mode, it |
113 | + sets the X-Identity-Status header to 'Confirmed' if the token is valid, |
114 | + or 'Indeterminate' if the token doesn't exist. |
115 | + |
116 | + With a non-empty reseller prefix, acts as the definitive auth service |
117 | + for just tokens and accounts that begin with that prefix, but will deny |
118 | + requests outside this prefix if no other auth middleware overrides it. |
119 | + |
120 | + With an empty reseller prefix, acts as the definitive auth service only |
121 | + for tokens that validate to a non-empty set of groups. For all other |
122 | + requests, acts as the fallback auth service when no other auth |
123 | + middleware overrides it. |
124 | + """ |
125 | + |
126 | + def custom_start_response(status, headers): |
127 | + if self.delegated: |
128 | + headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) |
129 | + return start_response(status, headers) |
130 | + |
131 | + token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) |
132 | + if token and token.startswith(self.reseller_prefix): |
133 | + # Note: Empty reseller_prefix will match all tokens. |
134 | + # Attempt to auth my token with my auth server |
135 | + groups = \ |
136 | + self.get_groups(token, memcache_client=cache_from_env(env)) |
137 | + if groups: |
138 | + user = groups and groups.split(',', 1)[0] or '' |
139 | + env['SWIFT_GROUPS'] = groups |
140 | + env['HTTP_X_AUTHORIZATION'] = "Proxy " + user |
141 | + if self.delegated: |
142 | + env['HTTP_X_IDENTITY_STATUS'] = "Confirmed" |
143 | + # We know the proxy logs the token, so we augment it just |
144 | + # a bit to also log the authenticated user. |
145 | + env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token) |
146 | + else: |
147 | + if self.delegated and not self.reseller_prefix: |
148 | + env['HTTP_X_IDENTITY_STATUS'] = "Invalid" |
149 | + else: |
150 | + # Unauthorized token |
151 | + return HTTPUnauthorized()(env, custom_start_response) |
152 | + else: |
153 | + env['HTTP_X_AUTHORIZATION'] = "Proxy" |
154 | + if self.delegated: |
155 | + env['HTTP_X_IDENTITY_STATUS'] = "Indeterminate" |
156 | + else: |
157 | + return HTTPUnauthorized()(env, custom_start_response) |
158 | + |
159 | + env['HTTP_AUTHORIZATION'] = "Basic dTpw" |
160 | + return self.app(env, custom_start_response) |
161 | + |
162 | + def get_groups(self, token, memcache_client=None): |
163 | + """ |
164 | + Get groups for the given token. |
165 | + |
166 | + If memcache_client is set, token credentials will be cached |
167 | + appropriately. |
168 | + |
169 | + With a cache miss, or no memcache_client, the configurated external |
170 | + authentication server will be queried for the group information. |
171 | + |
172 | + :param token: Token to validate and return a group string for. |
173 | + :param memcache_client: Memcached client to use for caching token |
174 | + credentials; None if no caching is desired. |
175 | + :returns: None if the token is invalid or a string containing a comma |
176 | + separated list of groups the authenticated user is a member |
177 | + of. The first group in the list is also considered a unique |
178 | + identifier for that user. |
179 | + """ |
180 | + groups = None |
181 | + key = '%s/token/%s' % (self.reseller_prefix, token) |
182 | + cached_auth_data = memcache_client and memcache_client.get(key) |
183 | + if cached_auth_data: |
184 | + start, expiration, groups = cached_auth_data |
185 | + if time() - start > expiration: |
186 | + groups = None |
187 | + if not groups: |
188 | + with Timeout(self.timeout): |
189 | + conn = http_connect(self.auth_host, self.auth_port, 'GET', |
190 | + '%stoken/%s' % (self.auth_prefix, token), ssl=self.ssl) |
191 | + resp = conn.getresponse() |
192 | + resp.read() |
193 | + conn.close() |
194 | + if resp.status // 100 != 2: |
195 | + return None |
196 | + expiration = float(resp.getheader('x-auth-ttl')) |
197 | + groups = resp.getheader('x-auth-groups') |
198 | + if memcache_client: |
199 | + memcache_client.set(key, (time(), expiration, groups), |
200 | + timeout=expiration) |
201 | + return groups |
202 | + |
203 | + |
204 | +def filter_factory(global_conf, **local_conf): |
205 | + """Returns a WSGI filter app for use with paste.deploy.""" |
206 | + conf = global_conf.copy() |
207 | + conf.update(local_conf) |
208 | + |
209 | + def auth_filter(app): |
210 | + return DevAuthN(app, conf) |
211 | + return auth_filter |
212 | |
213 | === renamed file 'swift/common/middleware/auth.py' => 'swift/common/middleware/devauthz.py' |
214 | --- swift/common/middleware/auth.py 2011-01-05 16:14:31 +0000 |
215 | +++ swift/common/middleware/devauthz.py 2011-01-11 20:54:28 +0000 |
216 | @@ -13,17 +13,13 @@ |
217 | # See the License for the specific language governing permissions and |
218 | # limitations under the License. |
219 | |
220 | -from time import time |
221 | - |
222 | -from eventlet.timeout import Timeout |
223 | from webob.exc import HTTPForbidden, HTTPUnauthorized, HTTPNotFound |
224 | |
225 | -from swift.common.bufferedhttp import http_connect_raw as http_connect |
226 | from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed |
227 | -from swift.common.utils import cache_from_env, split_path, TRUE_VALUES |
228 | - |
229 | - |
230 | -class DevAuth(object): |
231 | +from swift.common.utils import split_path, TRUE_VALUES |
232 | + |
233 | + |
234 | +class DevAuthZ(object): |
235 | """Auth Middleware that uses the dev auth server.""" |
236 | |
237 | def __init__(self, app, conf): |
238 | @@ -42,110 +38,50 @@ |
239 | """ |
240 | Accepts a standard WSGI application call, authenticating the request |
241 | and installing callback hooks for authorization and ACL header |
242 | - validation. For an authenticated request, REMOTE_USER will be set to a |
243 | - comma separated list of the user's groups. |
244 | - |
245 | - With a non-empty reseller prefix, acts as the definitive auth service |
246 | - for just tokens and accounts that begin with that prefix, but will deny |
247 | - requests outside this prefix if no other auth middleware overrides it. |
248 | - |
249 | - With an empty reseller prefix, acts as the definitive auth service only |
250 | - for tokens that validate to a non-empty set of groups. For all other |
251 | - requests, acts as the fallback auth service when no other auth |
252 | - middleware overrides it. |
253 | + validation. |
254 | """ |
255 | - token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) |
256 | - if token and token.startswith(self.reseller_prefix): |
257 | - # Note: Empty reseller_prefix will match all tokens. |
258 | - # Attempt to auth my token with my auth server |
259 | - groups = \ |
260 | - self.get_groups(token, memcache_client=cache_from_env(env)) |
261 | - if groups: |
262 | - env['REMOTE_USER'] = groups |
263 | - user = groups and groups.split(',', 1)[0] or '' |
264 | - # We know the proxy logs the token, so we augment it just a bit |
265 | - # to also log the authenticated user. |
266 | - env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token) |
267 | + groups = None |
268 | + if 'SWIFT_GROUPS' in env: |
269 | + groups = env['SWIFT_GROUPS'] |
270 | + env['REMOTE_USER'] = groups |
271 | + self.authorize = self.dev_authorize |
272 | + env['swift.clean_acl'] = clean_acl |
273 | + elif 'swift.authorize' not in env: |
274 | + self.authorize = self.empty_authorize |
275 | + |
276 | + if self.reseller_prefix: |
277 | + # With a non-empty reseller_prefix, I would like to be called |
278 | + # back for anonymous access to accounts I know I'm the |
279 | + # definitive auth for. |
280 | + try: |
281 | + version, rest = split_path(env.get('PATH_INFO', ''), |
282 | + 1, 2, True) |
283 | + except ValueError: |
284 | + return HTTPNotFound()(env, start_response) |
285 | + if rest and rest.startswith(self.reseller_prefix): |
286 | + # Handle anonymous access to accounts I'm the definitive |
287 | + # auth for. |
288 | env['swift.authorize'] = self.authorize |
289 | env['swift.clean_acl'] = clean_acl |
290 | - else: |
291 | - # Unauthorized token |
292 | - if self.reseller_prefix: |
293 | - # Because I know I'm the definitive auth for this token, I |
294 | - # can deny it outright. |
295 | - return HTTPUnauthorized()(env, start_response) |
296 | - # Because I'm not certain if I'm the definitive auth for empty |
297 | - # reseller_prefixed tokens, I won't overwrite swift.authorize. |
298 | - elif 'swift.authorize' not in env: |
299 | - env['swift.authorize'] = self.denied_response |
300 | - else: |
301 | - if self.reseller_prefix: |
302 | - # With a non-empty reseller_prefix, I would like to be called |
303 | - # back for anonymous access to accounts I know I'm the |
304 | - # definitive auth for. |
305 | - try: |
306 | - version, rest = split_path(env.get('PATH_INFO', ''), |
307 | - 1, 2, True) |
308 | - except ValueError: |
309 | - return HTTPNotFound()(env, start_response) |
310 | - if rest and rest.startswith(self.reseller_prefix): |
311 | - # Handle anonymous access to accounts I'm the definitive |
312 | - # auth for. |
313 | - env['swift.authorize'] = self.authorize |
314 | - env['swift.clean_acl'] = clean_acl |
315 | - # Not my token, not my account, I can't authorize this request, |
316 | - # deny all is a good idea if not already set... |
317 | - elif 'swift.authorize' not in env: |
318 | - env['swift.authorize'] = self.denied_response |
319 | - # Because I'm not certain if I'm the definitive auth for empty |
320 | - # reseller_prefixed accounts, I won't overwrite swift.authorize. |
321 | + # Not my token, not my account, I can't authorize this request, |
322 | + # deny all is a good idea if not already set... |
323 | elif 'swift.authorize' not in env: |
324 | - env['swift.authorize'] = self.authorize |
325 | - env['swift.clean_acl'] = clean_acl |
326 | + env['swift.authorize'] = self.denied_response |
327 | + # Because I'm not certain if I'm the definitive auth for empty |
328 | + # reseller_prefixed accounts, I won't overwrite swift.authorize. |
329 | + elif 'swift.authorize' not in env: |
330 | + env['swift.authorize'] = self.empty_authorize |
331 | return self.app(env, start_response) |
332 | |
333 | - def get_groups(self, token, memcache_client=None): |
334 | - """ |
335 | - Get groups for the given token. |
336 | - |
337 | - If memcache_client is set, token credentials will be cached |
338 | - appropriately. |
339 | - |
340 | - With a cache miss, or no memcache_client, the configurated external |
341 | - authentication server will be queried for the group information. |
342 | - |
343 | - :param token: Token to validate and return a group string for. |
344 | - :param memcache_client: Memcached client to use for caching token |
345 | - credentials; None if no caching is desired. |
346 | - :returns: None if the token is invalid or a string containing a comma |
347 | - separated list of groups the authenticated user is a member |
348 | - of. The first group in the list is also considered a unique |
349 | - identifier for that user. |
350 | - """ |
351 | - groups = None |
352 | - key = '%s/token/%s' % (self.reseller_prefix, token) |
353 | - cached_auth_data = memcache_client and memcache_client.get(key) |
354 | - if cached_auth_data: |
355 | - start, expiration, groups = cached_auth_data |
356 | - if time() - start > expiration: |
357 | - groups = None |
358 | - if not groups: |
359 | - with Timeout(self.timeout): |
360 | - conn = http_connect(self.auth_host, self.auth_port, 'GET', |
361 | - '%stoken/%s' % (self.auth_prefix, token), ssl=self.ssl) |
362 | - resp = conn.getresponse() |
363 | - resp.read() |
364 | - conn.close() |
365 | - if resp.status // 100 != 2: |
366 | - return None |
367 | - expiration = float(resp.getheader('x-auth-ttl')) |
368 | - groups = resp.getheader('x-auth-groups') |
369 | - if memcache_client: |
370 | - memcache_client.set(key, (time(), expiration, groups), |
371 | - timeout=expiration) |
372 | - return groups |
373 | - |
374 | - def authorize(self, req): |
375 | + def empty_authorize(self, req): |
376 | + if 'x_identity_status' in req.headers: |
377 | + if req.headers['x_identity_status'] == 'Invalid': |
378 | + return self.denied_response(req) |
379 | + elif req.headers['x_identity_status'] == 'Indeterminate': |
380 | + return self.dev_authorize(req) |
381 | + return None |
382 | + |
383 | + def dev_authorize(self, req): |
384 | """ |
385 | Returns None if the request is authorized to continue or a standard |
386 | WSGI response callable if not. |
387 | @@ -179,10 +115,13 @@ |
388 | Returns a standard WSGI response callable with the status of 403 or 401 |
389 | depending on whether the REMOTE_USER is set or not. |
390 | """ |
391 | + headers = [('www-authenticate', 'delegated')] |
392 | if req.remote_user: |
393 | - return HTTPForbidden(request=req) |
394 | + resp = HTTPForbidden(headers=headers, request=req) |
395 | else: |
396 | - return HTTPUnauthorized(request=req) |
397 | + resp = HTTPUnauthorized(headers=headers, request=req) |
398 | + |
399 | + return resp |
400 | |
401 | |
402 | def filter_factory(global_conf, **local_conf): |
403 | @@ -191,5 +130,5 @@ |
404 | conf.update(local_conf) |
405 | |
406 | def auth_filter(app): |
407 | - return DevAuth(app, conf) |
408 | + return DevAuthZ(app, conf) |
409 | return auth_filter |
410 | |
411 | === added file 'swift/common/middleware/papiauth.py' |
412 | --- swift/common/middleware/papiauth.py 1970-01-01 00:00:00 +0000 |
413 | +++ swift/common/middleware/papiauth.py 2011-01-11 20:54:28 +0000 |
414 | @@ -0,0 +1,62 @@ |
415 | +# Copyright (c) 2010 OpenStack, LLC. |
416 | +# |
417 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
418 | +# you may not use this file except in compliance with the License. |
419 | +# You may obtain a copy of the License at |
420 | +# |
421 | +# http://www.apache.org/licenses/LICENSE-2.0 |
422 | +# |
423 | +# Unless required by applicable law or agreed to in writing, software |
424 | +# distributed under the License is distributed on an "AS IS" BASIS, |
425 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
426 | +# implied. |
427 | +# See the License for the specific language governing permissions and |
428 | +# limitations under the License. |
429 | + |
430 | +from webob.exc import HTTPUseProxy, HTTPUnauthorized |
431 | + |
432 | +from swift.common.utils import TRUE_VALUES |
433 | + |
434 | + |
435 | +class PAPIAuth(object): |
436 | + """Auth Middleware that uses the dev auth server.""" |
437 | + |
438 | + def __init__(self, app, conf): |
439 | + self.app = app |
440 | + self.conf = conf |
441 | + self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip() |
442 | + if self.reseller_prefix and self.reseller_prefix[-1] != '_': |
443 | + self.reseller_prefix += '_' |
444 | + self.auth_host = conf.get('ip', '127.0.0.1') |
445 | + self.auth_port = int(conf.get('port', 11000)) |
446 | + self.auth_pass = conf.get('pass', 'dTpw') |
447 | + self.ssl = conf.get('ssl', 'false').lower() in TRUE_VALUES |
448 | + self.timeout = int(conf.get('node_timeout', 10)) |
449 | + |
450 | + def __call__(self, env, start_response): |
451 | + # Make sure that the user has been authenticated by the Auth Service |
452 | + if 'HTTP_X_AUTHORIZATION' not in env: |
453 | + proxy_location = 'http://' + self.auth_host + ':' + \ |
454 | + str(self.auth_port) + '/' |
455 | + return HTTPUseProxy(location=proxy_location)(env, start_response) |
456 | + |
457 | + # Authenticate the Auth component itself. |
458 | + headers = [('www-authenticate', 'Basic realm="swift"')] |
459 | + if 'HTTP_AUTHORIZATION' not in env: |
460 | + return HTTPUnauthorized(headers=headers)(env, start_response) |
461 | + else: |
462 | + auth_type, encoded_creds = env['HTTP_AUTHORIZATION'].split(None, 1) |
463 | + if encoded_creds != self.auth_pass: |
464 | + return HTTPUnauthorized(headers=headers)(env, start_response) |
465 | + |
466 | + return self.app(env, start_response) |
467 | + |
468 | + |
469 | +def filter_factory(global_conf, **local_conf): |
470 | + """Returns a WSGI filter app for use with paste.deploy.""" |
471 | + conf = global_conf.copy() |
472 | + conf.update(local_conf) |
473 | + |
474 | + def auth_filter(app): |
475 | + return PAPIAuth(app, conf) |
476 | + return auth_filter |
477 | |
478 | === modified file 'test/unit/common/middleware/test_auth.py' |
479 | --- test/unit/common/middleware/test_auth.py 2011-01-05 16:14:31 +0000 |
480 | +++ test/unit/common/middleware/test_auth.py 2011-01-11 20:54:28 +0000 |
481 | @@ -23,7 +23,7 @@ |
482 | import eventlet |
483 | from webob import Request |
484 | |
485 | -from swift.common.middleware import auth |
486 | +from swift.common.middleware import devauthn, devauthz, papiauth |
487 | |
488 | # mocks |
489 | logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) |
490 | @@ -122,243 +122,281 @@ |
491 | class TestAuth(unittest.TestCase): |
492 | |
493 | def setUp(self): |
494 | - self.test_auth = auth.filter_factory({})(FakeApp()) |
495 | + self.test_authn = devauthn.filter_factory({})(FakeApp()) |
496 | + self.test_authz = devauthz.filter_factory({})(FakeApp()) |
497 | + self.test_papiauth = papiauth.filter_factory({})(FakeApp()) |
498 | |
499 | def test_auth_deny_non_reseller_prefix(self): |
500 | - old_http_connect = auth.http_connect |
501 | - try: |
502 | - auth.http_connect = mock_http_connect(204, |
503 | - {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'}) |
504 | - reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/BLAH_account', |
505 | - 'HTTP_X_AUTH_TOKEN': 'BLAH_t', 'swift.cache': FakeMemcache()} |
506 | - result = ''.join(self.test_auth(reqenv, lambda x, y: None)) |
507 | - self.assert_(result.startswith('401'), result) |
508 | - self.assertEquals(reqenv['swift.authorize'], |
509 | - self.test_auth.denied_response) |
510 | - finally: |
511 | - auth.http_connect = old_http_connect |
512 | + reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/BLAH_account', |
513 | + 'HTTP_X_AUTH_TOKEN': 'BLAH_t', 'swift.cache': FakeMemcache()} |
514 | + result = ''.join(self.test_authz(reqenv, lambda x, y: None)) |
515 | + self.assert_(result.startswith('401'), result) |
516 | + self.assertEquals(reqenv['swift.authorize'], |
517 | + self.test_authz.denied_response) |
518 | |
519 | def test_auth_deny_non_reseller_prefix_no_override(self): |
520 | - old_http_connect = auth.http_connect |
521 | + old_http_connect = devauthn.http_connect |
522 | try: |
523 | - auth.http_connect = mock_http_connect(204, |
524 | + local_app = FakeApp() |
525 | + local_authz = \ |
526 | + devauthz.filter_factory({'reseller_prefix': ''})(local_app) |
527 | + local_authn = \ |
528 | + devauthn.filter_factory({'delegated': 1})(local_authz) |
529 | + devauthn.http_connect = mock_http_connect(204, |
530 | {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'}) |
531 | fake_authorize = lambda x: lambda x, y: ['500 Fake'] |
532 | reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/BLAH_account', |
533 | 'HTTP_X_AUTH_TOKEN': 'BLAH_t', 'swift.cache': FakeMemcache(), |
534 | 'swift.authorize': fake_authorize} |
535 | - result = ''.join(self.test_auth(reqenv, lambda x, y: None)) |
536 | + result = ''.join(local_authn(reqenv, lambda x, y: None)) |
537 | self.assert_(result.startswith('500 Fake'), result) |
538 | self.assertEquals(reqenv['swift.authorize'], fake_authorize) |
539 | finally: |
540 | - auth.http_connect = old_http_connect |
541 | + devauthn.http_connect = old_http_connect |
542 | |
543 | - def test_auth_no_reseller_prefix_deny(self): |
544 | + def test_auth_no_reseller_prefix_deny_delegated(self): |
545 | # Ensures that when we have no reseller prefix, we don't deny a request |
546 | # outright but set up a denial swift.authorize and pass the request on |
547 | # down the chain. |
548 | - old_http_connect = auth.http_connect |
549 | + old_http_connect = devauthn.http_connect |
550 | try: |
551 | local_app = FakeApp() |
552 | - local_auth = \ |
553 | - auth.filter_factory({'reseller_prefix': ''})(local_app) |
554 | - auth.http_connect = mock_http_connect(404) |
555 | + local_authz = \ |
556 | + devauthz.filter_factory({'reseller_prefix': ''})(local_app) |
557 | + local_authn = \ |
558 | + devauthn.filter_factory(\ |
559 | + {'reseller_prefix': '', 'delegated': 1})(local_authz) |
560 | + devauthn.http_connect = mock_http_connect(404) |
561 | reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/account', |
562 | 'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': FakeMemcache()} |
563 | - result = ''.join(local_auth(reqenv, lambda x, y: None)) |
564 | + result = ''.join(local_authn(reqenv, lambda x, y: None)) |
565 | self.assert_(result.startswith('401'), result) |
566 | self.assert_(local_app.i_was_called) |
567 | self.assertEquals(reqenv['swift.authorize'], |
568 | - local_auth.denied_response) |
569 | - finally: |
570 | - auth.http_connect = old_http_connect |
571 | + local_authz.empty_authorize) |
572 | + finally: |
573 | + devauthn.http_connect = old_http_connect |
574 | + |
575 | + def test_auth_no_reseller_prefix_deny_non_delegated(self): |
576 | + # Ensures that when we have no reseller prefix, we don't deny a request |
577 | + # outright but set up a denial swift.authorize and pass the request on |
578 | + # down the chain. |
579 | + old_http_connect = devauthn.http_connect |
580 | + try: |
581 | + local_app = FakeApp() |
582 | + local_authn = \ |
583 | + devauthn.filter_factory(\ |
584 | + {'reseller_prefix': ''})(local_app) |
585 | + devauthn.http_connect = mock_http_connect(404) |
586 | + reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/account', |
587 | + 'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': FakeMemcache()} |
588 | + result = ''.join(local_authn(reqenv, lambda x, y: None)) |
589 | + self.assert_(result.startswith('401'), result) |
590 | + self.assert_(not local_app.i_was_called) |
591 | + finally: |
592 | + devauthn.http_connect = old_http_connect |
593 | + |
594 | |
595 | def test_auth_no_reseller_prefix_allow(self): |
596 | # Ensures that when we have no reseller prefix, we can still allow |
597 | # access if our auth server accepts requests |
598 | - old_http_connect = auth.http_connect |
599 | - try: |
600 | - local_app = FakeApp() |
601 | - local_auth = \ |
602 | - auth.filter_factory({'reseller_prefix': ''})(local_app) |
603 | - auth.http_connect = mock_http_connect(204, |
604 | - {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'}) |
605 | - reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/act', |
606 | - 'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': None} |
607 | - result = ''.join(local_auth(reqenv, lambda x, y: None)) |
608 | - self.assert_(result.startswith('204'), result) |
609 | - self.assert_(local_app.i_was_called) |
610 | - self.assertEquals(reqenv['swift.authorize'], |
611 | - local_auth.authorize) |
612 | - finally: |
613 | - auth.http_connect = old_http_connect |
614 | + local_app = FakeApp() |
615 | + local_auth = \ |
616 | + devauthz.filter_factory({'reseller_prefix': ''})(local_app) |
617 | + reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/act', |
618 | + 'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': None, |
619 | + 'SWIFT_GROUPS': 'act:usr,act,AUTH_cfa'} |
620 | + result = ''.join(local_auth(reqenv, lambda x, y: None)) |
621 | + self.assert_(result.startswith('204'), result) |
622 | + self.assert_(local_app.i_was_called) |
623 | + self.assertEquals(reqenv['swift.authorize'], |
624 | + local_auth.empty_authorize) |
625 | |
626 | - def test_auth_no_reseller_prefix_no_token(self): |
627 | + def test_auth_no_reseller_prefix_no_token_delegated(self): |
628 | # Check that normally we set up a call back to our authorize. |
629 | - local_auth = \ |
630 | - auth.filter_factory({'reseller_prefix': ''})(FakeApp()) |
631 | + local_authz = \ |
632 | + devauthz.filter_factory({'reseller_prefix': ''})(FakeApp()) |
633 | + local_authn = \ |
634 | + devauthn.filter_factory(\ |
635 | + {'reseller_prefix': '', 'delegated':1})(local_authz) |
636 | reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/account', |
637 | 'swift.cache': FakeMemcache()} |
638 | - result = ''.join(local_auth(reqenv, lambda x, y: None)) |
639 | + result = ''.join(local_authn(reqenv, lambda x, y: None)) |
640 | self.assert_(result.startswith('401'), result) |
641 | - self.assertEquals(reqenv['swift.authorize'], local_auth.authorize) |
642 | + self.assertEquals(\ |
643 | + reqenv['swift.authorize'], local_authz.empty_authorize) |
644 | # Now make sure we don't override an existing swift.authorize when we |
645 | # have no reseller prefix. |
646 | local_authorize = lambda req: None |
647 | reqenv['swift.authorize'] = local_authorize |
648 | - result = ''.join(local_auth(reqenv, lambda x, y: None)) |
649 | + result = ''.join(local_authz(reqenv, lambda x, y: None)) |
650 | self.assert_(result.startswith('204'), result) |
651 | self.assertEquals(reqenv['swift.authorize'], local_authorize) |
652 | |
653 | + def test_auth_no_reseller_prefix_no_token_non_delegated(self): |
654 | + # Ensure that in a non_delegated mode, no token gets denied |
655 | + # right away. |
656 | + local_app = FakeApp() |
657 | + local_authn = \ |
658 | + devauthn.filter_factory(\ |
659 | + {'reseller_prefix': ''})(local_app) |
660 | + reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/account', |
661 | + 'swift.cache': FakeMemcache()} |
662 | + result = ''.join(local_authn(reqenv, lambda x, y: None)) |
663 | + self.assert_(result.startswith('401'), result) |
664 | + self.assert_(not local_app.i_was_called) |
665 | + |
666 | def test_auth_fail(self): |
667 | - old_http_connect = auth.http_connect |
668 | + old_http_connect = devauthn.http_connect |
669 | try: |
670 | - auth.http_connect = mock_http_connect(404) |
671 | - result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', |
672 | + devauthn.http_connect = mock_http_connect(404) |
673 | + result = ''.join(self.test_authn({'REQUEST_METHOD': 'GET', |
674 | 'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': FakeMemcache()}, |
675 | lambda x, y: None)) |
676 | self.assert_(result.startswith('401'), result) |
677 | finally: |
678 | - auth.http_connect = old_http_connect |
679 | + devauthn.http_connect = old_http_connect |
680 | |
681 | def test_auth_success(self): |
682 | - old_http_connect = auth.http_connect |
683 | + old_http_connect = devauthn.http_connect |
684 | try: |
685 | - auth.http_connect = mock_http_connect(204, |
686 | + devauthn.http_connect = mock_http_connect(204, |
687 | {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'}) |
688 | - result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', |
689 | + result = ''.join(self.test_authn({'REQUEST_METHOD': 'GET', |
690 | 'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t', |
691 | 'swift.cache': FakeMemcache()}, lambda x, y: None)) |
692 | self.assert_(result.startswith('204'), result) |
693 | finally: |
694 | - auth.http_connect = old_http_connect |
695 | + devauthn.http_connect = old_http_connect |
696 | |
697 | def test_auth_memcache(self): |
698 | - old_http_connect = auth.http_connect |
699 | + old_http_connect = devauthn.http_connect |
700 | try: |
701 | fake_memcache = FakeMemcache() |
702 | - auth.http_connect = mock_http_connect(204, |
703 | + devauthn.http_connect = mock_http_connect(204, |
704 | {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'}) |
705 | - result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', |
706 | + result = ''.join(self.test_authn({'REQUEST_METHOD': 'GET', |
707 | 'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t', |
708 | 'swift.cache': fake_memcache}, lambda x, y: None)) |
709 | self.assert_(result.startswith('204'), result) |
710 | - auth.http_connect = mock_http_connect(404) |
711 | + devauthn.http_connect = mock_http_connect(404) |
712 | # Should still be in memcache |
713 | - result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', |
714 | + result = ''.join(self.test_authn({'REQUEST_METHOD': 'GET', |
715 | 'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t', |
716 | 'swift.cache': fake_memcache}, lambda x, y: None)) |
717 | self.assert_(result.startswith('204'), result) |
718 | finally: |
719 | - auth.http_connect = old_http_connect |
720 | + devauthn.http_connect = old_http_connect |
721 | |
722 | def test_auth_just_expired(self): |
723 | - old_http_connect = auth.http_connect |
724 | + old_http_connect = devauthn.http_connect |
725 | try: |
726 | fake_memcache = FakeMemcache() |
727 | - auth.http_connect = mock_http_connect(204, |
728 | + devauthn.http_connect = mock_http_connect(204, |
729 | {'x-auth-ttl': '0', 'x-auth-groups': 'act:usr,act,AUTH_cfa'}) |
730 | - result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', |
731 | + result = ''.join(self.test_authn({'REQUEST_METHOD': 'GET', |
732 | 'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t', |
733 | 'swift.cache': fake_memcache}, lambda x, y: None)) |
734 | self.assert_(result.startswith('204'), result) |
735 | - auth.http_connect = mock_http_connect(404) |
736 | + devauthn.http_connect = mock_http_connect(404) |
737 | # Should still be in memcache, but expired |
738 | - result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', |
739 | + result = ''.join(self.test_authn({'REQUEST_METHOD': 'GET', |
740 | 'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache}, |
741 | lambda x, y: None)) |
742 | self.assert_(result.startswith('401'), result) |
743 | finally: |
744 | - auth.http_connect = old_http_connect |
745 | + devauthn.http_connect = old_http_connect |
746 | |
747 | def test_middleware_success(self): |
748 | - old_http_connect = auth.http_connect |
749 | + old_http_connect = devauthn.http_connect |
750 | try: |
751 | - auth.http_connect = mock_http_connect(204, |
752 | + devauthn.http_connect = mock_http_connect(204, |
753 | {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'}) |
754 | req = Request.blank('/v/AUTH_cfa/c/o', |
755 | headers={'x-auth-token': 'AUTH_t'}) |
756 | req.environ['swift.cache'] = FakeMemcache() |
757 | - result = ''.join(self.test_auth(req.environ, start_response)) |
758 | + result = ''.join(self.test_authn(req.environ, start_response)) |
759 | self.assert_(result.startswith('204'), result) |
760 | - self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa') |
761 | + self.assertEquals(\ |
762 | + req.environ['SWIFT_GROUPS'], 'act:usr,act,AUTH_cfa') |
763 | finally: |
764 | - auth.http_connect = old_http_connect |
765 | + devauthn.http_connect = old_http_connect |
766 | |
767 | def test_middleware_no_header(self): |
768 | - old_http_connect = auth.http_connect |
769 | + old_http_connect = devauthn.http_connect |
770 | try: |
771 | - auth.http_connect = mock_http_connect(204, |
772 | + devauthn.http_connect = mock_http_connect(204, |
773 | {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'}) |
774 | req = Request.blank('/v/AUTH_cfa/c/o') |
775 | req.environ['swift.cache'] = FakeMemcache() |
776 | - result = ''.join(self.test_auth(req.environ, start_response)) |
777 | + result = ''.join(self.test_authn(req.environ, start_response)) |
778 | self.assert_(result.startswith('401'), result) |
779 | self.assert_(not req.remote_user, req.remote_user) |
780 | finally: |
781 | - auth.http_connect = old_http_connect |
782 | + devauthn.http_connect = old_http_connect |
783 | |
784 | def test_middleware_storage_token(self): |
785 | - old_http_connect = auth.http_connect |
786 | + old_http_connect = devauthn.http_connect |
787 | try: |
788 | - auth.http_connect = mock_http_connect(204, |
789 | + devauthn.http_connect = mock_http_connect(204, |
790 | {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'}) |
791 | req = Request.blank('/v/AUTH_cfa/c/o', |
792 | headers={'x-storage-token': 'AUTH_t'}) |
793 | req.environ['swift.cache'] = FakeMemcache() |
794 | - result = ''.join(self.test_auth(req.environ, start_response)) |
795 | + result = ''.join(self.test_authn(req.environ, start_response)) |
796 | self.assert_(result.startswith('204'), result) |
797 | - self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa') |
798 | + self.assertEquals(\ |
799 | + req.environ['SWIFT_GROUPS'], 'act:usr,act,AUTH_cfa') |
800 | finally: |
801 | - auth.http_connect = old_http_connect |
802 | + devauthn.http_connect = old_http_connect |
803 | |
804 | def test_authorize_bad_path(self): |
805 | req = Request.blank('/badpath') |
806 | - resp = self.test_auth.authorize(req) |
807 | + resp = self.test_authz.dev_authorize(req) |
808 | self.assertEquals(resp and resp.status_int, 401) |
809 | req = Request.blank('/badpath') |
810 | req.remote_user = 'act:usr,act,AUTH_cfa' |
811 | - resp = self.test_auth.authorize(req) |
812 | + resp = self.test_authz.dev_authorize(req) |
813 | self.assertEquals(resp and resp.status_int, 403) |
814 | req = Request.blank('') |
815 | - resp = self.test_auth.authorize(req) |
816 | + resp = self.test_authz.dev_authorize(req) |
817 | self.assertEquals(resp and resp.status_int, 404) |
818 | req = Request.blank('') |
819 | req.environ['swift.cache'] = FakeMemcache() |
820 | - result = ''.join(self.test_auth(req.environ, lambda x, y: None)) |
821 | + result = ''.join(self.test_authz(req.environ, lambda x, y: None)) |
822 | self.assert_(result.startswith('404'), result) |
823 | |
824 | def test_authorize_account_access(self): |
825 | req = Request.blank('/v1/AUTH_cfa') |
826 | req.remote_user = 'act:usr,act,AUTH_cfa' |
827 | - self.assertEquals(self.test_auth.authorize(req), None) |
828 | + self.assertEquals(self.test_authz.dev_authorize(req), None) |
829 | req = Request.blank('/v1/AUTH_cfa') |
830 | req.remote_user = 'act:usr,act' |
831 | - resp = self.test_auth.authorize(req) |
832 | + resp = self.test_authz.dev_authorize(req) |
833 | self.assertEquals(resp and resp.status_int, 403) |
834 | |
835 | def test_authorize_acl_group_access(self): |
836 | req = Request.blank('/v1/AUTH_cfa') |
837 | req.remote_user = 'act:usr,act' |
838 | - resp = self.test_auth.authorize(req) |
839 | + resp = self.test_authz.dev_authorize(req) |
840 | self.assertEquals(resp and resp.status_int, 403) |
841 | req = Request.blank('/v1/AUTH_cfa') |
842 | req.remote_user = 'act:usr,act' |
843 | req.acl = 'act' |
844 | - self.assertEquals(self.test_auth.authorize(req), None) |
845 | + self.assertEquals(self.test_authz.dev_authorize(req), None) |
846 | req = Request.blank('/v1/AUTH_cfa') |
847 | req.remote_user = 'act:usr,act' |
848 | req.acl = 'act:usr' |
849 | - self.assertEquals(self.test_auth.authorize(req), None) |
850 | + self.assertEquals(self.test_authz.dev_authorize(req), None) |
851 | req = Request.blank('/v1/AUTH_cfa') |
852 | req.remote_user = 'act:usr,act' |
853 | req.acl = 'act2' |
854 | - resp = self.test_auth.authorize(req) |
855 | + resp = self.test_authz.dev_authorize(req) |
856 | self.assertEquals(resp and resp.status_int, 403) |
857 | req = Request.blank('/v1/AUTH_cfa') |
858 | req.remote_user = 'act:usr,act' |
859 | req.acl = 'act:usr2' |
860 | - resp = self.test_auth.authorize(req) |
861 | + resp = self.test_authz.dev_authorize(req) |
862 | self.assertEquals(resp and resp.status_int, 403) |
863 | |
864 | def test_deny_cross_reseller(self): |
865 | @@ -366,96 +404,96 @@ |
866 | req = Request.blank('/v1/OTHER_cfa') |
867 | req.remote_user = 'act:usr,act,AUTH_cfa' |
868 | req.acl = 'act' |
869 | - resp = self.test_auth.authorize(req) |
870 | + resp = self.test_authz.dev_authorize(req) |
871 | self.assertEquals(resp and resp.status_int, 403) |
872 | |
873 | def test_authorize_acl_referrer_access(self): |
874 | req = Request.blank('/v1/AUTH_cfa') |
875 | req.remote_user = 'act:usr,act' |
876 | - resp = self.test_auth.authorize(req) |
877 | - self.assertEquals(resp and resp.status_int, 403) |
878 | - req = Request.blank('/v1/AUTH_cfa') |
879 | - req.remote_user = 'act:usr,act' |
880 | - req.acl = '.r:*' |
881 | - self.assertEquals(self.test_auth.authorize(req), None) |
882 | - req = Request.blank('/v1/AUTH_cfa') |
883 | - req.remote_user = 'act:usr,act' |
884 | - req.acl = '.r:.example.com' |
885 | - resp = self.test_auth.authorize(req) |
886 | - self.assertEquals(resp and resp.status_int, 403) |
887 | - req = Request.blank('/v1/AUTH_cfa') |
888 | - req.remote_user = 'act:usr,act' |
889 | - req.referer = 'http://www.example.com/index.html' |
890 | - req.acl = '.r:.example.com' |
891 | - self.assertEquals(self.test_auth.authorize(req), None) |
892 | - req = Request.blank('/v1/AUTH_cfa') |
893 | - resp = self.test_auth.authorize(req) |
894 | - self.assertEquals(resp and resp.status_int, 401) |
895 | - req = Request.blank('/v1/AUTH_cfa') |
896 | - req.acl = '.r:*' |
897 | - self.assertEquals(self.test_auth.authorize(req), None) |
898 | - req = Request.blank('/v1/AUTH_cfa') |
899 | - req.acl = '.r:.example.com' |
900 | - resp = self.test_auth.authorize(req) |
901 | - self.assertEquals(resp and resp.status_int, 401) |
902 | - req = Request.blank('/v1/AUTH_cfa') |
903 | - req.referer = 'http://www.example.com/index.html' |
904 | - req.acl = '.r:.example.com' |
905 | - self.assertEquals(self.test_auth.authorize(req), None) |
906 | + resp = self.test_authz.dev_authorize(req) |
907 | + self.assertEquals(resp and resp.status_int, 403) |
908 | + req = Request.blank('/v1/AUTH_cfa') |
909 | + req.remote_user = 'act:usr,act' |
910 | + req.acl = '.r:*' |
911 | + self.assertEquals(self.test_authz.dev_authorize(req), None) |
912 | + req = Request.blank('/v1/AUTH_cfa') |
913 | + req.remote_user = 'act:usr,act' |
914 | + req.acl = '.r:.example.com' |
915 | + resp = self.test_authz.dev_authorize(req) |
916 | + self.assertEquals(resp and resp.status_int, 403) |
917 | + req = Request.blank('/v1/AUTH_cfa') |
918 | + req.remote_user = 'act:usr,act' |
919 | + req.referer = 'http://www.example.com/index.html' |
920 | + req.acl = '.r:.example.com' |
921 | + self.assertEquals(self.test_authz.dev_authorize(req), None) |
922 | + req = Request.blank('/v1/AUTH_cfa') |
923 | + resp = self.test_authz.dev_authorize(req) |
924 | + self.assertEquals(resp and resp.status_int, 401) |
925 | + req = Request.blank('/v1/AUTH_cfa') |
926 | + req.acl = '.r:*' |
927 | + self.assertEquals(self.test_authz.dev_authorize(req), None) |
928 | + req = Request.blank('/v1/AUTH_cfa') |
929 | + req.acl = '.r:.example.com' |
930 | + resp = self.test_authz.dev_authorize(req) |
931 | + self.assertEquals(resp and resp.status_int, 401) |
932 | + req = Request.blank('/v1/AUTH_cfa') |
933 | + req.referer = 'http://www.example.com/index.html' |
934 | + req.acl = '.r:.example.com' |
935 | + self.assertEquals(self.test_authz.dev_authorize(req), None) |
936 | |
937 | def test_account_put_permissions(self): |
938 | req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'}) |
939 | req.remote_user = 'act:usr,act' |
940 | - resp = self.test_auth.authorize(req) |
941 | + resp = self.test_authz.dev_authorize(req) |
942 | self.assertEquals(resp and resp.status_int, 403) |
943 | |
944 | req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'}) |
945 | req.remote_user = 'act:usr,act,AUTH_other' |
946 | - resp = self.test_auth.authorize(req) |
947 | + resp = self.test_authz.dev_authorize(req) |
948 | self.assertEquals(resp and resp.status_int, 403) |
949 | |
950 | # Even PUTs to your own account as account admin should fail |
951 | req = Request.blank('/v1/AUTH_old', environ={'REQUEST_METHOD': 'PUT'}) |
952 | req.remote_user = 'act:usr,act,AUTH_old' |
953 | - resp = self.test_auth.authorize(req) |
954 | + resp = self.test_authz.dev_authorize(req) |
955 | self.assertEquals(resp and resp.status_int, 403) |
956 | |
957 | req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'}) |
958 | req.remote_user = 'act:usr,act,.reseller_admin' |
959 | - resp = self.test_auth.authorize(req) |
960 | + resp = self.test_authz.dev_authorize(req) |
961 | self.assertEquals(resp, None) |
962 | |
963 | # .super_admin is not something the middleware should ever see or care |
964 | # about |
965 | req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'}) |
966 | req.remote_user = 'act:usr,act,.super_admin' |
967 | - resp = self.test_auth.authorize(req) |
968 | + resp = self.test_authz.dev_authorize(req) |
969 | self.assertEquals(resp and resp.status_int, 403) |
970 | |
971 | def test_account_delete_permissions(self): |
972 | req = Request.blank('/v1/AUTH_new', |
973 | environ={'REQUEST_METHOD': 'DELETE'}) |
974 | req.remote_user = 'act:usr,act' |
975 | - resp = self.test_auth.authorize(req) |
976 | + resp = self.test_authz.dev_authorize(req) |
977 | self.assertEquals(resp and resp.status_int, 403) |
978 | |
979 | req = Request.blank('/v1/AUTH_new', |
980 | environ={'REQUEST_METHOD': 'DELETE'}) |
981 | req.remote_user = 'act:usr,act,AUTH_other' |
982 | - resp = self.test_auth.authorize(req) |
983 | + resp = self.test_authz.dev_authorize(req) |
984 | self.assertEquals(resp and resp.status_int, 403) |
985 | |
986 | # Even DELETEs to your own account as account admin should fail |
987 | req = Request.blank('/v1/AUTH_old', |
988 | environ={'REQUEST_METHOD': 'DELETE'}) |
989 | req.remote_user = 'act:usr,act,AUTH_old' |
990 | - resp = self.test_auth.authorize(req) |
991 | + resp = self.test_authz.dev_authorize(req) |
992 | self.assertEquals(resp and resp.status_int, 403) |
993 | |
994 | req = Request.blank('/v1/AUTH_new', |
995 | environ={'REQUEST_METHOD': 'DELETE'}) |
996 | req.remote_user = 'act:usr,act,.reseller_admin' |
997 | - resp = self.test_auth.authorize(req) |
998 | + resp = self.test_authz.dev_authorize(req) |
999 | self.assertEquals(resp, None) |
1000 | |
1001 | # .super_admin is not something the middleware should ever see or care |
1002 | @@ -463,7 +501,7 @@ |
1003 | req = Request.blank('/v1/AUTH_new', |
1004 | environ={'REQUEST_METHOD': 'DELETE'}) |
1005 | req.remote_user = 'act:usr,act,.super_admin' |
1006 | - resp = self.test_auth.authorize(req) |
1007 | + resp = self.test_authz.dev_authorize(req) |
1008 | self.assertEquals(resp and resp.status_int, 403) |
1009 | |
1010 |
> self.response_ status = status
self.response_ status is storing per-request info in a shared object. That could be wrong under concurrency.
> ''.join( response_ itr) content_ length = sum(map(len, response.app_iter))
> return [resp.read()]
> response.
Storing many GB of data in RAM is a bad idea.
> usersConfig. readfp( open('/ etc/openstack/ users.ini' ))
You shouldn't re-read and parse the basic auth credentials file on every request. The local imports there are kind of ugly too.
> def validateCreds(self, username, password):
only major pep8 violation I see is those camel caps.
> - 'auth=swift. auth.server: app_factory' , swift.auth. server: app_factory' , auth.basicauth: app_factory' ,
> + #'auth=
> + 'auth=swift.
Was leaving it this way a mistake? The basic auth isn't super useful right now, and removing the devauth filter completely breaks our dev and testing environments.