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

Proposed by gholt
Status: Merged
Approved by: Chuck Thier
Approved revision: 73
Merged at revision: 72
Proposed branch: lp:~gholt/swift/authlock
Merge into: lp:~hudson-openstack/swift/trunk
Diff against target: 1818 lines (+810/-240)
16 files modified
bin/swift-auth-add-user (+15/-1)
bin/swift-auth-recreate-accounts (+20/-8)
doc/source/development_auth.rst (+7/-4)
doc/source/development_saio.rst (+8/-4)
doc/source/howto_cyberduck.rst (+27/-10)
etc/auth-server.conf-sample (+2/-0)
swift/auth/server.py (+127/-81)
swift/common/constraints.py (+2/-0)
swift/common/middleware/auth.py (+8/-3)
swift/proxy/server.py (+56/-4)
test/functional/swift.py (+1/-1)
test/functional/tests.py (+1/-1)
test/probe/common.py (+14/-2)
test/unit/auth/test_server.py (+403/-79)
test/unit/common/middleware/test_auth.py (+31/-0)
test/unit/proxy/test_server.py (+88/-42)
To merge this branch: bzr merge lp:~gholt/swift/authlock
Reviewer Review Type Date Requested Status
Chuck Thier (community) Approve
Review via email: mp+35165@code.launchpad.net

Commit message

Locking down the DevAuth by adding support for a super admin and reseller admins.

Description of the change

Locking down the DevAuth:

There are two new classes of users, super admin and reseller admin, for a total of four now:

user
    Standard user, only has access to containers if they are specifically listed in the containers' ACLs.

account admin
    Has full access within a single account. Can also create additional standard users and account admins.

reseller admin
    Is treated as an account admin for all accounts with the reseller's prefix. Can also create additional account admins for any account with the reseller's prefix. Cannot create additional reseller admins.

super admin
    Has the same access as the reseller admin and can create additional reseller admins. Also can issue a "recreate accounts" call.

Tool changes:

swift-auth-add-user:
    Accepts new options: -r to make the user a reseller admin, -U and -K to define the admin issuing the request. Simplest change to existing usage: "swift-auth-add-user account user key" becomes "swift-auth-add-user -K devauth account user key"

swift-auth-recreate-accounts:
    Refactored to use optparse. Accepts new options: -c for the auth-server.conf location, -U -K to define the admin issuing the request (must be super admin at the moment). Simplest change to existing usage: "swift-auth-recreate-accounts" becomes "swift-auth-recreate-accounts -K devauth"

Code changes:

swift/auth/server.py:
    No longer needs the account ring or access to backend account servers; instead it creates accounts through a normal PUT request to the proxy server. So now it can be running anywhere and actually work with multiple clusters if needed.
    Requires a super_admin_key be set in the conf file, allowing the permission set indicated above.
    Supports creating reseller admins.
    Everything is locked down as specified above.
    Can return a special group named .reseller_admin to the auth middleware, indicating that account PUTs are allowed by the user.

swift/common/middware/auth.py:
    Just changed to lockdown the new account PUT facility to just .reseller_admin and to treat .reseller_admin as also an account admin.

swift/proxy/server.py:
    Supports account PUTs. Moved MAX_CONTAINER_NAME_LENGTH to swift/common/constraints.py

And, of course, doc and test updates.

To post a comment you must log in.
Revision history for this message
gholt (gholt) wrote :

The implementation is done, existing tests and documentation has been updated. Just need tests and docs for new functionality.

Revision history for this message
Chuck Thier (cthier) wrote :

Looks good to me

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/swift-auth-add-user'
2--- bin/swift-auth-add-user 2010-09-06 02:53:08 +0000
3+++ bin/swift-auth-add-user 2010-09-12 00:25:58 +0000
4@@ -33,6 +33,16 @@
5 default=False, help='Give the user administrator access; otherwise '
6 'the user will only have access to containers specifically allowed '
7 'with ACLs.')
8+ parser.add_option('-r', '--reseller-admin', dest='reseller_admin',
9+ action='store_true', default=False, help='Give the user full reseller '
10+ 'administrator access, giving them full access to all accounts within '
11+ 'the reseller, including the ability to create new accounts. Creating '
12+ 'a new reseller admin requires super_admin rights.')
13+ parser.add_option('-U', '--admin-user', dest='admin_user',
14+ default='.super_admin', help='The user with admin rights to add users '
15+ '(default: .super_admin).')
16+ parser.add_option('-K', '--admin-key', dest='admin_key',
17+ help='The key for the user with admin rights to add users.')
18 args = argv[1:]
19 if not args:
20 args.append('-h')
21@@ -48,9 +58,13 @@
22 port = int(conf.get('bind_port', 11000))
23 ssl = conf.get('cert_file') is not None
24 path = '/account/%s/%s' % (account, user)
25- headers = {'X-Auth-User-Key': password}
26+ headers = {'X-Auth-Admin-User': options.admin_user,
27+ 'X-Auth-Admin-Key': options.admin_key,
28+ 'X-Auth-User-Key': password}
29 if options.admin:
30 headers['X-Auth-User-Admin'] = 'true'
31+ if options.reseller_admin:
32+ headers['X-Auth-User-Reseller-Admin'] = 'true'
33 conn = http_connect(host, port, 'PUT', path, headers, ssl=ssl)
34 resp = conn.getresponse()
35 if resp.status == 204:
36
37=== modified file 'bin/swift-auth-recreate-accounts'
38--- bin/swift-auth-recreate-accounts 2010-08-20 00:50:12 +0000
39+++ bin/swift-auth-recreate-accounts 2010-09-12 00:25:58 +0000
40@@ -15,25 +15,37 @@
41 # limitations under the License.
42
43 from ConfigParser import ConfigParser
44+from optparse import OptionParser
45 from sys import argv, exit
46
47 from swift.common.bufferedhttp import http_connect_raw as http_connect
48
49 if __name__ == '__main__':
50- f = '/etc/swift/auth-server.conf'
51- if len(argv) == 2:
52- f = argv[1]
53- elif len(argv) != 1:
54- exit('Syntax: %s [conf_file]' % argv[0])
55+ default_conf = '/etc/swift/auth-server.conf'
56+ parser = OptionParser(usage='Usage: %prog [options]')
57+ parser.add_option('-c', '--conf', dest='conf', default=default_conf,
58+ help='Configuration file to determine how to connect to the local '
59+ 'auth server (default: %s).' % default_conf)
60+ parser.add_option('-U', '--admin-user', dest='admin_user',
61+ default='.super_admin', help='The user with admin rights to recreate '
62+ 'accounts (default: .super_admin).')
63+ parser.add_option('-K', '--admin-key', dest='admin_key',
64+ help='The key for the user with admin rights to recreate accounts.')
65+ args = argv[1:]
66+ if not args:
67+ args.append('-h')
68+ (options, args) = parser.parse_args(args)
69 c = ConfigParser()
70- if not c.read(f):
71- exit('Unable to read conf file: %s' % f)
72+ if not c.read(options.conf):
73+ exit('Unable to read conf file: %s' % options.conf)
74 conf = dict(c.items('app:auth-server'))
75 host = conf.get('bind_ip', '127.0.0.1')
76 port = int(conf.get('bind_port', 11000))
77 ssl = conf.get('cert_file') is not None
78 path = '/recreate_accounts'
79- conn = http_connect(host, port, 'POST', path, ssl=ssl)
80+ conn = http_connect(host, port, 'POST', path, ssl=ssl,
81+ headers={'X-Auth-Admin-User': options.admin_user,
82+ 'X-Auth-Admin-Key': options.admin_key})
83 resp = conn.getresponse()
84 if resp.status == 200:
85 print resp.read()
86
87=== modified file 'doc/source/development_auth.rst'
88--- doc/source/development_auth.rst 2010-09-09 17:24:25 +0000
89+++ doc/source/development_auth.rst 2010-09-12 00:25:58 +0000
90@@ -6,10 +6,13 @@
91 Creating Your Own Auth Server and Middleware
92 --------------------------------------------
93
94-The included swift/common/middleware/auth.py is a good minimal example of how
95-to create auth middleware. The main points are that the auth middleware can
96-reject requests up front, before they ever get to the Swift Proxy application,
97-and afterwards when the proxy issues callbacks to verify authorization.
98+The included swift/auth/server.py and swift/common/middleware/auth.py are good
99+minimal examples of how to create an external auth server and proxy server auth
100+middleware. Also, see the `Swauth <https://launchpad.net/swauth>`_ project for
101+a more complete implementation. The main points are that the auth middleware
102+can reject requests up front, before they ever get to the Swift Proxy
103+application, and afterwards when the proxy issues callbacks to verify
104+authorization.
105
106 It's generally good to separate the authentication and authorization
107 procedures. Authentication verifies that a request actually comes from who it
108
109=== modified file 'doc/source/development_saio.rst'
110--- doc/source/development_saio.rst 2010-09-06 02:53:08 +0000
111+++ doc/source/development_saio.rst 2010-09-12 00:25:58 +0000
112@@ -177,6 +177,8 @@
113 [app:auth-server]
114 use = egg:swift#auth
115 default_cluster_url = http://127.0.0.1:8080/v1
116+ # Highly recommended to change this.
117+ super_admin_key = devauth
118
119 #. Create `/etc/swift/proxy-server.conf`::
120
121@@ -511,7 +513,9 @@
122
123 #!/bin/bash
124
125- swift-auth-recreate-accounts
126+ # Replace devauth with whatever your super_admin key is (recorded in
127+ # /etc/swift/auth-server.conf).
128+ swift-auth-recreate-accounts -K devauth
129 swift-init object-updater start
130 swift-init container-updater start
131 swift-init object-replicator start
132@@ -526,12 +530,12 @@
133 #. `remakerings`
134 #. `cd ~/swift/trunk; ./.unittests`
135 #. `startmain` (The ``Unable to increase file descriptor limit. Running as non-root?`` warnings are expected and ok.)
136- #. `swift-auth-add-user --admin test tester testing`
137+ #. `swift-auth-add-user -K devauth -a test tester testing` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
138 #. Get an `X-Storage-Url` and `X-Auth-Token`: ``curl -v -H 'X-Storage-User: test:tester' -H 'X-Storage-Pass: testing' http://127.0.0.1:11000/v1.0``
139 #. Check that you can GET account: ``curl -v -H 'X-Auth-Token: <token-from-x-auth-token-above>' <url-from-x-storage-url-above>``
140 #. Check that `st` works: `st -A http://127.0.0.1:11000/v1.0 -U test:tester -K testing stat`
141- #. `swift-auth-add-user --admin test2 tester2 testing2`
142- #. `swift-auth-add-user test tester3 testing3`
143+ #. `swift-auth-add-user -K devauth -a test2 tester2 testing2` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
144+ #. `swift-auth-add-user -K devauth test tester3 testing3` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
145 #. `cp ~/swift/trunk/test/functional/sample.conf /etc/swift/func_test.conf`
146 #. `cd ~/swift/trunk; ./.functests` (Note: functional tests will first delete
147 everything in the configured accounts.)
148
149=== modified file 'doc/source/howto_cyberduck.rst'
150--- doc/source/howto_cyberduck.rst 2010-09-06 02:21:08 +0000
151+++ doc/source/howto_cyberduck.rst 2010-09-12 00:25:58 +0000
152@@ -90,26 +90,43 @@
153
154 #. Example proxy-server config::
155
156- [proxy-server]
157- bind_port = 8080
158- user = swift
159+ [DEFAULT]
160 cert_file = /etc/swift/cert.crt
161 key_file = /etc/swift/cert.key
162-
163- [auth-server]
164+
165+ [pipeline:main]
166+ pipeline = healthcheck cache auth proxy-server
167+
168+ [app:proxy-server]
169+ use = egg:swift#proxy
170+
171+ [filter:auth]
172+ use = egg:swift#auth
173 ssl = true
174+
175+ [filter:healthcheck]
176+ use = egg:swift#healthcheck
177+
178+ [filter:cache]
179+ use = egg:swift#memcache
180
181 #. Example auth-server config::
182
183- [auth-server]
184+ [DEFAULT]
185+ cert_file = /etc/swift/cert.crt
186+ key_file = /etc/swift/cert.key
187+
188+ [pipeline:main]
189+ pipeline = auth-server
190+
191+ [app:auth-server]
192+ use = egg:swift#auth
193+ super_admin_key = devauth
194 default_cluster_url = https://ec2-184-72-156-130.compute-1.amazonaws.com:8080/v1
195- user = swift
196- cert_file = /etc/swift/cert.crt
197- key_file = /etc/swift/cert.key
198
199 #. Use swift-auth-add-user to create a new account and admin user::
200
201- ubuntu@domU-12-31-39-03-CD-06:/home/swift/swift/bin$ swift-auth-add-user --admin a3 b3 c3
202+ ubuntu@domU-12-31-39-03-CD-06:/home/swift/swift/bin$ swift-auth-add-user -K devauth -a a3 b3 c3
203 https://ec2-184-72-156-130.compute-1.amazonaws.com:8080/v1/06228ccf-6d0a-4395-889e-e971e8de8781
204
205 .. note::
206
207=== modified file 'etc/auth-server.conf-sample'
208--- etc/auth-server.conf-sample 2010-08-24 14:08:16 +0000
209+++ etc/auth-server.conf-sample 2010-09-12 00:25:58 +0000
210@@ -12,6 +12,8 @@
211
212 [app:auth-server]
213 use = egg:swift#auth
214+# Highly recommended to change this.
215+super_admin_key = devauth
216 # log_name = auth-server
217 # log_facility = LOG_LOCAL0
218 # log_level = INFO
219
220=== modified file 'swift/auth/server.py'
221--- swift/auth/server.py 2010-09-09 17:24:25 +0000
222+++ swift/auth/server.py 2010-09-12 00:25:58 +0000
223@@ -14,23 +14,21 @@
224 # limitations under the License.
225
226 from __future__ import with_statement
227-import errno
228 import os
229-import socket
230 from contextlib import contextmanager
231 from time import gmtime, strftime, time
232 from urllib import unquote, quote
233 from uuid import uuid4
234+from urlparse import urlparse
235
236 import sqlite3
237 from webob import Request, Response
238-from webob.exc import HTTPBadRequest, HTTPNoContent, HTTPUnauthorized, \
239- HTTPServiceUnavailable, HTTPNotFound
240+from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPNoContent, \
241+ HTTPUnauthorized, HTTPServiceUnavailable, HTTPNotFound
242
243-from swift.common.bufferedhttp import http_connect
244+from swift.common.bufferedhttp import http_connect_raw as http_connect
245 from swift.common.db import get_db_connection
246-from swift.common.ring import Ring
247-from swift.common.utils import get_logger, normalize_timestamp, split_path
248+from swift.common.utils import get_logger, split_path
249
250
251 class AuthController(object):
252@@ -69,8 +67,7 @@
253
254 * The developer makes a ReST call to create a new user.
255 * If the account for the user does not yet exist, the auth server makes
256- ReST calls to the Swift cluster's account servers to create a new account
257- on its end.
258+ a ReST call to the Swift cluster to create a new account on its end.
259 * The auth server records the information in its database.
260
261 A last use case is recreating existing accounts; this is really only useful
262@@ -78,34 +75,34 @@
263 the auth server's database is retained:
264
265 * A developer makes an ReST call to have the existing accounts recreated.
266- * For each account in its database, the auth server makes ReST calls to
267- the Swift cluster's account servers to create a specific account on its
268- end.
269+ * For each account in its database, the auth server makes a ReST call to
270+ the Swift cluster to create the specific account on its end.
271
272 :param conf: The [auth-server] dictionary of the auth server configuration
273 file
274- :param ring: Overrides loading the account ring from a file; useful for
275- testing.
276
277 See the etc/auth-server.conf-sample for information on the possible
278 configuration parameters.
279 """
280
281- def __init__(self, conf, ring=None):
282+ def __init__(self, conf):
283 self.logger = get_logger(conf)
284+ self.super_admin_key = conf.get('super_admin_key')
285+ if not self.super_admin_key:
286+ msg = 'No super_admin_key set in conf file! Exiting.'
287+ try:
288+ self.logger.critical(msg)
289+ except:
290+ pass
291+ raise ValueError(msg)
292 self.swift_dir = conf.get('swift_dir', '/etc/swift')
293 self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
294 if self.reseller_prefix and self.reseller_prefix[-1] != '_':
295 self.reseller_prefix += '_'
296- self.default_cluster_url = \
297- conf.get('default_cluster_url', 'http://127.0.0.1:8080/v1')
298+ self.default_cluster_url = conf.get('default_cluster_url',
299+ 'http://127.0.0.1:8080/v1').rstrip('/')
300 self.token_life = int(conf.get('token_life', 86400))
301 self.log_headers = conf.get('log_headers') == 'True'
302- if ring:
303- self.account_ring = ring
304- else:
305- self.account_ring = \
306- Ring(os.path.join(self.swift_dir, 'account.ring.gz'))
307 self.db_file = os.path.join(self.swift_dir, 'auth.db')
308 self.conn = get_db_connection(self.db_file, okay_to_create=True)
309 try:
310@@ -114,9 +111,16 @@
311 if str(err) == 'no such column: admin':
312 self.conn.execute("ALTER TABLE account ADD COLUMN admin TEXT")
313 self.conn.execute("UPDATE account SET admin = 't'")
314+ try:
315+ self.conn.execute('SELECT reseller_admin FROM account LIMIT 1')
316+ except sqlite3.OperationalError, err:
317+ if str(err) == 'no such column: reseller_admin':
318+ self.conn.execute(
319+ "ALTER TABLE account ADD COLUMN reseller_admin TEXT")
320 self.conn.execute('''CREATE TABLE IF NOT EXISTS account (
321 account TEXT, url TEXT, cfaccount TEXT,
322- user TEXT, password TEXT, admin TEXT)''')
323+ user TEXT, password TEXT, admin TEXT,
324+ reseller_admin TEXT)''')
325 self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_account_account
326 ON account (account)''')
327 try:
328@@ -139,51 +143,36 @@
329
330 def add_storage_account(self, account_name=''):
331 """
332- Creates an account within the Swift cluster by making a ReST call to
333- each of the responsible account servers.
334+ Creates an account within the Swift cluster by making a ReST call.
335
336 :param account_name: The desired name for the account; if omitted a
337 UUID4 will be used.
338 :returns: False upon failure, otherwise the name of the account
339 within the Swift cluster.
340 """
341- begin = time()
342 orig_account_name = account_name
343 if not account_name:
344 account_name = '%s%s' % (self.reseller_prefix, uuid4().hex)
345- partition, nodes = self.account_ring.get_nodes(account_name)
346- headers = {'X-Timestamp': normalize_timestamp(time()),
347- 'x-cf-trans-id': 'tx' + str(uuid4())}
348- statuses = []
349- for node in nodes:
350- try:
351- conn = None
352- conn = http_connect(node['ip'], node['port'], node['device'],
353- partition, 'PUT', '/' + account_name, headers)
354- source = conn.getresponse()
355- statuses.append(source.status)
356- if source.status >= 500:
357- self.logger.error('ERROR With account server %s:%s/%s: '
358- 'Response %s %s: %s' %
359- (node['ip'], node['port'], node['device'],
360- source.status, source.reason, source.read(1024)))
361- conn = None
362- except BaseException, err:
363- log_call = self.logger.exception
364- msg = 'ERROR With account server ' \
365- '%(ip)s:%(port)s/%(device)s (will retry later): ' % node
366- if isinstance(err, socket.error):
367- if err[0] == errno.ECONNREFUSED:
368- log_call = self.logger.error
369- msg += 'Connection refused'
370- elif err[0] == errno.EHOSTUNREACH:
371- log_call = self.logger.error
372- msg += 'Host unreachable'
373- log_call(msg)
374- rv = False
375- if len([s for s in statuses if (200 <= s < 300)]) > len(nodes) / 2:
376- rv = account_name
377- return rv
378+ url = '%s/%s' % (self.default_cluster_url, account_name)
379+ parsed = urlparse(url)
380+ # Create a single use token.
381+ token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
382+ with self.get_conn() as conn:
383+ conn.execute('''
384+ INSERT INTO token
385+ (token, created, account, user, cfaccount) VALUES
386+ (?, ?, '.super_admin', '.single_use', '.reseller_admin')''',
387+ (token, time()))
388+ conn.commit()
389+ conn = http_connect(parsed.hostname, parsed.port, 'PUT', parsed.path,
390+ {'X-Auth-Token': token}, ssl=(parsed.scheme == 'https'))
391+ resp = conn.getresponse()
392+ resp.read()
393+ if resp.status // 100 != 2:
394+ self.logger.error('ERROR attempting to create account %s: %s %s' %
395+ (url, resp.status, resp.reason))
396+ return False
397+ return account_name
398
399 @contextmanager
400 def get_conn(self):
401@@ -229,7 +218,9 @@
402
403 :param token: The token to validate
404 :returns: (TTL, account, user, cfaccount) if valid, False otherwise.
405- cfaccount will be None for users without admin access.
406+ cfaccount will be None for users without admin access for the
407+ account. cfaccount will be .reseller_admin for users with
408+ full reseller admin rights.
409 """
410 begin = time()
411 self.purge_old_tokens()
412@@ -241,18 +232,20 @@
413 (token,)).fetchone()
414 if row is not None:
415 created = row[0]
416- if time() - created >= self.token_life:
417+ if time() - created < self.token_life:
418+ rv = (self.token_life - (time() - created), row[1], row[2],
419+ row[3])
420+ # Remove the token if it was expired or single use.
421+ if not rv or rv[2] == '.single_use':
422 conn.execute('''
423 DELETE FROM token WHERE token = ?''', (token,))
424 conn.commit()
425- else:
426- rv = (self.token_life - (time() - created), row[1], row[2],
427- row[3])
428 self.logger.info('validate_token(%s, _, _) = %s [%.02f]' %
429 (repr(token), repr(rv), time() - begin))
430 return rv
431
432- def create_user(self, account, user, password, admin=False):
433+ def create_user(self, account, user, password, admin=False,
434+ reseller_admin=False):
435 """
436 Handles the create_user call for developers, used to request a user be
437 added in the auth server database. If the account does not yet exist,
438@@ -274,6 +267,9 @@
439 :param admin: If true, the user will be granted full access to the
440 account; otherwise, another user will have to add the
441 user to the ACLs for containers to grant access.
442+ :param reseller_admin: If true, the user will be granted full access to
443+ all accounts within this reseller, including the
444+ ability to create additional accounts.
445
446 :returns: False if the create fails, 'already exists' if the user
447 already exists, or storage url if successful
448@@ -287,9 +283,9 @@
449 (account, user)).fetchone()
450 if row:
451 self.logger.info(
452- 'ALREADY EXISTS create_user(%s, %s, _, %s) [%.02f]' %
453+ 'ALREADY EXISTS create_user(%s, %s, _, %s, %s) [%.02f]' %
454 (repr(account), repr(user), repr(admin),
455- time() - begin))
456+ repr(reseller_admin), time() - begin))
457 return 'already exists'
458 row = conn.execute(
459 'SELECT url, cfaccount FROM account WHERE account = ?',
460@@ -301,21 +297,22 @@
461 account_hash = self.add_storage_account()
462 if not account_hash:
463 self.logger.info(
464- 'FAILED create_user(%s, %s, _, %s) [%.02f]' %
465+ 'FAILED create_user(%s, %s, _, %s, %s) [%.02f]' %
466 (repr(account), repr(user), repr(admin),
467- time() - begin))
468+ repr(reseller_admin), time() - begin))
469 return False
470 url = self.default_cluster_url.rstrip('/') + '/' + account_hash
471 conn.execute('''INSERT INTO account
472- (account, url, cfaccount, user, password, admin)
473- VALUES (?, ?, ?, ?, ?, ?)''',
474+ (account, url, cfaccount, user, password, admin,
475+ reseller_admin)
476+ VALUES (?, ?, ?, ?, ?, ?, ?)''',
477 (account, url, account_hash, user, password,
478- admin and 't' or ''))
479+ admin and 't' or '', reseller_admin and 't' or ''))
480 conn.commit()
481 self.logger.info(
482- 'SUCCESS create_user(%s, %s, _, %s) = %s [%.02f]' %
483- (repr(account), repr(user), repr(admin), repr(url),
484- time() - begin))
485+ 'SUCCESS create_user(%s, %s, _, %s, %s) = %s [%.02f]' %
486+ (repr(account), repr(user), repr(admin), repr(reseller_admin),
487+ repr(url), time() - begin))
488 return url
489
490 def recreate_accounts(self):
491@@ -339,6 +336,32 @@
492 (rv, time() - begin))
493 return rv
494
495+ def is_account_admin(self, request, for_account):
496+ """
497+ Returns True if the request represents coming from .super_admin, a
498+ .reseller_admin, or an admin for the account specified.
499+ """
500+ if request.headers.get('X-Auth-Admin-User') == '.super_admin' and \
501+ request.headers.get('X-Auth-Admin-Key') == self.super_admin_key:
502+ return True
503+ try:
504+ account, user = \
505+ request.headers.get('X-Auth-Admin-User').split(':', 1)
506+ except (AttributeError, ValueError):
507+ return False
508+ with self.get_conn() as conn:
509+ row = conn.execute('''
510+ SELECT reseller_admin, admin FROM account
511+ WHERE account = ? AND user = ? AND password = ?''',
512+ (account, user,
513+ request.headers.get('X-Auth-Admin-Key'))).fetchone()
514+ if row:
515+ if row[0] == 't':
516+ return True
517+ if row[1] == 't' and account == for_account:
518+ return True
519+ return False
520+
521 def handle_token(self, request):
522 """
523 Handles ReST requests from Swift to validate tokens
524@@ -362,7 +385,9 @@
525 if not validation:
526 return HTTPNotFound()
527 groups = ['%s:%s' % (validation[1], validation[2]), validation[1]]
528- if validation[3]: # admin access to a cfaccount
529+ if validation[3]:
530+ # admin access to a cfaccount or ".reseller_admin" to access to all
531+ # accounts, including creating new ones.
532 groups.append(validation[3])
533 return HTTPNoContent(headers={'X-Auth-TTL': validation[0],
534 'X-Auth-Groups': ','.join(groups)})
535@@ -380,6 +405,7 @@
536 Valid headers:
537 * X-Auth-User-Key: <password>
538 * X-Auth-User-Admin: <true|false>
539+ * X-Auth-User-Reseller-Admin: <true|false>
540
541 If the HTTP request returns with a 204, then the user was added,
542 and the storage url will be available in the X-Storage-Url header.
543@@ -390,13 +416,24 @@
544 _, account_name, user_name = split_path(request.path, minsegs=3)
545 except ValueError:
546 return HTTPBadRequest()
547+ create_reseller_admin = \
548+ request.headers.get('x-auth-user-reseller-admin') == 'true'
549+ if create_reseller_admin and (
550+ request.headers.get('X-Auth-Admin-User') != '.super_admin' or
551+ request.headers.get('X-Auth-Admin-Key') != self.super_admin_key):
552+ return HTTPForbidden(request=request)
553+ create_account_admin = \
554+ request.headers.get('x-auth-user-admin') == 'true'
555+ if create_account_admin and \
556+ not self.is_account_admin(request, account_name):
557+ return HTTPForbidden(request=request)
558 if 'X-Auth-User-Key' not in request.headers:
559- return HTTPBadRequest('X-Auth-User-Key is required')
560+ return HTTPBadRequest(body='X-Auth-User-Key is required')
561 password = request.headers['x-auth-user-key']
562 storage_url = self.create_user(account_name, user_name, password,
563- request.headers.get('x-auth-user-admin') == 'true')
564+ create_account_admin, create_reseller_admin)
565 if storage_url == 'already exists':
566- return HTTPBadRequest(storage_url)
567+ return HTTPBadRequest(body=storage_url)
568 if not storage_url:
569 return HTTPServiceUnavailable()
570 return HTTPNoContent(headers={'x-storage-url': storage_url})
571@@ -412,6 +449,9 @@
572
573 :param request: webob.Request object
574 """
575+ if request.headers.get('X-Auth-Admin-User') != '.super_admin' or \
576+ request.headers.get('X-Auth-Admin-Key') != self.super_admin_key:
577+ return HTTPForbidden(request=request)
578 result = self.recreate_accounts()
579 return Response(result, 200, request=request)
580
581@@ -471,7 +511,7 @@
582 self.purge_old_tokens()
583 with self.get_conn() as conn:
584 row = conn.execute('''
585- SELECT cfaccount, url, admin FROM account
586+ SELECT cfaccount, url, admin, reseller_admin FROM account
587 WHERE account = ? AND user = ? AND password = ?''',
588 (account, user, password)).fetchone()
589 if row is None:
590@@ -479,6 +519,7 @@
591 cfaccount = row[0]
592 url = row[1]
593 admin = row[2] == 't'
594+ reseller_admin = row[3] == 't'
595 row = conn.execute('''
596 SELECT token FROM token WHERE account = ? AND user = ?''',
597 (account, user)).fetchone()
598@@ -486,11 +527,16 @@
599 token = row[0]
600 else:
601 token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
602+ token_cfaccount = ''
603+ if admin:
604+ token_cfaccount = cfaccount
605+ if reseller_admin:
606+ token_cfaccount = '.reseller_admin'
607 conn.execute('''
608 INSERT INTO token
609 (token, created, account, user, cfaccount)
610 VALUES (?, ?, ?, ?, ?)''',
611- (token, time(), account, user, admin and cfaccount or ''))
612+ (token, time(), account, user, token_cfaccount))
613 conn.commit()
614 return HTTPNoContent(headers={'x-auth-token': token,
615 'x-storage-token': token,
616
617=== modified file 'swift/common/constraints.py'
618--- swift/common/constraints.py 2010-08-16 22:30:27 +0000
619+++ swift/common/constraints.py 2010-09-12 00:25:58 +0000
620@@ -36,6 +36,8 @@
621 CONTAINER_LISTING_LIMIT = 10000
622 #: Max container list length of a get request for an account
623 ACCOUNT_LISTING_LIMIT = 10000
624+MAX_ACCOUNT_NAME_LENGTH = 256
625+MAX_CONTAINER_NAME_LENGTH = 256
626
627
628 def check_metadata(req, target_type):
629
630=== modified file 'swift/common/middleware/auth.py'
631--- swift/common/middleware/auth.py 2010-09-09 17:24:25 +0000
632+++ swift/common/middleware/auth.py 2010-09-12 00:25:58 +0000
633@@ -49,7 +49,7 @@
634 token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
635 if token and token.startswith(self.reseller_prefix):
636 memcache_client = cache_from_env(env)
637- key = 'devauth/%s' % token
638+ key = '%s/token/%s' % (self.reseller_prefix, token)
639 cached_auth_data = memcache_client.get(key)
640 if cached_auth_data:
641 start, expiration, groups = cached_auth_data
642@@ -85,14 +85,19 @@
643 version, account, container, obj = split_path(req.path, 1, 4, True)
644 if not account or not account.startswith(self.reseller_prefix):
645 return self.denied_response(req)
646- if req.remote_user and account in req.remote_user.split(','):
647+ user_groups = (req.remote_user or '').split(',')
648+ if '.reseller_admin' in user_groups:
649+ return None
650+ if account in user_groups and (req.method != 'PUT' or container):
651+ # If the user is admin for the account and is not trying to do an
652+ # account PUT...
653 return None
654 referrers, groups = parse_acl(getattr(req, 'acl', None))
655 if referrer_allowed(req.referer, referrers):
656 return None
657 if not req.remote_user:
658 return self.denied_response(req)
659- for user_group in req.remote_user.split(','):
660+ for user_group in user_groups:
661 if user_group in groups:
662 return None
663 return self.denied_response(req)
664
665=== modified file 'swift/proxy/server.py'
666--- swift/proxy/server.py 2010-09-10 14:52:10 +0000
667+++ swift/proxy/server.py 2010-09-12 00:25:58 +0000
668@@ -35,13 +35,12 @@
669 from swift.common.utils import get_logger, normalize_timestamp, split_path, \
670 cache_from_env
671 from swift.common.bufferedhttp import http_connect
672-from swift.common.constraints import check_object_creation, check_metadata, \
673- MAX_FILE_SIZE, check_xml_encodable
674+from swift.common.constraints import check_metadata, check_object_creation, \
675+ check_xml_encodable, MAX_ACCOUNT_NAME_LENGTH, MAX_CONTAINER_NAME_LENGTH, \
676+ MAX_FILE_SIZE
677 from swift.common.exceptions import ChunkReadTimeout, \
678 ChunkWriteTimeout, ConnectionTimeout
679
680-MAX_CONTAINER_NAME_LENGTH = 256
681-
682
683 def update_headers(response, headers):
684 """
685@@ -1080,6 +1079,59 @@
686 req.path_info.rstrip('/'), self.app.account_ring.replica_count)
687
688 @public
689+ def PUT(self, req):
690+ """HTTP PUT request handler."""
691+ error_response = check_metadata(req, 'account')
692+ if error_response:
693+ return error_response
694+ if len(self.account_name) > MAX_ACCOUNT_NAME_LENGTH:
695+ resp = HTTPBadRequest(request=req)
696+ resp.body = 'Account name length of %d longer than %d' % \
697+ (len(self.account_name), MAX_ACCOUNT_NAME_LENGTH)
698+ return resp
699+ account_partition, accounts = \
700+ self.app.account_ring.get_nodes(self.account_name)
701+ headers = {'X-Timestamp': normalize_timestamp(time.time()),
702+ 'x-cf-trans-id': self.trans_id}
703+ headers.update(value for value in req.headers.iteritems()
704+ if value[0].lower().startswith('x-account-meta-'))
705+ statuses = []
706+ reasons = []
707+ bodies = []
708+ for node in self.iter_nodes(account_partition, accounts,
709+ self.app.account_ring):
710+ if self.error_limited(node):
711+ continue
712+ try:
713+ with ConnectionTimeout(self.app.conn_timeout):
714+ conn = http_connect(node['ip'], node['port'],
715+ node['device'], account_partition, 'PUT',
716+ req.path_info, headers)
717+ with Timeout(self.app.node_timeout):
718+ source = conn.getresponse()
719+ body = source.read()
720+ if 200 <= source.status < 300 \
721+ or 400 <= source.status < 500:
722+ statuses.append(source.status)
723+ reasons.append(source.reason)
724+ bodies.append(body)
725+ else:
726+ if source.status == 507:
727+ self.error_limit(node)
728+ except:
729+ self.exception_occurred(node, 'Account',
730+ 'Trying to PUT to %s' % req.path)
731+ if len(statuses) >= len(accounts):
732+ break
733+ while len(statuses) < len(accounts):
734+ statuses.append(503)
735+ reasons.append('')
736+ bodies.append('')
737+ self.app.memcache.delete('account%s' % req.path_info.rstrip('/'))
738+ return self.best_response(req, statuses, reasons, bodies,
739+ 'Account PUT')
740+
741+ @public
742 def POST(self, req):
743 """HTTP POST request handler."""
744 error_response = check_metadata(req, 'account')
745
746=== modified file 'test/functional/swift.py'
747--- test/functional/swift.py 2010-07-12 22:03:45 +0000
748+++ test/functional/swift.py 2010-09-12 00:25:58 +0000
749@@ -124,7 +124,7 @@
750 if response.status == 401:
751 raise AuthenticationFailed()
752
753- if response.status != 204:
754+ if response.status not in (200, 204):
755 raise ResponseError(response)
756
757 for hdr in response.getheaders():
758
759=== modified file 'test/functional/tests.py'
760--- test/functional/tests.py 2010-09-03 04:50:16 +0000
761+++ test/functional/tests.py 2010-09-12 00:25:58 +0000
762@@ -172,7 +172,7 @@
763
764 def testPUT(self):
765 self.env.account.conn.make_request('PUT')
766- self.assert_status(405)
767+ self.assert_status([403, 405])
768
769 def testAccountHead(self):
770 try_count = 0
771
772=== modified file 'test/probe/common.py'
773--- test/probe/common.py 2010-07-19 03:00:28 +0000
774+++ test/probe/common.py 2010-09-12 00:25:58 +0000
775@@ -13,16 +13,26 @@
776 # See the License for the specific language governing permissions and
777 # limitations under the License.
778
779-from os import kill
780+from os import environ, kill
781 from signal import SIGTERM
782 from subprocess import call, Popen
783 from time import sleep
784+from ConfigParser import ConfigParser
785
786 from swift.common.bufferedhttp import http_connect_raw as http_connect
787 from swift.common.client import get_auth
788 from swift.common.ring import Ring
789
790
791+AUTH_SERVER_CONF_FILE = environ.get('SWIFT_AUTH_SERVER_CONF_FILE',
792+ '/etc/swift/auth-server.conf')
793+c = ConfigParser()
794+if not c.read(AUTH_SERVER_CONF_FILE):
795+ exit('Unable to read config file: %s' % AUTH_SERVER_CONF_FILE)
796+conf = dict(c.items('app:auth-server'))
797+SUPER_ADMIN_KEY = conf.get('super_admin_key', 'devauth')
798+
799+
800 def kill_pids(pids):
801 for pid in pids.values():
802 try:
803@@ -50,7 +60,9 @@
804 container_ring = Ring('/etc/swift/container.ring.gz')
805 object_ring = Ring('/etc/swift/object.ring.gz')
806 sleep(5)
807- conn = http_connect('127.0.0.1', '11000', 'POST', '/recreate_accounts')
808+ conn = http_connect('127.0.0.1', '11000', 'POST', '/recreate_accounts',
809+ headers={'X-Auth-Admin-User': '.super_admin',
810+ 'X-Auth-Admin-Key': SUPER_ADMIN_KEY})
811 resp = conn.getresponse()
812 if resp.status != 200:
813 raise Exception('Recreating accounts failed. (%d)' % resp.status)
814
815=== modified file 'test/unit/auth/test_server.py'
816--- test/unit/auth/test_server.py 2010-09-06 20:26:31 +0000
817+++ test/unit/auth/test_server.py 2010-09-12 00:25:58 +0000
818@@ -53,33 +53,27 @@
819 def getheader(self, name):
820 return self.getheaders().get(name.lower())
821 code_iter = iter(code_iter)
822- def connect(*args, **ckwargs):
823- if 'give_content_type' in kwargs:
824- if len(args) >= 7 and 'content_type' in args[6]:
825- kwargs['give_content_type'](args[6]['content-type'])
826- else:
827- kwargs['give_content_type']('')
828+ def connect(*args, **kwargs):
829+ connect.last_args = args
830+ connect.last_kwargs = kwargs
831 return FakeConn(code_iter.next())
832 return connect
833
834
835-class FakeRing(object):
836- def get_nodes(self, path):
837- return 1, [{'ip': '10.0.0.%s' % x, 'port': 1000+x, 'device': 'sda'}
838- for x in xrange(3)]
839-
840-
841 class TestAuthServer(unittest.TestCase):
842
843 def setUp(self):
844+ self.ohttp_connect = auth_server.http_connect
845 self.testdir = os.path.join(os.path.dirname(__file__),
846 'auth_server')
847 rmtree(self.testdir, ignore_errors=1)
848 os.mkdir(self.testdir)
849- self.conf = {'swift_dir': self.testdir, 'log_name': 'auth'}
850- self.controller = auth_server.AuthController(self.conf, FakeRing())
851+ self.conf = {'swift_dir': self.testdir, 'log_name': 'auth',
852+ 'super_admin_key': 'testkey'}
853+ self.controller = auth_server.AuthController(self.conf)
854
855 def tearDown(self):
856+ auth_server.http_connect = self.ohttp_connect
857 rmtree(self.testdir, ignore_errors=1)
858
859 def test_get_conn(self):
860@@ -106,7 +100,7 @@
861 self.assert_(conn is not None)
862
863 def test_validate_token_non_existant_token(self):
864- auth_server.http_connect = fake_http_connect(201, 201, 201)
865+ auth_server.http_connect = fake_http_connect(201)
866 cfaccount = self.controller.create_user(
867 'test', 'tester', 'testing',).split('/')[-1]
868 res = self.controller.handle_auth(Request.blank('/v1/test/auth',
869@@ -117,7 +111,7 @@
870 self.assertEquals(self.controller.validate_token(token + 'bad'), False)
871
872 def test_validate_token_good(self):
873- auth_server.http_connect = fake_http_connect(201, 201, 201)
874+ auth_server.http_connect = fake_http_connect(201)
875 cfaccount = self.controller.create_user(
876 'test', 'tester', 'testing',).split('/')[-1]
877 res = self.controller.handle_auth(Request.blank('/v1/test/auth',
878@@ -125,14 +119,14 @@
879 headers={'X-Storage-User': 'tester',
880 'X-Storage-Pass': 'testing'}))
881 token = res.headers['x-storage-token']
882- ttl = self.controller.validate_token(token)
883+ ttl, _, _, _ = self.controller.validate_token(token)
884 self.assert_(ttl > 0, repr(ttl))
885
886 def test_validate_token_expired(self):
887 orig_time = auth_server.time
888 try:
889 auth_server.time = lambda: 1
890- auth_server.http_connect = fake_http_connect(201, 201, 201)
891+ auth_server.http_connect = fake_http_connect(201)
892 cfaccount = self.controller.create_user('test', 'tester',
893 'testing').split('/')[-1]
894 res = self.controller.handle_auth(Request.blank('/v1/test/auth',
895@@ -140,7 +134,7 @@
896 headers={'X-Storage-User': 'tester',
897 'X-Storage-Pass': 'testing'}))
898 token = res.headers['x-storage-token']
899- ttl = self.controller.validate_token(token)
900+ ttl, _, _, _ = self.controller.validate_token(token)
901 self.assert_(ttl > 0, repr(ttl))
902 auth_server.time = lambda: 1 + self.controller.token_life
903 self.assertEquals(self.controller.validate_token(token), False)
904@@ -148,107 +142,98 @@
905 auth_server.time = orig_time
906
907 def test_create_user_no_new_account(self):
908- auth_server.http_connect = fake_http_connect(201, 201, 201)
909+ auth_server.http_connect = fake_http_connect(201)
910 result = self.controller.create_user('', 'tester', 'testing')
911 self.assertFalse(result)
912
913 def test_create_user_no_new_user(self):
914- auth_server.http_connect = fake_http_connect(201, 201, 201)
915+ auth_server.http_connect = fake_http_connect(201)
916 result = self.controller.create_user('test', '', 'testing')
917 self.assertFalse(result)
918
919 def test_create_user_no_new_password(self):
920- auth_server.http_connect = fake_http_connect(201, 201, 201)
921+ auth_server.http_connect = fake_http_connect(201)
922 result = self.controller.create_user('test', 'tester', '')
923 self.assertFalse(result)
924
925 def test_create_user_good(self):
926- auth_server.http_connect = fake_http_connect(201, 201, 201)
927+ auth_server.http_connect = fake_http_connect(201)
928 url = self.controller.create_user('test', 'tester', 'testing')
929 self.assert_(url)
930 self.assertEquals('/'.join(url.split('/')[:-1]),
931 self.controller.default_cluster_url.rstrip('/'), repr(url))
932
933 def test_recreate_accounts_none(self):
934- auth_server.http_connect = fake_http_connect(201, 201, 201)
935+ auth_server.http_connect = fake_http_connect(201)
936 rv = self.controller.recreate_accounts()
937 self.assertEquals(rv.split()[0], '0', repr(rv))
938 self.assertEquals(rv.split()[-1], '[]', repr(rv))
939
940 def test_recreate_accounts_one(self):
941- auth_server.http_connect = fake_http_connect(201, 201, 201)
942+ auth_server.http_connect = fake_http_connect(201)
943 self.controller.create_user('test', 'tester', 'testing')
944- auth_server.http_connect = fake_http_connect(201, 201, 201)
945+ auth_server.http_connect = fake_http_connect(201)
946 rv = self.controller.recreate_accounts()
947 self.assertEquals(rv.split()[0], '1', repr(rv))
948 self.assertEquals(rv.split()[-1], '[]', repr(rv))
949
950 def test_recreate_accounts_several(self):
951- auth_server.http_connect = fake_http_connect(201, 201, 201)
952+ auth_server.http_connect = fake_http_connect(201)
953 self.controller.create_user('test1', 'tester', 'testing')
954- auth_server.http_connect = fake_http_connect(201, 201, 201)
955+ auth_server.http_connect = fake_http_connect(201)
956 self.controller.create_user('test2', 'tester', 'testing')
957- auth_server.http_connect = fake_http_connect(201, 201, 201)
958+ auth_server.http_connect = fake_http_connect(201)
959 self.controller.create_user('test3', 'tester', 'testing')
960- auth_server.http_connect = fake_http_connect(201, 201, 201)
961+ auth_server.http_connect = fake_http_connect(201)
962 self.controller.create_user('test4', 'tester', 'testing')
963- auth_server.http_connect = fake_http_connect(201, 201, 201,
964- 201, 201, 201,
965- 201, 201, 201,
966- 201, 201, 201)
967+ auth_server.http_connect = fake_http_connect(201, 201, 201, 201)
968 rv = self.controller.recreate_accounts()
969 self.assertEquals(rv.split()[0], '4', repr(rv))
970 self.assertEquals(rv.split()[-1], '[]', repr(rv))
971
972 def test_recreate_accounts_one_fail(self):
973- auth_server.http_connect = fake_http_connect(201, 201, 201)
974+ auth_server.http_connect = fake_http_connect(201)
975 url = self.controller.create_user('test', 'tester', 'testing')
976 cfaccount = url.split('/')[-1]
977- auth_server.http_connect = fake_http_connect(500, 500, 500)
978+ auth_server.http_connect = fake_http_connect(500)
979 rv = self.controller.recreate_accounts()
980 self.assertEquals(rv.split()[0], '1', repr(rv))
981 self.assertEquals(rv.split()[-1], '[%s]' % repr(cfaccount),
982 repr(rv))
983
984 def test_recreate_accounts_several_fail(self):
985- auth_server.http_connect = fake_http_connect(201, 201, 201)
986+ auth_server.http_connect = fake_http_connect(201)
987 url = self.controller.create_user('test1', 'tester', 'testing')
988 cfaccounts = [url.split('/')[-1]]
989- auth_server.http_connect = fake_http_connect(201, 201, 201)
990+ auth_server.http_connect = fake_http_connect(201)
991 url = self.controller.create_user('test2', 'tester', 'testing')
992 cfaccounts.append(url.split('/')[-1])
993- auth_server.http_connect = fake_http_connect(201, 201, 201)
994+ auth_server.http_connect = fake_http_connect(201)
995 url = self.controller.create_user('test3', 'tester', 'testing')
996 cfaccounts.append(url.split('/')[-1])
997- auth_server.http_connect = fake_http_connect(201, 201, 201)
998+ auth_server.http_connect = fake_http_connect(201)
999 url = self.controller.create_user('test4', 'tester', 'testing')
1000 cfaccounts.append(url.split('/')[-1])
1001- auth_server.http_connect = fake_http_connect(500, 500, 500,
1002- 500, 500, 500,
1003- 500, 500, 500,
1004- 500, 500, 500)
1005+ auth_server.http_connect = fake_http_connect(500, 500, 500, 500)
1006 rv = self.controller.recreate_accounts()
1007 self.assertEquals(rv.split()[0], '4', repr(rv))
1008 failed = rv.split('[', 1)[-1][:-1].split(', ')
1009 self.assertEquals(set(failed), set(repr(a) for a in cfaccounts))
1010
1011 def test_recreate_accounts_several_fail_some(self):
1012- auth_server.http_connect = fake_http_connect(201, 201, 201)
1013+ auth_server.http_connect = fake_http_connect(201)
1014 url = self.controller.create_user('test1', 'tester', 'testing')
1015 cfaccounts = [url.split('/')[-1]]
1016- auth_server.http_connect = fake_http_connect(201, 201, 201)
1017+ auth_server.http_connect = fake_http_connect(201)
1018 url = self.controller.create_user('test2', 'tester', 'testing')
1019 cfaccounts.append(url.split('/')[-1])
1020- auth_server.http_connect = fake_http_connect(201, 201, 201)
1021+ auth_server.http_connect = fake_http_connect(201)
1022 url = self.controller.create_user('test3', 'tester', 'testing')
1023 cfaccounts.append(url.split('/')[-1])
1024- auth_server.http_connect = fake_http_connect(201, 201, 201)
1025+ auth_server.http_connect = fake_http_connect(201)
1026 url = self.controller.create_user('test4', 'tester', 'testing')
1027 cfaccounts.append(url.split('/')[-1])
1028- auth_server.http_connect = fake_http_connect(500, 500, 500,
1029- 201, 201, 201,
1030- 500, 500, 500,
1031- 201, 201, 201)
1032+ auth_server.http_connect = fake_http_connect(500, 201, 500, 201)
1033 rv = self.controller.recreate_accounts()
1034 self.assertEquals(rv.split()[0], '4', repr(rv))
1035 failed = rv.split('[', 1)[-1][:-1].split(', ')
1036@@ -263,7 +248,7 @@
1037 self.assertEquals(res.status_int, 400)
1038
1039 def test_auth_SOSO_missing_headers(self):
1040- auth_server.http_connect = fake_http_connect(201, 201, 201)
1041+ auth_server.http_connect = fake_http_connect(201)
1042 cfaccount = self.controller.create_user(
1043 'test', 'tester', 'testing').split('/')[-1]
1044 res = self.controller.handle_auth(Request.blank('/v1/test/auth',
1045@@ -279,7 +264,7 @@
1046 self.assertEquals(res.status_int, 401)
1047
1048 def test_auth_SOSO_bad_account(self):
1049- auth_server.http_connect = fake_http_connect(201, 201, 201)
1050+ auth_server.http_connect = fake_http_connect(201)
1051 cfaccount = self.controller.create_user(
1052 'test', 'tester', 'testing').split('/')[-1]
1053 res = self.controller.handle_auth(Request.blank('/v1/testbad/auth',
1054@@ -294,7 +279,7 @@
1055 self.assertEquals(res.status_int, 401)
1056
1057 def test_auth_SOSO_bad_user(self):
1058- auth_server.http_connect = fake_http_connect(201, 201, 201)
1059+ auth_server.http_connect = fake_http_connect(201)
1060 cfaccount = self.controller.create_user(
1061 'test', 'tester', 'testing').split('/')[-1]
1062 res = self.controller.handle_auth(Request.blank('/v1/test/auth',
1063@@ -309,7 +294,7 @@
1064 self.assertEquals(res.status_int, 401)
1065
1066 def test_auth_SOSO_bad_password(self):
1067- auth_server.http_connect = fake_http_connect(201, 201, 201)
1068+ auth_server.http_connect = fake_http_connect(201)
1069 cfaccount = self.controller.create_user(
1070 'test', 'tester', 'testing').split('/')[-1]
1071 res = self.controller.handle_auth(Request.blank('/v1/test/auth',
1072@@ -324,7 +309,7 @@
1073 self.assertEquals(res.status_int, 401)
1074
1075 def test_auth_SOSO_good(self):
1076- auth_server.http_connect = fake_http_connect(201, 201, 201)
1077+ auth_server.http_connect = fake_http_connect(201)
1078 cfaccount = self.controller.create_user(
1079 'test', 'tester', 'testing').split('/')[-1]
1080 res = self.controller.handle_auth(Request.blank('/v1/test/auth',
1081@@ -332,11 +317,11 @@
1082 headers={'X-Storage-User': 'tester',
1083 'X-Storage-Pass': 'testing'}))
1084 token = res.headers['x-storage-token']
1085- ttl = self.controller.validate_token(token)
1086+ ttl, _, _, _ = self.controller.validate_token(token)
1087 self.assert_(ttl > 0, repr(ttl))
1088
1089 def test_auth_SOSO_good_Mosso_headers(self):
1090- auth_server.http_connect = fake_http_connect(201, 201, 201)
1091+ auth_server.http_connect = fake_http_connect(201)
1092 cfaccount = self.controller.create_user(
1093 'test', 'tester', 'testing').split('/')[-1]
1094 res = self.controller.handle_auth(Request.blank('/v1/test/auth',
1095@@ -344,11 +329,11 @@
1096 headers={'X-Auth-User': 'test:tester',
1097 'X-Auth-Key': 'testing'}))
1098 token = res.headers['x-storage-token']
1099- ttl = self.controller.validate_token(token)
1100+ ttl, _, _, _ = self.controller.validate_token(token)
1101 self.assert_(ttl > 0, repr(ttl))
1102
1103 def test_auth_SOSO_bad_Mosso_headers(self):
1104- auth_server.http_connect = fake_http_connect(201, 201, 201)
1105+ auth_server.http_connect = fake_http_connect(201)
1106 cfaccount = self.controller.create_user(
1107 'test', 'tester', 'testing',).split('/')[-1]
1108 res = self.controller.handle_auth(Request.blank('/v1/test/auth',
1109@@ -368,7 +353,7 @@
1110 self.assertEquals(res.status_int, 401)
1111
1112 def test_auth_Mosso_missing_headers(self):
1113- auth_server.http_connect = fake_http_connect(201, 201, 201)
1114+ auth_server.http_connect = fake_http_connect(201)
1115 cfaccount = self.controller.create_user(
1116 'test', 'tester', 'testing').split('/')[-1]
1117 res = self.controller.handle_auth(Request.blank('/auth',
1118@@ -384,7 +369,7 @@
1119 self.assertEquals(res.status_int, 401)
1120
1121 def test_auth_Mosso_bad_header_format(self):
1122- auth_server.http_connect = fake_http_connect(201, 201, 201)
1123+ auth_server.http_connect = fake_http_connect(201)
1124 cfaccount = self.controller.create_user(
1125 'test', 'tester', 'testing').split('/')[-1]
1126 res = self.controller.handle_auth(Request.blank('/auth',
1127@@ -399,7 +384,7 @@
1128 self.assertEquals(res.status_int, 401)
1129
1130 def test_auth_Mosso_bad_account(self):
1131- auth_server.http_connect = fake_http_connect(201, 201, 201)
1132+ auth_server.http_connect = fake_http_connect(201)
1133 cfaccount = self.controller.create_user(
1134 'test', 'tester', 'testing').split('/')[-1]
1135 res = self.controller.handle_auth(Request.blank('/auth',
1136@@ -414,7 +399,7 @@
1137 self.assertEquals(res.status_int, 401)
1138
1139 def test_auth_Mosso_bad_user(self):
1140- auth_server.http_connect = fake_http_connect(201, 201, 201)
1141+ auth_server.http_connect = fake_http_connect(201)
1142 cfaccount = self.controller.create_user(
1143 'test', 'tester', 'testing').split('/')[-1]
1144 res = self.controller.handle_auth(Request.blank('/auth',
1145@@ -429,7 +414,7 @@
1146 self.assertEquals(res.status_int, 401)
1147
1148 def test_auth_Mosso_bad_password(self):
1149- auth_server.http_connect = fake_http_connect(201, 201, 201)
1150+ auth_server.http_connect = fake_http_connect(201)
1151 cfaccount = self.controller.create_user(
1152 'test', 'tester', 'testing').split('/')[-1]
1153 res = self.controller.handle_auth(Request.blank('/auth',
1154@@ -444,7 +429,7 @@
1155 self.assertEquals(res.status_int, 401)
1156
1157 def test_auth_Mosso_good(self):
1158- auth_server.http_connect = fake_http_connect(201, 201, 201)
1159+ auth_server.http_connect = fake_http_connect(201)
1160 cfaccount = self.controller.create_user(
1161 'test', 'tester', 'testing').split('/')[-1]
1162 res = self.controller.handle_auth(Request.blank('/auth',
1163@@ -452,11 +437,11 @@
1164 headers={'X-Auth-User': 'test:tester',
1165 'X-Auth-Key': 'testing'}))
1166 token = res.headers['x-storage-token']
1167- ttl = self.controller.validate_token(token)
1168+ ttl, _, _, _ = self.controller.validate_token(token)
1169 self.assert_(ttl > 0, repr(ttl))
1170
1171 def test_auth_Mosso_good_SOSO_header_names(self):
1172- auth_server.http_connect = fake_http_connect(201, 201, 201)
1173+ auth_server.http_connect = fake_http_connect(201)
1174 cfaccount = self.controller.create_user(
1175 'test', 'tester', 'testing').split('/')[-1]
1176 res = self.controller.handle_auth(Request.blank('/auth',
1177@@ -464,7 +449,7 @@
1178 headers={'X-Storage-User': 'test:tester',
1179 'X-Storage-Pass': 'testing'}))
1180 token = res.headers['x-storage-token']
1181- ttl = self.controller.validate_token(token)
1182+ ttl, _, _, _ = self.controller.validate_token(token)
1183 self.assert_(ttl > 0, repr(ttl))
1184
1185 def test_basic_logging(self):
1186@@ -473,11 +458,11 @@
1187 logger = get_logger(self.conf, 'auth')
1188 logger.logger.addHandler(log_handler)
1189 try:
1190- auth_server.http_connect = fake_http_connect(201, 201, 201)
1191+ auth_server.http_connect = fake_http_connect(201)
1192 url = self.controller.create_user('test', 'tester', 'testing')
1193 self.assertEquals(log.getvalue().rsplit(' ', 1)[0],
1194- "auth SUCCESS create_user('test', 'tester', _, False) = %s"
1195- % repr(url))
1196+ "auth SUCCESS create_user('test', 'tester', _, False, False) "
1197+ "= %s" % repr(url))
1198 log.truncate(0)
1199 def start_response(*args):
1200 pass
1201@@ -603,8 +588,8 @@
1202 conn.commit()
1203 conn.close()
1204 # Upgrade to current db
1205- conf = {'swift_dir': swift_dir}
1206- controller = auth_server.AuthController(conf, FakeRing())
1207+ conf = {'swift_dir': swift_dir, 'super_admin_key': 'testkey'}
1208+ controller = auth_server.AuthController(conf)
1209 # Check new items exist and are correct
1210 conn = get_db_connection(db_file)
1211 row = conn.execute('SELECT admin FROM account').fetchone()
1212@@ -614,21 +599,360 @@
1213 finally:
1214 rmtree(swift_dir)
1215
1216+ def test_upgrading_from_db2(self):
1217+ swift_dir = '/tmp/swift_test_auth_%s' % uuid4().hex
1218+ os.mkdir(swift_dir)
1219+ try:
1220+ # Create db1
1221+ db_file = os.path.join(swift_dir, 'auth.db')
1222+ conn = get_db_connection(db_file, okay_to_create=True)
1223+ conn.execute('''CREATE TABLE IF NOT EXISTS account (
1224+ account TEXT, url TEXT, cfaccount TEXT,
1225+ user TEXT, password TEXT, admin TEXT)''')
1226+ conn.execute('''CREATE INDEX IF NOT EXISTS ix_account_account
1227+ ON account (account)''')
1228+ conn.execute('''CREATE TABLE IF NOT EXISTS token (
1229+ token TEXT, created FLOAT,
1230+ account TEXT, user TEXT, cfaccount TEXT)''')
1231+ conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_token
1232+ ON token (token)''')
1233+ conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_created
1234+ ON token (created)''')
1235+ conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_account
1236+ ON token (account)''')
1237+ conn.execute('''INSERT INTO account
1238+ (account, url, cfaccount, user, password, admin)
1239+ VALUES ('act', 'url', 'cfa', 'us1', 'pas', '')''')
1240+ conn.execute('''INSERT INTO account
1241+ (account, url, cfaccount, user, password, admin)
1242+ VALUES ('act', 'url', 'cfa', 'us2', 'pas', 't')''')
1243+ conn.execute('''INSERT INTO token
1244+ (token, created, account, user, cfaccount)
1245+ VALUES ('tok', '1', 'act', 'us1', 'cfa')''')
1246+ conn.commit()
1247+ conn.close()
1248+ # Upgrade to current db
1249+ conf = {'swift_dir': swift_dir, 'super_admin_key': 'testkey'}
1250+ controller = auth_server.AuthController(conf)
1251+ # Check new items exist and are correct
1252+ conn = get_db_connection(db_file)
1253+ row = conn.execute('''SELECT admin, reseller_admin
1254+ FROM account WHERE user = 'us1' ''').fetchone()
1255+ self.assert_(not row[0], row[0])
1256+ self.assert_(not row[1], row[1])
1257+ row = conn.execute('''SELECT admin, reseller_admin
1258+ FROM account WHERE user = 'us2' ''').fetchone()
1259+ self.assertEquals(row[0], 't')
1260+ self.assert_(not row[1], row[1])
1261+ row = conn.execute('SELECT user FROM token').fetchone()
1262+ self.assert_(row)
1263+ finally:
1264+ rmtree(swift_dir)
1265+
1266 def test_create_user_twice(self):
1267- auth_server.http_connect = fake_http_connect(201, 201, 201)
1268+ auth_server.http_connect = fake_http_connect(201)
1269 self.controller.create_user('test', 'tester', 'testing')
1270- auth_server.http_connect = fake_http_connect(201, 201, 201)
1271+ auth_server.http_connect = fake_http_connect(201)
1272 self.assertEquals(
1273 self.controller.create_user('test', 'tester', 'testing'),
1274 'already exists')
1275
1276 def test_create_2users_1account(self):
1277- auth_server.http_connect = fake_http_connect(201, 201, 201)
1278+ auth_server.http_connect = fake_http_connect(201)
1279 url = self.controller.create_user('test', 'tester', 'testing')
1280- auth_server.http_connect = fake_http_connect(201, 201, 201)
1281+ auth_server.http_connect = fake_http_connect(201)
1282 url2 = self.controller.create_user('test', 'tester2', 'testing2')
1283 self.assertEquals(url, url2)
1284
1285+ def test_no_super_admin_key(self):
1286+ conf = {'swift_dir': self.testdir, 'log_name': 'auth'}
1287+ self.assertRaises(ValueError, auth_server.AuthController, conf)
1288+ conf['super_admin_key'] = 'testkey'
1289+ auth_server.AuthController(conf)
1290+
1291+ def test_add_storage_account(self):
1292+ auth_server.http_connect = fake_http_connect(201)
1293+ stgact = self.controller.add_storage_account()
1294+ self.assert_(stgact.startswith(self.controller.reseller_prefix),
1295+ stgact)
1296+ # Make sure token given is the expected single use token
1297+ token = auth_server.http_connect.last_args[-1]['X-Auth-Token']
1298+ self.assert_(self.controller.validate_token(token))
1299+ self.assert_(not self.controller.validate_token(token))
1300+ auth_server.http_connect = fake_http_connect(201)
1301+ stgact = self.controller.add_storage_account('bob')
1302+ self.assertEquals(stgact, 'bob')
1303+ # Make sure token given is the expected single use token
1304+ token = auth_server.http_connect.last_args[-1]['X-Auth-Token']
1305+ self.assert_(self.controller.validate_token(token))
1306+ self.assert_(not self.controller.validate_token(token))
1307+
1308+ def test_regular_user(self):
1309+ auth_server.http_connect = fake_http_connect(201)
1310+ self.controller.create_user('act', 'usr', 'pas').split('/')[-1]
1311+ res = self.controller.handle_auth(Request.blank('/v1.0',
1312+ environ={'REQUEST_METHOD': 'GET'},
1313+ headers={'X-Auth-User': 'act:usr', 'X-Auth-Key': 'pas'}))
1314+ _, _, _, stgact = \
1315+ self.controller.validate_token(res.headers['x-auth-token'])
1316+ self.assertEquals(stgact, '')
1317+
1318+ def test_account_admin(self):
1319+ auth_server.http_connect = fake_http_connect(201)
1320+ stgact = self.controller.create_user(
1321+ 'act', 'usr', 'pas', admin=True).split('/')[-1]
1322+ res = self.controller.handle_auth(Request.blank('/v1.0',
1323+ environ={'REQUEST_METHOD': 'GET'},
1324+ headers={'X-Auth-User': 'act:usr', 'X-Auth-Key': 'pas'}))
1325+ _, _, _, vstgact = \
1326+ self.controller.validate_token(res.headers['x-auth-token'])
1327+ self.assertEquals(stgact, vstgact)
1328+
1329+ def test_reseller_admin(self):
1330+ auth_server.http_connect = fake_http_connect(201)
1331+ self.controller.create_user(
1332+ 'act', 'usr', 'pas', reseller_admin=True).split('/')[-1]
1333+ res = self.controller.handle_auth(Request.blank('/v1.0',
1334+ environ={'REQUEST_METHOD': 'GET'},
1335+ headers={'X-Auth-User': 'act:usr', 'X-Auth-Key': 'pas'}))
1336+ _, _, _, stgact = \
1337+ self.controller.validate_token(res.headers['x-auth-token'])
1338+ self.assertEquals(stgact, '.reseller_admin')
1339+
1340+ def test_is_account_admin(self):
1341+ req = Request.blank('/', headers={'X-Auth-Admin-User': '.super_admin',
1342+ 'X-Auth-Admin-Key': 'testkey'})
1343+ self.assert_(self.controller.is_account_admin(req, 'any'))
1344+ req = Request.blank('/', headers={'X-Auth-Admin-User': '.super_admin',
1345+ 'X-Auth-Admin-Key': 'testkey2'})
1346+ self.assert_(not self.controller.is_account_admin(req, 'any'))
1347+ req = Request.blank('/', headers={'X-Auth-Admin-User': '.super_admi',
1348+ 'X-Auth-Admin-Key': 'testkey'})
1349+ self.assert_(not self.controller.is_account_admin(req, 'any'))
1350+
1351+ auth_server.http_connect = fake_http_connect(201, 201)
1352+ self.controller.create_user(
1353+ 'act1', 'resadmin', 'pas', reseller_admin=True).split('/')[-1]
1354+ self.controller.create_user('act1', 'usr', 'pas').split('/')[-1]
1355+ self.controller.create_user(
1356+ 'act2', 'actadmin', 'pas', admin=True).split('/')[-1]
1357+
1358+ req = Request.blank('/', headers={'X-Auth-Admin-User': 'act1:resadmin',
1359+ 'X-Auth-Admin-Key': 'pas'})
1360+ self.assert_(self.controller.is_account_admin(req, 'any'))
1361+ self.assert_(self.controller.is_account_admin(req, 'act1'))
1362+ self.assert_(self.controller.is_account_admin(req, 'act2'))
1363+
1364+ req = Request.blank('/', headers={'X-Auth-Admin-User': 'act1:usr',
1365+ 'X-Auth-Admin-Key': 'pas'})
1366+ self.assert_(not self.controller.is_account_admin(req, 'any'))
1367+ self.assert_(not self.controller.is_account_admin(req, 'act1'))
1368+ self.assert_(not self.controller.is_account_admin(req, 'act2'))
1369+
1370+ req = Request.blank('/', headers={'X-Auth-Admin-User': 'act2:actadmin',
1371+ 'X-Auth-Admin-Key': 'pas'})
1372+ self.assert_(not self.controller.is_account_admin(req, 'any'))
1373+ self.assert_(not self.controller.is_account_admin(req, 'act1'))
1374+ self.assert_(self.controller.is_account_admin(req, 'act2'))
1375+
1376+ def test_handle_add_user_create_reseller_admin(self):
1377+ auth_server.http_connect = fake_http_connect(201)
1378+ self.controller.create_user('act', 'usr', 'pas')
1379+ self.controller.create_user('act', 'actadmin', 'pas', admin=True)
1380+ self.controller.create_user('act', 'resadmin', 'pas',
1381+ reseller_admin=True)
1382+
1383+ req = Request.blank('/account/act/resadmin2',
1384+ headers={'X-Auth-User-Key': 'pas',
1385+ 'X-Auth-User-Reseller-Admin': 'true'})
1386+ resp = self.controller.handle_add_user(req)
1387+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1388+
1389+ req = Request.blank('/account/act/resadmin2',
1390+ headers={'X-Auth-User-Key': 'pas',
1391+ 'X-Auth-User-Reseller-Admin': 'true',
1392+ 'X-Auth-Admin-User': 'act:usr',
1393+ 'X-Auth-Admin-Key': 'pas'})
1394+ resp = self.controller.handle_add_user(req)
1395+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1396+
1397+ req = Request.blank('/account/act/resadmin2',
1398+ headers={'X-Auth-User-Key': 'pas',
1399+ 'X-Auth-User-Reseller-Admin': 'true',
1400+ 'X-Auth-Admin-User': 'act:actadmin',
1401+ 'X-Auth-Admin-Key': 'pas'})
1402+ resp = self.controller.handle_add_user(req)
1403+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1404+
1405+ req = Request.blank('/account/act/resadmin2',
1406+ headers={'X-Auth-User-Key': 'pas',
1407+ 'X-Auth-User-Reseller-Admin': 'true',
1408+ 'X-Auth-Admin-User': 'act:resadmin',
1409+ 'X-Auth-Admin-Key': 'pas'})
1410+ resp = self.controller.handle_add_user(req)
1411+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1412+
1413+ req = Request.blank('/account/act/resadmin2',
1414+ headers={'X-Auth-User-Key': 'pas',
1415+ 'X-Auth-User-Reseller-Admin': 'true',
1416+ 'X-Auth-Admin-User': '.super_admin',
1417+ 'X-Auth-Admin-Key': 'testkey'})
1418+ resp = self.controller.handle_add_user(req)
1419+ self.assert_(resp.status_int // 100 == 2, resp.status_int)
1420+
1421+ def test_handle_add_user_create_account_admin(self):
1422+ auth_server.http_connect = fake_http_connect(201, 201)
1423+ self.controller.create_user('act', 'usr', 'pas')
1424+ self.controller.create_user('act', 'actadmin', 'pas', admin=True)
1425+ self.controller.create_user('act2', 'actadmin', 'pas', admin=True)
1426+ self.controller.create_user('act2', 'resadmin', 'pas',
1427+ reseller_admin=True)
1428+
1429+ req = Request.blank('/account/act/actadmin2',
1430+ headers={'X-Auth-User-Key': 'pas',
1431+ 'X-Auth-User-Admin': 'true'})
1432+ resp = self.controller.handle_add_user(req)
1433+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1434+
1435+ req = Request.blank('/account/act/actadmin2',
1436+ headers={'X-Auth-User-Key': 'pas',
1437+ 'X-Auth-User-Admin': 'true',
1438+ 'X-Auth-Admin-User': 'act:usr',
1439+ 'X-Auth-Admin-Key': 'pas'})
1440+ resp = self.controller.handle_add_user(req)
1441+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1442+
1443+ req = Request.blank('/account/act/actadmin2',
1444+ headers={'X-Auth-User-Key': 'pas',
1445+ 'X-Auth-User-Admin': 'true',
1446+ 'X-Auth-Admin-User': 'act2:actadmin',
1447+ 'X-Auth-Admin-Key': 'pas'})
1448+ resp = self.controller.handle_add_user(req)
1449+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1450+
1451+ req = Request.blank('/account/act/actadmin2',
1452+ headers={'X-Auth-User-Key': 'pas',
1453+ 'X-Auth-User-Admin': 'true',
1454+ 'X-Auth-Admin-User': 'act:actadmin',
1455+ 'X-Auth-Admin-Key': 'pas'})
1456+ resp = self.controller.handle_add_user(req)
1457+ self.assert_(resp.status_int // 100 == 2, resp.status_int)
1458+
1459+ req = Request.blank('/account/act/actadmin3',
1460+ headers={'X-Auth-User-Key': 'pas',
1461+ 'X-Auth-User-Admin': 'true',
1462+ 'X-Auth-Admin-User': 'act2:resadmin',
1463+ 'X-Auth-Admin-Key': 'pas'})
1464+ resp = self.controller.handle_add_user(req)
1465+ self.assert_(resp.status_int // 100 == 2, resp.status_int)
1466+
1467+ req = Request.blank('/account/act/actadmin4',
1468+ headers={'X-Auth-User-Key': 'pas',
1469+ 'X-Auth-User-Admin': 'true',
1470+ 'X-Auth-Admin-User': '.super_admin',
1471+ 'X-Auth-Admin-Key': 'testkey'})
1472+ resp = self.controller.handle_add_user(req)
1473+ self.assert_(resp.status_int // 100 == 2, resp.status_int)
1474+
1475+ def test_handle_add_user_create_normal_user(self):
1476+ auth_server.http_connect = fake_http_connect(201, 201)
1477+ self.controller.create_user('act', 'usr', 'pas')
1478+ self.controller.create_user('act', 'actadmin', 'pas', admin=True)
1479+ self.controller.create_user('act2', 'actadmin', 'pas', admin=True)
1480+ self.controller.create_user('act2', 'resadmin', 'pas',
1481+ reseller_admin=True)
1482+
1483+ req = Request.blank('/account/act/usr2',
1484+ headers={'X-Auth-User-Key': 'pas',
1485+ 'X-Auth-User-Admin': 'true'})
1486+ resp = self.controller.handle_add_user(req)
1487+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1488+
1489+ req = Request.blank('/account/act/usr2',
1490+ headers={'X-Auth-User-Key': 'pas',
1491+ 'X-Auth-User-Admin': 'true',
1492+ 'X-Auth-Admin-User': 'act:usr',
1493+ 'X-Auth-Admin-Key': 'pas'})
1494+ resp = self.controller.handle_add_user(req)
1495+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1496+
1497+ req = Request.blank('/account/act/usr2',
1498+ headers={'X-Auth-User-Key': 'pas',
1499+ 'X-Auth-User-Admin': 'true',
1500+ 'X-Auth-Admin-User': 'act2:actadmin',
1501+ 'X-Auth-Admin-Key': 'pas'})
1502+ resp = self.controller.handle_add_user(req)
1503+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1504+
1505+ req = Request.blank('/account/act/usr2',
1506+ headers={'X-Auth-User-Key': 'pas',
1507+ 'X-Auth-User-Admin': 'true',
1508+ 'X-Auth-Admin-User': 'act:actadmin',
1509+ 'X-Auth-Admin-Key': 'pas'})
1510+ resp = self.controller.handle_add_user(req)
1511+ self.assert_(resp.status_int // 100 == 2, resp.status_int)
1512+
1513+ req = Request.blank('/account/act/usr3',
1514+ headers={'X-Auth-User-Key': 'pas',
1515+ 'X-Auth-User-Admin': 'true',
1516+ 'X-Auth-Admin-User': 'act2:resadmin',
1517+ 'X-Auth-Admin-Key': 'pas'})
1518+ resp = self.controller.handle_add_user(req)
1519+ self.assert_(resp.status_int // 100 == 2, resp.status_int)
1520+
1521+ req = Request.blank('/account/act/usr4',
1522+ headers={'X-Auth-User-Key': 'pas',
1523+ 'X-Auth-User-Admin': 'true',
1524+ 'X-Auth-Admin-User': '.super_admin',
1525+ 'X-Auth-Admin-Key': 'testkey'})
1526+ resp = self.controller.handle_add_user(req)
1527+ self.assert_(resp.status_int // 100 == 2, resp.status_int)
1528+
1529+ def test_handle_account_recreate_permissions(self):
1530+ auth_server.http_connect = fake_http_connect(201, 201)
1531+ self.controller.create_user('act', 'usr', 'pas')
1532+ self.controller.create_user('act', 'actadmin', 'pas', admin=True)
1533+ self.controller.create_user('act', 'resadmin', 'pas',
1534+ reseller_admin=True)
1535+
1536+ req = Request.blank('/recreate_accounts',
1537+ headers={'X-Auth-User-Key': 'pas',
1538+ 'X-Auth-User-Admin': 'true'})
1539+ resp = self.controller.handle_account_recreate(req)
1540+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1541+
1542+ req = Request.blank('/recreate_accounts',
1543+ headers={'X-Auth-User-Key': 'pas',
1544+ 'X-Auth-User-Admin': 'true',
1545+ 'X-Auth-Admin-User': 'act:usr',
1546+ 'X-Auth-Admin-Key': 'pas'})
1547+ resp = self.controller.handle_account_recreate(req)
1548+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1549+
1550+ req = Request.blank('/recreate_accounts',
1551+ headers={'X-Auth-User-Key': 'pas',
1552+ 'X-Auth-User-Admin': 'true',
1553+ 'X-Auth-Admin-User': 'act:actadmin',
1554+ 'X-Auth-Admin-Key': 'pas'})
1555+ resp = self.controller.handle_account_recreate(req)
1556+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1557+
1558+ req = Request.blank('/recreate_accounts',
1559+ headers={'X-Auth-User-Key': 'pas',
1560+ 'X-Auth-User-Admin': 'true',
1561+ 'X-Auth-Admin-User': 'act:resadmin',
1562+ 'X-Auth-Admin-Key': 'pas'})
1563+ resp = self.controller.handle_account_recreate(req)
1564+ self.assert_(resp.status_int // 100 == 4, resp.status_int)
1565+
1566+ req = Request.blank('/recreate_accounts',
1567+ headers={'X-Auth-User-Key': 'pas',
1568+ 'X-Auth-User-Admin': 'true',
1569+ 'X-Auth-Admin-User': '.super_admin',
1570+ 'X-Auth-Admin-Key': 'testkey'})
1571+ resp = self.controller.handle_account_recreate(req)
1572+ self.assert_(resp.status_int // 100 == 2, resp.status_int)
1573+
1574
1575 if __name__ == '__main__':
1576 unittest.main()
1577
1578=== modified file 'test/unit/common/middleware/test_auth.py'
1579--- test/unit/common/middleware/test_auth.py 2010-09-09 17:24:25 +0000
1580+++ test/unit/common/middleware/test_auth.py 2010-09-12 00:25:58 +0000
1581@@ -289,6 +289,37 @@
1582 req.acl = '.r:.example.com'
1583 self.assertEquals(self.test_auth.authorize(req), None)
1584
1585+ def test_account_put_permissions(self):
1586+ req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
1587+ req.remote_user = 'act:usr,act'
1588+ resp = str(self.test_auth.authorize(req))
1589+ self.assert_(resp.startswith('403'), resp)
1590+
1591+ req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
1592+ req.remote_user = 'act:usr,act,AUTH_other'
1593+ resp = str(self.test_auth.authorize(req))
1594+ self.assert_(resp.startswith('403'), resp)
1595+
1596+ # Even PUTs to your own account as account admin should fail
1597+ req = Request.blank('/v1/AUTH_old', environ={'REQUEST_METHOD': 'PUT'})
1598+ req.remote_user = 'act:usr,act,AUTH_old'
1599+ resp = str(self.test_auth.authorize(req))
1600+ self.assert_(resp.startswith('403'), resp)
1601+
1602+ req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
1603+ req.remote_user = 'act:usr,act,.reseller_admin'
1604+ resp = self.test_auth.authorize(req)
1605+ self.assertEquals(resp, None)
1606+
1607+ # .super_admin is not something the middleware should ever see or care
1608+ # about
1609+ req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
1610+ req.remote_user = 'act:usr,act,.super_admin'
1611+ resp = self.test_auth.authorize(req)
1612+ resp = str(self.test_auth.authorize(req))
1613+ self.assert_(resp.startswith('403'), resp)
1614+
1615+
1616
1617 if __name__ == '__main__':
1618 unittest.main()
1619
1620=== modified file 'test/unit/proxy/test_server.py'
1621--- test/unit/proxy/test_server.py 2010-09-09 05:37:27 +0000
1622+++ test/unit/proxy/test_server.py 2010-09-12 00:25:58 +0000
1623@@ -2153,90 +2153,136 @@
1624 finally:
1625 self.app.object_chunk_size = orig_object_chunk_size
1626
1627+ def test_PUT(self):
1628+ with save_globals():
1629+ controller = proxy_server.AccountController(self.app, 'account')
1630+ def test_status_map(statuses, expected, **kwargs):
1631+ proxy_server.http_connect = \
1632+ fake_http_connect(*statuses, **kwargs)
1633+ self.app.memcache.store = {}
1634+ req = Request.blank('/a', {})
1635+ req.content_length = 0
1636+ self.app.update_request(req)
1637+ res = controller.PUT(req)
1638+ expected = str(expected)
1639+ self.assertEquals(res.status[:len(expected)], expected)
1640+ test_status_map((201, 201, 201), 201)
1641+ test_status_map((201, 201, 500), 201)
1642+ test_status_map((201, 500, 500), 503)
1643+ test_status_map((204, 500, 404), 503)
1644+
1645+ def test_PUT_max_account_name_length(self):
1646+ with save_globals():
1647+ controller = proxy_server.AccountController(self.app, '1'*256)
1648+ self.assert_status_map(controller.PUT, (201, 201, 201), 201)
1649+ controller = proxy_server.AccountController(self.app, '2'*257)
1650+ self.assert_status_map(controller.PUT, (201, 201, 201), 400)
1651+
1652+ def test_PUT_connect_exceptions(self):
1653+ with save_globals():
1654+ controller = proxy_server.AccountController(self.app, 'account')
1655+ self.assert_status_map(controller.PUT, (201, 201, -1), 201)
1656+ self.assert_status_map(controller.PUT, (201, -1, -1), 503)
1657+ self.assert_status_map(controller.PUT, (503, 503, -1), 503)
1658+
1659+ def test_PUT_metadata(self):
1660+ self.metadata_helper('PUT')
1661+
1662 def test_POST_metadata(self):
1663+ self.metadata_helper('POST')
1664+
1665+ def metadata_helper(self, method):
1666 for test_header, test_value in (
1667 ('X-Account-Meta-TestHeader', 'TestValue'),
1668 ('X-Account-Meta-TestHeader', '')):
1669 test_errors = []
1670 def test_connect(ipaddr, port, device, partition, method, path,
1671 headers=None, query_string=None):
1672- for k, v in headers.iteritems():
1673- if k.lower() == test_header.lower() and \
1674- v == test_value:
1675- break
1676- else:
1677- test_errors.append('%s: %s not in %s' %
1678- (test_header, test_value, headers))
1679+ if path == '/a':
1680+ for k, v in headers.iteritems():
1681+ if k.lower() == test_header.lower() and \
1682+ v == test_value:
1683+ break
1684+ else:
1685+ test_errors.append('%s: %s not in %s' %
1686+ (test_header, test_value, headers))
1687 with save_globals():
1688 controller = \
1689 proxy_server.AccountController(self.app, 'a')
1690 proxy_server.http_connect = fake_http_connect(201, 201, 201,
1691- give_connect=test_connect)
1692- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'},
1693+ give_connect=test_connect)
1694+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method},
1695 headers={test_header: test_value})
1696 self.app.update_request(req)
1697- res = controller.POST(req)
1698+ res = getattr(controller, method)(req)
1699 self.assertEquals(test_errors, [])
1700
1701+
1702+ def test_PUT_bad_metadata(self):
1703+ self.bad_metadata_helper('PUT')
1704+
1705 def test_POST_bad_metadata(self):
1706+ self.bad_metadata_helper('POST')
1707+
1708+ def bad_metadata_helper(self, method):
1709 with save_globals():
1710 controller = proxy_server.AccountController(self.app, 'a')
1711- proxy_server.http_connect = fake_http_connect(204, 204, 204)
1712- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'})
1713+ proxy_server.http_connect = fake_http_connect(200, 201, 201, 201)
1714+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method})
1715 self.app.update_request(req)
1716- resp = controller.POST(req)
1717- self.assertEquals(resp.status_int, 204)
1718+ resp = getattr(controller, method)(req)
1719+ self.assertEquals(resp.status_int, 201)
1720
1721- proxy_server.http_connect = fake_http_connect(204, 204, 204)
1722- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'},
1723+ proxy_server.http_connect = fake_http_connect(201, 201, 201)
1724+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method},
1725 headers={'X-Account-Meta-' +
1726 ('a' * MAX_META_NAME_LENGTH): 'v'})
1727 self.app.update_request(req)
1728- resp = controller.POST(req)
1729- self.assertEquals(resp.status_int, 204)
1730- proxy_server.http_connect = fake_http_connect(204, 204, 204)
1731- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'},
1732+ resp = getattr(controller, method)(req)
1733+ self.assertEquals(resp.status_int, 201)
1734+ proxy_server.http_connect = fake_http_connect(201, 201, 201)
1735+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method},
1736 headers={'X-Account-Meta-' +
1737 ('a' * (MAX_META_NAME_LENGTH + 1)): 'v'})
1738 self.app.update_request(req)
1739- resp = controller.POST(req)
1740+ resp = getattr(controller, method)(req)
1741 self.assertEquals(resp.status_int, 400)
1742
1743- proxy_server.http_connect = fake_http_connect(204, 204, 204)
1744- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'},
1745+ proxy_server.http_connect = fake_http_connect(201, 201, 201)
1746+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method},
1747 headers={'X-Account-Meta-Too-Long':
1748 'a' * MAX_META_VALUE_LENGTH})
1749 self.app.update_request(req)
1750- resp = controller.POST(req)
1751- self.assertEquals(resp.status_int, 204)
1752- proxy_server.http_connect = fake_http_connect(204, 204, 204)
1753- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'},
1754+ resp = getattr(controller, method)(req)
1755+ self.assertEquals(resp.status_int, 201)
1756+ proxy_server.http_connect = fake_http_connect(201, 201, 201)
1757+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method},
1758 headers={'X-Account-Meta-Too-Long':
1759 'a' * (MAX_META_VALUE_LENGTH + 1)})
1760 self.app.update_request(req)
1761- resp = controller.POST(req)
1762+ resp = getattr(controller, method)(req)
1763 self.assertEquals(resp.status_int, 400)
1764
1765- proxy_server.http_connect = fake_http_connect(204, 204, 204)
1766+ proxy_server.http_connect = fake_http_connect(201, 201, 201)
1767 headers = {}
1768 for x in xrange(MAX_META_COUNT):
1769 headers['X-Account-Meta-%d' % x] = 'v'
1770- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'},
1771+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method},
1772 headers=headers)
1773 self.app.update_request(req)
1774- resp = controller.POST(req)
1775- self.assertEquals(resp.status_int, 204)
1776- proxy_server.http_connect = fake_http_connect(204, 204, 204)
1777+ resp = getattr(controller, method)(req)
1778+ self.assertEquals(resp.status_int, 201)
1779+ proxy_server.http_connect = fake_http_connect(201, 201, 201)
1780 headers = {}
1781 for x in xrange(MAX_META_COUNT + 1):
1782 headers['X-Account-Meta-%d' % x] = 'v'
1783- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'},
1784+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method},
1785 headers=headers)
1786 self.app.update_request(req)
1787- resp = controller.POST(req)
1788+ resp = getattr(controller, method)(req)
1789 self.assertEquals(resp.status_int, 400)
1790
1791- proxy_server.http_connect = fake_http_connect(204, 204, 204)
1792+ proxy_server.http_connect = fake_http_connect(201, 201, 201)
1793 headers = {}
1794 header_value = 'a' * MAX_META_VALUE_LENGTH
1795 size = 0
1796@@ -2248,18 +2294,18 @@
1797 if MAX_META_OVERALL_SIZE - size > 1:
1798 headers['X-Account-Meta-a'] = \
1799 'a' * (MAX_META_OVERALL_SIZE - size - 1)
1800- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'},
1801+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method},
1802 headers=headers)
1803 self.app.update_request(req)
1804- resp = controller.POST(req)
1805- self.assertEquals(resp.status_int, 204)
1806- proxy_server.http_connect = fake_http_connect(204, 204, 204)
1807+ resp = getattr(controller, method)(req)
1808+ self.assertEquals(resp.status_int, 201)
1809+ proxy_server.http_connect = fake_http_connect(201, 201, 201)
1810 headers['X-Account-Meta-a'] = \
1811 'a' * (MAX_META_OVERALL_SIZE - size)
1812- req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'},
1813+ req = Request.blank('/a/c', environ={'REQUEST_METHOD': method},
1814 headers=headers)
1815 self.app.update_request(req)
1816- resp = controller.POST(req)
1817+ resp = getattr(controller, method)(req)
1818 self.assertEquals(resp.status_int, 400)
1819
1820