Merge lp:~gholt/swift/deswauth into lp:~hudson-openstack/swift/trunk

Proposed by gholt
Status: Merged
Approved by: David Goetz
Approved revision: 307
Merged at revision: 306
Proposed branch: lp:~gholt/swift/deswauth
Merge into: lp:~hudson-openstack/swift/trunk
Diff against target: 6463 lines (+675/-5219)
22 files modified
bin/swauth-add-account (+0/-68)
bin/swauth-add-user (+0/-93)
bin/swauth-cleanup-tokens (+0/-118)
bin/swauth-delete-account (+0/-60)
bin/swauth-delete-user (+0/-60)
bin/swauth-list (+0/-86)
bin/swauth-prep (+0/-59)
bin/swauth-set-account-service (+0/-73)
doc/source/admin_guide.rst (+0/-16)
doc/source/deployment_guide.rst (+33/-28)
doc/source/development_auth.rst (+7/-7)
doc/source/development_saio.rst (+11/-19)
doc/source/howto_installmultinode.rst (+11/-52)
doc/source/misc.rst (+6/-6)
doc/source/overview_auth.rst (+8/-151)
etc/proxy-server.conf-sample (+24/-17)
setup.py (+1/-5)
swift/common/middleware/staticweb.py (+1/-1)
swift/common/middleware/swauth.py (+0/-1374)
swift/common/middleware/tempauth.py (+482/-0)
test/probe/common.py (+19/-21)
test/unit/common/middleware/test_tempauth.py (+72/-2905)
To merge this branch: bzr merge lp:~gholt/swift/deswauth
Reviewer Review Type Date Requested Status
David Goetz (community) Approve
John Dickinson Approve
Review via email: mp+62392@code.launchpad.net

Description of the change

In light of the upcoming Keystone auth service, which should become the standard OpenStack auth service, Swauth will need to move to its own project. Because we don't want the Swift project to require Keystone (or Swauth or any other auth service for that matter) we need a "placeholder". In this proposal I'm adding a TempAuth to fill our auth testing needs within Swift and removing Swauth.

To quickly change from Swauth on a standard SAIO install, change swauth in your pipeline to tempauth and add the following section:

[filter:tempauth]
use = egg:swift#tempauth
user_test_tester = testing .admin
user_test2_tester2 = testing2 .admin
user_test_tester3 = testing3
user_system_root = testpass .admin .reseller_admin

To post a comment you must log in.
lp:~gholt/swift/deswauth updated
305. By gholt

Merged from trunk

306. By gholt

Made probe tests more resilent against timing issues.

Revision history for this message
John Dickinson (notmyname) wrote :

quite a large diff, but it's mostly simple changes to remove swauth. The tempauth looks fine.

review: Approve
lp:~gholt/swift/deswauth updated
307. By gholt

tempauth: Check and make all handled accounts

Revision history for this message
David Goetz (david-goetz) wrote :

looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'bin/swauth-add-account'
2--- bin/swauth-add-account 2011-04-18 16:08:48 +0000
3+++ bin/swauth-add-account 1970-01-01 00:00:00 +0000
4@@ -1,68 +0,0 @@
5-#!/usr/bin/env python
6-# Copyright (c) 2010 OpenStack, LLC.
7-#
8-# Licensed under the Apache License, Version 2.0 (the "License");
9-# you may not use this file except in compliance with the License.
10-# You may obtain a copy of the License at
11-#
12-# http://www.apache.org/licenses/LICENSE-2.0
13-#
14-# Unless required by applicable law or agreed to in writing, software
15-# distributed under the License is distributed on an "AS IS" BASIS,
16-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17-# implied.
18-# See the License for the specific language governing permissions and
19-# limitations under the License.
20-
21-import gettext
22-from optparse import OptionParser
23-from os.path import basename
24-from sys import argv, exit
25-
26-from swift.common.bufferedhttp import http_connect_raw as http_connect
27-from swift.common.utils import urlparse
28-
29-
30-if __name__ == '__main__':
31- gettext.install('swift', unicode=1)
32- parser = OptionParser(usage='Usage: %prog [options] <account>')
33- parser.add_option('-s', '--suffix', dest='suffix',
34- default='', help='The suffix to use with the reseller prefix as the '
35- 'storage account name (default: <randomly-generated-uuid4>) Note: If '
36- 'the account already exists, this will have no effect on existing '
37- 'service URLs. Those will need to be updated with '
38- 'swauth-set-account-service')
39- parser.add_option('-A', '--admin-url', dest='admin_url',
40- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
41- 'subsystem (default: http://127.0.0.1:8080/auth/)')
42- parser.add_option('-U', '--admin-user', dest='admin_user',
43- default='.super_admin', help='The user with admin rights to add users '
44- '(default: .super_admin).')
45- parser.add_option('-K', '--admin-key', dest='admin_key',
46- help='The key for the user with admin rights to add users.')
47- args = argv[1:]
48- if not args:
49- args.append('-h')
50- (options, args) = parser.parse_args(args)
51- if len(args) != 1:
52- parser.parse_args(['-h'])
53- account = args[0]
54- parsed = urlparse(options.admin_url)
55- if parsed.scheme not in ('http', 'https'):
56- raise Exception('Cannot handle protocol scheme %s for url %s' %
57- (parsed.scheme, repr(options.admin_url)))
58- parsed_path = parsed.path
59- if not parsed_path:
60- parsed_path = '/'
61- elif parsed_path[-1] != '/':
62- parsed_path += '/'
63- path = '%sv2/%s' % (parsed_path, account)
64- headers = {'X-Auth-Admin-User': options.admin_user,
65- 'X-Auth-Admin-Key': options.admin_key}
66- if options.suffix:
67- headers['X-Account-Suffix'] = options.suffix
68- conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
69- ssl=(parsed.scheme == 'https'))
70- resp = conn.getresponse()
71- if resp.status // 100 != 2:
72- exit('Account creation failed: %s %s' % (resp.status, resp.reason))
73
74=== removed file 'bin/swauth-add-user'
75--- bin/swauth-add-user 2011-04-18 16:08:48 +0000
76+++ bin/swauth-add-user 1970-01-01 00:00:00 +0000
77@@ -1,93 +0,0 @@
78-#!/usr/bin/env python
79-# Copyright (c) 2010 OpenStack, LLC.
80-#
81-# Licensed under the Apache License, Version 2.0 (the "License");
82-# you may not use this file except in compliance with the License.
83-# You may obtain a copy of the License at
84-#
85-# http://www.apache.org/licenses/LICENSE-2.0
86-#
87-# Unless required by applicable law or agreed to in writing, software
88-# distributed under the License is distributed on an "AS IS" BASIS,
89-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
90-# implied.
91-# See the License for the specific language governing permissions and
92-# limitations under the License.
93-
94-import gettext
95-from optparse import OptionParser
96-from os.path import basename
97-from sys import argv, exit
98-
99-from swift.common.bufferedhttp import http_connect_raw as http_connect
100-from swift.common.utils import urlparse
101-
102-
103-if __name__ == '__main__':
104- gettext.install('swift', unicode=1)
105- parser = OptionParser(
106- usage='Usage: %prog [options] <account> <user> <password>')
107- parser.add_option('-a', '--admin', dest='admin', action='store_true',
108- default=False, help='Give the user administrator access; otherwise '
109- 'the user will only have access to containers specifically allowed '
110- 'with ACLs.')
111- parser.add_option('-r', '--reseller-admin', dest='reseller_admin',
112- action='store_true', default=False, help='Give the user full reseller '
113- 'administrator access, giving them full access to all accounts within '
114- 'the reseller, including the ability to create new accounts. Creating '
115- 'a new reseller admin requires super_admin rights.')
116- parser.add_option('-s', '--suffix', dest='suffix',
117- default='', help='The suffix to use with the reseller prefix as the '
118- 'storage account name (default: <randomly-generated-uuid4>) Note: If '
119- 'the account already exists, this will have no effect on existing '
120- 'service URLs. Those will need to be updated with '
121- 'swauth-set-account-service')
122- parser.add_option('-A', '--admin-url', dest='admin_url',
123- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
124- 'subsystem (default: http://127.0.0.1:8080/auth/')
125- parser.add_option('-U', '--admin-user', dest='admin_user',
126- default='.super_admin', help='The user with admin rights to add users '
127- '(default: .super_admin).')
128- parser.add_option('-K', '--admin-key', dest='admin_key',
129- help='The key for the user with admin rights to add users.')
130- args = argv[1:]
131- if not args:
132- args.append('-h')
133- (options, args) = parser.parse_args(args)
134- if len(args) != 3:
135- parser.parse_args(['-h'])
136- account, user, password = args
137- parsed = urlparse(options.admin_url)
138- if parsed.scheme not in ('http', 'https'):
139- raise Exception('Cannot handle protocol scheme %s for url %s' %
140- (parsed.scheme, repr(options.admin_url)))
141- parsed_path = parsed.path
142- if not parsed_path:
143- parsed_path = '/'
144- elif parsed_path[-1] != '/':
145- parsed_path += '/'
146- # Ensure the account exists
147- path = '%sv2/%s' % (parsed_path, account)
148- headers = {'X-Auth-Admin-User': options.admin_user,
149- 'X-Auth-Admin-Key': options.admin_key}
150- if options.suffix:
151- headers['X-Account-Suffix'] = options.suffix
152- conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
153- ssl=(parsed.scheme == 'https'))
154- resp = conn.getresponse()
155- if resp.status // 100 != 2:
156- print 'Account creation failed: %s %s' % (resp.status, resp.reason)
157- # Add the user
158- path = '%sv2/%s/%s' % (parsed_path, account, user)
159- headers = {'X-Auth-Admin-User': options.admin_user,
160- 'X-Auth-Admin-Key': options.admin_key,
161- 'X-Auth-User-Key': password}
162- if options.admin:
163- headers['X-Auth-User-Admin'] = 'true'
164- if options.reseller_admin:
165- headers['X-Auth-User-Reseller-Admin'] = 'true'
166- conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
167- ssl=(parsed.scheme == 'https'))
168- resp = conn.getresponse()
169- if resp.status // 100 != 2:
170- exit('User creation failed: %s %s' % (resp.status, resp.reason))
171
172=== removed file 'bin/swauth-cleanup-tokens'
173--- bin/swauth-cleanup-tokens 2011-04-18 16:08:48 +0000
174+++ bin/swauth-cleanup-tokens 1970-01-01 00:00:00 +0000
175@@ -1,118 +0,0 @@
176-#!/usr/bin/env python
177-# Copyright (c) 2010 OpenStack, LLC.
178-#
179-# Licensed under the Apache License, Version 2.0 (the "License");
180-# you may not use this file except in compliance with the License.
181-# You may obtain a copy of the License at
182-#
183-# http://www.apache.org/licenses/LICENSE-2.0
184-#
185-# Unless required by applicable law or agreed to in writing, software
186-# distributed under the License is distributed on an "AS IS" BASIS,
187-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
188-# implied.
189-# See the License for the specific language governing permissions and
190-# limitations under the License.
191-
192-try:
193- import simplejson as json
194-except ImportError:
195- import json
196-import gettext
197-import re
198-from datetime import datetime, timedelta
199-from optparse import OptionParser
200-from sys import argv, exit
201-from time import sleep, time
202-
203-from swift.common.client import Connection, ClientException
204-
205-
206-if __name__ == '__main__':
207- gettext.install('swift', unicode=1)
208- parser = OptionParser(usage='Usage: %prog [options]')
209- parser.add_option('-t', '--token-life', dest='token_life',
210- default='86400', help='The expected life of tokens; token objects '
211- 'modified more than this number of seconds ago will be checked for '
212- 'expiration (default: 86400).')
213- parser.add_option('-s', '--sleep', dest='sleep',
214- default='0.1', help='The number of seconds to sleep between token '
215- 'checks (default: 0.1)')
216- parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
217- default=False, help='Outputs everything done instead of just the '
218- 'deletions.')
219- parser.add_option('-A', '--admin-url', dest='admin_url',
220- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
221- 'subsystem (default: http://127.0.0.1:8080/auth/)')
222- parser.add_option('-K', '--admin-key', dest='admin_key',
223- help='The key for .super_admin.')
224- args = argv[1:]
225- if not args:
226- args.append('-h')
227- (options, args) = parser.parse_args(args)
228- if len(args) != 0:
229- parser.parse_args(['-h'])
230- options.admin_url = options.admin_url.rstrip('/')
231- if not options.admin_url.endswith('/v1.0'):
232- options.admin_url += '/v1.0'
233- options.admin_user = '.super_admin:.super_admin'
234- options.token_life = timedelta(0, float(options.token_life))
235- options.sleep = float(options.sleep)
236- conn = Connection(options.admin_url, options.admin_user, options.admin_key)
237- for x in xrange(16):
238- container = '.token_%x' % x
239- marker = None
240- while True:
241- if options.verbose:
242- print 'GET %s?marker=%s' % (container, marker)
243- try:
244- objs = conn.get_container(container, marker=marker)[1]
245- except ClientException, e:
246- if e.http_status == 404:
247- exit('Container %s not found. swauth-prep needs to be '
248- 'rerun' % (container))
249- else:
250- exit('Object listing on container %s failed with status '
251- 'code %d' % (container, e.http_status))
252- if objs:
253- marker = objs[-1]['name']
254- else:
255- if options.verbose:
256- print 'No more objects in %s' % container
257- break
258- for obj in objs:
259- last_modified = datetime(*map(int, re.split('[^\d]',
260- obj['last_modified'])[:-1]))
261- ago = datetime.utcnow() - last_modified
262- if ago > options.token_life:
263- if options.verbose:
264- print '%s/%s last modified %ss ago; investigating' % \
265- (container, obj['name'],
266- ago.days * 86400 + ago.seconds)
267- print 'GET %s/%s' % (container, obj['name'])
268- detail = conn.get_object(container, obj['name'])[1]
269- detail = json.loads(detail)
270- if detail['expires'] < time():
271- if options.verbose:
272- print '%s/%s expired %ds ago; deleting' % \
273- (container, obj['name'],
274- time() - detail['expires'])
275- print 'DELETE %s/%s' % (container, obj['name'])
276- try:
277- conn.delete_object(container, obj['name'])
278- except ClientException, e:
279- if e.http_status != 404:
280- print 'DELETE of %s/%s failed with status ' \
281- 'code %d' % (container, obj['name'],
282- e.http_status)
283- elif options.verbose:
284- print "%s/%s won't expire for %ds; skipping" % \
285- (container, obj['name'],
286- detail['expires'] - time())
287- elif options.verbose:
288- print '%s/%s last modified %ss ago; skipping' % \
289- (container, obj['name'],
290- ago.days * 86400 + ago.seconds)
291- sleep(options.sleep)
292- if options.verbose:
293- print 'Done.'
294
295=== removed file 'bin/swauth-delete-account'
296--- bin/swauth-delete-account 2011-04-18 16:08:48 +0000
297+++ bin/swauth-delete-account 1970-01-01 00:00:00 +0000
298@@ -1,60 +0,0 @@
299-#!/usr/bin/env python
300-# Copyright (c) 2010 OpenStack, LLC.
301-#
302-# Licensed under the Apache License, Version 2.0 (the "License");
303-# you may not use this file except in compliance with the License.
304-# You may obtain a copy of the License at
305-#
306-# http://www.apache.org/licenses/LICENSE-2.0
307-#
308-# Unless required by applicable law or agreed to in writing, software
309-# distributed under the License is distributed on an "AS IS" BASIS,
310-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
311-# implied.
312-# See the License for the specific language governing permissions and
313-# limitations under the License.
314-
315-import gettext
316-from optparse import OptionParser
317-from os.path import basename
318-from sys import argv, exit
319-
320-from swift.common.bufferedhttp import http_connect_raw as http_connect
321-from swift.common.utils import urlparse
322-
323-
324-if __name__ == '__main__':
325- gettext.install('swift', unicode=1)
326- parser = OptionParser(usage='Usage: %prog [options] <account>')
327- parser.add_option('-A', '--admin-url', dest='admin_url',
328- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
329- 'subsystem (default: http://127.0.0.1:8080/auth/')
330- parser.add_option('-U', '--admin-user', dest='admin_user',
331- default='.super_admin', help='The user with admin rights to add users '
332- '(default: .super_admin).')
333- parser.add_option('-K', '--admin-key', dest='admin_key',
334- help='The key for the user with admin rights to add users.')
335- args = argv[1:]
336- if not args:
337- args.append('-h')
338- (options, args) = parser.parse_args(args)
339- if len(args) != 1:
340- parser.parse_args(['-h'])
341- account = args[0]
342- parsed = urlparse(options.admin_url)
343- if parsed.scheme not in ('http', 'https'):
344- raise Exception('Cannot handle protocol scheme %s for url %s' %
345- (parsed.scheme, repr(options.admin_url)))
346- parsed_path = parsed.path
347- if not parsed_path:
348- parsed_path = '/'
349- elif parsed_path[-1] != '/':
350- parsed_path += '/'
351- path = '%sv2/%s' % (parsed_path, account)
352- headers = {'X-Auth-Admin-User': options.admin_user,
353- 'X-Auth-Admin-Key': options.admin_key}
354- conn = http_connect(parsed.hostname, parsed.port, 'DELETE', path, headers,
355- ssl=(parsed.scheme == 'https'))
356- resp = conn.getresponse()
357- if resp.status // 100 != 2:
358- exit('Account deletion failed: %s %s' % (resp.status, resp.reason))
359
360=== removed file 'bin/swauth-delete-user'
361--- bin/swauth-delete-user 2011-04-18 16:08:48 +0000
362+++ bin/swauth-delete-user 1970-01-01 00:00:00 +0000
363@@ -1,60 +0,0 @@
364-#!/usr/bin/env python
365-# Copyright (c) 2010 OpenStack, LLC.
366-#
367-# Licensed under the Apache License, Version 2.0 (the "License");
368-# you may not use this file except in compliance with the License.
369-# You may obtain a copy of the License at
370-#
371-# http://www.apache.org/licenses/LICENSE-2.0
372-#
373-# Unless required by applicable law or agreed to in writing, software
374-# distributed under the License is distributed on an "AS IS" BASIS,
375-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
376-# implied.
377-# See the License for the specific language governing permissions and
378-# limitations under the License.
379-
380-import gettext
381-from optparse import OptionParser
382-from os.path import basename
383-from sys import argv, exit
384-
385-from swift.common.bufferedhttp import http_connect_raw as http_connect
386-from swift.common.utils import urlparse
387-
388-
389-if __name__ == '__main__':
390- gettext.install('swift', unicode=1)
391- parser = OptionParser(usage='Usage: %prog [options] <account> <user>')
392- parser.add_option('-A', '--admin-url', dest='admin_url',
393- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
394- 'subsystem (default: http://127.0.0.1:8080/auth/')
395- parser.add_option('-U', '--admin-user', dest='admin_user',
396- default='.super_admin', help='The user with admin rights to add users '
397- '(default: .super_admin).')
398- parser.add_option('-K', '--admin-key', dest='admin_key',
399- help='The key for the user with admin rights to add users.')
400- args = argv[1:]
401- if not args:
402- args.append('-h')
403- (options, args) = parser.parse_args(args)
404- if len(args) != 2:
405- parser.parse_args(['-h'])
406- account, user = args
407- parsed = urlparse(options.admin_url)
408- if parsed.scheme not in ('http', 'https'):
409- raise Exception('Cannot handle protocol scheme %s for url %s' %
410- (parsed.scheme, repr(options.admin_url)))
411- parsed_path = parsed.path
412- if not parsed_path:
413- parsed_path = '/'
414- elif parsed_path[-1] != '/':
415- parsed_path += '/'
416- path = '%sv2/%s/%s' % (parsed_path, account, user)
417- headers = {'X-Auth-Admin-User': options.admin_user,
418- 'X-Auth-Admin-Key': options.admin_key}
419- conn = http_connect(parsed.hostname, parsed.port, 'DELETE', path, headers,
420- ssl=(parsed.scheme == 'https'))
421- resp = conn.getresponse()
422- if resp.status // 100 != 2:
423- exit('User deletion failed: %s %s' % (resp.status, resp.reason))
424
425=== removed file 'bin/swauth-list'
426--- bin/swauth-list 2011-04-18 16:08:48 +0000
427+++ bin/swauth-list 1970-01-01 00:00:00 +0000
428@@ -1,86 +0,0 @@
429-#!/usr/bin/env python
430-# Copyright (c) 2010 OpenStack, LLC.
431-#
432-# Licensed under the Apache License, Version 2.0 (the "License");
433-# you may not use this file except in compliance with the License.
434-# You may obtain a copy of the License at
435-#
436-# http://www.apache.org/licenses/LICENSE-2.0
437-#
438-# Unless required by applicable law or agreed to in writing, software
439-# distributed under the License is distributed on an "AS IS" BASIS,
440-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
441-# implied.
442-# See the License for the specific language governing permissions and
443-# limitations under the License.
444-
445-try:
446- import simplejson as json
447-except ImportError:
448- import json
449-import gettext
450-from optparse import OptionParser
451-from os.path import basename
452-from sys import argv, exit
453-
454-from swift.common.bufferedhttp import http_connect_raw as http_connect
455-from swift.common.utils import urlparse
456-
457-
458-if __name__ == '__main__':
459- gettext.install('swift', unicode=1)
460- parser = OptionParser(usage='''
461-Usage: %prog [options] [account] [user]
462-
463-If [account] and [user] are omitted, a list of accounts will be output.
464-
465-If [account] is included but not [user], an account's information will be
466-output, including a list of users within the account.
467-
468-If [account] and [user] are included, the user's information will be output,
469-including a list of groups the user belongs to.
470-
471-If the [user] is '.groups', the active groups for the account will be listed.
472-'''.strip())
473- parser.add_option('-p', '--plain-text', dest='plain_text',
474- action='store_true', default=False, help='Changes the output from '
475- 'JSON to plain text. This will cause an account to list only the '
476- 'users and a user to list only the groups.')
477- parser.add_option('-A', '--admin-url', dest='admin_url',
478- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
479- 'subsystem (default: http://127.0.0.1:8080/auth/')
480- parser.add_option('-U', '--admin-user', dest='admin_user',
481- default='.super_admin', help='The user with admin rights to add users '
482- '(default: .super_admin).')
483- parser.add_option('-K', '--admin-key', dest='admin_key',
484- help='The key for the user with admin rights to add users.')
485- args = argv[1:]
486- if not args:
487- args.append('-h')
488- (options, args) = parser.parse_args(args)
489- if len(args) > 2:
490- parser.parse_args(['-h'])
491- parsed = urlparse(options.admin_url)
492- if parsed.scheme not in ('http', 'https'):
493- raise Exception('Cannot handle protocol scheme %s for url %s' %
494- (parsed.scheme, repr(options.admin_url)))
495- parsed_path = parsed.path
496- if not parsed_path:
497- parsed_path = '/'
498- elif parsed_path[-1] != '/':
499- parsed_path += '/'
500- path = '%sv2/%s' % (parsed_path, '/'.join(args))
501- headers = {'X-Auth-Admin-User': options.admin_user,
502- 'X-Auth-Admin-Key': options.admin_key}
503- conn = http_connect(parsed.hostname, parsed.port, 'GET', path, headers,
504- ssl=(parsed.scheme == 'https'))
505- resp = conn.getresponse()
506- body = resp.read()
507- if resp.status // 100 != 2:
508- exit('List failed: %s %s' % (resp.status, resp.reason))
509- if options.plain_text:
510- info = json.loads(body)
511- for group in info[['accounts', 'users', 'groups'][len(args)]]:
512- print group['name']
513- else:
514- print body
515
516=== removed file 'bin/swauth-prep'
517--- bin/swauth-prep 2011-04-18 16:08:48 +0000
518+++ bin/swauth-prep 1970-01-01 00:00:00 +0000
519@@ -1,59 +0,0 @@
520-#!/usr/bin/env python
521-# Copyright (c) 2010 OpenStack, LLC.
522-#
523-# Licensed under the Apache License, Version 2.0 (the "License");
524-# you may not use this file except in compliance with the License.
525-# You may obtain a copy of the License at
526-#
527-# http://www.apache.org/licenses/LICENSE-2.0
528-#
529-# Unless required by applicable law or agreed to in writing, software
530-# distributed under the License is distributed on an "AS IS" BASIS,
531-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
532-# implied.
533-# See the License for the specific language governing permissions and
534-# limitations under the License.
535-
536-import gettext
537-from optparse import OptionParser
538-from os.path import basename
539-from sys import argv, exit
540-
541-from swift.common.bufferedhttp import http_connect_raw as http_connect
542-from swift.common.utils import urlparse
543-
544-
545-if __name__ == '__main__':
546- gettext.install('swift', unicode=1)
547- parser = OptionParser(usage='Usage: %prog [options]')
548- parser.add_option('-A', '--admin-url', dest='admin_url',
549- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
550- 'subsystem (default: http://127.0.0.1:8080/auth/')
551- parser.add_option('-U', '--admin-user', dest='admin_user',
552- default='.super_admin', help='The user with admin rights to add users '
553- '(default: .super_admin).')
554- parser.add_option('-K', '--admin-key', dest='admin_key',
555- help='The key for the user with admin rights to add users.')
556- args = argv[1:]
557- if not args:
558- args.append('-h')
559- (options, args) = parser.parse_args(args)
560- if args:
561- parser.parse_args(['-h'])
562- parsed = urlparse(options.admin_url)
563- if parsed.scheme not in ('http', 'https'):
564- raise Exception('Cannot handle protocol scheme %s for url %s' %
565- (parsed.scheme, repr(options.admin_url)))
566- parsed_path = parsed.path
567- if not parsed_path:
568- parsed_path = '/'
569- elif parsed_path[-1] != '/':
570- parsed_path += '/'
571- path = '%sv2/.prep' % parsed_path
572- headers = {'X-Auth-Admin-User': options.admin_user,
573- 'X-Auth-Admin-Key': options.admin_key}
574- conn = http_connect(parsed.hostname, parsed.port, 'POST', path, headers,
575- ssl=(parsed.scheme == 'https'))
576- resp = conn.getresponse()
577- if resp.status // 100 != 2:
578- exit('Auth subsystem prep failed: %s %s' % (resp.status, resp.reason))
579
580=== removed file 'bin/swauth-set-account-service'
581--- bin/swauth-set-account-service 2011-04-18 16:08:48 +0000
582+++ bin/swauth-set-account-service 1970-01-01 00:00:00 +0000
583@@ -1,73 +0,0 @@
584-#!/usr/bin/env python
585-# Copyright (c) 2010 OpenStack, LLC.
586-#
587-# Licensed under the Apache License, Version 2.0 (the "License");
588-# you may not use this file except in compliance with the License.
589-# You may obtain a copy of the License at
590-#
591-# http://www.apache.org/licenses/LICENSE-2.0
592-#
593-# Unless required by applicable law or agreed to in writing, software
594-# distributed under the License is distributed on an "AS IS" BASIS,
595-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
596-# implied.
597-# See the License for the specific language governing permissions and
598-# limitations under the License.
599-
600-try:
601- import simplejson as json
602-except ImportError:
603- import json
604-import gettext
605-from optparse import OptionParser
606-from os.path import basename
607-from sys import argv, exit
608-
609-from swift.common.bufferedhttp import http_connect_raw as http_connect
610-from swift.common.utils import urlparse
611-
612-
613-if __name__ == '__main__':
614- gettext.install('swift', unicode=1)
615- parser = OptionParser(usage='''
616-Usage: %prog [options] <account> <service> <name> <value>
617-
618-Sets a service URL for an account. Can only be set by a reseller admin.
619-
620-Example: %prog -K swauthkey test storage local http://127.0.0.1:8080/v1/AUTH_018c3946-23f8-4efb-a8fb-b67aae8e4162
621-'''.strip())
622- parser.add_option('-A', '--admin-url', dest='admin_url',
623- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
624- 'subsystem (default: http://127.0.0.1:8080/auth/)')
625- parser.add_option('-U', '--admin-user', dest='admin_user',
626- default='.super_admin', help='The user with admin rights to add users '
627- '(default: .super_admin).')
628- parser.add_option('-K', '--admin-key', dest='admin_key',
629- help='The key for the user with admin rights to add users.')
630- args = argv[1:]
631- if not args:
632- args.append('-h')
633- (options, args) = parser.parse_args(args)
634- if len(args) != 4:
635- parser.parse_args(['-h'])
636- account, service, name, url = args
637- parsed = urlparse(options.admin_url)
638- if parsed.scheme not in ('http', 'https'):
639- raise Exception('Cannot handle protocol scheme %s for url %s' %
640- (parsed.scheme, repr(options.admin_url)))
641- parsed_path = parsed.path
642- if not parsed_path:
643- parsed_path = '/'
644- elif parsed_path[-1] != '/':
645- parsed_path += '/'
646- path = '%sv2/%s/.services' % (parsed_path, account)
647- body = json.dumps({service: {name: url}})
648- headers = {'Content-Length': str(len(body)),
649- 'X-Auth-Admin-User': options.admin_user,
650- 'X-Auth-Admin-Key': options.admin_key}
651- conn = http_connect(parsed.hostname, parsed.port, 'POST', path, headers,
652- ssl=(parsed.scheme == 'https'))
653- conn.send(body)
654- resp = conn.getresponse()
655- if resp.status // 100 != 2:
656- exit('Service set failed: %s %s' % (resp.status, resp.reason))
657
658=== modified file 'doc/source/admin_guide.rst'
659--- doc/source/admin_guide.rst 2011-03-31 22:32:41 +0000
660+++ doc/source/admin_guide.rst 2011-06-09 21:09:39 +0000
661@@ -222,22 +222,6 @@
662 Sample represents 1.00% of the object partition space
663
664
665-------------------------------------
666-Additional Cleanup Script for Swauth
667-------------------------------------
668-
669-With Swauth, you'll want to install a cronjob to clean up any
670-orphaned expired tokens. These orphaned tokens can occur when a "stampede"
671-occurs where a single user authenticates several times concurrently. Generally,
672-these orphaned tokens don't pose much of an issue, but it's good to clean them
673-up once a "token life" period (default: 1 day or 86400 seconds).
674-
675-This should be as simple as adding `swauth-cleanup-tokens -A
676-https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
677-entry on one of the proxies that is running Swauth; but run
678-`swauth-cleanup-tokens` with no arguments for detailed help on the options
679-available.
680-
681 ------------------------
682 Debugging Tips and Tools
683 ------------------------
684
685=== modified file 'doc/source/deployment_guide.rst'
686--- doc/source/deployment_guide.rst 2011-01-25 00:28:22 +0000
687+++ doc/source/deployment_guide.rst 2011-06-09 21:09:39 +0000
688@@ -549,35 +549,17 @@
689 are even callable
690 ============================ =============== =============================
691
692-[auth]
693-
694-============ =================================== ========================
695-Option Default Description
696------------- ----------------------------------- ------------------------
697-use Entry point for paste.deploy
698- to use for auth. To
699- use the swift dev auth,
700- set to:
701- `egg:swift#auth`
702-ip 127.0.0.1 IP address of auth
703- server
704-port 11000 Port of auth server
705-ssl False If True, use SSL to
706- connect to auth
707-node_timeout 10 Request timeout
708-============ =================================== ========================
709-
710-[swauth]
711+[tempauth]
712
713 ===================== =============================== =======================
714 Option Default Description
715 --------------------- ------------------------------- -----------------------
716 use Entry point for
717 paste.deploy to use for
718- auth. To use the swauth
719+ auth. To use tempauth
720 set to:
721- `egg:swift#swauth`
722-set log_name auth-server Label used when logging
723+ `egg:swift#tempauth`
724+set log_name tempauth Label used when logging
725 set log_facility LOG_LOCAL0 Syslog log facility
726 set log_level INFO Log level
727 set log_headers True If True, log headers in
728@@ -593,16 +575,39 @@
729 reserves anything
730 beginning with the
731 letter `v`.
732-default_swift_cluster local#http://127.0.0.1:8080/v1 The default Swift
733- cluster to place newly
734- created accounts on.
735 token_life 86400 The number of seconds a
736 token is valid.
737-node_timeout 10 Request timeout
738-super_admin_key None The key for the
739- .super_admin account.
740 ===================== =============================== =======================
741
742+Additionally, you need to list all the accounts/users you want here. The format
743+is::
744+
745+ user_<account>_<user> = <key> [group] [group] [...] [storage_url]
746+
747+There are special groups of::
748+
749+ .reseller_admin = can do anything to any account for this auth
750+ .admin = can do anything within the account
751+
752+If neither of these groups are specified, the user can only access containers
753+that have been explicitly allowed for them by a .admin or .reseller_admin.
754+
755+The trailing optional storage_url allows you to specify an alternate url to
756+hand back to the user upon authentication. If not specified, this defaults to::
757+
758+ http[s]://<ip>:<port>/v1/<reseller_prefix>_<account>
759+
760+Where http or https depends on whether cert_file is specified in the [DEFAULT]
761+section, <ip> and <port> are based on the [DEFAULT] section's bind_ip and
762+bind_port (falling back to 127.0.0.1 and 8080), <reseller_prefix> is from this
763+section, and <account> is from the user_<account>_<user> name.
764+
765+Here are example entries, required for running the tests::
766+
767+ user_admin_admin = admin .admin .reseller_admin
768+ user_test_tester = testing .admin
769+ user_test2_tester2 = testing2 .admin
770+ user_test_tester3 = testing3
771
772 ------------------------
773 Memcached Considerations
774
775=== modified file 'doc/source/development_auth.rst'
776--- doc/source/development_auth.rst 2011-03-14 02:56:37 +0000
777+++ doc/source/development_auth.rst 2011-06-09 21:09:39 +0000
778@@ -6,7 +6,7 @@
779 Creating Your Own Auth Server and Middleware
780 --------------------------------------------
781
782-The included swift/common/middleware/swauth.py is a good example of how to
783+The included swift/common/middleware/tempauth.py is a good example of how to
784 create an auth subsystem with proxy server auth middleware. The main points are
785 that the auth middleware can reject requests up front, before they ever get to
786 the Swift Proxy application, and afterwards when the proxy issues callbacks to
787@@ -27,7 +27,7 @@
788 environ['REMOTE_USER'] set to the authenticated user string but often more
789 information is needed than just that.
790
791-The included Swauth will set the REMOTE_USER to a comma separated list of
792+The included TempAuth will set the REMOTE_USER to a comma separated list of
793 groups the user belongs to. The first group will be the "user's group", a group
794 that only the user belongs to. The second group will be the "account's group",
795 a group that includes all users for that auth account (different than the
796@@ -37,7 +37,7 @@
797
798 It is highly recommended that authentication server implementers prefix their
799 tokens and Swift storage accounts they create with a configurable reseller
800-prefix (`AUTH_` by default with the included Swauth). This prefix will avoid
801+prefix (`AUTH_` by default with the included TempAuth). This prefix will avoid
802 conflicts with other authentication servers that might be using the same
803 Swift cluster. Otherwise, the Swift cluster will have to try all the resellers
804 until one validates a token or all fail.
805@@ -46,14 +46,14 @@
806 '.' as that is reserved for internal Swift use (such as the .r for referrer
807 designations as you'll see later).
808
809-Example Authentication with Swauth:
810+Example Authentication with TempAuth:
811
812- * Token AUTH_tkabcd is given to the Swauth middleware in a request's
813+ * Token AUTH_tkabcd is given to the TempAuth middleware in a request's
814 X-Auth-Token header.
815- * The Swauth middleware validates the token AUTH_tkabcd and discovers
816+ * The TempAuth middleware validates the token AUTH_tkabcd and discovers
817 it matches the "tester" user within the "test" account for the storage
818 account "AUTH_storage_xyz".
819- * The Swauth server sets the REMOTE_USER to
820+ * The TempAuth middleware sets the REMOTE_USER to
821 "test:tester,test,AUTH_storage_xyz"
822 * Now this user will have full access (via authorization procedures later)
823 to the AUTH_storage_xyz Swift storage account and access to containers in
824
825=== modified file 'doc/source/development_saio.rst'
826--- doc/source/development_saio.rst 2011-05-18 15:26:52 +0000
827+++ doc/source/development_saio.rst 2011-06-09 21:09:39 +0000
828@@ -265,16 +265,18 @@
829 log_facility = LOG_LOCAL1
830
831 [pipeline:main]
832- pipeline = healthcheck cache swauth proxy-server
833+ pipeline = healthcheck cache tempauth proxy-server
834
835 [app:proxy-server]
836 use = egg:swift#proxy
837 allow_account_management = true
838
839- [filter:swauth]
840- use = egg:swift#swauth
841- # Highly recommended to change this.
842- super_admin_key = swauthkey
843+ [filter:tempauth]
844+ use = egg:swift#tempauth
845+ user_admin_admin = admin .admin .reseller_admin
846+ user_test_tester = testing .admin
847+ user_test2_tester2 = testing2 .admin
848+ user_test_tester3 = testing3
849
850 [filter:healthcheck]
851 use = egg:swift#healthcheck
852@@ -558,8 +560,10 @@
853 ------------------------------------
854
855 #. Create `~/bin/resetswift.`
856- If you are using a loopback device substitute `/dev/sdb1` with `/srv/swift-disk`.
857- If you did not set up rsyslog for individual logging, remove the `find /var/log/swift...` line::
858+
859+ If you are using a loopback device substitute `/dev/sdb1` with `/srv/swift-disk`.
860+
861+ If you did not set up rsyslog for individual logging, remove the `find /var/log/swift...` line::
862
863 #!/bin/bash
864
865@@ -608,18 +612,6 @@
866
867 swift-init main start
868
869- #. Create `~/bin/recreateaccounts`::
870-
871- #!/bin/bash
872-
873- # Replace swauthkey with whatever your super_admin key is (recorded in
874- # /etc/swift/proxy-server.conf).
875- swauth-prep -K swauthkey
876- swauth-add-user -K swauthkey -a test tester testing
877- swauth-add-user -K swauthkey -a test2 tester2 testing2
878- swauth-add-user -K swauthkey test tester3 testing3
879- swauth-add-user -K swauthkey -a -r reseller reseller reseller
880-
881 #. Create `~/bin/startrest`::
882
883 #!/bin/bash
884
885=== modified file 'doc/source/howto_installmultinode.rst'
886--- doc/source/howto_installmultinode.rst 2011-05-17 03:59:57 +0000
887+++ doc/source/howto_installmultinode.rst 2011-06-09 21:09:39 +0000
888@@ -13,7 +13,7 @@
889 Basic architecture and terms
890 ----------------------------
891 - *node* - a host machine running one or more Swift services
892-- *Proxy node* - node that runs Proxy services; also runs Swauth
893+- *Proxy node* - node that runs Proxy services; also runs TempAuth
894 - *Storage node* - node that runs Account, Container, and Object services
895 - *ring* - a set of mappings of Swift data to physical devices
896
897@@ -23,7 +23,7 @@
898
899 - Runs the swift-proxy-server processes which proxy requests to the
900 appropriate Storage nodes. The proxy server will also contain
901- the Swauth service as WSGI middleware.
902+ the TempAuth service as WSGI middleware.
903
904 - five Storage nodes
905
906@@ -130,17 +130,15 @@
907 user = swift
908
909 [pipeline:main]
910- pipeline = healthcheck cache swauth proxy-server
911+ pipeline = healthcheck cache tempauth proxy-server
912
913 [app:proxy-server]
914 use = egg:swift#proxy
915 allow_account_management = true
916
917- [filter:swauth]
918- use = egg:swift#swauth
919- default_swift_cluster = local#https://$PROXY_LOCAL_NET_IP:8080/v1
920- # Highly recommended to change this key to something else!
921- super_admin_key = swauthkey
922+ [filter:tempauth]
923+ use = egg:swift#tempauth
924+ user_system_root = testpass .admin https://$PROXY_LOCAL_NET_IP:8080/v1/AUTH_system
925
926 [filter:healthcheck]
927 use = egg:swift#healthcheck
928@@ -366,16 +364,6 @@
929
930 You run these commands from the Proxy node.
931
932-#. Create a user with administrative privileges (account = system,
933- username = root, password = testpass). Make sure to replace
934- ``swauthkey`` with whatever super_admin key you assigned in
935- the proxy-server.conf file
936- above. *Note: None of the values of
937- account, username, or password are special - they can be anything.*::
938-
939- swauth-prep -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey
940- swauth-add-user -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey -a system root testpass
941-
942 #. Get an X-Storage-Url and X-Auth-Token::
943
944 curl -k -v -H 'X-Storage-User: system:root' -H 'X-Storage-Pass: testpass' https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0
945@@ -430,45 +418,16 @@
946 use = egg:swift#memcache
947 memcache_servers = $PROXY_LOCAL_NET_IP:11211
948
949-#. Change the default_cluster_url to point to the load balanced url, rather than the first proxy server you created in /etc/swift/proxy-server.conf::
950-
951- [filter:swauth]
952- use = egg:swift#swauth
953- default_swift_cluster = local#http://<LOAD_BALANCER_HOSTNAME>/v1
954- # Highly recommended to change this key to something else!
955- super_admin_key = swauthkey
956-
957-#. The above will make new accounts with the new default_swift_cluster URL, however it won't change any existing accounts. You can change a service URL for existing accounts with::
958-
959- First retreve what the URL was::
960-
961- swauth-list -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey <account>
962-
963- And then update it with::
964-
965- swauth-set-account-service -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey <account> storage local <new_url_for_the_account>
966-
967- Make the <new_url_for_the_account> look just like it's original URL but with the host:port update you want.
968+#. Change the storage url for any users to point to the load balanced url, rather than the first proxy server you created in /etc/swift/proxy-server.conf::
969+
970+ [filter:tempauth]
971+ use = egg:swift#tempauth
972+ user_system_root = testpass .admin http[s]://<LOAD_BALANCER_HOSTNAME>:<PORT>/v1/AUTH_system
973
974 #. Next, copy all the ring information to all the nodes, including your new proxy nodes, and ensure the ring info gets to all the storage nodes as well.
975
976 #. After you sync all the nodes, make sure the admin has the keys in /etc/swift and the ownership for the ring file is correct.
977
978-Additional Cleanup Script for Swauth
979-------------------------------------
980-
981-With Swauth, you'll want to install a cronjob to clean up any
982-orphaned expired tokens. These orphaned tokens can occur when a "stampede"
983-occurs where a single user authenticates several times concurrently. Generally,
984-these orphaned tokens don't pose much of an issue, but it's good to clean them
985-up once a "token life" period (default: 1 day or 86400 seconds).
986-
987-This should be as simple as adding `swauth-cleanup-tokens -A
988-https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
989-entry on one of the proxies that is running Swauth; but run
990-`swauth-cleanup-tokens` with no arguments for detailed help on the options
991-available.
992-
993 Troubleshooting Notes
994 ---------------------
995 If you see problems, look in var/log/syslog (or messages on some distros).
996
997=== modified file 'doc/source/misc.rst'
998--- doc/source/misc.rst 2011-03-24 03:37:07 +0000
999+++ doc/source/misc.rst 2011-06-09 21:09:39 +0000
1000@@ -33,12 +33,12 @@
1001 :members:
1002 :show-inheritance:
1003
1004-.. _common_swauth:
1005-
1006-Swauth
1007-======
1008-
1009-.. automodule:: swift.common.middleware.swauth
1010+.. _common_tempauth:
1011+
1012+TempAuth
1013+========
1014+
1015+.. automodule:: swift.common.middleware.tempauth
1016 :members:
1017 :show-inheritance:
1018
1019
1020=== modified file 'doc/source/overview_auth.rst'
1021--- doc/source/overview_auth.rst 2011-03-14 02:56:37 +0000
1022+++ doc/source/overview_auth.rst 2011-06-09 21:09:39 +0000
1023@@ -2,9 +2,9 @@
1024 The Auth System
1025 ===============
1026
1027-------
1028-Swauth
1029-------
1030+--------
1031+TempAuth
1032+--------
1033
1034 The auth system for Swift is loosely based on the auth system from the existing
1035 Rackspace architecture -- actually from a few existing auth systems -- and is
1036@@ -27,7 +27,7 @@
1037 Swift will make calls to the auth system, giving the auth token to be
1038 validated. For a valid token, the auth system responds with an overall
1039 expiration in seconds from now. Swift will cache the token up to the expiration
1040-time. The included Swauth also has the concept of admin and non-admin users
1041+time. The included TempAuth also has the concept of admin and non-admin users
1042 within an account. Admin users can do anything within the account. Non-admin
1043 users can only perform operations per container based on the container's
1044 X-Container-Read and X-Container-Write ACLs. For more information on ACLs, see
1045@@ -40,152 +40,9 @@
1046 Extending Auth
1047 --------------
1048
1049-Swauth is written as wsgi middleware, so implementing your own auth is as easy
1050-as writing new wsgi middleware, and plugging it in to the proxy server.
1051+TempAuth is written as wsgi middleware, so implementing your own auth is as
1052+easy as writing new wsgi middleware, and plugging it in to the proxy server.
1053+The KeyStone project and the Swauth project are examples of additional auth
1054+services.
1055
1056 Also, see :doc:`development_auth`.
1057-
1058-
1059---------------
1060-Swauth Details
1061---------------
1062-
1063-The Swauth system is included at swift/common/middleware/swauth.py; a scalable
1064-authentication and authorization system that uses Swift itself as its backing
1065-store. This section will describe how it stores its data.
1066-
1067-At the topmost level, the auth system has its own Swift account it stores its
1068-own account information within. This Swift account is known as
1069-self.auth_account in the code and its name is in the format
1070-self.reseller_prefix + ".auth". In this text, we'll refer to this account as
1071-<auth_account>.
1072-
1073-The containers whose names do not begin with a period represent the accounts
1074-within the auth service. For example, the <auth_account>/test container would
1075-represent the "test" account.
1076-
1077-The objects within each container represent the users for that auth service
1078-account. For example, the <auth_account>/test/bob object would represent the
1079-user "bob" within the auth service account of "test". Each of these user
1080-objects contain a JSON dictionary of the format::
1081-
1082- {"auth": "<auth_type>:<auth_value>", "groups": <groups_array>}
1083-
1084-The `<auth_type>` can only be `plaintext` at this time, and the `<auth_value>`
1085-is the plain text password itself.
1086-
1087-The `<groups_array>` contains at least two groups. The first is a unique group
1088-identifying that user and it's name is of the format `<user>:<account>`. The
1089-second group is the `<account>` itself. Additional groups of `.admin` for
1090-account administrators and `.reseller_admin` for reseller administrators may
1091-exist. Here's an example user JSON dictionary::
1092-
1093- {"auth": "plaintext:testing",
1094- "groups": ["name": "test:tester", "name": "test", "name": ".admin"]}
1095-
1096-To map an auth service account to a Swift storage account, the Service Account
1097-Id string is stored in the `X-Container-Meta-Account-Id` header for the
1098-<auth_account>/<account> container. To map back the other way, an
1099-<auth_account>/.account_id/<account_id> object is created with the contents of
1100-the corresponding auth service's account name.
1101-
1102-Also, to support a future where the auth service will support multiple Swift
1103-clusters or even multiple services for the same auth service account, an
1104-<auth_account>/<account>/.services object is created with its contents having a
1105-JSON dictionary of the format::
1106-
1107- {"storage": {"default": "local", "local": <url>}}
1108-
1109-The "default" is always "local" right now, and "local" is always the single
1110-Swift cluster URL; but in the future there can be more than one cluster with
1111-various names instead of just "local", and the "default" key's value will
1112-contain the primary cluster to use for that account. Also, there may be more
1113-services in addition to the current "storage" service right now.
1114-
1115-Here's an example .services dictionary at the moment::
1116-
1117- {"storage":
1118- {"default": "local",
1119- "local": "http://127.0.0.1:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
1120-
1121-But, here's an example of what the dictionary may look like in the future::
1122-
1123- {"storage":
1124- {"default": "dfw",
1125- "dfw": "http://dfw.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
1126- "ord": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
1127- "sat": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"},
1128- "servers":
1129- {"default": "dfw",
1130- "dfw": "http://dfw.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
1131- "ord": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
1132- "sat": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
1133-
1134-Lastly, the tokens themselves are stored as objects in the
1135-`<auth_account>/.token_[0-f]` containers. The names of the objects are the
1136-token strings themselves, such as `AUTH_tked86bbd01864458aa2bd746879438d5a`.
1137-The exact `.token_[0-f]` container chosen is based on the final digit of the
1138-token name, such as `.token_a` for the token
1139-`AUTH_tked86bbd01864458aa2bd746879438d5a`. The contents of the token objects
1140-are JSON dictionaries of the format::
1141-
1142- {"account": <account>,
1143- "user": <user>,
1144- "account_id": <account_id>,
1145- "groups": <groups_array>,
1146- "expires": <time.time() value>}
1147-
1148-The `<account>` is the auth service account's name for that token. The `<user>`
1149-is the user within the account for that token. The `<account_id>` is the
1150-same as the `X-Container-Meta-Account-Id` for the auth service's account,
1151-as described above. The `<groups_array>` is the user's groups, as described
1152-above with the user object. The "expires" value indicates when the token is no
1153-longer valid, as compared to Python's time.time() value.
1154-
1155-Here's an example token object's JSON dictionary::
1156-
1157- {"account": "test",
1158- "user": "tester",
1159- "account_id": "AUTH_8980f74b1cda41e483cbe0a925f448a9",
1160- "groups": ["name": "test:tester", "name": "test", "name": ".admin"],
1161- "expires": 1291273147.1624689}
1162-
1163-To easily map a user to an already issued token, the token name is stored in
1164-the user object's `X-Object-Meta-Auth-Token` header.
1165-
1166-Here is an example full listing of an <auth_account>::
1167-
1168- .account_id
1169- AUTH_2282f516-559f-4966-b239-b5c88829e927
1170- AUTH_f6f57a3c-33b5-4e85-95a5-a801e67505c8
1171- AUTH_fea96a36-c177-4ca4-8c7e-b8c715d9d37b
1172- .token_0
1173- .token_1
1174- .token_2
1175- .token_3
1176- .token_4
1177- .token_5
1178- .token_6
1179- AUTH_tk9d2941b13d524b268367116ef956dee6
1180- .token_7
1181- .token_8
1182- AUTH_tk93627c6324c64f78be746f1e6a4e3f98
1183- .token_9
1184- .token_a
1185- .token_b
1186- .token_c
1187- .token_d
1188- .token_e
1189- AUTH_tk0d37d286af2c43ffad06e99112b3ec4e
1190- .token_f
1191- AUTH_tk766bbde93771489982d8dc76979d11cf
1192- reseller
1193- .services
1194- reseller
1195- test
1196- .services
1197- tester
1198- tester3
1199- test2
1200- .services
1201- tester2
1202
1203=== modified file 'etc/proxy-server.conf-sample'
1204--- etc/proxy-server.conf-sample 2011-03-25 08:33:46 +0000
1205+++ etc/proxy-server.conf-sample 2011-06-09 21:09:39 +0000
1206@@ -13,7 +13,7 @@
1207 # log_level = INFO
1208
1209 [pipeline:main]
1210-pipeline = catch_errors healthcheck cache ratelimit swauth proxy-server
1211+pipeline = catch_errors healthcheck cache ratelimit tempauth proxy-server
1212
1213 [app:proxy-server]
1214 use = egg:swift#proxy
1215@@ -41,10 +41,10 @@
1216 # 'false' no one, even authorized, can.
1217 # allow_account_management = false
1218
1219-[filter:swauth]
1220-use = egg:swift#swauth
1221+[filter:tempauth]
1222+use = egg:swift#tempauth
1223 # You can override the default log routing for this filter here:
1224-# set log_name = auth-server
1225+# set log_name = tempauth
1226 # set log_facility = LOG_LOCAL0
1227 # set log_level = INFO
1228 # set log_headers = False
1229@@ -54,21 +54,28 @@
1230 # multiple auth systems are in use for one Swift cluster.
1231 # reseller_prefix = AUTH
1232 # The auth prefix will cause requests beginning with this prefix to be routed
1233-# to the auth subsystem, for granting tokens, creating accounts, users, etc.
1234+# to the auth subsystem, for granting tokens, etc.
1235 # auth_prefix = /auth/
1236-# Cluster strings are of the format name#url where name is a short name for the
1237-# Swift cluster and url is the url to the proxy server(s) for the cluster.
1238-# default_swift_cluster = local#http://127.0.0.1:8080/v1
1239-# You may also use the format name#url#url where the first url is the one
1240-# given to users to access their account (public url) and the second is the one
1241-# used by swauth itself to create and delete accounts (private url). This is
1242-# useful when a load balancer url should be used by users, but swauth itself is
1243-# behind the load balancer. Example:
1244-# default_swift_cluster = local#https://public.com:8080/v1#http://private.com:8080/v1
1245 # token_life = 86400
1246-# node_timeout = 10
1247-# Highly recommended to change this.
1248-super_admin_key = swauthkey
1249+# Lastly, you need to list all the accounts/users you want here. The format is:
1250+# user_<account>_<user> = <key> [group] [group] [...] [storage_url]
1251+# There are special groups of:
1252+# .reseller_admin = can do anything to any account for this auth
1253+# .admin = can do anything within the account
1254+# If neither of these groups are specified, the user can only access containers
1255+# that have been explicitly allowed for them by a .admin or .reseller_admin.
1256+# The trailing optional storage_url allows you to specify an alternate url to
1257+# hand back to the user upon authentication. If not specified, this defaults to
1258+# http[s]://<ip>:<port>/v1/<reseller_prefix>_<account> where http or https
1259+# depends on whether cert_file is specified in the [DEFAULT] section, <ip> and
1260+# <port> are based on the [DEFAULT] section's bind_ip and bind_port (falling
1261+# back to 127.0.0.1 and 8080), <reseller_prefix> is from this section, and
1262+# <account> is from the user_<account>_<user> name.
1263+# Here are example entries, required for running the tests:
1264+user_admin_admin = admin .admin .reseller_admin
1265+user_test_tester = testing .admin
1266+user_test2_tester2 = testing2 .admin
1267+user_test_tester3 = testing3
1268
1269 [filter:healthcheck]
1270 use = egg:swift#healthcheck
1271
1272=== modified file 'setup.py'
1273--- setup.py 2011-05-26 09:25:39 +0000
1274+++ setup.py 2011-06-09 21:09:39 +0000
1275@@ -96,10 +96,6 @@
1276 'bin/swift-log-stats-collector',
1277 'bin/swift-account-stats-logger',
1278 'bin/swift-container-stats-logger',
1279- 'bin/swauth-add-account', 'bin/swauth-add-user',
1280- 'bin/swauth-cleanup-tokens', 'bin/swauth-delete-account',
1281- 'bin/swauth-delete-user', 'bin/swauth-list', 'bin/swauth-prep',
1282- 'bin/swauth-set-account-service',
1283 ],
1284 entry_points={
1285 'paste.app_factory': [
1286@@ -109,7 +105,6 @@
1287 'account=swift.account.server:app_factory',
1288 ],
1289 'paste.filter_factory': [
1290- 'swauth=swift.common.middleware.swauth:filter_factory',
1291 'healthcheck=swift.common.middleware.healthcheck:filter_factory',
1292 'memcache=swift.common.middleware.memcache:filter_factory',
1293 'ratelimit=swift.common.middleware.ratelimit:filter_factory',
1294@@ -118,6 +113,7 @@
1295 'domain_remap=swift.common.middleware.domain_remap:filter_factory',
1296 'swift3=swift.common.middleware.swift3:filter_factory',
1297 'staticweb=swift.common.middleware.staticweb:filter_factory',
1298+ 'tempauth=swift.common.middleware.tempauth:filter_factory',
1299 ],
1300 },
1301 )
1302
1303=== modified file 'swift/common/middleware/staticweb.py'
1304--- swift/common/middleware/staticweb.py 2011-03-25 19:21:35 +0000
1305+++ swift/common/middleware/staticweb.py 2011-06-09 21:09:39 +0000
1306@@ -28,7 +28,7 @@
1307 ...
1308
1309 [pipeline:main]
1310- pipeline = healthcheck cache swauth staticweb proxy-server
1311+ pipeline = healthcheck cache tempauth staticweb proxy-server
1312
1313 ...
1314
1315
1316=== removed file 'swift/common/middleware/swauth.py'
1317--- swift/common/middleware/swauth.py 2011-05-09 20:21:34 +0000
1318+++ swift/common/middleware/swauth.py 1970-01-01 00:00:00 +0000
1319@@ -1,1374 +0,0 @@
1320-# Copyright (c) 2010 OpenStack, LLC.
1321-#
1322-# Licensed under the Apache License, Version 2.0 (the "License");
1323-# you may not use this file except in compliance with the License.
1324-# You may obtain a copy of the License at
1325-#
1326-# http://www.apache.org/licenses/LICENSE-2.0
1327-#
1328-# Unless required by applicable law or agreed to in writing, software
1329-# distributed under the License is distributed on an "AS IS" BASIS,
1330-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
1331-# implied.
1332-# See the License for the specific language governing permissions and
1333-# limitations under the License.
1334-
1335-try:
1336- import simplejson as json
1337-except ImportError:
1338- import json
1339-from httplib import HTTPConnection, HTTPSConnection
1340-from time import gmtime, strftime, time
1341-from traceback import format_exc
1342-from urllib import quote, unquote
1343-from uuid import uuid4
1344-from hashlib import md5, sha1
1345-import hmac
1346-import base64
1347-
1348-from eventlet.timeout import Timeout
1349-from eventlet import TimeoutError
1350-from webob import Response, Request
1351-from webob.exc import HTTPAccepted, HTTPBadRequest, HTTPConflict, \
1352- HTTPCreated, HTTPForbidden, HTTPNoContent, HTTPNotFound, \
1353- HTTPServiceUnavailable, HTTPUnauthorized
1354-
1355-from swift.common.bufferedhttp import http_connect_raw as http_connect
1356-from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
1357-from swift.common.utils import cache_from_env, get_logger, split_path, urlparse
1358-
1359-
1360-class Swauth(object):
1361- """
1362- Scalable authentication and authorization system that uses Swift as its
1363- backing store.
1364-
1365- :param app: The next WSGI app in the pipeline
1366- :param conf: The dict of configuration values
1367- """
1368-
1369- def __init__(self, app, conf):
1370- self.app = app
1371- self.conf = conf
1372- self.logger = get_logger(conf, log_route='swauth')
1373- self.log_headers = conf.get('log_headers') == 'True'
1374- self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
1375- if self.reseller_prefix and self.reseller_prefix[-1] != '_':
1376- self.reseller_prefix += '_'
1377- self.auth_prefix = conf.get('auth_prefix', '/auth/')
1378- if not self.auth_prefix:
1379- self.auth_prefix = '/auth/'
1380- if self.auth_prefix[0] != '/':
1381- self.auth_prefix = '/' + self.auth_prefix
1382- if self.auth_prefix[-1] != '/':
1383- self.auth_prefix += '/'
1384- self.auth_account = '%s.auth' % self.reseller_prefix
1385- self.default_swift_cluster = conf.get('default_swift_cluster',
1386- 'local#http://127.0.0.1:8080/v1')
1387- # This setting is a little messy because of the options it has to
1388- # provide. The basic format is cluster_name#url, such as the default
1389- # value of local#http://127.0.0.1:8080/v1.
1390- # If the URL given to the user needs to differ from the url used by
1391- # Swauth to create/delete accounts, there's a more complex format:
1392- # cluster_name#url#url, such as
1393- # local#https://public.com:8080/v1#http://private.com:8080/v1.
1394- cluster_parts = self.default_swift_cluster.split('#', 2)
1395- self.dsc_name = cluster_parts[0]
1396- if len(cluster_parts) == 3:
1397- self.dsc_url = cluster_parts[1].rstrip('/')
1398- self.dsc_url2 = cluster_parts[2].rstrip('/')
1399- elif len(cluster_parts) == 2:
1400- self.dsc_url = self.dsc_url2 = cluster_parts[1].rstrip('/')
1401- else:
1402- raise Exception('Invalid cluster format')
1403- self.dsc_parsed = urlparse(self.dsc_url)
1404- if self.dsc_parsed.scheme not in ('http', 'https'):
1405- raise Exception('Cannot handle protocol scheme %s for url %s' %
1406- (self.dsc_parsed.scheme, repr(self.dsc_url)))
1407- self.dsc_parsed2 = urlparse(self.dsc_url2)
1408- if self.dsc_parsed2.scheme not in ('http', 'https'):
1409- raise Exception('Cannot handle protocol scheme %s for url %s' %
1410- (self.dsc_parsed2.scheme, repr(self.dsc_url2)))
1411- self.super_admin_key = conf.get('super_admin_key')
1412- if not self.super_admin_key:
1413- msg = _('No super_admin_key set in conf file! Exiting.')
1414- try:
1415- self.logger.critical(msg)
1416- except Exception:
1417- pass
1418- raise ValueError(msg)
1419- self.token_life = int(conf.get('token_life', 86400))
1420- self.timeout = int(conf.get('node_timeout', 10))
1421- self.itoken = None
1422- self.itoken_expires = None
1423-
1424- def __call__(self, env, start_response):
1425- """
1426- Accepts a standard WSGI application call, authenticating the request
1427- and installing callback hooks for authorization and ACL header
1428- validation. For an authenticated request, REMOTE_USER will be set to a
1429- comma separated list of the user's groups.
1430-
1431- With a non-empty reseller prefix, acts as the definitive auth service
1432- for just tokens and accounts that begin with that prefix, but will deny
1433- requests outside this prefix if no other auth middleware overrides it.
1434-
1435- With an empty reseller prefix, acts as the definitive auth service only
1436- for tokens that validate to a non-empty set of groups. For all other
1437- requests, acts as the fallback auth service when no other auth
1438- middleware overrides it.
1439-
1440- Alternatively, if the request matches the self.auth_prefix, the request
1441- will be routed through the internal auth request handler (self.handle).
1442- This is to handle creating users, accounts, granting tokens, etc.
1443- """
1444- if 'HTTP_X_CF_TRANS_ID' not in env:
1445- env['HTTP_X_CF_TRANS_ID'] = 'tx' + str(uuid4())
1446- if env.get('PATH_INFO', '').startswith(self.auth_prefix):
1447- return self.handle(env, start_response)
1448- s3 = env.get('HTTP_AUTHORIZATION')
1449- token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
1450- if s3 or (token and token.startswith(self.reseller_prefix)):
1451- # Note: Empty reseller_prefix will match all tokens.
1452- groups = self.get_groups(env, token)
1453- if groups:
1454- env['REMOTE_USER'] = groups
1455- user = groups and groups.split(',', 1)[0] or ''
1456- # We know the proxy logs the token, so we augment it just a bit
1457- # to also log the authenticated user.
1458- env['HTTP_X_AUTH_TOKEN'] = \
1459- '%s,%s' % (user, 's3' if s3 else token)
1460- env['swift.authorize'] = self.authorize
1461- env['swift.clean_acl'] = clean_acl
1462- else:
1463- # Unauthorized token
1464- if self.reseller_prefix:
1465- # Because I know I'm the definitive auth for this token, I
1466- # can deny it outright.
1467- return HTTPUnauthorized()(env, start_response)
1468- # Because I'm not certain if I'm the definitive auth for empty
1469- # reseller_prefixed tokens, I won't overwrite swift.authorize.
1470- elif 'swift.authorize' not in env:
1471- env['swift.authorize'] = self.denied_response
1472- else:
1473- if self.reseller_prefix:
1474- # With a non-empty reseller_prefix, I would like to be called
1475- # back for anonymous access to accounts I know I'm the
1476- # definitive auth for.
1477- try:
1478- version, rest = split_path(env.get('PATH_INFO', ''),
1479- 1, 2, True)
1480- except ValueError:
1481- return HTTPNotFound()(env, start_response)
1482- if rest and rest.startswith(self.reseller_prefix):
1483- # Handle anonymous access to accounts I'm the definitive
1484- # auth for.
1485- env['swift.authorize'] = self.authorize
1486- env['swift.clean_acl'] = clean_acl
1487- # Not my token, not my account, I can't authorize this request,
1488- # deny all is a good idea if not already set...
1489- elif 'swift.authorize' not in env:
1490- env['swift.authorize'] = self.denied_response
1491- # Because I'm not certain if I'm the definitive auth for empty
1492- # reseller_prefixed accounts, I won't overwrite swift.authorize.
1493- elif 'swift.authorize' not in env:
1494- env['swift.authorize'] = self.authorize
1495- env['swift.clean_acl'] = clean_acl
1496- return self.app(env, start_response)
1497-
1498- def get_groups(self, env, token):
1499- """
1500- Get groups for the given token.
1501-
1502- :param env: The current WSGI environment dictionary.
1503- :param token: Token to validate and return a group string for.
1504-
1505- :returns: None if the token is invalid or a string containing a comma
1506- separated list of groups the authenticated user is a member
1507- of. The first group in the list is also considered a unique
1508- identifier for that user.
1509- """
1510- groups = None
1511- memcache_client = cache_from_env(env)
1512- if memcache_client:
1513- memcache_key = '%s/auth/%s' % (self.reseller_prefix, token)
1514- cached_auth_data = memcache_client.get(memcache_key)
1515- if cached_auth_data:
1516- expires, groups = cached_auth_data
1517- if expires < time():
1518- groups = None
1519-
1520- if env.get('HTTP_AUTHORIZATION'):
1521- account = env['HTTP_AUTHORIZATION'].split(' ')[1]
1522- account, user, sign = account.split(':')
1523- path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user))
1524- resp = self.make_request(env, 'GET', path).get_response(self.app)
1525- if resp.status_int // 100 != 2:
1526- return None
1527-
1528- if 'x-object-meta-account-id' in resp.headers:
1529- account_id = resp.headers['x-object-meta-account-id']
1530- else:
1531- path = quote('/v1/%s/%s' % (self.auth_account, account))
1532- resp2 = self.make_request(env, 'HEAD',
1533- path).get_response(self.app)
1534- if resp2.status_int // 100 != 2:
1535- return None
1536- account_id = resp2.headers['x-container-meta-account-id']
1537-
1538- path = env['PATH_INFO']
1539- env['PATH_INFO'] = path.replace("%s:%s" % (account, user),
1540- account_id, 1)
1541- detail = json.loads(resp.body)
1542-
1543- password = detail['auth'].split(':')[-1]
1544- msg = base64.urlsafe_b64decode(unquote(token))
1545- s = base64.encodestring(hmac.new(detail['auth'].split(':')[-1],
1546- msg, sha1).digest()).strip()
1547- if s != sign:
1548- return None
1549- groups = [g['name'] for g in detail['groups']]
1550- if '.admin' in groups:
1551- groups.remove('.admin')
1552- groups.append(account_id)
1553- groups = ','.join(groups)
1554- return groups
1555-
1556- if not groups:
1557- path = quote('/v1/%s/.token_%s/%s' %
1558- (self.auth_account, token[-1], token))
1559- resp = self.make_request(env, 'GET', path).get_response(self.app)
1560- if resp.status_int // 100 != 2:
1561- return None
1562- detail = json.loads(resp.body)
1563- if detail['expires'] < time():
1564- self.make_request(env, 'DELETE', path).get_response(self.app)
1565- return None
1566- groups = [g['name'] for g in detail['groups']]
1567- if '.admin' in groups:
1568- groups.remove('.admin')
1569- groups.append(detail['account_id'])
1570- groups = ','.join(groups)
1571- if memcache_client:
1572- memcache_client.set(memcache_key, (detail['expires'], groups),
1573- timeout=float(detail['expires'] - time()))
1574- return groups
1575-
1576- def authorize(self, req):
1577- """
1578- Returns None if the request is authorized to continue or a standard
1579- WSGI response callable if not.
1580- """
1581- try:
1582- version, account, container, obj = split_path(req.path, 1, 4, True)
1583- except ValueError:
1584- return HTTPNotFound(request=req)
1585- if not account or not account.startswith(self.reseller_prefix):
1586- return self.denied_response(req)
1587- user_groups = (req.remote_user or '').split(',')
1588- if '.reseller_admin' in user_groups and \
1589- account != self.reseller_prefix and \
1590- account[len(self.reseller_prefix)] != '.':
1591- return None
1592- if account in user_groups and \
1593- (req.method not in ('DELETE', 'PUT') or container):
1594- # If the user is admin for the account and is not trying to do an
1595- # account DELETE or PUT...
1596- return None
1597- referrers, groups = parse_acl(getattr(req, 'acl', None))
1598- if referrer_allowed(req.referer, referrers):
1599- if obj or '.rlistings' in groups:
1600- return None
1601- return self.denied_response(req)
1602- if not req.remote_user:
1603- return self.denied_response(req)
1604- for user_group in user_groups:
1605- if user_group in groups:
1606- return None
1607- return self.denied_response(req)
1608-
1609- def denied_response(self, req):
1610- """
1611- Returns a standard WSGI response callable with the status of 403 or 401
1612- depending on whether the REMOTE_USER is set or not.
1613- """
1614- if req.remote_user:
1615- return HTTPForbidden(request=req)
1616- else:
1617- return HTTPUnauthorized(request=req)
1618-
1619- def handle(self, env, start_response):
1620- """
1621- WSGI entry point for auth requests (ones that match the
1622- self.auth_prefix).
1623- Wraps env in webob.Request object and passes it down.
1624-
1625- :param env: WSGI environment dictionary
1626- :param start_response: WSGI callable
1627- """
1628- try:
1629- req = Request(env)
1630- if self.auth_prefix:
1631- req.path_info_pop()
1632- req.bytes_transferred = '-'
1633- req.client_disconnect = False
1634- if 'x-storage-token' in req.headers and \
1635- 'x-auth-token' not in req.headers:
1636- req.headers['x-auth-token'] = req.headers['x-storage-token']
1637- if 'eventlet.posthooks' in env:
1638- env['eventlet.posthooks'].append(
1639- (self.posthooklogger, (req,), {}))
1640- return self.handle_request(req)(env, start_response)
1641- else:
1642- # Lack of posthook support means that we have to log on the
1643- # start of the response, rather than after all the data has
1644- # been sent. This prevents logging client disconnects
1645- # differently than full transmissions.
1646- response = self.handle_request(req)(env, start_response)
1647- self.posthooklogger(env, req)
1648- return response
1649- except (Exception, TimeoutError):
1650- print "EXCEPTION IN handle: %s: %s" % (format_exc(), env)
1651- start_response('500 Server Error',
1652- [('Content-Type', 'text/plain')])
1653- return ['Internal server error.\n']
1654-
1655- def handle_request(self, req):
1656- """
1657- Entry point for auth requests (ones that match the self.auth_prefix).
1658- Should return a WSGI-style callable (such as webob.Response).
1659-
1660- :param req: webob.Request object
1661- """
1662- req.start_time = time()
1663- handler = None
1664- try:
1665- version, account, user, _junk = split_path(req.path_info,
1666- minsegs=1, maxsegs=4, rest_with_last=True)
1667- except ValueError:
1668- return HTTPNotFound(request=req)
1669- if version in ('v1', 'v1.0', 'auth'):
1670- if req.method == 'GET':
1671- handler = self.handle_get_token
1672- elif version == 'v2':
1673- req.path_info_pop()
1674- if req.method == 'GET':
1675- if not account and not user:
1676- handler = self.handle_get_reseller
1677- elif account:
1678- if not user:
1679- handler = self.handle_get_account
1680- elif account == '.token':
1681- req.path_info_pop()
1682- handler = self.handle_validate_token
1683- else:
1684- handler = self.handle_get_user
1685- elif req.method == 'PUT':
1686- if not user:
1687- handler = self.handle_put_account
1688- else:
1689- handler = self.handle_put_user
1690- elif req.method == 'DELETE':
1691- if not user:
1692- handler = self.handle_delete_account
1693- else:
1694- handler = self.handle_delete_user
1695- elif req.method == 'POST':
1696- if account == '.prep':
1697- handler = self.handle_prep
1698- elif user == '.services':
1699- handler = self.handle_set_services
1700- if not handler:
1701- req.response = HTTPBadRequest(request=req)
1702- else:
1703- req.response = handler(req)
1704- return req.response
1705-
1706- def handle_prep(self, req):
1707- """
1708- Handles the POST v2/.prep call for preparing the backing store Swift
1709- cluster for use with the auth subsystem. Can only be called by
1710- .super_admin.
1711-
1712- :param req: The webob.Request to process.
1713- :returns: webob.Response, 204 on success
1714- """
1715- if not self.is_super_admin(req):
1716- return HTTPForbidden(request=req)
1717- path = quote('/v1/%s' % self.auth_account)
1718- resp = self.make_request(req.environ, 'PUT',
1719- path).get_response(self.app)
1720- if resp.status_int // 100 != 2:
1721- raise Exception('Could not create the main auth account: %s %s' %
1722- (path, resp.status))
1723- path = quote('/v1/%s/.account_id' % self.auth_account)
1724- resp = self.make_request(req.environ, 'PUT',
1725- path).get_response(self.app)
1726- if resp.status_int // 100 != 2:
1727- raise Exception('Could not create container: %s %s' %
1728- (path, resp.status))
1729- for container in xrange(16):
1730- path = quote('/v1/%s/.token_%x' % (self.auth_account, container))
1731- resp = self.make_request(req.environ, 'PUT',
1732- path).get_response(self.app)
1733- if resp.status_int // 100 != 2:
1734- raise Exception('Could not create container: %s %s' %
1735- (path, resp.status))
1736- return HTTPNoContent(request=req)
1737-
1738- def handle_get_reseller(self, req):
1739- """
1740- Handles the GET v2 call for getting general reseller information
1741- (currently just a list of accounts). Can only be called by a
1742- .reseller_admin.
1743-
1744- On success, a JSON dictionary will be returned with a single `accounts`
1745- key whose value is list of dicts. Each dict represents an account and
1746- currently only contains the single key `name`. For example::
1747-
1748- {"accounts": [{"name": "reseller"}, {"name": "test"},
1749- {"name": "test2"}]}
1750-
1751- :param req: The webob.Request to process.
1752- :returns: webob.Response, 2xx on success with a JSON dictionary as
1753- explained above.
1754- """
1755- if not self.is_reseller_admin(req):
1756- return HTTPForbidden(request=req)
1757- listing = []
1758- marker = ''
1759- while True:
1760- path = '/v1/%s?format=json&marker=%s' % (quote(self.auth_account),
1761- quote(marker))
1762- resp = self.make_request(req.environ, 'GET',
1763- path).get_response(self.app)
1764- if resp.status_int // 100 != 2:
1765- raise Exception('Could not list main auth account: %s %s' %
1766- (path, resp.status))
1767- sublisting = json.loads(resp.body)
1768- if not sublisting:
1769- break
1770- for container in sublisting:
1771- if container['name'][0] != '.':
1772- listing.append({'name': container['name']})
1773- marker = sublisting[-1]['name']
1774- return Response(body=json.dumps({'accounts': listing}))
1775-
1776- def handle_get_account(self, req):
1777- """
1778- Handles the GET v2/<account> call for getting account information.
1779- Can only be called by an account .admin.
1780-
1781- On success, a JSON dictionary will be returned containing the keys
1782- `account_id`, `services`, and `users`. The `account_id` is the value
1783- used when creating service accounts. The `services` value is a dict as
1784- described in the :func:`handle_get_token` call. The `users` value is a
1785- list of dicts, each dict representing a user and currently only
1786- containing the single key `name`. For example::
1787-
1788- {"account_id": "AUTH_018c3946-23f8-4efb-a8fb-b67aae8e4162",
1789- "services": {"storage": {"default": "local",
1790- "local": "http://127.0.0.1:8080/v1/AUTH_018c3946"}},
1791- "users": [{"name": "tester"}, {"name": "tester3"}]}
1792-
1793- :param req: The webob.Request to process.
1794- :returns: webob.Response, 2xx on success with a JSON dictionary as
1795- explained above.
1796- """
1797- account = req.path_info_pop()
1798- if req.path_info or not account or account[0] == '.':
1799- return HTTPBadRequest(request=req)
1800- if not self.is_account_admin(req, account):
1801- return HTTPForbidden(request=req)
1802- path = quote('/v1/%s/%s/.services' % (self.auth_account, account))
1803- resp = self.make_request(req.environ, 'GET',
1804- path).get_response(self.app)
1805- if resp.status_int == 404:
1806- return HTTPNotFound(request=req)
1807- if resp.status_int // 100 != 2:
1808- raise Exception('Could not obtain the .services object: %s %s' %
1809- (path, resp.status))
1810- services = json.loads(resp.body)
1811- listing = []
1812- marker = ''
1813- while True:
1814- path = '/v1/%s?format=json&marker=%s' % (quote('%s/%s' %
1815- (self.auth_account, account)), quote(marker))
1816- resp = self.make_request(req.environ, 'GET',
1817- path).get_response(self.app)
1818- if resp.status_int == 404:
1819- return HTTPNotFound(request=req)
1820- if resp.status_int // 100 != 2:
1821- raise Exception('Could not list in main auth account: %s %s' %
1822- (path, resp.status))
1823- account_id = resp.headers['X-Container-Meta-Account-Id']
1824- sublisting = json.loads(resp.body)
1825- if not sublisting:
1826- break
1827- for obj in sublisting:
1828- if obj['name'][0] != '.':
1829- listing.append({'name': obj['name']})
1830- marker = sublisting[-1]['name']
1831- return Response(body=json.dumps({'account_id': account_id,
1832- 'services': services, 'users': listing}))
1833-
1834- def handle_set_services(self, req):
1835- """
1836- Handles the POST v2/<account>/.services call for setting services
1837- information. Can only be called by a reseller .admin.
1838-
1839- In the :func:`handle_get_account` (GET v2/<account>) call, a section of
1840- the returned JSON dict is `services`. This section looks something like
1841- this::
1842-
1843- "services": {"storage": {"default": "local",
1844- "local": "http://127.0.0.1:8080/v1/AUTH_018c3946"}}
1845-
1846- Making use of this section is described in :func:`handle_get_token`.
1847-
1848- This function allows setting values within this section for the
1849- <account>, allowing the addition of new service end points or updating
1850- existing ones.
1851-
1852- The body of the POST request should contain a JSON dict with the
1853- following format::
1854-
1855- {"service_name": {"end_point_name": "end_point_value"}}
1856-
1857- There can be multiple services and multiple end points in the same
1858- call.
1859-
1860- Any new services or end points will be added to the existing set of
1861- services and end points. Any existing services with the same service
1862- name will be merged with the new end points. Any existing end points
1863- with the same end point name will have their values updated.
1864-
1865- The updated services dictionary will be returned on success.
1866-
1867- :param req: The webob.Request to process.
1868- :returns: webob.Response, 2xx on success with the udpated services JSON
1869- dict as described above
1870- """
1871- if not self.is_reseller_admin(req):
1872- return HTTPForbidden(request=req)
1873- account = req.path_info_pop()
1874- if req.path_info != '/.services' or not account or account[0] == '.':
1875- return HTTPBadRequest(request=req)
1876- try:
1877- new_services = json.loads(req.body)
1878- except ValueError, err:
1879- return HTTPBadRequest(body=str(err))
1880- # Get the current services information
1881- path = quote('/v1/%s/%s/.services' % (self.auth_account, account))
1882- resp = self.make_request(req.environ, 'GET',
1883- path).get_response(self.app)
1884- if resp.status_int == 404:
1885- return HTTPNotFound(request=req)
1886- if resp.status_int // 100 != 2:
1887- raise Exception('Could not obtain services info: %s %s' %
1888- (path, resp.status))
1889- services = json.loads(resp.body)
1890- for new_service, value in new_services.iteritems():
1891- if new_service in services:
1892- services[new_service].update(value)
1893- else:
1894- services[new_service] = value
1895- # Save the new services information
1896- services = json.dumps(services)
1897- resp = self.make_request(req.environ, 'PUT', path,
1898- services).get_response(self.app)
1899- if resp.status_int // 100 != 2:
1900- raise Exception('Could not save .services object: %s %s' %
1901- (path, resp.status))
1902- return Response(request=req, body=services)
1903-
1904- def handle_put_account(self, req):
1905- """
1906- Handles the PUT v2/<account> call for adding an account to the auth
1907- system. Can only be called by a .reseller_admin.
1908-
1909- By default, a newly created UUID4 will be used with the reseller prefix
1910- as the account id used when creating corresponding service accounts.
1911- However, you can provide an X-Account-Suffix header to replace the
1912- UUID4 part.
1913-
1914- :param req: The webob.Request to process.
1915- :returns: webob.Response, 2xx on success.
1916- """
1917- if not self.is_reseller_admin(req):
1918- return HTTPForbidden(request=req)
1919- account = req.path_info_pop()
1920- if req.path_info or not account or account[0] == '.':
1921- return HTTPBadRequest(request=req)
1922- # Ensure the container in the main auth account exists (this
1923- # container represents the new account)
1924- path = quote('/v1/%s/%s' % (self.auth_account, account))
1925- resp = self.make_request(req.environ, 'HEAD',
1926- path).get_response(self.app)
1927- if resp.status_int == 404:
1928- resp = self.make_request(req.environ, 'PUT',
1929- path).get_response(self.app)
1930- if resp.status_int // 100 != 2:
1931- raise Exception('Could not create account within main auth '
1932- 'account: %s %s' % (path, resp.status))
1933- elif resp.status_int // 100 == 2:
1934- if 'x-container-meta-account-id' in resp.headers:
1935- # Account was already created
1936- return HTTPAccepted(request=req)
1937- else:
1938- raise Exception('Could not verify account within main auth '
1939- 'account: %s %s' % (path, resp.status))
1940- account_suffix = req.headers.get('x-account-suffix')
1941- if not account_suffix:
1942- account_suffix = str(uuid4())
1943- # Create the new account in the Swift cluster
1944- path = quote('%s/%s%s' % (self.dsc_parsed2.path,
1945- self.reseller_prefix, account_suffix))
1946- try:
1947- conn = self.get_conn()
1948- conn.request('PUT', path,
1949- headers={'X-Auth-Token': self.get_itoken(req.environ)})
1950- resp = conn.getresponse()
1951- resp.read()
1952- if resp.status // 100 != 2:
1953- raise Exception('Could not create account on the Swift '
1954- 'cluster: %s %s %s' % (path, resp.status, resp.reason))
1955- except (Exception, TimeoutError):
1956- self.logger.error(_('ERROR: Exception while trying to communicate '
1957- 'with %(scheme)s://%(host)s:%(port)s/%(path)s'),
1958- {'scheme': self.dsc_parsed2.scheme,
1959- 'host': self.dsc_parsed2.hostname,
1960- 'port': self.dsc_parsed2.port, 'path': path})
1961- raise
1962- # Record the mapping from account id back to account name
1963- path = quote('/v1/%s/.account_id/%s%s' %
1964- (self.auth_account, self.reseller_prefix, account_suffix))
1965- resp = self.make_request(req.environ, 'PUT', path,
1966- account).get_response(self.app)
1967- if resp.status_int // 100 != 2:
1968- raise Exception('Could not create account id mapping: %s %s' %
1969- (path, resp.status))
1970- # Record the cluster url(s) for the account
1971- path = quote('/v1/%s/%s/.services' % (self.auth_account, account))
1972- services = {'storage': {}}
1973- services['storage'][self.dsc_name] = '%s/%s%s' % (self.dsc_url,
1974- self.reseller_prefix, account_suffix)
1975- services['storage']['default'] = self.dsc_name
1976- resp = self.make_request(req.environ, 'PUT', path,
1977- json.dumps(services)).get_response(self.app)
1978- if resp.status_int // 100 != 2:
1979- raise Exception('Could not create .services object: %s %s' %
1980- (path, resp.status))
1981- # Record the mapping from account name to the account id
1982- path = quote('/v1/%s/%s' % (self.auth_account, account))
1983- resp = self.make_request(req.environ, 'POST', path,
1984- headers={'X-Container-Meta-Account-Id': '%s%s' %
1985- (self.reseller_prefix, account_suffix)}).get_response(self.app)
1986- if resp.status_int // 100 != 2:
1987- raise Exception('Could not record the account id on the account: '
1988- '%s %s' % (path, resp.status))
1989- return HTTPCreated(request=req)
1990-
1991- def handle_delete_account(self, req):
1992- """
1993- Handles the DELETE v2/<account> call for removing an account from the
1994- auth system. Can only be called by a .reseller_admin.
1995-
1996- :param req: The webob.Request to process.
1997- :returns: webob.Response, 2xx on success.
1998- """
1999- if not self.is_reseller_admin(req):
2000- return HTTPForbidden(request=req)
2001- account = req.path_info_pop()
2002- if req.path_info or not account or account[0] == '.':
2003- return HTTPBadRequest(request=req)
2004- # Make sure the account has no users and get the account_id
2005- marker = ''
2006- while True:
2007- path = '/v1/%s?format=json&marker=%s' % (quote('%s/%s' %
2008- (self.auth_account, account)), quote(marker))
2009- resp = self.make_request(req.environ, 'GET',
2010- path).get_response(self.app)
2011- if resp.status_int == 404:
2012- return HTTPNotFound(request=req)
2013- if resp.status_int // 100 != 2:
2014- raise Exception('Could not list in main auth account: %s %s' %
2015- (path, resp.status))
2016- account_id = resp.headers['x-container-meta-account-id']
2017- sublisting = json.loads(resp.body)
2018- if not sublisting:
2019- break
2020- for obj in sublisting:
2021- if obj['name'][0] != '.':
2022- return HTTPConflict(request=req)
2023- marker = sublisting[-1]['name']
2024- # Obtain the listing of services the account is on.
2025- path = quote('/v1/%s/%s/.services' % (self.auth_account, account))
2026- resp = self.make_request(req.environ, 'GET',
2027- path).get_response(self.app)
2028- if resp.status_int // 100 != 2 and resp.status_int != 404:
2029- raise Exception('Could not obtain .services object: %s %s' %
2030- (path, resp.status))
2031- if resp.status_int // 100 == 2:
2032- services = json.loads(resp.body)
2033- # Delete the account on each cluster it is on.
2034- deleted_any = False
2035- for name, url in services['storage'].iteritems():
2036- if name != 'default':
2037- parsed = urlparse(url)
2038- conn = self.get_conn(parsed)
2039- conn.request('DELETE', parsed.path,
2040- headers={'X-Auth-Token': self.get_itoken(req.environ)})
2041- resp = conn.getresponse()
2042- resp.read()
2043- if resp.status == 409:
2044- if deleted_any:
2045- raise Exception('Managed to delete one or more '
2046- 'service end points, but failed with: '
2047- '%s %s %s' % (url, resp.status, resp.reason))
2048- else:
2049- return HTTPConflict(request=req)
2050- if resp.status // 100 != 2 and resp.status != 404:
2051- raise Exception('Could not delete account on the '
2052- 'Swift cluster: %s %s %s' %
2053- (url, resp.status, resp.reason))
2054- deleted_any = True
2055- # Delete the .services object itself.
2056- path = quote('/v1/%s/%s/.services' %
2057- (self.auth_account, account))
2058- resp = self.make_request(req.environ, 'DELETE',
2059- path).get_response(self.app)
2060- if resp.status_int // 100 != 2 and resp.status_int != 404:
2061- raise Exception('Could not delete .services object: %s %s' %
2062- (path, resp.status))
2063- # Delete the account id mapping for the account.
2064- path = quote('/v1/%s/.account_id/%s' %
2065- (self.auth_account, account_id))
2066- resp = self.make_request(req.environ, 'DELETE',
2067- path).get_response(self.app)
2068- if resp.status_int // 100 != 2 and resp.status_int != 404:
2069- raise Exception('Could not delete account id mapping: %s %s' %
2070- (path, resp.status))
2071- # Delete the account marker itself.
2072- path = quote('/v1/%s/%s' % (self.auth_account, account))
2073- resp = self.make_request(req.environ, 'DELETE',
2074- path).get_response(self.app)
2075- if resp.status_int // 100 != 2 and resp.status_int != 404:
2076- raise Exception('Could not delete account marked: %s %s' %
2077- (path, resp.status))
2078- return HTTPNoContent(request=req)
2079-
2080- def handle_get_user(self, req):
2081- """
2082- Handles the GET v2/<account>/<user> call for getting user information.
2083- Can only be called by an account .admin.
2084-
2085- On success, a JSON dict will be returned as described::
2086-
2087- {"groups": [ # List of groups the user is a member of
2088- {"name": "<act>:<usr>"},
2089- # The first group is a unique user identifier
2090- {"name": "<account>"},
2091- # The second group is the auth account name
2092- {"name": "<additional-group>"}
2093- # There may be additional groups, .admin being a special
2094- # group indicating an account admin and .reseller_admin
2095- # indicating a reseller admin.
2096- ],
2097- "auth": "plaintext:<key>"
2098- # The auth-type and key for the user; currently only plaintext is
2099- # implemented.
2100- }
2101-
2102- For example::
2103-
2104- {"groups": [{"name": "test:tester"}, {"name": "test"},
2105- {"name": ".admin"}],
2106- "auth": "plaintext:testing"}
2107-
2108- If the <user> in the request is the special user `.groups`, the JSON
2109- dict will contain a single key of `groups` whose value is a list of
2110- dicts representing the active groups within the account. Each dict
2111- currently has the single key `name`. For example::
2112-
2113- {"groups": [{"name": ".admin"}, {"name": "test"},
2114- {"name": "test:tester"}, {"name": "test:tester3"}]}
2115-
2116- :param req: The webob.Request to process.
2117- :returns: webob.Response, 2xx on success with a JSON dictionary as
2118- explained above.
2119- """
2120- account = req.path_info_pop()
2121- user = req.path_info_pop()
2122- if req.path_info or not account or account[0] == '.' or not user or \
2123- (user[0] == '.' and user != '.groups'):
2124- return HTTPBadRequest(request=req)
2125- if not self.is_account_admin(req, account):
2126- return HTTPForbidden(request=req)
2127- if user == '.groups':
2128- # TODO: This could be very slow for accounts with a really large
2129- # number of users. Speed could be improved by concurrently
2130- # requesting user group information. Then again, I don't *know*
2131- # it's slow for `normal` use cases, so testing should be done.
2132- groups = set()
2133- marker = ''
2134- while True:
2135- path = '/v1/%s?format=json&marker=%s' % (quote('%s/%s' %
2136- (self.auth_account, account)), quote(marker))
2137- resp = self.make_request(req.environ, 'GET',
2138- path).get_response(self.app)
2139- if resp.status_int == 404:
2140- return HTTPNotFound(request=req)
2141- if resp.status_int // 100 != 2:
2142- raise Exception('Could not list in main auth account: '
2143- '%s %s' % (path, resp.status))
2144- sublisting = json.loads(resp.body)
2145- if not sublisting:
2146- break
2147- for obj in sublisting:
2148- if obj['name'][0] != '.':
2149- path = quote('/v1/%s/%s/%s' % (self.auth_account,
2150- account, obj['name']))
2151- resp = self.make_request(req.environ, 'GET',
2152- path).get_response(self.app)
2153- if resp.status_int // 100 != 2:
2154- raise Exception('Could not retrieve user object: '
2155- '%s %s' % (path, resp.status))
2156- groups.update(g['name']
2157- for g in json.loads(resp.body)['groups'])
2158- marker = sublisting[-1]['name']
2159- body = json.dumps({'groups':
2160- [{'name': g} for g in sorted(groups)]})
2161- else:
2162- path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user))
2163- resp = self.make_request(req.environ, 'GET',
2164- path).get_response(self.app)
2165- if resp.status_int == 404:
2166- return HTTPNotFound(request=req)
2167- if resp.status_int // 100 != 2:
2168- raise Exception('Could not retrieve user object: %s %s' %
2169- (path, resp.status))
2170- body = resp.body
2171- display_groups = [g['name'] for g in json.loads(body)['groups']]
2172- if ('.admin' in display_groups and
2173- not self.is_reseller_admin(req)) or \
2174- ('.reseller_admin' in display_groups and
2175- not self.is_super_admin(req)):
2176- return HTTPForbidden(request=req)
2177- return Response(body=body)
2178-
2179- def handle_put_user(self, req):
2180- """
2181- Handles the PUT v2/<account>/<user> call for adding a user to an
2182- account.
2183-
2184- X-Auth-User-Key represents the user's key, X-Auth-User-Admin may be set
2185- to `true` to create an account .admin, and X-Auth-User-Reseller-Admin
2186- may be set to `true` to create a .reseller_admin.
2187-
2188- Can only be called by an account .admin unless the user is to be a
2189- .reseller_admin, in which case the request must be by .super_admin.
2190-
2191- :param req: The webob.Request to process.
2192- :returns: webob.Response, 2xx on success.
2193- """
2194- # Validate path info
2195- account = req.path_info_pop()
2196- user = req.path_info_pop()
2197- key = req.headers.get('x-auth-user-key')
2198- admin = req.headers.get('x-auth-user-admin') == 'true'
2199- reseller_admin = \
2200- req.headers.get('x-auth-user-reseller-admin') == 'true'
2201- if reseller_admin:
2202- admin = True
2203- if req.path_info or not account or account[0] == '.' or not user or \
2204- user[0] == '.' or not key:
2205- return HTTPBadRequest(request=req)
2206- if reseller_admin:
2207- if not self.is_super_admin(req):
2208- return HTTPForbidden(request=req)
2209- elif not self.is_account_admin(req, account):
2210- return HTTPForbidden(request=req)
2211-
2212- path = quote('/v1/%s/%s' % (self.auth_account, account))
2213- resp = self.make_request(req.environ, 'HEAD',
2214- path).get_response(self.app)
2215- if resp.status_int // 100 != 2:
2216- raise Exception('Could not retrieve account id value: %s %s' %
2217- (path, resp.status))
2218- headers = {'X-Object-Meta-Account-Id':
2219- resp.headers['x-container-meta-account-id']}
2220- # Create the object in the main auth account (this object represents
2221- # the user)
2222- path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user))
2223- groups = ['%s:%s' % (account, user), account]
2224- if admin:
2225- groups.append('.admin')
2226- if reseller_admin:
2227- groups.append('.reseller_admin')
2228- resp = self.make_request(req.environ, 'PUT', path,
2229- json.dumps({'auth': 'plaintext:%s' % key,
2230- 'groups': [{'name': g} for g in groups]}),
2231- headers=headers).get_response(self.app)
2232- if resp.status_int == 404:
2233- return HTTPNotFound(request=req)
2234- if resp.status_int // 100 != 2:
2235- raise Exception('Could not create user object: %s %s' %
2236- (path, resp.status))
2237- return HTTPCreated(request=req)
2238-
2239- def handle_delete_user(self, req):
2240- """
2241- Handles the DELETE v2/<account>/<user> call for deleting a user from an
2242- account.
2243-
2244- Can only be called by an account .admin.
2245-
2246- :param req: The webob.Request to process.
2247- :returns: webob.Response, 2xx on success.
2248- """
2249- # Validate path info
2250- account = req.path_info_pop()
2251- user = req.path_info_pop()
2252- if req.path_info or not account or account[0] == '.' or not user or \
2253- user[0] == '.':
2254- return HTTPBadRequest(request=req)
2255- if not self.is_account_admin(req, account):
2256- return HTTPForbidden(request=req)
2257- # Delete the user's existing token, if any.
2258- path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user))
2259- resp = self.make_request(req.environ, 'HEAD',
2260- path).get_response(self.app)
2261- if resp.status_int == 404:
2262- return HTTPNotFound(request=req)
2263- elif resp.status_int // 100 != 2:
2264- raise Exception('Could not obtain user details: %s %s' %
2265- (path, resp.status))
2266- candidate_token = resp.headers.get('x-object-meta-auth-token')
2267- if candidate_token:
2268- path = quote('/v1/%s/.token_%s/%s' %
2269- (self.auth_account, candidate_token[-1], candidate_token))
2270- resp = self.make_request(req.environ, 'DELETE',
2271- path).get_response(self.app)
2272- if resp.status_int // 100 != 2 and resp.status_int != 404:
2273- raise Exception('Could not delete possibly existing token: '
2274- '%s %s' % (path, resp.status))
2275- # Delete the user entry itself.
2276- path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user))
2277- resp = self.make_request(req.environ, 'DELETE',
2278- path).get_response(self.app)
2279- if resp.status_int // 100 != 2 and resp.status_int != 404:
2280- raise Exception('Could not delete the user object: %s %s' %
2281- (path, resp.status))
2282- return HTTPNoContent(request=req)
2283-
2284- def handle_get_token(self, req):
2285- """
2286- Handles the various `request for token and service end point(s)` calls.
2287- There are various formats to support the various auth servers in the
2288- past. Examples::
2289-
2290- GET <auth-prefix>/v1/<act>/auth
2291- X-Auth-User: <act>:<usr> or X-Storage-User: <usr>
2292- X-Auth-Key: <key> or X-Storage-Pass: <key>
2293- GET <auth-prefix>/auth
2294- X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr>
2295- X-Auth-Key: <key> or X-Storage-Pass: <key>
2296- GET <auth-prefix>/v1.0
2297- X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr>
2298- X-Auth-Key: <key> or X-Storage-Pass: <key>
2299-
2300- On successful authentication, the response will have X-Auth-Token and
2301- X-Storage-Token set to the token to use with Swift and X-Storage-URL
2302- set to the URL to the default Swift cluster to use.
2303-
2304- The response body will be set to the account's services JSON object as
2305- described here::
2306-
2307- {"storage": { # Represents the Swift storage service end points
2308- "default": "cluster1", # Indicates which cluster is the default
2309- "cluster1": "<URL to use with Swift>",
2310- # A Swift cluster that can be used with this account,
2311- # "cluster1" is the name of the cluster which is usually a
2312- # location indicator (like "dfw" for a datacenter region).
2313- "cluster2": "<URL to use with Swift>"
2314- # Another Swift cluster that can be used with this account,
2315- # there will always be at least one Swift cluster to use or
2316- # this whole "storage" dict won't be included at all.
2317- },
2318- "servers": { # Represents the Nova server service end points
2319- # Expected to be similar to the "storage" dict, but not
2320- # implemented yet.
2321- },
2322- # Possibly other service dicts, not implemented yet.
2323- }
2324-
2325- :param req: The webob.Request to process.
2326- :returns: webob.Response, 2xx on success with data set as explained
2327- above.
2328- """
2329- # Validate the request info
2330- try:
2331- pathsegs = split_path(req.path_info, minsegs=1, maxsegs=3,
2332- rest_with_last=True)
2333- except ValueError:
2334- return HTTPNotFound(request=req)
2335- if pathsegs[0] == 'v1' and pathsegs[2] == 'auth':
2336- account = pathsegs[1]
2337- user = req.headers.get('x-storage-user')
2338- if not user:
2339- user = req.headers.get('x-auth-user')
2340- if not user or ':' not in user:
2341- return HTTPUnauthorized(request=req)
2342- account2, user = user.split(':', 1)
2343- if account != account2:
2344- return HTTPUnauthorized(request=req)
2345- key = req.headers.get('x-storage-pass')
2346- if not key:
2347- key = req.headers.get('x-auth-key')
2348- elif pathsegs[0] in ('auth', 'v1.0'):
2349- user = req.headers.get('x-auth-user')
2350- if not user:
2351- user = req.headers.get('x-storage-user')
2352- if not user or ':' not in user:
2353- return HTTPUnauthorized(request=req)
2354- account, user = user.split(':', 1)
2355- key = req.headers.get('x-auth-key')
2356- if not key:
2357- key = req.headers.get('x-storage-pass')
2358- else:
2359- return HTTPBadRequest(request=req)
2360- if not all((account, user, key)):
2361- return HTTPUnauthorized(request=req)
2362- if user == '.super_admin' and key == self.super_admin_key:
2363- token = self.get_itoken(req.environ)
2364- url = '%s/%s.auth' % (self.dsc_url, self.reseller_prefix)
2365- return Response(request=req,
2366- body=json.dumps({'storage': {'default': 'local', 'local': url}}),
2367- headers={'x-auth-token': token, 'x-storage-token': token,
2368- 'x-storage-url': url})
2369- # Authenticate user
2370- path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user))
2371- resp = self.make_request(req.environ, 'GET',
2372- path).get_response(self.app)
2373- if resp.status_int == 404:
2374- return HTTPUnauthorized(request=req)
2375- if resp.status_int // 100 != 2:
2376- raise Exception('Could not obtain user details: %s %s' %
2377- (path, resp.status))
2378- user_detail = json.loads(resp.body)
2379- if not self.credentials_match(user_detail, key):
2380- return HTTPUnauthorized(request=req)
2381- # See if a token already exists and hasn't expired
2382- token = None
2383- candidate_token = resp.headers.get('x-object-meta-auth-token')
2384- if candidate_token:
2385- path = quote('/v1/%s/.token_%s/%s' %
2386- (self.auth_account, candidate_token[-1], candidate_token))
2387- resp = self.make_request(req.environ, 'GET',
2388- path).get_response(self.app)
2389- if resp.status_int // 100 == 2:
2390- token_detail = json.loads(resp.body)
2391- if token_detail['expires'] > time():
2392- token = candidate_token
2393- else:
2394- self.make_request(req.environ, 'DELETE',
2395- path).get_response(self.app)
2396- elif resp.status_int != 404:
2397- raise Exception('Could not detect whether a token already '
2398- 'exists: %s %s' % (path, resp.status))
2399- # Create a new token if one didn't exist
2400- if not token:
2401- # Retrieve account id, we'll save this in the token
2402- path = quote('/v1/%s/%s' % (self.auth_account, account))
2403- resp = self.make_request(req.environ, 'HEAD',
2404- path).get_response(self.app)
2405- if resp.status_int // 100 != 2:
2406- raise Exception('Could not retrieve account id value: '
2407- '%s %s' % (path, resp.status))
2408- account_id = \
2409- resp.headers['x-container-meta-account-id']
2410- # Generate new token
2411- token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
2412- # Save token info
2413- path = quote('/v1/%s/.token_%s/%s' %
2414- (self.auth_account, token[-1], token))
2415- resp = self.make_request(req.environ, 'PUT', path,
2416- json.dumps({'account': account, 'user': user,
2417- 'account_id': account_id,
2418- 'groups': user_detail['groups'],
2419- 'expires': time() + self.token_life})).get_response(self.app)
2420- if resp.status_int // 100 != 2:
2421- raise Exception('Could not create new token: %s %s' %
2422- (path, resp.status))
2423- # Record the token with the user info for future use.
2424- path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user))
2425- resp = self.make_request(req.environ, 'POST', path,
2426- headers={'X-Object-Meta-Auth-Token': token}
2427- ).get_response(self.app)
2428- if resp.status_int // 100 != 2:
2429- raise Exception('Could not save new token: %s %s' %
2430- (path, resp.status))
2431- # Get the services information
2432- path = quote('/v1/%s/%s/.services' % (self.auth_account, account))
2433- resp = self.make_request(req.environ, 'GET',
2434- path).get_response(self.app)
2435- if resp.status_int // 100 != 2:
2436- raise Exception('Could not obtain services info: %s %s' %
2437- (path, resp.status))
2438- detail = json.loads(resp.body)
2439- url = detail['storage'][detail['storage']['default']]
2440- return Response(request=req, body=resp.body,
2441- headers={'x-auth-token': token, 'x-storage-token': token,
2442- 'x-storage-url': url})
2443-
2444- def handle_validate_token(self, req):
2445- """
2446- Handles the GET v2/.token/<token> call for validating a token, usually
2447- called by a service like Swift.
2448-
2449- On a successful validation, X-Auth-TTL will be set for how much longer
2450- this token is valid and X-Auth-Groups will contain a comma separated
2451- list of groups the user belongs to.
2452-
2453- The first group listed will be a unique identifier for the user the
2454- token represents.
2455-
2456- .reseller_admin is a special group that indicates the user should be
2457- allowed to do anything on any account.
2458-
2459- :param req: The webob.Request to process.
2460- :returns: webob.Response, 2xx on success with data set as explained
2461- above.
2462- """
2463- token = req.path_info_pop()
2464- if req.path_info or not token.startswith(self.reseller_prefix):
2465- return HTTPBadRequest(request=req)
2466- expires = groups = None
2467- memcache_client = cache_from_env(req.environ)
2468- if memcache_client:
2469- memcache_key = '%s/auth/%s' % (self.reseller_prefix, token)
2470- cached_auth_data = memcache_client.get(memcache_key)
2471- if cached_auth_data:
2472- expires, groups = cached_auth_data
2473- if expires < time():
2474- groups = None
2475- if not groups:
2476- path = quote('/v1/%s/.token_%s/%s' %
2477- (self.auth_account, token[-1], token))
2478- resp = self.make_request(req.environ, 'GET',
2479- path).get_response(self.app)
2480- if resp.status_int // 100 != 2:
2481- return HTTPNotFound(request=req)
2482- detail = json.loads(resp.body)
2483- expires = detail['expires']
2484- if expires < time():
2485- self.make_request(req.environ, 'DELETE',
2486- path).get_response(self.app)
2487- return HTTPNotFound(request=req)
2488- groups = [g['name'] for g in detail['groups']]
2489- if '.admin' in groups:
2490- groups.remove('.admin')
2491- groups.append(detail['account_id'])
2492- groups = ','.join(groups)
2493- return HTTPNoContent(headers={'X-Auth-TTL': expires - time(),
2494- 'X-Auth-Groups': groups})
2495-
2496- def make_request(self, env, method, path, body=None, headers=None):
2497- """
2498- Makes a new webob.Request based on the current env but with the
2499- parameters specified.
2500-
2501- :param env: Current WSGI environment dictionary
2502- :param method: HTTP method of new request
2503- :param path: HTTP path of new request
2504- :param body: HTTP body of new request; None by default
2505- :param headers: Extra HTTP headers of new request; None by default
2506-
2507- :returns: webob.Request object
2508- """
2509- newenv = {'REQUEST_METHOD': method, 'HTTP_USER_AGENT': 'Swauth'}
2510- for name in ('swift.cache', 'HTTP_X_CF_TRANS_ID'):
2511- if name in env:
2512- newenv[name] = env[name]
2513- if not headers:
2514- headers = {}
2515- if body:
2516- return Request.blank(path, environ=newenv, body=body,
2517- headers=headers)
2518- else:
2519- return Request.blank(path, environ=newenv, headers=headers)
2520-
2521- def get_conn(self, urlparsed=None):
2522- """
2523- Returns an HTTPConnection based on the urlparse result given or the
2524- default Swift cluster (internal url) urlparse result.
2525-
2526- :param urlparsed: The result from urlparse.urlparse or None to use the
2527- default Swift cluster's value
2528- """
2529- if not urlparsed:
2530- urlparsed = self.dsc_parsed2
2531- if urlparsed.scheme == 'http':
2532- return HTTPConnection(urlparsed.netloc)
2533- else:
2534- return HTTPSConnection(urlparsed.netloc)
2535-
2536- def get_itoken(self, env):
2537- """
2538- Returns the current internal token to use for the auth system's own
2539- actions with other services. Each process will create its own
2540- itoken and the token will be deleted and recreated based on the
2541- token_life configuration value. The itoken information is stored in
2542- memcache because the auth process that is asked by Swift to validate
2543- the token may not be the same as the auth process that created the
2544- token.
2545- """
2546- if not self.itoken or self.itoken_expires < time():
2547- self.itoken = '%sitk%s' % (self.reseller_prefix, uuid4().hex)
2548- memcache_key = '%s/auth/%s' % (self.reseller_prefix, self.itoken)
2549- self.itoken_expires = time() + self.token_life - 60
2550- memcache_client = cache_from_env(env)
2551- if not memcache_client:
2552- raise Exception(
2553- 'No memcache set up; required for Swauth middleware')
2554- memcache_client.set(memcache_key, (self.itoken_expires,
2555- '.auth,.reseller_admin,%s.auth' % self.reseller_prefix),
2556- timeout=self.token_life)
2557- return self.itoken
2558-
2559- def get_admin_detail(self, req):
2560- """
2561- Returns the dict for the user specified as the admin in the request
2562- with the addition of an `account` key set to the admin user's account.
2563-
2564- :param req: The webob request to retrieve X-Auth-Admin-User and
2565- X-Auth-Admin-Key from.
2566- :returns: The dict for the admin user with the addition of the
2567- `account` key.
2568- """
2569- if ':' not in req.headers.get('x-auth-admin-user', ''):
2570- return None
2571- admin_account, admin_user = \
2572- req.headers.get('x-auth-admin-user').split(':', 1)
2573- path = quote('/v1/%s/%s/%s' % (self.auth_account, admin_account,
2574- admin_user))
2575- resp = self.make_request(req.environ, 'GET',
2576- path).get_response(self.app)
2577- if resp.status_int == 404:
2578- return None
2579- if resp.status_int // 100 != 2:
2580- raise Exception('Could not get admin user object: %s %s' %
2581- (path, resp.status))
2582- admin_detail = json.loads(resp.body)
2583- admin_detail['account'] = admin_account
2584- return admin_detail
2585-
2586- def credentials_match(self, user_detail, key):
2587- """
2588- Returns True if the key is valid for the user_detail. Currently, this
2589- only supports plaintext key matching.
2590-
2591- :param user_detail: The dict for the user.
2592- :param key: The key to validate for the user.
2593- :returns: True if the key is valid for the user, False if not.
2594- """
2595- return user_detail and user_detail.get('auth') == 'plaintext:%s' % key
2596-
2597- def is_super_admin(self, req):
2598- """
2599- Returns True if the admin specified in the request represents the
2600- .super_admin.
2601-
2602- :param req: The webob.Request to check.
2603- :param returns: True if .super_admin.
2604- """
2605- return req.headers.get('x-auth-admin-user') == '.super_admin' and \
2606- req.headers.get('x-auth-admin-key') == self.super_admin_key
2607-
2608- def is_reseller_admin(self, req, admin_detail=None):
2609- """
2610- Returns True if the admin specified in the request represents a
2611- .reseller_admin.
2612-
2613- :param req: The webob.Request to check.
2614- :param admin_detail: The previously retrieved dict from
2615- :func:`get_admin_detail` or None for this function
2616- to retrieve the admin_detail itself.
2617- :param returns: True if .reseller_admin.
2618- """
2619- if self.is_super_admin(req):
2620- return True
2621- if not admin_detail:
2622- admin_detail = self.get_admin_detail(req)
2623- if not self.credentials_match(admin_detail,
2624- req.headers.get('x-auth-admin-key')):
2625- return False
2626- return '.reseller_admin' in (g['name'] for g in admin_detail['groups'])
2627-
2628- def is_account_admin(self, req, account):
2629- """
2630- Returns True if the admin specified in the request represents a .admin
2631- for the account specified.
2632-
2633- :param req: The webob.Request to check.
2634- :param account: The account to check for .admin against.
2635- :param returns: True if .admin.
2636- """
2637- if self.is_super_admin(req):
2638- return True
2639- admin_detail = self.get_admin_detail(req)
2640- if admin_detail:
2641- if self.is_reseller_admin(req, admin_detail=admin_detail):
2642- return True
2643- if not self.credentials_match(admin_detail,
2644- req.headers.get('x-auth-admin-key')):
2645- return False
2646- return admin_detail and admin_detail['account'] == account and \
2647- '.admin' in (g['name'] for g in admin_detail['groups'])
2648- return False
2649-
2650- def posthooklogger(self, env, req):
2651- if not req.path.startswith(self.auth_prefix):
2652- return
2653- response = getattr(req, 'response', None)
2654- if not response:
2655- return
2656- trans_time = '%.4f' % (time() - req.start_time)
2657- the_request = quote(unquote(req.path))
2658- if req.query_string:
2659- the_request = the_request + '?' + req.query_string
2660- # remote user for zeus
2661- client = req.headers.get('x-cluster-client-ip')
2662- if not client and 'x-forwarded-for' in req.headers:
2663- # remote user for other lbs
2664- client = req.headers['x-forwarded-for'].split(',')[0].strip()
2665- logged_headers = None
2666- if self.log_headers:
2667- logged_headers = '\n'.join('%s: %s' % (k, v)
2668- for k, v in req.headers.items())
2669- status_int = response.status_int
2670- if getattr(req, 'client_disconnect', False) or \
2671- getattr(response, 'client_disconnect', False):
2672- status_int = 499
2673- self.logger.info(' '.join(quote(str(x)) for x in (client or '-',
2674- req.remote_addr or '-', strftime('%d/%b/%Y/%H/%M/%S', gmtime()),
2675- req.method, the_request, req.environ['SERVER_PROTOCOL'],
2676- status_int, req.referer or '-', req.user_agent or '-',
2677- req.headers.get('x-auth-token',
2678- req.headers.get('x-auth-admin-user', '-')),
2679- getattr(req, 'bytes_transferred', 0) or '-',
2680- getattr(response, 'bytes_transferred', 0) or '-',
2681- req.headers.get('etag', '-'),
2682- req.headers.get('x-trans-id', '-'), logged_headers or '-',
2683- trans_time)))
2684-
2685-
2686-def filter_factory(global_conf, **local_conf):
2687- """Returns a WSGI filter app for use with paste.deploy."""
2688- conf = global_conf.copy()
2689- conf.update(local_conf)
2690-
2691- def auth_filter(app):
2692- return Swauth(app, conf)
2693- return auth_filter
2694
2695=== added file 'swift/common/middleware/tempauth.py'
2696--- swift/common/middleware/tempauth.py 1970-01-01 00:00:00 +0000
2697+++ swift/common/middleware/tempauth.py 2011-06-09 21:09:39 +0000
2698@@ -0,0 +1,482 @@
2699+# Copyright (c) 2011 OpenStack, LLC.
2700+#
2701+# Licensed under the Apache License, Version 2.0 (the "License");
2702+# you may not use this file except in compliance with the License.
2703+# You may obtain a copy of the License at
2704+#
2705+# http://www.apache.org/licenses/LICENSE-2.0
2706+#
2707+# Unless required by applicable law or agreed to in writing, software
2708+# distributed under the License is distributed on an "AS IS" BASIS,
2709+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
2710+# implied.
2711+# See the License for the specific language governing permissions and
2712+# limitations under the License.
2713+
2714+from time import gmtime, strftime, time
2715+from traceback import format_exc
2716+from urllib import quote, unquote
2717+from uuid import uuid4
2718+from hashlib import sha1
2719+import hmac
2720+import base64
2721+
2722+from eventlet import TimeoutError
2723+from webob import Response, Request
2724+from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \
2725+ HTTPUnauthorized
2726+
2727+from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
2728+from swift.common.utils import cache_from_env, get_logger, split_path
2729+
2730+
2731+class TempAuth(object):
2732+ """
2733+ Test authentication and authorization system.
2734+
2735+ Add to your pipeline in proxy-server.conf, such as::
2736+
2737+ [pipeline:main]
2738+ pipeline = catch_errors cache tempauth proxy-server
2739+
2740+ And add a tempauth filter section, such as::
2741+
2742+ [filter:tempauth]
2743+ use = egg:swift#tempauth
2744+ user_admin_admin = admin .admin .reseller_admin
2745+ user_test_tester = testing .admin
2746+ user_test2_tester2 = testing2 .admin
2747+ user_test_tester3 = testing3
2748+
2749+ See the proxy-server.conf-sample for more information.
2750+
2751+ :param app: The next WSGI app in the pipeline
2752+ :param conf: The dict of configuration values
2753+ """
2754+
2755+ def __init__(self, app, conf):
2756+ self.app = app
2757+ self.conf = conf
2758+ self.logger = get_logger(conf, log_route='tempauth')
2759+ self.log_headers = conf.get('log_headers') == 'True'
2760+ self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
2761+ if self.reseller_prefix and self.reseller_prefix[-1] != '_':
2762+ self.reseller_prefix += '_'
2763+ self.auth_prefix = conf.get('auth_prefix', '/auth/')
2764+ if not self.auth_prefix:
2765+ self.auth_prefix = '/auth/'
2766+ if self.auth_prefix[0] != '/':
2767+ self.auth_prefix = '/' + self.auth_prefix
2768+ if self.auth_prefix[-1] != '/':
2769+ self.auth_prefix += '/'
2770+ self.token_life = int(conf.get('token_life', 86400))
2771+ self.users = {}
2772+ for conf_key in conf:
2773+ if conf_key.startswith('user_'):
2774+ values = conf[conf_key].split()
2775+ if not values:
2776+ raise ValueError('%s has no key set' % conf_key)
2777+ key = values.pop(0)
2778+ if values and '://' in values[-1]:
2779+ url = values.pop()
2780+ else:
2781+ url = 'https://' if 'cert_file' in conf else 'http://'
2782+ ip = conf.get('bind_ip', '127.0.0.1')
2783+ if ip == '0.0.0.0':
2784+ ip = '127.0.0.1'
2785+ url += ip
2786+ url += ':' + conf.get('bind_port', 80) + '/v1/' + \
2787+ self.reseller_prefix + conf_key.split('_')[1]
2788+ groups = values
2789+ self.users[conf_key.split('_', 1)[1].replace('_', ':')] = {
2790+ 'key': key, 'url': url, 'groups': values}
2791+ self.created_accounts = False
2792+
2793+ def __call__(self, env, start_response):
2794+ """
2795+ Accepts a standard WSGI application call, authenticating the request
2796+ and installing callback hooks for authorization and ACL header
2797+ validation. For an authenticated request, REMOTE_USER will be set to a
2798+ comma separated list of the user's groups.
2799+
2800+ With a non-empty reseller prefix, acts as the definitive auth service
2801+ for just tokens and accounts that begin with that prefix, but will deny
2802+ requests outside this prefix if no other auth middleware overrides it.
2803+
2804+ With an empty reseller prefix, acts as the definitive auth service only
2805+ for tokens that validate to a non-empty set of groups. For all other
2806+ requests, acts as the fallback auth service when no other auth
2807+ middleware overrides it.
2808+
2809+ Alternatively, if the request matches the self.auth_prefix, the request
2810+ will be routed through the internal auth request handler (self.handle).
2811+ This is to handle granting tokens, etc.
2812+ """
2813+ # Ensure the accounts we handle have been created
2814+ if not self.created_accounts and self.users:
2815+ newenv = {'REQUEST_METHOD': 'HEAD', 'HTTP_USER_AGENT': 'TempAuth'}
2816+ for name in ('swift.cache', 'HTTP_X_TRANS_ID'):
2817+ if name in env:
2818+ newenv[name] = env[name]
2819+ for key, value in self.users.iteritems():
2820+ account_id = value['url'].rsplit('/', 1)[-1]
2821+ newenv['REQUEST_METHOD'] = 'HEAD'
2822+ resp = Request.blank('/v1/' + account_id,
2823+ environ=newenv).get_response(self.app)
2824+ if resp.status_int // 100 != 2:
2825+ newenv['REQUEST_METHOD'] = 'PUT'
2826+ resp = Request.blank('/v1/' + account_id,
2827+ environ=newenv).get_response(self.app)
2828+ if resp.status_int // 100 != 2:
2829+ raise Exception('Could not create account %s for user '
2830+ '%s' % (account_id, key))
2831+ self.created_accounts = True
2832+
2833+ if env.get('PATH_INFO', '').startswith(self.auth_prefix):
2834+ return self.handle(env, start_response)
2835+ s3 = env.get('HTTP_AUTHORIZATION')
2836+ token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
2837+ if s3 or (token and token.startswith(self.reseller_prefix)):
2838+ # Note: Empty reseller_prefix will match all tokens.
2839+ groups = self.get_groups(env, token)
2840+ if groups:
2841+ env['REMOTE_USER'] = groups
2842+ user = groups and groups.split(',', 1)[0] or ''
2843+ # We know the proxy logs the token, so we augment it just a bit
2844+ # to also log the authenticated user.
2845+ env['HTTP_X_AUTH_TOKEN'] = \
2846+ '%s,%s' % (user, 's3' if s3 else token)
2847+ env['swift.authorize'] = self.authorize
2848+ env['swift.clean_acl'] = clean_acl
2849+ else:
2850+ # Unauthorized token
2851+ if self.reseller_prefix:
2852+ # Because I know I'm the definitive auth for this token, I
2853+ # can deny it outright.
2854+ return HTTPUnauthorized()(env, start_response)
2855+ # Because I'm not certain if I'm the definitive auth for empty
2856+ # reseller_prefixed tokens, I won't overwrite swift.authorize.
2857+ elif 'swift.authorize' not in env:
2858+ env['swift.authorize'] = self.denied_response
2859+ else:
2860+ if self.reseller_prefix:
2861+ # With a non-empty reseller_prefix, I would like to be called
2862+ # back for anonymous access to accounts I know I'm the
2863+ # definitive auth for.
2864+ try:
2865+ version, rest = split_path(env.get('PATH_INFO', ''),
2866+ 1, 2, True)
2867+ except ValueError:
2868+ return HTTPNotFound()(env, start_response)
2869+ if rest and rest.startswith(self.reseller_prefix):
2870+ # Handle anonymous access to accounts I'm the definitive
2871+ # auth for.
2872+ env['swift.authorize'] = self.authorize
2873+ env['swift.clean_acl'] = clean_acl
2874+ # Not my token, not my account, I can't authorize this request,
2875+ # deny all is a good idea if not already set...
2876+ elif 'swift.authorize' not in env:
2877+ env['swift.authorize'] = self.denied_response
2878+ # Because I'm not certain if I'm the definitive auth for empty
2879+ # reseller_prefixed accounts, I won't overwrite swift.authorize.
2880+ elif 'swift.authorize' not in env:
2881+ env['swift.authorize'] = self.authorize
2882+ env['swift.clean_acl'] = clean_acl
2883+ return self.app(env, start_response)
2884+
2885+ def get_groups(self, env, token):
2886+ """
2887+ Get groups for the given token.
2888+
2889+ :param env: The current WSGI environment dictionary.
2890+ :param token: Token to validate and return a group string for.
2891+
2892+ :returns: None if the token is invalid or a string containing a comma
2893+ separated list of groups the authenticated user is a member
2894+ of. The first group in the list is also considered a unique
2895+ identifier for that user.
2896+ """
2897+ groups = None
2898+ memcache_client = cache_from_env(env)
2899+ if not memcache_client:
2900+ raise Exception('Memcache required')
2901+ memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
2902+ cached_auth_data = memcache_client.get(memcache_token_key)
2903+ if cached_auth_data:
2904+ expires, groups = cached_auth_data
2905+ if expires < time():
2906+ groups = None
2907+
2908+ if env.get('HTTP_AUTHORIZATION'):
2909+ account_user, sign = \
2910+ env['HTTP_AUTHORIZATION'].split(' ')[1].rsplit(':', 1)
2911+ if account_user not in self.users:
2912+ return None
2913+ account, user = account_user.split(':', 1)
2914+ account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
2915+ path = env['PATH_INFO']
2916+ env['PATH_INFO'] = path.replace(account_user, account_id, 1)
2917+ msg = base64.urlsafe_b64decode(unquote(token))
2918+ key = self.users[account_user]['key']
2919+ s = base64.encodestring(hmac.new(key, msg, sha1).digest()).strip()
2920+ if s != sign:
2921+ return None
2922+ groups = [account, account_user]
2923+ groups.extend(self.users[account_user]['groups'])
2924+ if '.admin' in groups:
2925+ groups.remove('.admin')
2926+ groups.append(account_id)
2927+ groups = ','.join(groups)
2928+
2929+ return groups
2930+
2931+ def authorize(self, req):
2932+ """
2933+ Returns None if the request is authorized to continue or a standard
2934+ WSGI response callable if not.
2935+ """
2936+ try:
2937+ version, account, container, obj = split_path(req.path, 1, 4, True)
2938+ except ValueError:
2939+ return HTTPNotFound(request=req)
2940+ if not account or not account.startswith(self.reseller_prefix):
2941+ return self.denied_response(req)
2942+ user_groups = (req.remote_user or '').split(',')
2943+ if '.reseller_admin' in user_groups and \
2944+ account != self.reseller_prefix and \
2945+ account[len(self.reseller_prefix)] != '.':
2946+ return None
2947+ if account in user_groups and \
2948+ (req.method not in ('DELETE', 'PUT') or container):
2949+ # If the user is admin for the account and is not trying to do an
2950+ # account DELETE or PUT...
2951+ return None
2952+ referrers, groups = parse_acl(getattr(req, 'acl', None))
2953+ if referrer_allowed(req.referer, referrers):
2954+ if obj or '.rlistings' in groups:
2955+ return None
2956+ return self.denied_response(req)
2957+ if not req.remote_user:
2958+ return self.denied_response(req)
2959+ for user_group in user_groups:
2960+ if user_group in groups:
2961+ return None
2962+ return self.denied_response(req)
2963+
2964+ def denied_response(self, req):
2965+ """
2966+ Returns a standard WSGI response callable with the status of 403 or 401
2967+ depending on whether the REMOTE_USER is set or not.
2968+ """
2969+ if req.remote_user:
2970+ return HTTPForbidden(request=req)
2971+ else:
2972+ return HTTPUnauthorized(request=req)
2973+
2974+ def handle(self, env, start_response):
2975+ """
2976+ WSGI entry point for auth requests (ones that match the
2977+ self.auth_prefix).
2978+ Wraps env in webob.Request object and passes it down.
2979+
2980+ :param env: WSGI environment dictionary
2981+ :param start_response: WSGI callable
2982+ """
2983+ try:
2984+ req = Request(env)
2985+ if self.auth_prefix:
2986+ req.path_info_pop()
2987+ req.bytes_transferred = '-'
2988+ req.client_disconnect = False
2989+ if 'x-storage-token' in req.headers and \
2990+ 'x-auth-token' not in req.headers:
2991+ req.headers['x-auth-token'] = req.headers['x-storage-token']
2992+ if 'eventlet.posthooks' in env:
2993+ env['eventlet.posthooks'].append(
2994+ (self.posthooklogger, (req,), {}))
2995+ return self.handle_request(req)(env, start_response)
2996+ else:
2997+ # Lack of posthook support means that we have to log on the
2998+ # start of the response, rather than after all the data has
2999+ # been sent. This prevents logging client disconnects
3000+ # differently than full transmissions.
3001+ response = self.handle_request(req)(env, start_response)
3002+ self.posthooklogger(env, req)
3003+ return response
3004+ except (Exception, TimeoutError):
3005+ print "EXCEPTION IN handle: %s: %s" % (format_exc(), env)
3006+ start_response('500 Server Error',
3007+ [('Content-Type', 'text/plain')])
3008+ return ['Internal server error.\n']
3009+
3010+ def handle_request(self, req):
3011+ """
3012+ Entry point for auth requests (ones that match the self.auth_prefix).
3013+ Should return a WSGI-style callable (such as webob.Response).
3014+
3015+ :param req: webob.Request object
3016+ """
3017+ req.start_time = time()
3018+ handler = None
3019+ try:
3020+ version, account, user, _junk = split_path(req.path_info,
3021+ minsegs=1, maxsegs=4, rest_with_last=True)
3022+ except ValueError:
3023+ return HTTPNotFound(request=req)
3024+ if version in ('v1', 'v1.0', 'auth'):
3025+ if req.method == 'GET':
3026+ handler = self.handle_get_token
3027+ if not handler:
3028+ req.response = HTTPBadRequest(request=req)
3029+ else:
3030+ req.response = handler(req)
3031+ return req.response
3032+
3033+ def handle_get_token(self, req):
3034+ """
3035+ Handles the various `request for token and service end point(s)` calls.
3036+ There are various formats to support the various auth servers in the
3037+ past. Examples::
3038+
3039+ GET <auth-prefix>/v1/<act>/auth
3040+ X-Auth-User: <act>:<usr> or X-Storage-User: <usr>
3041+ X-Auth-Key: <key> or X-Storage-Pass: <key>
3042+ GET <auth-prefix>/auth
3043+ X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr>
3044+ X-Auth-Key: <key> or X-Storage-Pass: <key>
3045+ GET <auth-prefix>/v1.0
3046+ X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr>
3047+ X-Auth-Key: <key> or X-Storage-Pass: <key>
3048+
3049+ On successful authentication, the response will have X-Auth-Token and
3050+ X-Storage-Token set to the token to use with Swift and X-Storage-URL
3051+ set to the URL to the default Swift cluster to use.
3052+
3053+ :param req: The webob.Request to process.
3054+ :returns: webob.Response, 2xx on success with data set as explained
3055+ above.
3056+ """
3057+ # Validate the request info
3058+ try:
3059+ pathsegs = split_path(req.path_info, minsegs=1, maxsegs=3,
3060+ rest_with_last=True)
3061+ except ValueError:
3062+ return HTTPNotFound(request=req)
3063+ if pathsegs[0] == 'v1' and pathsegs[2] == 'auth':
3064+ account = pathsegs[1]
3065+ user = req.headers.get('x-storage-user')
3066+ if not user:
3067+ user = req.headers.get('x-auth-user')
3068+ if not user or ':' not in user:
3069+ return HTTPUnauthorized(request=req)
3070+ account2, user = user.split(':', 1)
3071+ if account != account2:
3072+ return HTTPUnauthorized(request=req)
3073+ key = req.headers.get('x-storage-pass')
3074+ if not key:
3075+ key = req.headers.get('x-auth-key')
3076+ elif pathsegs[0] in ('auth', 'v1.0'):
3077+ user = req.headers.get('x-auth-user')
3078+ if not user:
3079+ user = req.headers.get('x-storage-user')
3080+ if not user or ':' not in user:
3081+ return HTTPUnauthorized(request=req)
3082+ account, user = user.split(':', 1)
3083+ key = req.headers.get('x-auth-key')
3084+ if not key:
3085+ key = req.headers.get('x-storage-pass')
3086+ else:
3087+ return HTTPBadRequest(request=req)
3088+ if not all((account, user, key)):
3089+ return HTTPUnauthorized(request=req)
3090+ # Authenticate user
3091+ account_user = account + ':' + user
3092+ if account_user not in self.users:
3093+ return HTTPUnauthorized(request=req)
3094+ if self.users[account_user]['key'] != key:
3095+ return HTTPUnauthorized(request=req)
3096+ # Get memcache client
3097+ memcache_client = cache_from_env(req.environ)
3098+ if not memcache_client:
3099+ raise Exception('Memcache required')
3100+ # See if a token already exists and hasn't expired
3101+ token = None
3102+ memcache_user_key = '%s/user/%s' % (self.reseller_prefix, account_user)
3103+ candidate_token = memcache_client.get(memcache_user_key)
3104+ if candidate_token:
3105+ memcache_token_key = \
3106+ '%s/token/%s' % (self.reseller_prefix, candidate_token)
3107+ cached_auth_data = memcache_client.get(memcache_token_key)
3108+ if cached_auth_data:
3109+ expires, groups = cached_auth_data
3110+ if expires > time():
3111+ token = candidate_token
3112+ # Create a new token if one didn't exist
3113+ if not token:
3114+ # Generate new token
3115+ token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
3116+ expires = time() + self.token_life
3117+ groups = [account, account_user]
3118+ groups.extend(self.users[account_user]['groups'])
3119+ if '.admin' in groups:
3120+ groups.remove('.admin')
3121+ account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
3122+ groups.append(account_id)
3123+ groups = ','.join(groups)
3124+ # Save token
3125+ memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
3126+ memcache_client.set(memcache_token_key, (expires, groups),
3127+ timeout=float(expires - time()))
3128+ # Record the token with the user info for future use.
3129+ memcache_user_key = \
3130+ '%s/user/%s' % (self.reseller_prefix, account_user)
3131+ memcache_client.set(memcache_user_key, token,
3132+ timeout=float(expires - time()))
3133+ return Response(request=req,
3134+ headers={'x-auth-token': token, 'x-storage-token': token,
3135+ 'x-storage-url': self.users[account_user]['url']})
3136+
3137+ def posthooklogger(self, env, req):
3138+ if not req.path.startswith(self.auth_prefix):
3139+ return
3140+ response = getattr(req, 'response', None)
3141+ if not response:
3142+ return
3143+ trans_time = '%.4f' % (time() - req.start_time)
3144+ the_request = quote(unquote(req.path))
3145+ if req.query_string:
3146+ the_request = the_request + '?' + req.query_string
3147+ # remote user for zeus
3148+ client = req.headers.get('x-cluster-client-ip')
3149+ if not client and 'x-forwarded-for' in req.headers:
3150+ # remote user for other lbs
3151+ client = req.headers['x-forwarded-for'].split(',')[0].strip()
3152+ logged_headers = None
3153+ if self.log_headers:
3154+ logged_headers = '\n'.join('%s: %s' % (k, v)
3155+ for k, v in req.headers.items())
3156+ status_int = response.status_int
3157+ if getattr(req, 'client_disconnect', False) or \
3158+ getattr(response, 'client_disconnect', False):
3159+ status_int = 499
3160+ self.logger.info(' '.join(quote(str(x)) for x in (client or '-',
3161+ req.remote_addr or '-', strftime('%d/%b/%Y/%H/%M/%S', gmtime()),
3162+ req.method, the_request, req.environ['SERVER_PROTOCOL'],
3163+ status_int, req.referer or '-', req.user_agent or '-',
3164+ req.headers.get('x-auth-token',
3165+ req.headers.get('x-auth-admin-user', '-')),
3166+ getattr(req, 'bytes_transferred', 0) or '-',
3167+ getattr(response, 'bytes_transferred', 0) or '-',
3168+ req.headers.get('etag', '-'),
3169+ req.headers.get('x-trans-id', '-'), logged_headers or '-',
3170+ trans_time)))
3171+
3172+
3173+def filter_factory(global_conf, **local_conf):
3174+ """Returns a WSGI filter app for use with paste.deploy."""
3175+ conf = global_conf.copy()
3176+ conf.update(local_conf)
3177+
3178+ def auth_filter(app):
3179+ return TempAuth(app, conf)
3180+ return auth_filter
3181
3182=== modified file 'test/probe/common.py'
3183--- test/probe/common.py 2011-03-14 02:56:37 +0000
3184+++ test/probe/common.py 2011-06-09 21:09:39 +0000
3185@@ -13,29 +13,16 @@
3186 # See the License for the specific language governing permissions and
3187 # limitations under the License.
3188
3189-from os import environ, kill
3190+from os import kill
3191 from signal import SIGTERM
3192 from subprocess import call, Popen
3193 from time import sleep
3194-from ConfigParser import ConfigParser
3195
3196 from swift.common.bufferedhttp import http_connect_raw as http_connect
3197 from swift.common.client import get_auth
3198 from swift.common.ring import Ring
3199
3200
3201-SUPER_ADMIN_KEY = None
3202-
3203-c = ConfigParser()
3204-PROXY_SERVER_CONF_FILE = environ.get('SWIFT_PROXY_SERVER_CONF_FILE',
3205- '/etc/swift/proxy-server.conf')
3206-if c.read(PROXY_SERVER_CONF_FILE):
3207- conf = dict(c.items('filter:swauth'))
3208- SUPER_ADMIN_KEY = conf.get('super_admin_key', 'swauthkey')
3209-else:
3210- exit('Unable to read config file: %s' % PROXY_SERVER_CONF_FILE)
3211-
3212-
3213 def kill_pids(pids):
3214 for pid in pids.values():
3215 try:
3216@@ -48,8 +35,6 @@
3217 call(['resetswift'])
3218 pids = {}
3219 try:
3220- pids['proxy'] = Popen(['swift-proxy-server',
3221- '/etc/swift/proxy-server.conf']).pid
3222 port2server = {}
3223 for s, p in (('account', 6002), ('container', 6001), ('object', 6000)):
3224 for n in xrange(1, 5):
3225@@ -57,14 +42,27 @@
3226 Popen(['swift-%s-server' % s,
3227 '/etc/swift/%s-server/%d.conf' % (s, n)]).pid
3228 port2server[p + (n * 10)] = '%s%d' % (s, n)
3229+ pids['proxy'] = Popen(['swift-proxy-server',
3230+ '/etc/swift/proxy-server.conf']).pid
3231 account_ring = Ring('/etc/swift/account.ring.gz')
3232 container_ring = Ring('/etc/swift/container.ring.gz')
3233 object_ring = Ring('/etc/swift/object.ring.gz')
3234- sleep(5)
3235- call(['recreateaccounts'])
3236- url, token = get_auth('http://127.0.0.1:8080/auth/v1.0',
3237- 'test:tester', 'testing')
3238- account = url.split('/')[-1]
3239+ attempt = 0
3240+ while True:
3241+ attempt += 1
3242+ try:
3243+ url, token = get_auth('http://127.0.0.1:8080/auth/v1.0',
3244+ 'test:tester', 'testing')
3245+ account = url.split('/')[-1]
3246+ break
3247+ except Exception, err:
3248+ if attempt > 9:
3249+ print err
3250+ print 'Giving up after %s retries.' % attempt
3251+ raise err
3252+ print err
3253+ print 'Retrying in 1 second...'
3254+ sleep(1)
3255 except BaseException, err:
3256 kill_pids(pids)
3257 raise err
3258
3259=== renamed file 'test/unit/common/middleware/test_swauth.py' => 'test/unit/common/middleware/test_tempauth.py'
3260--- test/unit/common/middleware/test_swauth.py 2011-04-01 21:17:47 +0000
3261+++ test/unit/common/middleware/test_tempauth.py 2011-06-09 21:09:39 +0000
3262@@ -1,4 +1,4 @@
3263-# Copyright (c) 2010 OpenStack, LLC.
3264+# Copyright (c) 2011 OpenStack, LLC.
3265 #
3266 # Licensed under the Apache License, Version 2.0 (the "License");
3267 # you may not use this file except in compliance with the License.
3268@@ -23,7 +23,7 @@
3269
3270 from webob import Request, Response
3271
3272-from swift.common.middleware import swauth as auth
3273+from swift.common.middleware import tempauth as auth
3274
3275
3276 class FakeMemcache(object):
3277@@ -102,85 +102,49 @@
3278 class TestAuth(unittest.TestCase):
3279
3280 def setUp(self):
3281- self.test_auth = \
3282- auth.filter_factory({'super_admin_key': 'supertest'})(FakeApp())
3283+ self.test_auth = auth.filter_factory({})(FakeApp())
3284
3285- def test_super_admin_key_required(self):
3286- app = FakeApp()
3287- exc = None
3288- try:
3289- auth.filter_factory({})(app)
3290- except ValueError, err:
3291- exc = err
3292- self.assertEquals(str(exc),
3293- 'No super_admin_key set in conf file! Exiting.')
3294- auth.filter_factory({'super_admin_key': 'supertest'})(app)
3295+ def _make_request(self, path, **kwargs):
3296+ req = Request.blank(path, **kwargs)
3297+ req.environ['swift.cache'] = FakeMemcache()
3298+ return req
3299
3300 def test_reseller_prefix_init(self):
3301 app = FakeApp()
3302- ath = auth.filter_factory({'super_admin_key': 'supertest'})(app)
3303+ ath = auth.filter_factory({})(app)
3304 self.assertEquals(ath.reseller_prefix, 'AUTH_')
3305- ath = auth.filter_factory({'super_admin_key': 'supertest',
3306- 'reseller_prefix': 'TEST'})(app)
3307+ ath = auth.filter_factory({'reseller_prefix': 'TEST'})(app)
3308 self.assertEquals(ath.reseller_prefix, 'TEST_')
3309- ath = auth.filter_factory({'super_admin_key': 'supertest',
3310- 'reseller_prefix': 'TEST_'})(app)
3311+ ath = auth.filter_factory({'reseller_prefix': 'TEST_'})(app)
3312 self.assertEquals(ath.reseller_prefix, 'TEST_')
3313
3314 def test_auth_prefix_init(self):
3315 app = FakeApp()
3316- ath = auth.filter_factory({'super_admin_key': 'supertest'})(app)
3317- self.assertEquals(ath.auth_prefix, '/auth/')
3318- ath = auth.filter_factory({'super_admin_key': 'supertest',
3319- 'auth_prefix': ''})(app)
3320- self.assertEquals(ath.auth_prefix, '/auth/')
3321- ath = auth.filter_factory({'super_admin_key': 'supertest',
3322- 'auth_prefix': '/test/'})(app)
3323- self.assertEquals(ath.auth_prefix, '/test/')
3324- ath = auth.filter_factory({'super_admin_key': 'supertest',
3325- 'auth_prefix': '/test'})(app)
3326- self.assertEquals(ath.auth_prefix, '/test/')
3327- ath = auth.filter_factory({'super_admin_key': 'supertest',
3328- 'auth_prefix': 'test/'})(app)
3329- self.assertEquals(ath.auth_prefix, '/test/')
3330- ath = auth.filter_factory({'super_admin_key': 'supertest',
3331- 'auth_prefix': 'test'})(app)
3332- self.assertEquals(ath.auth_prefix, '/test/')
3333-
3334- def test_default_swift_cluster_init(self):
3335- app = FakeApp()
3336- self.assertRaises(Exception, auth.filter_factory({
3337- 'super_admin_key': 'supertest',
3338- 'default_swift_cluster': 'local#badscheme://host/path'}), app)
3339- ath = auth.filter_factory({'super_admin_key': 'supertest'})(app)
3340- self.assertEquals(ath.default_swift_cluster,
3341- 'local#http://127.0.0.1:8080/v1')
3342- ath = auth.filter_factory({'super_admin_key': 'supertest',
3343- 'default_swift_cluster': 'local#http://host/path'})(app)
3344- self.assertEquals(ath.default_swift_cluster,
3345- 'local#http://host/path')
3346- ath = auth.filter_factory({'super_admin_key': 'supertest',
3347- 'default_swift_cluster': 'local#https://host/path/'})(app)
3348- self.assertEquals(ath.dsc_url, 'https://host/path')
3349- self.assertEquals(ath.dsc_url2, 'https://host/path')
3350- ath = auth.filter_factory({'super_admin_key': 'supertest',
3351- 'default_swift_cluster':
3352- 'local#https://host/path/#http://host2/path2/'})(app)
3353- self.assertEquals(ath.dsc_url, 'https://host/path')
3354- self.assertEquals(ath.dsc_url2, 'http://host2/path2')
3355+ ath = auth.filter_factory({})(app)
3356+ self.assertEquals(ath.auth_prefix, '/auth/')
3357+ ath = auth.filter_factory({'auth_prefix': ''})(app)
3358+ self.assertEquals(ath.auth_prefix, '/auth/')
3359+ ath = auth.filter_factory({'auth_prefix': '/test/'})(app)
3360+ self.assertEquals(ath.auth_prefix, '/test/')
3361+ ath = auth.filter_factory({'auth_prefix': '/test'})(app)
3362+ self.assertEquals(ath.auth_prefix, '/test/')
3363+ ath = auth.filter_factory({'auth_prefix': 'test/'})(app)
3364+ self.assertEquals(ath.auth_prefix, '/test/')
3365+ ath = auth.filter_factory({'auth_prefix': 'test'})(app)
3366+ self.assertEquals(ath.auth_prefix, '/test/')
3367
3368 def test_top_level_ignore(self):
3369- resp = Request.blank('/').get_response(self.test_auth)
3370+ resp = self._make_request('/').get_response(self.test_auth)
3371 self.assertEquals(resp.status_int, 404)
3372
3373 def test_anon(self):
3374- resp = Request.blank('/v1/AUTH_account').get_response(self.test_auth)
3375+ resp = self._make_request('/v1/AUTH_account').get_response(self.test_auth)
3376 self.assertEquals(resp.status_int, 401)
3377 self.assertEquals(resp.environ['swift.authorize'],
3378 self.test_auth.authorize)
3379
3380 def test_auth_deny_non_reseller_prefix(self):
3381- resp = Request.blank('/v1/BLAH_account',
3382+ resp = self._make_request('/v1/BLAH_account',
3383 headers={'X-Auth-Token': 'BLAH_t'}).get_response(self.test_auth)
3384 self.assertEquals(resp.status_int, 401)
3385 self.assertEquals(resp.environ['swift.authorize'],
3386@@ -188,7 +152,7 @@
3387
3388 def test_auth_deny_non_reseller_prefix_no_override(self):
3389 fake_authorize = lambda x: Response(status='500 Fake')
3390- resp = Request.blank('/v1/BLAH_account',
3391+ resp = self._make_request('/v1/BLAH_account',
3392 headers={'X-Auth-Token': 'BLAH_t'},
3393 environ={'swift.authorize': fake_authorize}
3394 ).get_response(self.test_auth)
3395@@ -200,192 +164,74 @@
3396 # outright but set up a denial swift.authorize and pass the request on
3397 # down the chain.
3398 local_app = FakeApp()
3399- local_auth = auth.filter_factory({'super_admin_key': 'supertest',
3400- 'reseller_prefix': ''})(local_app)
3401- resp = Request.blank('/v1/account',
3402+ local_auth = auth.filter_factory({'reseller_prefix': ''})(local_app)
3403+ resp = self._make_request('/v1/account',
3404 headers={'X-Auth-Token': 't'}).get_response(local_auth)
3405 self.assertEquals(resp.status_int, 401)
3406- # one for checking auth, two for request passed along
3407- self.assertEquals(local_app.calls, 2)
3408+ self.assertEquals(local_app.calls, 1)
3409 self.assertEquals(resp.environ['swift.authorize'],
3410 local_auth.denied_response)
3411
3412- def test_auth_no_reseller_prefix_allow(self):
3413- # Ensures that when we have no reseller prefix, we can still allow
3414- # access if our auth server accepts requests
3415- local_app = FakeApp(iter([
3416- ('200 Ok', {},
3417- json.dumps({'account': 'act', 'user': 'act:usr',
3418- 'account_id': 'AUTH_cfa',
3419- 'groups': [{'name': 'act:usr'}, {'name': 'act'},
3420- {'name': '.admin'}],
3421- 'expires': time() + 60})),
3422- ('204 No Content', {}, '')]))
3423- local_auth = auth.filter_factory({'super_admin_key': 'supertest',
3424- 'reseller_prefix': ''})(local_app)
3425- resp = Request.blank('/v1/act',
3426- headers={'X-Auth-Token': 't'}).get_response(local_auth)
3427- self.assertEquals(resp.status_int, 204)
3428- self.assertEquals(local_app.calls, 2)
3429- self.assertEquals(resp.environ['swift.authorize'],
3430- local_auth.authorize)
3431-
3432 def test_auth_no_reseller_prefix_no_token(self):
3433 # Check that normally we set up a call back to our authorize.
3434 local_auth = \
3435- auth.filter_factory({'super_admin_key': 'supertest',
3436- 'reseller_prefix': ''})(FakeApp(iter([])))
3437- resp = Request.blank('/v1/account').get_response(local_auth)
3438+ auth.filter_factory({'reseller_prefix': ''})(FakeApp(iter([])))
3439+ resp = self._make_request('/v1/account').get_response(local_auth)
3440 self.assertEquals(resp.status_int, 401)
3441 self.assertEquals(resp.environ['swift.authorize'],
3442 local_auth.authorize)
3443 # Now make sure we don't override an existing swift.authorize when we
3444 # have no reseller prefix.
3445 local_auth = \
3446- auth.filter_factory({'super_admin_key': 'supertest',
3447- 'reseller_prefix': ''})(FakeApp())
3448+ auth.filter_factory({'reseller_prefix': ''})(FakeApp())
3449 local_authorize = lambda req: Response('test')
3450- resp = Request.blank('/v1/account', environ={'swift.authorize':
3451+ resp = self._make_request('/v1/account', environ={'swift.authorize':
3452 local_authorize}).get_response(local_auth)
3453 self.assertEquals(resp.status_int, 200)
3454 self.assertEquals(resp.environ['swift.authorize'], local_authorize)
3455
3456 def test_auth_fail(self):
3457- resp = Request.blank('/v1/AUTH_cfa',
3458- headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth)
3459- self.assertEquals(resp.status_int, 401)
3460-
3461- def test_auth_success(self):
3462- self.test_auth.app = FakeApp(iter([
3463- ('200 Ok', {},
3464- json.dumps({'account': 'act', 'user': 'act:usr',
3465- 'account_id': 'AUTH_cfa',
3466- 'groups': [{'name': 'act:usr'}, {'name': 'act'},
3467- {'name': '.admin'}],
3468- 'expires': time() + 60})),
3469- ('204 No Content', {}, '')]))
3470- resp = Request.blank('/v1/AUTH_cfa',
3471- headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth)
3472- self.assertEquals(resp.status_int, 204)
3473- self.assertEquals(self.test_auth.app.calls, 2)
3474-
3475- def test_auth_memcache(self):
3476- # First run our test without memcache, showing we need to return the
3477- # token contents twice.
3478- self.test_auth.app = FakeApp(iter([
3479- ('200 Ok', {},
3480- json.dumps({'account': 'act', 'user': 'act:usr',
3481- 'account_id': 'AUTH_cfa',
3482- 'groups': [{'name': 'act:usr'}, {'name': 'act'},
3483- {'name': '.admin'}],
3484- 'expires': time() + 60})),
3485- ('204 No Content', {}, ''),
3486- ('200 Ok', {},
3487- json.dumps({'account': 'act', 'user': 'act:usr',
3488- 'account_id': 'AUTH_cfa',
3489- 'groups': [{'name': 'act:usr'}, {'name': 'act'},
3490- {'name': '.admin'}],
3491- 'expires': time() + 60})),
3492- ('204 No Content', {}, '')]))
3493- resp = Request.blank('/v1/AUTH_cfa',
3494- headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth)
3495- self.assertEquals(resp.status_int, 204)
3496- resp = Request.blank('/v1/AUTH_cfa',
3497- headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth)
3498- self.assertEquals(resp.status_int, 204)
3499- self.assertEquals(self.test_auth.app.calls, 4)
3500- # Now run our test with memcache, showing we no longer need to return
3501- # the token contents twice.
3502- self.test_auth.app = FakeApp(iter([
3503- ('200 Ok', {},
3504- json.dumps({'account': 'act', 'user': 'act:usr',
3505- 'account_id': 'AUTH_cfa',
3506- 'groups': [{'name': 'act:usr'}, {'name': 'act'},
3507- {'name': '.admin'}],
3508- 'expires': time() + 60})),
3509- ('204 No Content', {}, ''),
3510- # Don't need a second token object returned if memcache is used
3511- ('204 No Content', {}, '')]))
3512- fake_memcache = FakeMemcache()
3513- resp = Request.blank('/v1/AUTH_cfa',
3514- headers={'X-Auth-Token': 'AUTH_t'},
3515- environ={'swift.cache': fake_memcache}
3516- ).get_response(self.test_auth)
3517- self.assertEquals(resp.status_int, 204)
3518- resp = Request.blank('/v1/AUTH_cfa',
3519- headers={'X-Auth-Token': 'AUTH_t'},
3520- environ={'swift.cache': fake_memcache}
3521- ).get_response(self.test_auth)
3522- self.assertEquals(resp.status_int, 204)
3523- self.assertEquals(self.test_auth.app.calls, 3)
3524-
3525- def test_auth_just_expired(self):
3526- self.test_auth.app = FakeApp(iter([
3527- # Request for token (which will have expired)
3528- ('200 Ok', {},
3529- json.dumps({'account': 'act', 'user': 'act:usr',
3530- 'account_id': 'AUTH_cfa',
3531- 'groups': [{'name': 'act:usr'}, {'name': 'act'},
3532- {'name': '.admin'}],
3533- 'expires': time() - 1})),
3534- # Request to delete token
3535- ('204 No Content', {}, '')]))
3536- resp = Request.blank('/v1/AUTH_cfa',
3537- headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth)
3538- self.assertEquals(resp.status_int, 401)
3539- self.assertEquals(self.test_auth.app.calls, 2)
3540-
3541- def test_middleware_storage_token(self):
3542- self.test_auth.app = FakeApp(iter([
3543- ('200 Ok', {},
3544- json.dumps({'account': 'act', 'user': 'act:usr',
3545- 'account_id': 'AUTH_cfa',
3546- 'groups': [{'name': 'act:usr'}, {'name': 'act'},
3547- {'name': '.admin'}],
3548- 'expires': time() + 60})),
3549- ('204 No Content', {}, '')]))
3550- resp = Request.blank('/v1/AUTH_cfa',
3551- headers={'X-Storage-Token': 'AUTH_t'}).get_response(self.test_auth)
3552- self.assertEquals(resp.status_int, 204)
3553- self.assertEquals(self.test_auth.app.calls, 2)
3554+ resp = self._make_request('/v1/AUTH_cfa',
3555+ headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth)
3556+ self.assertEquals(resp.status_int, 401)
3557
3558 def test_authorize_bad_path(self):
3559- req = Request.blank('/badpath')
3560+ req = self._make_request('/badpath')
3561 resp = self.test_auth.authorize(req)
3562 self.assertEquals(resp.status_int, 401)
3563- req = Request.blank('/badpath')
3564+ req = self._make_request('/badpath')
3565 req.remote_user = 'act:usr,act,AUTH_cfa'
3566 resp = self.test_auth.authorize(req)
3567 self.assertEquals(resp.status_int, 403)
3568
3569 def test_authorize_account_access(self):
3570- req = Request.blank('/v1/AUTH_cfa')
3571+ req = self._make_request('/v1/AUTH_cfa')
3572 req.remote_user = 'act:usr,act,AUTH_cfa'
3573 self.assertEquals(self.test_auth.authorize(req), None)
3574- req = Request.blank('/v1/AUTH_cfa')
3575+ req = self._make_request('/v1/AUTH_cfa')
3576 req.remote_user = 'act:usr,act'
3577 resp = self.test_auth.authorize(req)
3578 self.assertEquals(resp.status_int, 403)
3579
3580 def test_authorize_acl_group_access(self):
3581- req = Request.blank('/v1/AUTH_cfa')
3582+ req = self._make_request('/v1/AUTH_cfa')
3583 req.remote_user = 'act:usr,act'
3584 resp = self.test_auth.authorize(req)
3585 self.assertEquals(resp.status_int, 403)
3586- req = Request.blank('/v1/AUTH_cfa')
3587+ req = self._make_request('/v1/AUTH_cfa')
3588 req.remote_user = 'act:usr,act'
3589 req.acl = 'act'
3590 self.assertEquals(self.test_auth.authorize(req), None)
3591- req = Request.blank('/v1/AUTH_cfa')
3592+ req = self._make_request('/v1/AUTH_cfa')
3593 req.remote_user = 'act:usr,act'
3594 req.acl = 'act:usr'
3595 self.assertEquals(self.test_auth.authorize(req), None)
3596- req = Request.blank('/v1/AUTH_cfa')
3597+ req = self._make_request('/v1/AUTH_cfa')
3598 req.remote_user = 'act:usr,act'
3599 req.acl = 'act2'
3600 resp = self.test_auth.authorize(req)
3601 self.assertEquals(resp.status_int, 403)
3602- req = Request.blank('/v1/AUTH_cfa')
3603+ req = self._make_request('/v1/AUTH_cfa')
3604 req.remote_user = 'act:usr,act'
3605 req.acl = 'act:usr2'
3606 resp = self.test_auth.authorize(req)
3607@@ -393,105 +239,105 @@
3608
3609 def test_deny_cross_reseller(self):
3610 # Tests that cross-reseller is denied, even if ACLs/group names match
3611- req = Request.blank('/v1/OTHER_cfa')
3612+ req = self._make_request('/v1/OTHER_cfa')
3613 req.remote_user = 'act:usr,act,AUTH_cfa'
3614 req.acl = 'act'
3615 resp = self.test_auth.authorize(req)
3616 self.assertEquals(resp.status_int, 403)
3617
3618 def test_authorize_acl_referrer_access(self):
3619- req = Request.blank('/v1/AUTH_cfa/c')
3620+ req = self._make_request('/v1/AUTH_cfa/c')
3621 req.remote_user = 'act:usr,act'
3622 resp = self.test_auth.authorize(req)
3623 self.assertEquals(resp.status_int, 403)
3624- req = Request.blank('/v1/AUTH_cfa/c')
3625+ req = self._make_request('/v1/AUTH_cfa/c')
3626 req.remote_user = 'act:usr,act'
3627 req.acl = '.r:*,.rlistings'
3628 self.assertEquals(self.test_auth.authorize(req), None)
3629- req = Request.blank('/v1/AUTH_cfa/c')
3630+ req = self._make_request('/v1/AUTH_cfa/c')
3631 req.remote_user = 'act:usr,act'
3632 req.acl = '.r:*' # No listings allowed
3633 resp = self.test_auth.authorize(req)
3634 self.assertEquals(resp.status_int, 403)
3635- req = Request.blank('/v1/AUTH_cfa/c')
3636+ req = self._make_request('/v1/AUTH_cfa/c')
3637 req.remote_user = 'act:usr,act'
3638 req.acl = '.r:.example.com,.rlistings'
3639 resp = self.test_auth.authorize(req)
3640 self.assertEquals(resp.status_int, 403)
3641- req = Request.blank('/v1/AUTH_cfa/c')
3642+ req = self._make_request('/v1/AUTH_cfa/c')
3643 req.remote_user = 'act:usr,act'
3644 req.referer = 'http://www.example.com/index.html'
3645 req.acl = '.r:.example.com,.rlistings'
3646 self.assertEquals(self.test_auth.authorize(req), None)
3647- req = Request.blank('/v1/AUTH_cfa/c')
3648+ req = self._make_request('/v1/AUTH_cfa/c')
3649 resp = self.test_auth.authorize(req)
3650 self.assertEquals(resp.status_int, 401)
3651- req = Request.blank('/v1/AUTH_cfa/c')
3652+ req = self._make_request('/v1/AUTH_cfa/c')
3653 req.acl = '.r:*,.rlistings'
3654 self.assertEquals(self.test_auth.authorize(req), None)
3655- req = Request.blank('/v1/AUTH_cfa/c')
3656+ req = self._make_request('/v1/AUTH_cfa/c')
3657 req.acl = '.r:*' # No listings allowed
3658 resp = self.test_auth.authorize(req)
3659 self.assertEquals(resp.status_int, 401)
3660- req = Request.blank('/v1/AUTH_cfa/c')
3661+ req = self._make_request('/v1/AUTH_cfa/c')
3662 req.acl = '.r:.example.com,.rlistings'
3663 resp = self.test_auth.authorize(req)
3664 self.assertEquals(resp.status_int, 401)
3665- req = Request.blank('/v1/AUTH_cfa/c')
3666+ req = self._make_request('/v1/AUTH_cfa/c')
3667 req.referer = 'http://www.example.com/index.html'
3668 req.acl = '.r:.example.com,.rlistings'
3669 self.assertEquals(self.test_auth.authorize(req), None)
3670
3671 def test_account_put_permissions(self):
3672- req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
3673+ req = self._make_request('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
3674 req.remote_user = 'act:usr,act'
3675 resp = self.test_auth.authorize(req)
3676 self.assertEquals(resp.status_int, 403)
3677
3678- req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
3679+ req = self._make_request('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
3680 req.remote_user = 'act:usr,act,AUTH_other'
3681 resp = self.test_auth.authorize(req)
3682 self.assertEquals(resp.status_int, 403)
3683
3684 # Even PUTs to your own account as account admin should fail
3685- req = Request.blank('/v1/AUTH_old', environ={'REQUEST_METHOD': 'PUT'})
3686+ req = self._make_request('/v1/AUTH_old', environ={'REQUEST_METHOD': 'PUT'})
3687 req.remote_user = 'act:usr,act,AUTH_old'
3688 resp = self.test_auth.authorize(req)
3689 self.assertEquals(resp.status_int, 403)
3690
3691- req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
3692+ req = self._make_request('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
3693 req.remote_user = 'act:usr,act,.reseller_admin'
3694 resp = self.test_auth.authorize(req)
3695 self.assertEquals(resp, None)
3696
3697 # .super_admin is not something the middleware should ever see or care
3698 # about
3699- req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
3700+ req = self._make_request('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
3701 req.remote_user = 'act:usr,act,.super_admin'
3702 resp = self.test_auth.authorize(req)
3703 self.assertEquals(resp.status_int, 403)
3704
3705 def test_account_delete_permissions(self):
3706- req = Request.blank('/v1/AUTH_new',
3707+ req = self._make_request('/v1/AUTH_new',
3708 environ={'REQUEST_METHOD': 'DELETE'})
3709 req.remote_user = 'act:usr,act'
3710 resp = self.test_auth.authorize(req)
3711 self.assertEquals(resp.status_int, 403)
3712
3713- req = Request.blank('/v1/AUTH_new',
3714+ req = self._make_request('/v1/AUTH_new',
3715 environ={'REQUEST_METHOD': 'DELETE'})
3716 req.remote_user = 'act:usr,act,AUTH_other'
3717 resp = self.test_auth.authorize(req)
3718 self.assertEquals(resp.status_int, 403)
3719
3720 # Even DELETEs to your own account as account admin should fail
3721- req = Request.blank('/v1/AUTH_old',
3722+ req = self._make_request('/v1/AUTH_old',
3723 environ={'REQUEST_METHOD': 'DELETE'})
3724 req.remote_user = 'act:usr,act,AUTH_old'
3725 resp = self.test_auth.authorize(req)
3726 self.assertEquals(resp.status_int, 403)
3727
3728- req = Request.blank('/v1/AUTH_new',
3729+ req = self._make_request('/v1/AUTH_new',
3730 environ={'REQUEST_METHOD': 'DELETE'})
3731 req.remote_user = 'act:usr,act,.reseller_admin'
3732 resp = self.test_auth.authorize(req)
3733@@ -499,7 +345,7 @@
3734
3735 # .super_admin is not something the middleware should ever see or care
3736 # about
3737- req = Request.blank('/v1/AUTH_new',
3738+ req = self._make_request('/v1/AUTH_new',
3739 environ={'REQUEST_METHOD': 'DELETE'})
3740 req.remote_user = 'act:usr,act,.super_admin'
3741 resp = self.test_auth.authorize(req)
3742@@ -507,2715 +353,36 @@
3743 self.assertEquals(resp.status_int, 403)
3744
3745 def test_get_token_fail(self):
3746- resp = Request.blank('/auth/v1.0').get_response(self.test_auth)
3747+ resp = self._make_request('/auth/v1.0').get_response(self.test_auth)
3748 self.assertEquals(resp.status_int, 401)
3749- resp = Request.blank('/auth/v1.0',
3750+ resp = self._make_request('/auth/v1.0',
3751 headers={'X-Auth-User': 'act:usr',
3752 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3753 self.assertEquals(resp.status_int, 401)
3754
3755- def test_get_token_fail_invalid_key(self):
3756- self.test_auth.app = FakeApp(iter([
3757- # GET of user object
3758- ('200 Ok', {},
3759- json.dumps({"auth": "plaintext:key",
3760- "groups": [{'name': "act:usr"}, {'name': "act"},
3761- {'name': ".admin"}]}))]))
3762- resp = Request.blank('/auth/v1.0',
3763- headers={'X-Auth-User': 'act:usr',
3764- 'X-Auth-Key': 'invalid'}).get_response(self.test_auth)
3765- self.assertEquals(resp.status_int, 401)
3766- self.assertEquals(self.test_auth.app.calls, 1)
3767-
3768 def test_get_token_fail_invalid_x_auth_user_format(self):
3769- resp = Request.blank('/auth/v1/act/auth',
3770+ resp = self._make_request('/auth/v1/act/auth',
3771 headers={'X-Auth-User': 'usr',
3772 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3773 self.assertEquals(resp.status_int, 401)
3774
3775 def test_get_token_fail_non_matching_account_in_request(self):
3776- resp = Request.blank('/auth/v1/act/auth',
3777+ resp = self._make_request('/auth/v1/act/auth',
3778 headers={'X-Auth-User': 'act2:usr',
3779 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3780 self.assertEquals(resp.status_int, 401)
3781
3782 def test_get_token_fail_bad_path(self):
3783- resp = Request.blank('/auth/v1/act/auth/invalid',
3784+ resp = self._make_request('/auth/v1/act/auth/invalid',
3785 headers={'X-Auth-User': 'act:usr',
3786 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3787 self.assertEquals(resp.status_int, 400)
3788
3789 def test_get_token_fail_missing_key(self):
3790- resp = Request.blank('/auth/v1/act/auth',
3791+ resp = self._make_request('/auth/v1/act/auth',
3792 headers={'X-Auth-User': 'act:usr'}).get_response(self.test_auth)
3793 self.assertEquals(resp.status_int, 401)
3794
3795- def test_get_token_fail_get_user_details(self):
3796- self.test_auth.app = FakeApp(iter([
3797- ('503 Service Unavailable', {}, '')]))
3798- resp = Request.blank('/auth/v1.0',
3799- headers={'X-Auth-User': 'act:usr',
3800- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3801- self.assertEquals(resp.status_int, 500)
3802- self.assertEquals(self.test_auth.app.calls, 1)
3803-
3804- def test_get_token_fail_get_account(self):
3805- self.test_auth.app = FakeApp(iter([
3806- # GET of user object
3807- ('200 Ok', {},
3808- json.dumps({"auth": "plaintext:key",
3809- "groups": [{'name': "act:usr"}, {'name': "act"},
3810- {'name': ".admin"}]})),
3811- # GET of account
3812- ('503 Service Unavailable', {}, '')]))
3813- resp = Request.blank('/auth/v1.0',
3814- headers={'X-Auth-User': 'act:usr',
3815- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3816- self.assertEquals(resp.status_int, 500)
3817- self.assertEquals(self.test_auth.app.calls, 2)
3818-
3819- def test_get_token_fail_put_new_token(self):
3820- self.test_auth.app = FakeApp(iter([
3821- # GET of user object
3822- ('200 Ok', {},
3823- json.dumps({"auth": "plaintext:key",
3824- "groups": [{'name': "act:usr"}, {'name': "act"},
3825- {'name': ".admin"}]})),
3826- # GET of account
3827- ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
3828- # PUT of new token
3829- ('503 Service Unavailable', {}, '')]))
3830- resp = Request.blank('/auth/v1.0',
3831- headers={'X-Auth-User': 'act:usr',
3832- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3833- self.assertEquals(resp.status_int, 500)
3834- self.assertEquals(self.test_auth.app.calls, 3)
3835-
3836- def test_get_token_fail_post_to_user(self):
3837- self.test_auth.app = FakeApp(iter([
3838- # GET of user object
3839- ('200 Ok', {},
3840- json.dumps({"auth": "plaintext:key",
3841- "groups": [{'name': "act:usr"}, {'name': "act"},
3842- {'name': ".admin"}]})),
3843- # GET of account
3844- ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
3845- # PUT of new token
3846- ('201 Created', {}, ''),
3847- # POST of token to user object
3848- ('503 Service Unavailable', {}, '')]))
3849- resp = Request.blank('/auth/v1.0',
3850- headers={'X-Auth-User': 'act:usr',
3851- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3852- self.assertEquals(resp.status_int, 500)
3853- self.assertEquals(self.test_auth.app.calls, 4)
3854-
3855- def test_get_token_fail_get_services(self):
3856- self.test_auth.app = FakeApp(iter([
3857- # GET of user object
3858- ('200 Ok', {},
3859- json.dumps({"auth": "plaintext:key",
3860- "groups": [{'name': "act:usr"}, {'name': "act"},
3861- {'name': ".admin"}]})),
3862- # GET of account
3863- ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
3864- # PUT of new token
3865- ('201 Created', {}, ''),
3866- # POST of token to user object
3867- ('204 No Content', {}, ''),
3868- # GET of services object
3869- ('503 Service Unavailable', {}, '')]))
3870- resp = Request.blank('/auth/v1.0',
3871- headers={'X-Auth-User': 'act:usr',
3872- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3873- self.assertEquals(resp.status_int, 500)
3874- self.assertEquals(self.test_auth.app.calls, 5)
3875-
3876- def test_get_token_fail_get_existing_token(self):
3877- self.test_auth.app = FakeApp(iter([
3878- # GET of user object
3879- ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'},
3880- json.dumps({"auth": "plaintext:key",
3881- "groups": [{'name': "act:usr"}, {'name': "act"},
3882- {'name': ".admin"}]})),
3883- # GET of token
3884- ('503 Service Unavailable', {}, '')]))
3885- resp = Request.blank('/auth/v1.0',
3886- headers={'X-Auth-User': 'act:usr',
3887- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3888- self.assertEquals(resp.status_int, 500)
3889- self.assertEquals(self.test_auth.app.calls, 2)
3890-
3891- def test_get_token_success_v1_0(self):
3892- self.test_auth.app = FakeApp(iter([
3893- # GET of user object
3894- ('200 Ok', {},
3895- json.dumps({"auth": "plaintext:key",
3896- "groups": [{'name': "act:usr"}, {'name': "act"},
3897- {'name': ".admin"}]})),
3898- # GET of account
3899- ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
3900- # PUT of new token
3901- ('201 Created', {}, ''),
3902- # POST of token to user object
3903- ('204 No Content', {}, ''),
3904- # GET of services object
3905- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
3906- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))]))
3907- resp = Request.blank('/auth/v1.0',
3908- headers={'X-Auth-User': 'act:usr',
3909- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
3910- self.assertEquals(resp.status_int, 200)
3911- self.assert_(resp.headers.get('x-auth-token',
3912- '').startswith('AUTH_tk'), resp.headers.get('x-auth-token'))
3913- self.assertEquals(resp.headers.get('x-auth-token'),
3914- resp.headers.get('x-storage-token'))
3915- self.assertEquals(resp.headers.get('x-storage-url'),
3916- 'http://127.0.0.1:8080/v1/AUTH_cfa')
3917- self.assertEquals(json.loads(resp.body),
3918- {"storage": {"default": "local",
3919- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
3920- self.assertEquals(self.test_auth.app.calls, 5)
3921-
3922- def test_get_token_success_v1_act_auth(self):
3923- self.test_auth.app = FakeApp(iter([
3924- # GET of user object
3925- ('200 Ok', {},
3926- json.dumps({"auth": "plaintext:key",
3927- "groups": [{'name': "act:usr"}, {'name': "act"},
3928- {'name': ".admin"}]})),
3929- # GET of account
3930- ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
3931- # PUT of new token
3932- ('201 Created', {}, ''),
3933- # POST of token to user object
3934- ('204 No Content', {}, ''),
3935- # GET of services object
3936- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
3937- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))]))
3938- resp = Request.blank('/auth/v1/act/auth',
3939- headers={'X-Storage-User': 'usr',
3940- 'X-Storage-Pass': 'key'}).get_response(self.test_auth)
3941- self.assertEquals(resp.status_int, 200)
3942- self.assert_(resp.headers.get('x-auth-token',
3943- '').startswith('AUTH_tk'), resp.headers.get('x-auth-token'))
3944- self.assertEquals(resp.headers.get('x-auth-token'),
3945- resp.headers.get('x-storage-token'))
3946- self.assertEquals(resp.headers.get('x-storage-url'),
3947- 'http://127.0.0.1:8080/v1/AUTH_cfa')
3948- self.assertEquals(json.loads(resp.body),
3949- {"storage": {"default": "local",
3950- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
3951- self.assertEquals(self.test_auth.app.calls, 5)
3952-
3953- def test_get_token_success_storage_instead_of_auth(self):
3954- self.test_auth.app = FakeApp(iter([
3955- # GET of user object
3956- ('200 Ok', {},
3957- json.dumps({"auth": "plaintext:key",
3958- "groups": [{'name': "act:usr"}, {'name': "act"},
3959- {'name': ".admin"}]})),
3960- # GET of account
3961- ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
3962- # PUT of new token
3963- ('201 Created', {}, ''),
3964- # POST of token to user object
3965- ('204 No Content', {}, ''),
3966- # GET of services object
3967- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
3968- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))]))
3969- resp = Request.blank('/auth/v1.0',
3970- headers={'X-Storage-User': 'act:usr',
3971- 'X-Storage-Pass': 'key'}).get_response(self.test_auth)
3972- self.assertEquals(resp.status_int, 200)
3973- self.assert_(resp.headers.get('x-auth-token',
3974- '').startswith('AUTH_tk'), resp.headers.get('x-auth-token'))
3975- self.assertEquals(resp.headers.get('x-auth-token'),
3976- resp.headers.get('x-storage-token'))
3977- self.assertEquals(resp.headers.get('x-storage-url'),
3978- 'http://127.0.0.1:8080/v1/AUTH_cfa')
3979- self.assertEquals(json.loads(resp.body),
3980- {"storage": {"default": "local",
3981- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
3982- self.assertEquals(self.test_auth.app.calls, 5)
3983-
3984- def test_get_token_success_v1_act_auth_auth_instead_of_storage(self):
3985- self.test_auth.app = FakeApp(iter([
3986- # GET of user object
3987- ('200 Ok', {},
3988- json.dumps({"auth": "plaintext:key",
3989- "groups": [{'name': "act:usr"}, {'name': "act"},
3990- {'name': ".admin"}]})),
3991- # GET of account
3992- ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
3993- # PUT of new token
3994- ('201 Created', {}, ''),
3995- # POST of token to user object
3996- ('204 No Content', {}, ''),
3997- # GET of services object
3998- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
3999- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))]))
4000- resp = Request.blank('/auth/v1/act/auth',
4001- headers={'X-Auth-User': 'act:usr',
4002- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
4003- self.assertEquals(resp.status_int, 200)
4004- self.assert_(resp.headers.get('x-auth-token',
4005- '').startswith('AUTH_tk'), resp.headers.get('x-auth-token'))
4006- self.assertEquals(resp.headers.get('x-auth-token'),
4007- resp.headers.get('x-storage-token'))
4008- self.assertEquals(resp.headers.get('x-storage-url'),
4009- 'http://127.0.0.1:8080/v1/AUTH_cfa')
4010- self.assertEquals(json.loads(resp.body),
4011- {"storage": {"default": "local",
4012- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
4013- self.assertEquals(self.test_auth.app.calls, 5)
4014-
4015- def test_get_token_success_existing_token(self):
4016- self.test_auth.app = FakeApp(iter([
4017- # GET of user object
4018- ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'},
4019- json.dumps({"auth": "plaintext:key",
4020- "groups": [{'name': "act:usr"}, {'name': "act"},
4021- {'name': ".admin"}]})),
4022- # GET of token
4023- ('200 Ok', {}, json.dumps({"account": "act", "user": "usr",
4024- "account_id": "AUTH_cfa", "groups": [{'name': "act:usr"},
4025- {'name': "key"}, {'name': ".admin"}],
4026- "expires": 9999999999.9999999})),
4027- # GET of services object
4028- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4029- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))]))
4030- resp = Request.blank('/auth/v1.0',
4031- headers={'X-Auth-User': 'act:usr',
4032- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
4033- self.assertEquals(resp.status_int, 200)
4034- self.assertEquals(resp.headers.get('x-auth-token'), 'AUTH_tktest')
4035- self.assertEquals(resp.headers.get('x-auth-token'),
4036- resp.headers.get('x-storage-token'))
4037- self.assertEquals(resp.headers.get('x-storage-url'),
4038- 'http://127.0.0.1:8080/v1/AUTH_cfa')
4039- self.assertEquals(json.loads(resp.body),
4040- {"storage": {"default": "local",
4041- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
4042- self.assertEquals(self.test_auth.app.calls, 3)
4043-
4044- def test_get_token_success_existing_token_expired(self):
4045- self.test_auth.app = FakeApp(iter([
4046- # GET of user object
4047- ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'},
4048- json.dumps({"auth": "plaintext:key",
4049- "groups": [{'name': "act:usr"}, {'name': "act"},
4050- {'name': ".admin"}]})),
4051- # GET of token
4052- ('200 Ok', {}, json.dumps({"account": "act", "user": "usr",
4053- "account_id": "AUTH_cfa", "groups": [{'name': "act:usr"},
4054- {'name': "key"}, {'name': ".admin"}],
4055- "expires": 0.0})),
4056- # DELETE of expired token
4057- ('204 No Content', {}, ''),
4058- # GET of account
4059- ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
4060- # PUT of new token
4061- ('201 Created', {}, ''),
4062- # POST of token to user object
4063- ('204 No Content', {}, ''),
4064- # GET of services object
4065- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4066- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))]))
4067- resp = Request.blank('/auth/v1.0',
4068- headers={'X-Auth-User': 'act:usr',
4069- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
4070- self.assertEquals(resp.status_int, 200)
4071- self.assertNotEquals(resp.headers.get('x-auth-token'), 'AUTH_tktest')
4072- self.assertEquals(resp.headers.get('x-auth-token'),
4073- resp.headers.get('x-storage-token'))
4074- self.assertEquals(resp.headers.get('x-storage-url'),
4075- 'http://127.0.0.1:8080/v1/AUTH_cfa')
4076- self.assertEquals(json.loads(resp.body),
4077- {"storage": {"default": "local",
4078- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
4079- self.assertEquals(self.test_auth.app.calls, 7)
4080-
4081- def test_get_token_success_existing_token_expired_fail_deleting_old(self):
4082- self.test_auth.app = FakeApp(iter([
4083- # GET of user object
4084- ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'},
4085- json.dumps({"auth": "plaintext:key",
4086- "groups": [{'name': "act:usr"}, {'name': "act"},
4087- {'name': ".admin"}]})),
4088- # GET of token
4089- ('200 Ok', {}, json.dumps({"account": "act", "user": "usr",
4090- "account_id": "AUTH_cfa", "groups": [{'name': "act:usr"},
4091- {'name': "key"}, {'name': ".admin"}],
4092- "expires": 0.0})),
4093- # DELETE of expired token
4094- ('503 Service Unavailable', {}, ''),
4095- # GET of account
4096- ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
4097- # PUT of new token
4098- ('201 Created', {}, ''),
4099- # POST of token to user object
4100- ('204 No Content', {}, ''),
4101- # GET of services object
4102- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4103- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))]))
4104- resp = Request.blank('/auth/v1.0',
4105- headers={'X-Auth-User': 'act:usr',
4106- 'X-Auth-Key': 'key'}).get_response(self.test_auth)
4107- self.assertEquals(resp.status_int, 200)
4108- self.assertNotEquals(resp.headers.get('x-auth-token'), 'AUTH_tktest')
4109- self.assertEquals(resp.headers.get('x-auth-token'),
4110- resp.headers.get('x-storage-token'))
4111- self.assertEquals(resp.headers.get('x-storage-url'),
4112- 'http://127.0.0.1:8080/v1/AUTH_cfa')
4113- self.assertEquals(json.loads(resp.body),
4114- {"storage": {"default": "local",
4115- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
4116- self.assertEquals(self.test_auth.app.calls, 7)
4117-
4118- def test_prep_success(self):
4119- list_to_iter = [
4120- # PUT of .auth account
4121- ('201 Created', {}, ''),
4122- # PUT of .account_id container
4123- ('201 Created', {}, '')]
4124- # PUT of .token* containers
4125- for x in xrange(16):
4126- list_to_iter.append(('201 Created', {}, ''))
4127- self.test_auth.app = FakeApp(iter(list_to_iter))
4128- resp = Request.blank('/auth/v2/.prep',
4129- environ={'REQUEST_METHOD': 'POST'},
4130- headers={'X-Auth-Admin-User': '.super_admin',
4131- 'X-Auth-Admin-Key': 'supertest'}
4132- ).get_response(self.test_auth)
4133- self.assertEquals(resp.status_int, 204)
4134- self.assertEquals(self.test_auth.app.calls, 18)
4135-
4136- def test_prep_bad_method(self):
4137- resp = Request.blank('/auth/v2/.prep',
4138- headers={'X-Auth-Admin-User': '.super_admin',
4139- 'X-Auth-Admin-Key': 'supertest'}
4140- ).get_response(self.test_auth)
4141- self.assertEquals(resp.status_int, 400)
4142- resp = Request.blank('/auth/v2/.prep',
4143- environ={'REQUEST_METHOD': 'HEAD'},
4144- headers={'X-Auth-Admin-User': '.super_admin',
4145- 'X-Auth-Admin-Key': 'supertest'}
4146- ).get_response(self.test_auth)
4147- self.assertEquals(resp.status_int, 400)
4148- resp = Request.blank('/auth/v2/.prep',
4149- environ={'REQUEST_METHOD': 'PUT'},
4150- headers={'X-Auth-Admin-User': '.super_admin',
4151- 'X-Auth-Admin-Key': 'supertest'}
4152- ).get_response(self.test_auth)
4153- self.assertEquals(resp.status_int, 400)
4154-
4155- def test_prep_bad_creds(self):
4156- resp = Request.blank('/auth/v2/.prep',
4157- environ={'REQUEST_METHOD': 'POST'},
4158- headers={'X-Auth-Admin-User': 'super_admin',
4159- 'X-Auth-Admin-Key': 'supertest'}
4160- ).get_response(self.test_auth)
4161- self.assertEquals(resp.status_int, 403)
4162- resp = Request.blank('/auth/v2/.prep',
4163- environ={'REQUEST_METHOD': 'POST'},
4164- headers={'X-Auth-Admin-User': '.super_admin',
4165- 'X-Auth-Admin-Key': 'upertest'}
4166- ).get_response(self.test_auth)
4167- self.assertEquals(resp.status_int, 403)
4168- resp = Request.blank('/auth/v2/.prep',
4169- environ={'REQUEST_METHOD': 'POST'},
4170- headers={'X-Auth-Admin-User': '.super_admin'}
4171- ).get_response(self.test_auth)
4172- self.assertEquals(resp.status_int, 403)
4173- resp = Request.blank('/auth/v2/.prep',
4174- environ={'REQUEST_METHOD': 'POST'},
4175- headers={'X-Auth-Admin-Key': 'supertest'}
4176- ).get_response(self.test_auth)
4177- self.assertEquals(resp.status_int, 403)
4178- resp = Request.blank('/auth/v2/.prep',
4179- environ={'REQUEST_METHOD': 'POST'}).get_response(self.test_auth)
4180- self.assertEquals(resp.status_int, 403)
4181-
4182- def test_prep_fail_account_create(self):
4183- self.test_auth.app = FakeApp(iter([
4184- # PUT of .auth account
4185- ('503 Service Unavailable', {}, '')]))
4186- resp = Request.blank('/auth/v2/.prep',
4187- environ={'REQUEST_METHOD': 'POST'},
4188- headers={'X-Auth-Admin-User': '.super_admin',
4189- 'X-Auth-Admin-Key': 'supertest'}
4190- ).get_response(self.test_auth)
4191- self.assertEquals(resp.status_int, 500)
4192- self.assertEquals(self.test_auth.app.calls, 1)
4193-
4194- def test_prep_fail_token_container_create(self):
4195- self.test_auth.app = FakeApp(iter([
4196- # PUT of .auth account
4197- ('201 Created', {}, ''),
4198- # PUT of .token container
4199- ('503 Service Unavailable', {}, '')]))
4200- resp = Request.blank('/auth/v2/.prep',
4201- environ={'REQUEST_METHOD': 'POST'},
4202- headers={'X-Auth-Admin-User': '.super_admin',
4203- 'X-Auth-Admin-Key': 'supertest'}
4204- ).get_response(self.test_auth)
4205- self.assertEquals(resp.status_int, 500)
4206- self.assertEquals(self.test_auth.app.calls, 2)
4207-
4208- def test_prep_fail_account_id_container_create(self):
4209- self.test_auth.app = FakeApp(iter([
4210- # PUT of .auth account
4211- ('201 Created', {}, ''),
4212- # PUT of .token container
4213- ('201 Created', {}, ''),
4214- # PUT of .account_id container
4215- ('503 Service Unavailable', {}, '')]))
4216- resp = Request.blank('/auth/v2/.prep',
4217- environ={'REQUEST_METHOD': 'POST'},
4218- headers={'X-Auth-Admin-User': '.super_admin',
4219- 'X-Auth-Admin-Key': 'supertest'}
4220- ).get_response(self.test_auth)
4221- self.assertEquals(resp.status_int, 500)
4222- self.assertEquals(self.test_auth.app.calls, 3)
4223-
4224- def test_get_reseller_success(self):
4225- self.test_auth.app = FakeApp(iter([
4226- # GET of .auth account (list containers)
4227- ('200 Ok', {}, json.dumps([
4228- {"name": ".token", "count": 0, "bytes": 0},
4229- {"name": ".account_id", "count": 0, "bytes": 0},
4230- {"name": "act", "count": 0, "bytes": 0}])),
4231- # GET of .auth account (list containers continuation)
4232- ('200 Ok', {}, '[]')]))
4233- resp = Request.blank('/auth/v2',
4234- headers={'X-Auth-Admin-User': '.super_admin',
4235- 'X-Auth-Admin-Key': 'supertest'}
4236- ).get_response(self.test_auth)
4237- self.assertEquals(resp.status_int, 200)
4238- self.assertEquals(json.loads(resp.body),
4239- {"accounts": [{"name": "act"}]})
4240- self.assertEquals(self.test_auth.app.calls, 2)
4241-
4242- self.test_auth.app = FakeApp(iter([
4243- # GET of user object
4244- ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"},
4245- {"name": "test"}, {"name": ".admin"},
4246- {"name": ".reseller_admin"}], "auth": "plaintext:key"})),
4247- # GET of .auth account (list containers)
4248- ('200 Ok', {}, json.dumps([
4249- {"name": ".token", "count": 0, "bytes": 0},
4250- {"name": ".account_id", "count": 0, "bytes": 0},
4251- {"name": "act", "count": 0, "bytes": 0}])),
4252- # GET of .auth account (list containers continuation)
4253- ('200 Ok', {}, '[]')]))
4254- resp = Request.blank('/auth/v2',
4255- headers={'X-Auth-Admin-User': 'act:adm',
4256- 'X-Auth-Admin-Key': 'key'}
4257- ).get_response(self.test_auth)
4258- self.assertEquals(resp.status_int, 200)
4259- self.assertEquals(json.loads(resp.body),
4260- {"accounts": [{"name": "act"}]})
4261- self.assertEquals(self.test_auth.app.calls, 3)
4262-
4263- def test_get_reseller_fail_bad_creds(self):
4264- self.test_auth.app = FakeApp(iter([
4265- # GET of user object
4266- ('404 Not Found', {}, '')]))
4267- resp = Request.blank('/auth/v2',
4268- headers={'X-Auth-Admin-User': 'super:admin',
4269- 'X-Auth-Admin-Key': 'supertest'}
4270- ).get_response(self.test_auth)
4271- self.assertEquals(resp.status_int, 403)
4272- self.assertEquals(self.test_auth.app.calls, 1)
4273-
4274- self.test_auth.app = FakeApp(iter([
4275- # GET of user object (account admin, but not reseller admin)
4276- ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"},
4277- {"name": "test"}, {"name": ".admin"}],
4278- "auth": "plaintext:key"}))]))
4279- resp = Request.blank('/auth/v2',
4280- headers={'X-Auth-Admin-User': 'act:adm',
4281- 'X-Auth-Admin-Key': 'key'}
4282- ).get_response(self.test_auth)
4283- self.assertEquals(resp.status_int, 403)
4284- self.assertEquals(self.test_auth.app.calls, 1)
4285-
4286- self.test_auth.app = FakeApp(iter([
4287- # GET of user object (regular user)
4288- ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"},
4289- {"name": "test"}], "auth": "plaintext:key"}))]))
4290- resp = Request.blank('/auth/v2',
4291- headers={'X-Auth-Admin-User': 'act:usr',
4292- 'X-Auth-Admin-Key': 'key'}
4293- ).get_response(self.test_auth)
4294- self.assertEquals(resp.status_int, 403)
4295- self.assertEquals(self.test_auth.app.calls, 1)
4296-
4297- def test_get_reseller_fail_listing(self):
4298- self.test_auth.app = FakeApp(iter([
4299- # GET of .auth account (list containers)
4300- ('503 Service Unavailable', {}, '')]))
4301- resp = Request.blank('/auth/v2',
4302- headers={'X-Auth-Admin-User': '.super_admin',
4303- 'X-Auth-Admin-Key': 'supertest'}
4304- ).get_response(self.test_auth)
4305- self.assertEquals(resp.status_int, 500)
4306- self.assertEquals(self.test_auth.app.calls, 1)
4307-
4308- self.test_auth.app = FakeApp(iter([
4309- # GET of .auth account (list containers)
4310- ('200 Ok', {}, json.dumps([
4311- {"name": ".token", "count": 0, "bytes": 0},
4312- {"name": ".account_id", "count": 0, "bytes": 0},
4313- {"name": "act", "count": 0, "bytes": 0}])),
4314- # GET of .auth account (list containers continuation)
4315- ('503 Service Unavailable', {}, '')]))
4316- resp = Request.blank('/auth/v2',
4317- headers={'X-Auth-Admin-User': '.super_admin',
4318- 'X-Auth-Admin-Key': 'supertest'}
4319- ).get_response(self.test_auth)
4320- self.assertEquals(resp.status_int, 500)
4321- self.assertEquals(self.test_auth.app.calls, 2)
4322-
4323- def test_get_account_success(self):
4324- self.test_auth.app = FakeApp(iter([
4325- # GET of .services object
4326- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4327- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4328- # GET of account container (list objects)
4329- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'},
4330- json.dumps([
4331- {"name": ".services", "hash": "etag", "bytes": 112,
4332- "content_type": "application/octet-stream",
4333- "last_modified": "2010-12-03T17:16:27.618110"},
4334- {"name": "tester", "hash": "etag", "bytes": 104,
4335- "content_type": "application/octet-stream",
4336- "last_modified": "2010-12-03T17:16:27.736680"},
4337- {"name": "tester3", "hash": "etag", "bytes": 86,
4338- "content_type": "application/octet-stream",
4339- "last_modified": "2010-12-03T17:16:28.135530"}])),
4340- # GET of account container (list objects continuation)
4341- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')]))
4342- resp = Request.blank('/auth/v2/act',
4343- headers={'X-Auth-Admin-User': '.super_admin',
4344- 'X-Auth-Admin-Key': 'supertest'}
4345- ).get_response(self.test_auth)
4346- self.assertEquals(resp.status_int, 200)
4347- self.assertEquals(json.loads(resp.body),
4348- {'account_id': 'AUTH_cfa',
4349- 'services': {'storage':
4350- {'default': 'local',
4351- 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa'}},
4352- 'users': [{'name': 'tester'}, {'name': 'tester3'}]})
4353- self.assertEquals(self.test_auth.app.calls, 3)
4354-
4355- self.test_auth.app = FakeApp(iter([
4356- # GET of user object
4357- ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"},
4358- {"name": "test"}, {"name": ".admin"}],
4359- "auth": "plaintext:key"})),
4360- # GET of .services object
4361- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4362- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4363- # GET of account container (list objects)
4364- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'},
4365- json.dumps([
4366- {"name": ".services", "hash": "etag", "bytes": 112,
4367- "content_type": "application/octet-stream",
4368- "last_modified": "2010-12-03T17:16:27.618110"},
4369- {"name": "tester", "hash": "etag", "bytes": 104,
4370- "content_type": "application/octet-stream",
4371- "last_modified": "2010-12-03T17:16:27.736680"},
4372- {"name": "tester3", "hash": "etag", "bytes": 86,
4373- "content_type": "application/octet-stream",
4374- "last_modified": "2010-12-03T17:16:28.135530"}])),
4375- # GET of account container (list objects continuation)
4376- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')]))
4377- resp = Request.blank('/auth/v2/act',
4378- headers={'X-Auth-Admin-User': 'act:adm',
4379- 'X-Auth-Admin-Key': 'key'}
4380- ).get_response(self.test_auth)
4381- self.assertEquals(resp.status_int, 200)
4382- self.assertEquals(json.loads(resp.body),
4383- {'account_id': 'AUTH_cfa',
4384- 'services': {'storage':
4385- {'default': 'local',
4386- 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa'}},
4387- 'users': [{'name': 'tester'}, {'name': 'tester3'}]})
4388- self.assertEquals(self.test_auth.app.calls, 4)
4389-
4390- def test_get_account_fail_bad_account_name(self):
4391- resp = Request.blank('/auth/v2/.token',
4392- headers={'X-Auth-Admin-User': '.super_admin',
4393- 'X-Auth-Admin-Key': 'supertest'}
4394- ).get_response(self.test_auth)
4395- self.assertEquals(resp.status_int, 400)
4396- resp = Request.blank('/auth/v2/.anything',
4397- headers={'X-Auth-Admin-User': '.super_admin',
4398- 'X-Auth-Admin-Key': 'supertest'}
4399- ).get_response(self.test_auth)
4400- self.assertEquals(resp.status_int, 400)
4401-
4402- def test_get_account_fail_creds(self):
4403- self.test_auth.app = FakeApp(iter([
4404- # GET of user object
4405- ('404 Not Found', {}, '')]))
4406- resp = Request.blank('/auth/v2/act',
4407- headers={'X-Auth-Admin-User': 'super:admin',
4408- 'X-Auth-Admin-Key': 'supertest'}
4409- ).get_response(self.test_auth)
4410- self.assertEquals(resp.status_int, 403)
4411- self.assertEquals(self.test_auth.app.calls, 1)
4412-
4413- self.test_auth.app = FakeApp(iter([
4414- # GET of user object (account admin, but wrong account)
4415- ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"},
4416- {"name": "test"}, {"name": ".admin"}],
4417- "auth": "plaintext:key"}))]))
4418- resp = Request.blank('/auth/v2/act',
4419- headers={'X-Auth-Admin-User': 'act2:adm',
4420- 'X-Auth-Admin-Key': 'key'}
4421- ).get_response(self.test_auth)
4422- self.assertEquals(resp.status_int, 403)
4423- self.assertEquals(self.test_auth.app.calls, 1)
4424-
4425- self.test_auth.app = FakeApp(iter([
4426- # GET of user object (regular user)
4427- ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"},
4428- {"name": "test"}], "auth": "plaintext:key"}))]))
4429- resp = Request.blank('/auth/v2/act',
4430- headers={'X-Auth-Admin-User': 'act:usr',
4431- 'X-Auth-Admin-Key': 'key'}
4432- ).get_response(self.test_auth)
4433- self.assertEquals(resp.status_int, 403)
4434- self.assertEquals(self.test_auth.app.calls, 1)
4435-
4436- def test_get_account_fail_get_services(self):
4437- self.test_auth.app = FakeApp(iter([
4438- # GET of .services object
4439- ('503 Service Unavailable', {}, '')]))
4440- resp = Request.blank('/auth/v2/act',
4441- headers={'X-Auth-Admin-User': '.super_admin',
4442- 'X-Auth-Admin-Key': 'supertest'}
4443- ).get_response(self.test_auth)
4444- self.assertEquals(resp.status_int, 500)
4445- self.assertEquals(self.test_auth.app.calls, 1)
4446-
4447- self.test_auth.app = FakeApp(iter([
4448- # GET of .services object
4449- ('404 Not Found', {}, '')]))
4450- resp = Request.blank('/auth/v2/act',
4451- headers={'X-Auth-Admin-User': '.super_admin',
4452- 'X-Auth-Admin-Key': 'supertest'}
4453- ).get_response(self.test_auth)
4454- self.assertEquals(resp.status_int, 404)
4455- self.assertEquals(self.test_auth.app.calls, 1)
4456-
4457- def test_get_account_fail_listing(self):
4458- self.test_auth.app = FakeApp(iter([
4459- # GET of .services object
4460- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4461- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4462- # GET of account container (list objects)
4463- ('503 Service Unavailable', {}, '')]))
4464- resp = Request.blank('/auth/v2/act',
4465- headers={'X-Auth-Admin-User': '.super_admin',
4466- 'X-Auth-Admin-Key': 'supertest'}
4467- ).get_response(self.test_auth)
4468- self.assertEquals(resp.status_int, 500)
4469- self.assertEquals(self.test_auth.app.calls, 2)
4470-
4471- self.test_auth.app = FakeApp(iter([
4472- # GET of .services object
4473- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4474- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4475- # GET of account container (list objects)
4476- ('404 Not Found', {}, '')]))
4477- resp = Request.blank('/auth/v2/act',
4478- headers={'X-Auth-Admin-User': '.super_admin',
4479- 'X-Auth-Admin-Key': 'supertest'}
4480- ).get_response(self.test_auth)
4481- self.assertEquals(resp.status_int, 404)
4482- self.assertEquals(self.test_auth.app.calls, 2)
4483-
4484- self.test_auth.app = FakeApp(iter([
4485- # GET of .services object
4486- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4487- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4488- # GET of account container (list objects)
4489- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'},
4490- json.dumps([
4491- {"name": ".services", "hash": "etag", "bytes": 112,
4492- "content_type": "application/octet-stream",
4493- "last_modified": "2010-12-03T17:16:27.618110"},
4494- {"name": "tester", "hash": "etag", "bytes": 104,
4495- "content_type": "application/octet-stream",
4496- "last_modified": "2010-12-03T17:16:27.736680"},
4497- {"name": "tester3", "hash": "etag", "bytes": 86,
4498- "content_type": "application/octet-stream",
4499- "last_modified": "2010-12-03T17:16:28.135530"}])),
4500- # GET of account container (list objects continuation)
4501- ('503 Service Unavailable', {}, '')]))
4502- resp = Request.blank('/auth/v2/act',
4503- headers={'X-Auth-Admin-User': '.super_admin',
4504- 'X-Auth-Admin-Key': 'supertest'}
4505- ).get_response(self.test_auth)
4506- self.assertEquals(resp.status_int, 500)
4507- self.assertEquals(self.test_auth.app.calls, 3)
4508-
4509- def test_set_services_new_service(self):
4510- self.test_auth.app = FakeApp(iter([
4511- # GET of .services object
4512- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4513- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4514- # PUT of new .services object
4515- ('204 No Content', {}, '')]))
4516- resp = Request.blank('/auth/v2/act/.services',
4517- environ={'REQUEST_METHOD': 'POST'},
4518- headers={'X-Auth-Admin-User': '.super_admin',
4519- 'X-Auth-Admin-Key': 'supertest'},
4520- body=json.dumps({'new_service': {'new_endpoint': 'new_value'}})
4521- ).get_response(self.test_auth)
4522- self.assertEquals(resp.status_int, 200)
4523- self.assertEquals(json.loads(resp.body),
4524- {'storage': {'default': 'local',
4525- 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa'},
4526- 'new_service': {'new_endpoint': 'new_value'}})
4527- self.assertEquals(self.test_auth.app.calls, 2)
4528-
4529- def test_set_services_new_endpoint(self):
4530- self.test_auth.app = FakeApp(iter([
4531- # GET of .services object
4532- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4533- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4534- # PUT of new .services object
4535- ('204 No Content', {}, '')]))
4536- resp = Request.blank('/auth/v2/act/.services',
4537- environ={'REQUEST_METHOD': 'POST'},
4538- headers={'X-Auth-Admin-User': '.super_admin',
4539- 'X-Auth-Admin-Key': 'supertest'},
4540- body=json.dumps({'storage': {'new_endpoint': 'new_value'}})
4541- ).get_response(self.test_auth)
4542- self.assertEquals(resp.status_int, 200)
4543- self.assertEquals(json.loads(resp.body),
4544- {'storage': {'default': 'local',
4545- 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa',
4546- 'new_endpoint': 'new_value'}})
4547- self.assertEquals(self.test_auth.app.calls, 2)
4548-
4549- def test_set_services_update_endpoint(self):
4550- self.test_auth.app = FakeApp(iter([
4551- # GET of .services object
4552- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4553- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4554- # PUT of new .services object
4555- ('204 No Content', {}, '')]))
4556- resp = Request.blank('/auth/v2/act/.services',
4557- environ={'REQUEST_METHOD': 'POST'},
4558- headers={'X-Auth-Admin-User': '.super_admin',
4559- 'X-Auth-Admin-Key': 'supertest'},
4560- body=json.dumps({'storage': {'local': 'new_value'}})
4561- ).get_response(self.test_auth)
4562- self.assertEquals(resp.status_int, 200)
4563- self.assertEquals(json.loads(resp.body),
4564- {'storage': {'default': 'local',
4565- 'local': 'new_value'}})
4566- self.assertEquals(self.test_auth.app.calls, 2)
4567-
4568- def test_set_services_fail_bad_creds(self):
4569- self.test_auth.app = FakeApp(iter([
4570- # GET of user object
4571- ('404 Not Found', {}, '')]))
4572- resp = Request.blank('/auth/v2/act/.services',
4573- environ={'REQUEST_METHOD': 'POST'},
4574- headers={'X-Auth-Admin-User': 'super:admin',
4575- 'X-Auth-Admin-Key': 'supertest'},
4576- body=json.dumps({'storage': {'local': 'new_value'}})
4577- ).get_response(self.test_auth)
4578- self.assertEquals(resp.status_int, 403)
4579- self.assertEquals(self.test_auth.app.calls, 1)
4580-
4581- self.test_auth.app = FakeApp(iter([
4582- # GET of user object (account admin, but not reseller admin)
4583- ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"},
4584- {"name": "test"}, {"name": ".admin"}],
4585- "auth": "plaintext:key"}))]))
4586- resp = Request.blank('/auth/v2/act/.services',
4587- environ={'REQUEST_METHOD': 'POST'},
4588- headers={'X-Auth-Admin-User': 'act:adm',
4589- 'X-Auth-Admin-Key': 'key'},
4590- body=json.dumps({'storage': {'local': 'new_value'}})
4591- ).get_response(self.test_auth)
4592- self.assertEquals(resp.status_int, 403)
4593- self.assertEquals(self.test_auth.app.calls, 1)
4594-
4595- self.test_auth.app = FakeApp(iter([
4596- # GET of user object (regular user)
4597- ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"},
4598- {"name": "test"}], "auth": "plaintext:key"}))]))
4599- resp = Request.blank('/auth/v2/act/.services',
4600- environ={'REQUEST_METHOD': 'POST'},
4601- headers={'X-Auth-Admin-User': 'act:usr',
4602- 'X-Auth-Admin-Key': 'key'},
4603- body=json.dumps({'storage': {'local': 'new_value'}})
4604- ).get_response(self.test_auth)
4605- self.assertEquals(resp.status_int, 403)
4606- self.assertEquals(self.test_auth.app.calls, 1)
4607-
4608- def test_set_services_fail_bad_account_name(self):
4609- resp = Request.blank('/auth/v2/.act/.services',
4610- environ={'REQUEST_METHOD': 'POST'},
4611- headers={'X-Auth-Admin-User': '.super_admin',
4612- 'X-Auth-Admin-Key': 'supertest'},
4613- body=json.dumps({'storage': {'local': 'new_value'}})
4614- ).get_response(self.test_auth)
4615- self.assertEquals(resp.status_int, 400)
4616-
4617- def test_set_services_fail_bad_json(self):
4618- resp = Request.blank('/auth/v2/act/.services',
4619- environ={'REQUEST_METHOD': 'POST'},
4620- headers={'X-Auth-Admin-User': '.super_admin',
4621- 'X-Auth-Admin-Key': 'supertest'},
4622- body='garbage'
4623- ).get_response(self.test_auth)
4624- self.assertEquals(resp.status_int, 400)
4625- resp = Request.blank('/auth/v2/act/.services',
4626- environ={'REQUEST_METHOD': 'POST'},
4627- headers={'X-Auth-Admin-User': '.super_admin',
4628- 'X-Auth-Admin-Key': 'supertest'},
4629- body=''
4630- ).get_response(self.test_auth)
4631- self.assertEquals(resp.status_int, 400)
4632-
4633- def test_set_services_fail_get_services(self):
4634- self.test_auth.app = FakeApp(iter([
4635- # GET of .services object
4636- ('503 Unavailable', {}, '')]))
4637- resp = Request.blank('/auth/v2/act/.services',
4638- environ={'REQUEST_METHOD': 'POST'},
4639- headers={'X-Auth-Admin-User': '.super_admin',
4640- 'X-Auth-Admin-Key': 'supertest'},
4641- body=json.dumps({'new_service': {'new_endpoint': 'new_value'}})
4642- ).get_response(self.test_auth)
4643- self.assertEquals(resp.status_int, 500)
4644- self.assertEquals(self.test_auth.app.calls, 1)
4645-
4646- self.test_auth.app = FakeApp(iter([
4647- # GET of .services object
4648- ('404 Not Found', {}, '')]))
4649- resp = Request.blank('/auth/v2/act/.services',
4650- environ={'REQUEST_METHOD': 'POST'},
4651- headers={'X-Auth-Admin-User': '.super_admin',
4652- 'X-Auth-Admin-Key': 'supertest'},
4653- body=json.dumps({'new_service': {'new_endpoint': 'new_value'}})
4654- ).get_response(self.test_auth)
4655- self.assertEquals(resp.status_int, 404)
4656- self.assertEquals(self.test_auth.app.calls, 1)
4657-
4658- def test_set_services_fail_put_services(self):
4659- self.test_auth.app = FakeApp(iter([
4660- # GET of .services object
4661- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4662- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4663- # PUT of new .services object
4664- ('503 Unavailable', {}, '')]))
4665- resp = Request.blank('/auth/v2/act/.services',
4666- environ={'REQUEST_METHOD': 'POST'},
4667- headers={'X-Auth-Admin-User': '.super_admin',
4668- 'X-Auth-Admin-Key': 'supertest'},
4669- body=json.dumps({'new_service': {'new_endpoint': 'new_value'}})
4670- ).get_response(self.test_auth)
4671- self.assertEquals(resp.status_int, 500)
4672- self.assertEquals(self.test_auth.app.calls, 2)
4673-
4674- def test_put_account_success(self):
4675- conn = FakeConn(iter([
4676- # PUT of storage account itself
4677- ('201 Created', {}, '')]))
4678- self.test_auth.get_conn = lambda: conn
4679- self.test_auth.app = FakeApp(iter([
4680- # Initial HEAD of account container to check for pre-existence
4681- ('404 Not Found', {}, ''),
4682- # PUT of account container
4683- ('204 No Content', {}, ''),
4684- # PUT of .account_id mapping object
4685- ('204 No Content', {}, ''),
4686- # PUT of .services object
4687- ('204 No Content', {}, ''),
4688- # POST to account container updating X-Container-Meta-Account-Id
4689- ('204 No Content', {}, '')]))
4690- resp = Request.blank('/auth/v2/act',
4691- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4692- headers={'X-Auth-Admin-User': '.super_admin',
4693- 'X-Auth-Admin-Key': 'supertest'}
4694- ).get_response(self.test_auth)
4695- self.assertEquals(resp.status_int, 201)
4696- self.assertEquals(self.test_auth.app.calls, 5)
4697- self.assertEquals(conn.calls, 1)
4698-
4699- def test_put_account_success_preexist_but_not_completed(self):
4700- conn = FakeConn(iter([
4701- # PUT of storage account itself
4702- ('201 Created', {}, '')]))
4703- self.test_auth.get_conn = lambda: conn
4704- self.test_auth.app = FakeApp(iter([
4705- # Initial HEAD of account container to check for pre-existence
4706- # We're going to show it as existing this time, but with no
4707- # X-Container-Meta-Account-Id, indicating a failed previous attempt
4708- ('200 Ok', {}, ''),
4709- # PUT of .account_id mapping object
4710- ('204 No Content', {}, ''),
4711- # PUT of .services object
4712- ('204 No Content', {}, ''),
4713- # POST to account container updating X-Container-Meta-Account-Id
4714- ('204 No Content', {}, '')]))
4715- resp = Request.blank('/auth/v2/act',
4716- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4717- headers={'X-Auth-Admin-User': '.super_admin',
4718- 'X-Auth-Admin-Key': 'supertest'}
4719- ).get_response(self.test_auth)
4720- self.assertEquals(resp.status_int, 201)
4721- self.assertEquals(self.test_auth.app.calls, 4)
4722- self.assertEquals(conn.calls, 1)
4723-
4724- def test_put_account_success_preexist_and_completed(self):
4725- self.test_auth.app = FakeApp(iter([
4726- # Initial HEAD of account container to check for pre-existence
4727- # We're going to show it as existing this time, and with an
4728- # X-Container-Meta-Account-Id, indicating it already exists
4729- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '')]))
4730- resp = Request.blank('/auth/v2/act',
4731- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4732- headers={'X-Auth-Admin-User': '.super_admin',
4733- 'X-Auth-Admin-Key': 'supertest'}
4734- ).get_response(self.test_auth)
4735- self.assertEquals(resp.status_int, 202)
4736- self.assertEquals(self.test_auth.app.calls, 1)
4737-
4738- def test_put_account_success_with_given_suffix(self):
4739- conn = FakeConn(iter([
4740- # PUT of storage account itself
4741- ('201 Created', {}, '')]))
4742- self.test_auth.get_conn = lambda: conn
4743- self.test_auth.app = FakeApp(iter([
4744- # Initial HEAD of account container to check for pre-existence
4745- ('404 Not Found', {}, ''),
4746- # PUT of account container
4747- ('204 No Content', {}, ''),
4748- # PUT of .account_id mapping object
4749- ('204 No Content', {}, ''),
4750- # PUT of .services object
4751- ('204 No Content', {}, ''),
4752- # POST to account container updating X-Container-Meta-Account-Id
4753- ('204 No Content', {}, '')]))
4754- resp = Request.blank('/auth/v2/act',
4755- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4756- headers={'X-Auth-Admin-User': '.super_admin',
4757- 'X-Auth-Admin-Key': 'supertest',
4758- 'X-Account-Suffix': 'test-suffix'}
4759- ).get_response(self.test_auth)
4760- self.assertEquals(resp.status_int, 201)
4761- self.assertEquals(conn.request_path, '/v1/AUTH_test-suffix')
4762- self.assertEquals(self.test_auth.app.calls, 5)
4763- self.assertEquals(conn.calls, 1)
4764-
4765- def test_put_account_fail_bad_creds(self):
4766- self.test_auth.app = FakeApp(iter([
4767- # GET of user object
4768- ('404 Not Found', {}, '')]))
4769- resp = Request.blank('/auth/v2/act',
4770- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4771- headers={'X-Auth-Admin-User': 'super:admin',
4772- 'X-Auth-Admin-Key': 'supertest'},
4773- ).get_response(self.test_auth)
4774- self.assertEquals(resp.status_int, 403)
4775- self.assertEquals(self.test_auth.app.calls, 1)
4776-
4777- self.test_auth.app = FakeApp(iter([
4778- # GET of user object (account admin, but not reseller admin)
4779- ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"},
4780- {"name": "test"}, {"name": ".admin"}],
4781- "auth": "plaintext:key"}))]))
4782- resp = Request.blank('/auth/v2/act',
4783- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4784- headers={'X-Auth-Admin-User': 'act:adm',
4785- 'X-Auth-Admin-Key': 'key'},
4786- ).get_response(self.test_auth)
4787- self.assertEquals(resp.status_int, 403)
4788- self.assertEquals(self.test_auth.app.calls, 1)
4789-
4790- self.test_auth.app = FakeApp(iter([
4791- # GET of user object (regular user)
4792- ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"},
4793- {"name": "test"}], "auth": "plaintext:key"}))]))
4794- resp = Request.blank('/auth/v2/act',
4795- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4796- headers={'X-Auth-Admin-User': 'act:usr',
4797- 'X-Auth-Admin-Key': 'key'},
4798- ).get_response(self.test_auth)
4799- self.assertEquals(resp.status_int, 403)
4800- self.assertEquals(self.test_auth.app.calls, 1)
4801-
4802- def test_put_account_fail_invalid_account_name(self):
4803- resp = Request.blank('/auth/v2/.act',
4804- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4805- headers={'X-Auth-Admin-User': '.super_admin',
4806- 'X-Auth-Admin-Key': 'supertest'},
4807- ).get_response(self.test_auth)
4808- self.assertEquals(resp.status_int, 400)
4809-
4810- def test_put_account_fail_on_initial_account_head(self):
4811- self.test_auth.app = FakeApp(iter([
4812- # Initial HEAD of account container to check for pre-existence
4813- ('503 Service Unavailable', {}, '')]))
4814- resp = Request.blank('/auth/v2/act',
4815- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4816- headers={'X-Auth-Admin-User': '.super_admin',
4817- 'X-Auth-Admin-Key': 'supertest'}
4818- ).get_response(self.test_auth)
4819- self.assertEquals(resp.status_int, 500)
4820- self.assertEquals(self.test_auth.app.calls, 1)
4821-
4822- def test_put_account_fail_on_account_marker_put(self):
4823- self.test_auth.app = FakeApp(iter([
4824- # Initial HEAD of account container to check for pre-existence
4825- ('404 Not Found', {}, ''),
4826- # PUT of account container
4827- ('503 Service Unavailable', {}, '')]))
4828- resp = Request.blank('/auth/v2/act',
4829- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4830- headers={'X-Auth-Admin-User': '.super_admin',
4831- 'X-Auth-Admin-Key': 'supertest'}
4832- ).get_response(self.test_auth)
4833- self.assertEquals(resp.status_int, 500)
4834- self.assertEquals(self.test_auth.app.calls, 2)
4835-
4836- def test_put_account_fail_on_storage_account_put(self):
4837- conn = FakeConn(iter([
4838- # PUT of storage account itself
4839- ('503 Service Unavailable', {}, '')]))
4840- self.test_auth.get_conn = lambda: conn
4841- self.test_auth.app = FakeApp(iter([
4842- # Initial HEAD of account container to check for pre-existence
4843- ('404 Not Found', {}, ''),
4844- # PUT of account container
4845- ('204 No Content', {}, '')]))
4846- resp = Request.blank('/auth/v2/act',
4847- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4848- headers={'X-Auth-Admin-User': '.super_admin',
4849- 'X-Auth-Admin-Key': 'supertest'}
4850- ).get_response(self.test_auth)
4851- self.assertEquals(resp.status_int, 500)
4852- self.assertEquals(conn.calls, 1)
4853- self.assertEquals(self.test_auth.app.calls, 2)
4854-
4855- def test_put_account_fail_on_account_id_mapping(self):
4856- conn = FakeConn(iter([
4857- # PUT of storage account itself
4858- ('201 Created', {}, '')]))
4859- self.test_auth.get_conn = lambda: conn
4860- self.test_auth.app = FakeApp(iter([
4861- # Initial HEAD of account container to check for pre-existence
4862- ('404 Not Found', {}, ''),
4863- # PUT of account container
4864- ('204 No Content', {}, ''),
4865- # PUT of .account_id mapping object
4866- ('503 Service Unavailable', {}, '')]))
4867- resp = Request.blank('/auth/v2/act',
4868- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4869- headers={'X-Auth-Admin-User': '.super_admin',
4870- 'X-Auth-Admin-Key': 'supertest'}
4871- ).get_response(self.test_auth)
4872- self.assertEquals(resp.status_int, 500)
4873- self.assertEquals(conn.calls, 1)
4874- self.assertEquals(self.test_auth.app.calls, 3)
4875-
4876- def test_put_account_fail_on_services_object(self):
4877- conn = FakeConn(iter([
4878- # PUT of storage account itself
4879- ('201 Created', {}, '')]))
4880- self.test_auth.get_conn = lambda: conn
4881- self.test_auth.app = FakeApp(iter([
4882- # Initial HEAD of account container to check for pre-existence
4883- ('404 Not Found', {}, ''),
4884- # PUT of account container
4885- ('204 No Content', {}, ''),
4886- # PUT of .account_id mapping object
4887- ('204 No Content', {}, ''),
4888- # PUT of .services object
4889- ('503 Service Unavailable', {}, '')]))
4890- resp = Request.blank('/auth/v2/act',
4891- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4892- headers={'X-Auth-Admin-User': '.super_admin',
4893- 'X-Auth-Admin-Key': 'supertest'}
4894- ).get_response(self.test_auth)
4895- self.assertEquals(resp.status_int, 500)
4896- self.assertEquals(conn.calls, 1)
4897- self.assertEquals(self.test_auth.app.calls, 4)
4898-
4899- def test_put_account_fail_on_post_mapping(self):
4900- conn = FakeConn(iter([
4901- # PUT of storage account itself
4902- ('201 Created', {}, '')]))
4903- self.test_auth.get_conn = lambda: conn
4904- self.test_auth.app = FakeApp(iter([
4905- # Initial HEAD of account container to check for pre-existence
4906- ('404 Not Found', {}, ''),
4907- # PUT of account container
4908- ('204 No Content', {}, ''),
4909- # PUT of .account_id mapping object
4910- ('204 No Content', {}, ''),
4911- # PUT of .services object
4912- ('204 No Content', {}, ''),
4913- # POST to account container updating X-Container-Meta-Account-Id
4914- ('503 Service Unavailable', {}, '')]))
4915- resp = Request.blank('/auth/v2/act',
4916- environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()},
4917- headers={'X-Auth-Admin-User': '.super_admin',
4918- 'X-Auth-Admin-Key': 'supertest'}
4919- ).get_response(self.test_auth)
4920- self.assertEquals(resp.status_int, 500)
4921- self.assertEquals(conn.calls, 1)
4922- self.assertEquals(self.test_auth.app.calls, 5)
4923-
4924- def test_delete_account_success(self):
4925- conn = FakeConn(iter([
4926- # DELETE of storage account itself
4927- ('204 No Content', {}, '')]))
4928- self.test_auth.get_conn = lambda x: conn
4929- self.test_auth.app = FakeApp(iter([
4930- # Account's container listing, checking for users
4931- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'},
4932- json.dumps([
4933- {"name": ".services", "hash": "etag", "bytes": 112,
4934- "content_type": "application/octet-stream",
4935- "last_modified": "2010-12-03T17:16:27.618110"}])),
4936- # Account's container listing, checking for users (continuation)
4937- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'),
4938- # GET the .services object
4939- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4940- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4941- # DELETE the .services object
4942- ('204 No Content', {}, ''),
4943- # DELETE the .account_id mapping object
4944- ('204 No Content', {}, ''),
4945- # DELETE the account container
4946- ('204 No Content', {}, '')]))
4947- resp = Request.blank('/auth/v2/act',
4948- environ={'REQUEST_METHOD': 'DELETE',
4949- 'swift.cache': FakeMemcache()},
4950- headers={'X-Auth-Admin-User': '.super_admin',
4951- 'X-Auth-Admin-Key': 'supertest'}
4952- ).get_response(self.test_auth)
4953- self.assertEquals(resp.status_int, 204)
4954- self.assertEquals(self.test_auth.app.calls, 6)
4955- self.assertEquals(conn.calls, 1)
4956-
4957- def test_delete_account_success_missing_services(self):
4958- self.test_auth.app = FakeApp(iter([
4959- # Account's container listing, checking for users
4960- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'},
4961- json.dumps([
4962- {"name": ".services", "hash": "etag", "bytes": 112,
4963- "content_type": "application/octet-stream",
4964- "last_modified": "2010-12-03T17:16:27.618110"}])),
4965- # Account's container listing, checking for users (continuation)
4966- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'),
4967- # GET the .services object
4968- ('404 Not Found', {}, ''),
4969- # DELETE the .account_id mapping object
4970- ('204 No Content', {}, ''),
4971- # DELETE the account container
4972- ('204 No Content', {}, '')]))
4973- resp = Request.blank('/auth/v2/act',
4974- environ={'REQUEST_METHOD': 'DELETE',
4975- 'swift.cache': FakeMemcache()},
4976- headers={'X-Auth-Admin-User': '.super_admin',
4977- 'X-Auth-Admin-Key': 'supertest'}
4978- ).get_response(self.test_auth)
4979- self.assertEquals(resp.status_int, 204)
4980- self.assertEquals(self.test_auth.app.calls, 5)
4981-
4982- def test_delete_account_success_missing_storage_account(self):
4983- conn = FakeConn(iter([
4984- # DELETE of storage account itself
4985- ('404 Not Found', {}, '')]))
4986- self.test_auth.get_conn = lambda x: conn
4987- self.test_auth.app = FakeApp(iter([
4988- # Account's container listing, checking for users
4989- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'},
4990- json.dumps([
4991- {"name": ".services", "hash": "etag", "bytes": 112,
4992- "content_type": "application/octet-stream",
4993- "last_modified": "2010-12-03T17:16:27.618110"}])),
4994- # Account's container listing, checking for users (continuation)
4995- ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'),
4996- # GET the .services object
4997- ('200 Ok', {}, json.dumps({"storage": {"default": "local",
4998- "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})),
4999- # DELETE the .services object
5000- ('204 No Content', {}, ''),
The diff has been truncated for viewing.