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