From cfcfdd3de1108db25e765c4e725a1a048494e8c7 Mon Sep 17 00:00:00 2001 From: gholt Date: Thu, 2 Sep 2010 21:50:16 -0700 Subject: [PATCH] Refactored auth and adding ACLs using repoze.what --- bin/swift-auth-create-account | 33 ++- doc/source/development_saio.rst | 12 + swift/auth/server.py | 144 ++++++---- swift/common/middleware/auth.py | 319 ++++++++++++++++----- swift/container/server.py | 9 +- swift/proxy/server.py | 157 ++++++++-- test/functional/tests.py | 16 +- test/functionalnosetests/swift_testing.py | 67 +++-- test/functionalnosetests/test_container.py | 224 ++++++++++++++- test/functionalnosetests/test_object.py | 90 ++++++ test/unit/auth/test_server.py | 44 +-- test/unit/common/middleware/test_auth.py | 119 ++++---- test/unit/container/test_server.py | 45 +++ test/unit/proxy/test_server.py | 177 ++++++------ 14 files changed, 1087 insertions(+), 369 deletions(-) create mode 100644 test/functionalnosetests/test_object.py diff --git a/bin/swift-auth-create-account b/bin/swift-auth-create-account index bab2d3daea..a96e175529 100755 --- a/bin/swift-auth-create-account +++ b/bin/swift-auth-create-account @@ -15,6 +15,7 @@ # limitations under the License. from ConfigParser import ConfigParser +from os.path import basename from sys import argv, exit from swift.common.bufferedhttp import http_connect_raw as http_connect @@ -22,11 +23,27 @@ from swift.common.bufferedhttp import http_connect_raw as http_connect if __name__ == '__main__': f = '/etc/swift/auth-server.conf' - if len(argv) == 5: - f = argv[4] - elif len(argv) != 4: - exit('Syntax: %s [conf_file]' % - argv[0]) + good = False + noaccess = False + if len(argv) == 6 and argv[4] == 'noaccess': + good = True + noaccess = True + f = argv[5] + elif len(argv) == 5: + good = True + if argv[4] == 'noaccess': + noaccess = True + else: + f = argv[4] + elif len(argv) == 4: + good = True + if not good: + exit(''' +Syntax: %s [noaccess] [conf_file] +The noaccess keyword will create a user with no access to the account; another +user for the account will have to add the user to the ACLs for a container to +grant some access. + '''.strip() % basename(argv[0])) new_account = argv[1] new_user = argv[2] new_password = argv[3] @@ -38,8 +55,10 @@ if __name__ == '__main__': port = int(conf.get('bind_port', 11000)) ssl = conf.get('cert_file') is not None path = '/account/%s/%s' % (new_account, new_user) - conn = http_connect(host, port, 'PUT', path, {'x-auth-key':new_password}, - ssl=ssl) + headers = {'X-Auth-Key': new_password} + if noaccess: + headers['X-User-No-Access'] = 'true' + conn = http_connect(host, port, 'PUT', path, headers, ssl=ssl) resp = conn.getresponse() if resp.status == 204: print resp.getheader('x-storage-url') diff --git a/doc/source/development_saio.rst b/doc/source/development_saio.rst index 1eaaec5bbe..30e8d8cfc5 100644 --- a/doc/source/development_saio.rst +++ b/doc/source/development_saio.rst @@ -530,16 +530,28 @@ good idea what to do on other environments. #. 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`` #. Check that you can GET account: ``curl -v -H 'X-Auth-Token: ' `` #. Check that `st` works: `st -A http://127.0.0.1:11000/v1.0 -U test:tester -K testing stat` + #. `swift-auth-create-account test2 tester2 testing2` + #. `swift-auth-create-account test tester3 testing3 noaccess` #. Create `/etc/swift/func_test.conf`:: auth_host = 127.0.0.1 auth_port = 11000 auth_ssl = no + # Primary functional test account account = test username = tester password = testing + # User on a second account + account2 = test2 + username2 = tester2 + password2 = testing2 + + # User on same account as first, but with noaccess + username3 = tester3 + password3 = testing3 + collate = C #. `cd ~/swift/trunk; ./.functests` diff --git a/swift/auth/server.py b/swift/auth/server.py index 5658790c88..db290a72c4 100644 --- a/swift/auth/server.py +++ b/swift/auth/server.py @@ -22,6 +22,7 @@ from time import gmtime, strftime, time from urllib import unquote, quote from uuid import uuid4 +import sqlite3 from webob import Request, Response from webob.exc import HTTPBadRequest, HTTPNoContent, HTTPUnauthorized, \ HTTPServiceUnavailable, HTTPNotFound @@ -58,10 +59,10 @@ class AuthController(object): * The user makes a ReST call to the Swift cluster using the url given with the token as the X-Auth-Token header. * The Swift cluster makes an ReST call to the auth server to validate the - token for the given account hash, caching the result for future requests - up to the expiration the auth server returns. - * The auth server validates the token / account hash given and returns the - expiration for the token. + token, caching the result for future requests up to the expiration the + auth server returns. + * The auth server validates the token given and returns the expiration for + the token. * The Swift cluster completes the user's request. Another use case is creating a new account: @@ -103,17 +104,33 @@ class AuthController(object): Ring(os.path.join(self.swift_dir, 'account.ring.gz')) self.db_file = os.path.join(self.swift_dir, 'auth.db') self.conn = get_db_connection(self.db_file, okay_to_create=True) + try: + self.conn.execute('SELECT noaccess FROM account LIMIT 1') + except sqlite3.OperationalError, err: + if str(err) == 'no such column: noaccess': + self.conn.execute( + 'ALTER TABLE account ADD COLUMN noaccess TEXT') self.conn.execute('''CREATE TABLE IF NOT EXISTS account ( account TEXT, url TEXT, cfaccount TEXT, - user TEXT, password TEXT)''') + user TEXT, password TEXT, noaccess TEXT)''') self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_account_account ON account (account)''') + try: + self.conn.execute('SELECT user FROM token LIMIT 1') + except sqlite3.OperationalError, err: + if str(err) == 'no such column: user': + self.conn.execute('DROP INDEX IF EXISTS ix_token_created') + self.conn.execute('DROP INDEX IF EXISTS ix_token_cfaccount') + self.conn.execute('DROP TABLE IF EXISTS token') self.conn.execute('''CREATE TABLE IF NOT EXISTS token ( - cfaccount TEXT, token TEXT, created FLOAT)''') - self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_cfaccount - ON token (cfaccount)''') + token TEXT, created FLOAT, + account TEXT, user TEXT, cfaccount TEXT)''') + self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_token + ON token (token)''') self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_created ON token (created)''') + self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_account + ON token (account)''') self.conn.commit() def add_storage_account(self, account_name=''): @@ -202,38 +219,36 @@ class AuthController(object): (time() - self.token_life,)) conn.commit() - def validate_token(self, token, account_hash): + def validate_token(self, token): """ Tests if the given token is a valid token :param token: The token to validate - :param account_hash: The account hash the token is being used with - :returns: TTL if valid, False otherwise + :returns: (TTL, account, user, cfaccount) if valid, False otherwise """ begin = time() self.purge_old_tokens() rv = False with self.get_conn() as conn: row = conn.execute(''' - SELECT created FROM token - WHERE cfaccount = ? AND token = ?''', - (account_hash, token)).fetchone() + SELECT created, account, user, cfaccount FROM token + WHERE token = ?''', + (token,)).fetchone() if row is not None: created = row[0] if time() - created >= self.token_life: conn.execute(''' - DELETE FROM token - WHERE cfaccount = ? AND token = ?''', - (account_hash, token)) + DELETE FROM token WHERE token = ?''', (token,)) conn.commit() else: - rv = self.token_life - (time() - created) - self.logger.info('validate_token(%s, %s, _, _) = %s [%.02f]' % - (repr(token), repr(account_hash), repr(rv), - time() - begin)) + rv = (self.token_life - (time() - created), row[1], row[2], + row[3]) + self.logger.info('validate_token(%s, _, _) = %s [%.02f]' % + (repr(token), repr(rv), time() - begin)) return rv - def create_account(self, new_account, new_user, new_password): + def create_account(self, new_account, new_user, new_password, + noaccess=False): """ Handles the create_account call for developers, used to request an account be created both on a Swift cluster and in the auth server @@ -251,28 +266,51 @@ class AuthController(object): :param new_account: The name for the new account :param new_user: The name for the new user :param new_password: The password for the new account + :param noaccess: If true, the user will be granted no access to the + account by default; another user will have to add the + user to the ACLs for containers to grant access. - :returns: False if the create fails, storage url if successful + :returns: False if the create fails, 'already exists' if the user + already exists, or storage url if successful """ begin = time() if not all((new_account, new_user, new_password)): return False - account_hash = self.add_storage_account() - if not account_hash: - self.logger.info( - 'FAILED create_account(%s, %s, _,) [%.02f]' % - (repr(new_account), repr(new_user), time() - begin)) - return False - url = self.default_cluster_url.rstrip('/') + '/' + account_hash with self.get_conn() as conn: + row = conn.execute( + 'SELECT url FROM account WHERE account = ? AND user = ?', + (new_account, new_user)).fetchone() + if row: + self.logger.info( + 'ALREADY EXISTS create_account(%s, %s, _, %s) [%.02f]' % + (repr(new_account), repr(new_user), repr(noaccess), + time() - begin)) + return 'already exists' + row = conn.execute( + 'SELECT url, cfaccount FROM account WHERE account = ?', + (new_account,)).fetchone() + if row: + url = row[0] + account_hash = row[1] + else: + account_hash = self.add_storage_account() + if not account_hash: + self.logger.info( + 'FAILED create_account(%s, %s, _, %s) [%.02f]' % + (repr(new_account), repr(new_user), repr(noaccess), + time() - begin)) + return False + url = self.default_cluster_url.rstrip('/') + '/' + account_hash conn.execute('''INSERT INTO account - (account, url, cfaccount, user, password) - VALUES (?, ?, ?, ?, ?)''', - (new_account, url, account_hash, new_user, new_password)) + (account, url, cfaccount, user, password, noaccess) + VALUES (?, ?, ?, ?, ?, ?)''', + (new_account, url, account_hash, new_user, new_password, + noaccess and 't' or '')) conn.commit() self.logger.info( - 'SUCCESS create_account(%s, %s, _) = %s [%.02f]' % - (repr(new_account), repr(new_user), repr(url), time() - begin)) + 'SUCCESS create_account(%s, %s, _, %s) = %s [%.02f]' % + (repr(new_account), repr(new_user), repr(noaccess), repr(url), + time() - begin)) return url def recreate_accounts(self): @@ -285,8 +323,8 @@ class AuthController(object): """ begin = time() with self.get_conn() as conn: - account_hashes = [r[0] for r in - conn.execute('SELECT cfaccount FROM account').fetchall()] + account_hashes = [r[0] for r in conn.execute( + 'SELECT distinct(cfaccount) FROM account').fetchall()] failures = [] for i, account_hash in enumerate(account_hashes): if not self.add_storage_account(account_hash): @@ -301,7 +339,7 @@ class AuthController(object): Hanles ReST request from Swift to validate tokens Valid URL paths: - * GET /token// + * GET /token/ If the HTTP equest returns with a 204, then the token is valid, and the TTL of the token will be available in the X-Auth-Ttl header. @@ -309,13 +347,14 @@ class AuthController(object): :param request: webob.Request object """ try: - _, account_hash, token = split_path(request.path, minsegs=3) + _, token = split_path(request.path, minsegs=2) except ValueError: return HTTPBadRequest() - ttl = self.validate_token(token, account_hash) - if not ttl: + validation = self.validate_token(token) + if not validation: return HTTPNotFound() - return HTTPNoContent(headers={'x-auth-ttl': ttl}) + return HTTPNoContent(headers={'X-Auth-TTL': validation[0], + 'X-Auth-User': ':'.join(validation[1:])}) def handle_account_create(self, request): """ @@ -339,7 +378,10 @@ class AuthController(object): if 'X-Auth-Key' not in request.headers: return HTTPBadRequest('X-Auth-Key is required') password = request.headers['x-auth-key'] - storage_url = self.create_account(account_name, user_name, password) + storage_url = self.create_account(account_name, user_name, password, + request.headers.get('x-user-no-access')) + if storage_url == 'already exists': + return HTTPBadRequest(storage_url) if not storage_url: return HTTPServiceUnavailable() return HTTPNoContent(headers={'x-storage-url': storage_url}) @@ -414,23 +456,25 @@ class AuthController(object): self.purge_old_tokens() with self.get_conn() as conn: row = conn.execute(''' - SELECT cfaccount, url FROM account + SELECT cfaccount, url, noaccess FROM account WHERE account = ? AND user = ? AND password = ?''', (account, user, password)).fetchone() if row is None: return HTTPUnauthorized() - cfaccount = row[0] + cfaccount = row[2] and '.none' or row[0] url = row[1] - row = conn.execute('SELECT token FROM token WHERE cfaccount = ?', - (cfaccount,)).fetchone() + row = conn.execute(''' + SELECT token FROM token WHERE account = ? AND user = ?''', + (account, user)).fetchone() if row: token = row[0] else: token = 'tk' + str(uuid4()) conn.execute(''' - INSERT INTO token (cfaccount, token, created) - VALUES (?, ?, ?)''', - (cfaccount, token, time())) + INSERT INTO token + (token, created, account, user, cfaccount) + VALUES (?, ?, ?, ?, ?)''', + (token, time(), account, user, cfaccount)) conn.commit() return HTTPNoContent(headers={'x-auth-token': token, 'x-storage-token': token, diff --git a/swift/common/middleware/auth.py b/swift/common/middleware/auth.py index eb920fad41..32800839ba 100644 --- a/swift/common/middleware/auth.py +++ b/swift/common/middleware/auth.py @@ -13,30 +13,135 @@ # See the License for the specific language governing permissions and # limitations under the License. -import time +from time import time -from webob.request import Request -from webob.exc import HTTPUnauthorized, HTTPPreconditionFailed from eventlet.timeout import Timeout +from repoze.what.adapters import BaseSourceAdapter +from repoze.what.middleware import setup_auth +from repoze.what.predicates import in_any_group, NotAuthorizedError +from webob.exc import HTTPForbidden, HTTPUnauthorized -from swift.common.utils import split_path from swift.common.bufferedhttp import http_connect_raw as http_connect -from swift.common.utils import get_logger, cache_from_env -from swift.common.memcached import MemcacheRing +from swift.common.utils import cache_from_env, split_path -class DevAuthMiddleware(object): - """ - Auth Middleware that uses the dev auth server - """ +class DevAuthorization(object): - def __init__(self, app, conf, memcache_client=None, logger=None): + def __init__(self, app, conf): self.app = app - self.memcache_client = memcache_client - if logger is None: - self.logger = get_logger(conf) + self.conf = conf + + def __call__(self, environ, start_response): + environ['swift.authorize'] = self.authorize + environ['swift.clean_acl'] = self.clean_acl + return self.app(environ, start_response) + + def authorize(self, req): + version, account, container, obj = split_path(req.path, 1, 4, True) + if not account: + return self.denied_response(req) + groups = [account] + acl = self.parse_acl(getattr(req, 'acl', None)) + if acl: + referrers, accounts, users = acl + if referrers: + parts = req.referer.split('//', 1) + allow = False + if len(parts) == 2: + rhost = parts[1].split('/', 1)[0].split(':', 1)[0].lower() + else: + rhost = 'unknown' + for mhost in referrers: + if mhost[0] == '-': + mhost = mhost[1:] + if mhost == rhost or \ + (mhost[0] == '.' and rhost.endswith(mhost)): + allow = False + elif mhost == 'any' or mhost == rhost or \ + (mhost[0] == '.' and rhost.endswith(mhost)): + allow = True + if allow: + return None + groups.extend(accounts) + groups.extend(users) + try: + in_any_group(*groups).check_authorization(req.environ) + except NotAuthorizedError: + return self.denied_response(req) + return None + + def denied_response(self, req): + if req.remote_user: + return HTTPForbidden(request=req) else: - self.logger = logger + return HTTPUnauthorized(request=req) + + def clean_acl(self, header_name, value): + values = [] + for raw_value in value.lower().split(','): + raw_value = raw_value.strip() + if raw_value: + if ':' in raw_value: + first, second = \ + (v.strip() for v in raw_value.split(':', 1)) + if not first: + raise ValueError('No value before colon in %s' % + repr(raw_value)) + if first == '.ref' and 'write' in header_name: + raise ValueError('Referrers not allowed in write ' + 'ACLs: %s' % repr(raw_value)) + if second: + if first == '.ref' and second[0] == '-': + second = second[1:].strip() + if not second: + raise ValueError('No value after referrer ' + 'deny designation in %s' % repr(raw_value)) + second = '-' + second + values.append('%s:%s' % (first, second)) + elif first == '.ref': + raise ValueError('No value after referrer designation ' + 'in %s' % repr(raw_value)) + else: + values.append(first) + else: + values.append(raw_value) + return ','.join(values) + + def parse_acl(self, acl_string): + if not acl_string: + return None + referrers = [] + accounts = [] + users = [] + for value in acl_string.split(','): + if value.startswith('.ref:'): + referrers.append(value[len('.ref:'):]) + elif ':' in value: + users.append(value) + else: + accounts.append(value) + return (referrers, accounts, users) + + +class DevIdentifier(object): + + def __init__(self, conf): + self.conf = conf + + def identify(self, env): + return {'token': + env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))} + + def remember(self, env, identity): + return [] + + def forget(self, env, identity): + return [] + + +class DevAuthenticator(object): + + def __init__(self, conf): self.conf = conf self.auth_host = conf.get('ip', '127.0.0.1') self.auth_port = int(conf.get('port', 11000)) @@ -44,69 +149,135 @@ class DevAuthMiddleware(object): conf.get('ssl', 'false').lower() in ('true', 'on', '1', 'yes') self.timeout = int(conf.get('node_timeout', 10)) - def __call__(self, env, start_response): - if self.memcache_client is None: - self.memcache_client = cache_from_env(env) - req = Request(env) - if 'x-storage-token' in req.headers and \ - 'x-auth-token' not in req.headers: - req.headers['x-auth-token'] = req.headers['x-storage-token'] - try: - version, account, container, obj = split_path(req.path, 1, 4, True) - except ValueError, e: - version = account = container = obj = None - if account is None: - return HTTPPreconditionFailed(request=req, body='Bad URL')( - env, start_response) - if not req.headers.get('x-auth-token'): - return HTTPPreconditionFailed(request=req, - body='Missing Auth Token')(env, start_response) - if not self.auth(account, req.headers['x-auth-token']): - return HTTPUnauthorized(request=req)(env, start_response) - - # If we get here, then things should be good. - return self.app(env, start_response) - - def auth(self, account, token): - """ - Dev authorization implmentation - - :param account: account name - :param token: auth token - - :returns: True if authorization is successful, False otherwise - """ - key = 'auth/%s/%s' % (account, token) - now = time.time() - cached_auth_data = self.memcache_client.get(key) + def authenticate(self, env, identity): + token = identity.get('token') + if not token: + return None + memcache_client = cache_from_env(env) + key = 'devauth/%s' % token + cached_auth_data = memcache_client.get(key) if cached_auth_data: - start, expiration = cached_auth_data - if now - start <= expiration: - return True - try: - with Timeout(self.timeout): - conn = http_connect(self.auth_host, self.auth_port, 'GET', - '/token/%s/%s' % (account, token), ssl=self.ssl) - resp = conn.getresponse() - resp.read() - conn.close() - if resp.status == 204: - validated = float(resp.getheader('x-auth-ttl')) - else: - validated = False - except: - self.logger.exception('ERROR with auth') - return False - if not validated: - return False - else: - val = (now, validated) - self.memcache_client.set(key, val, timeout=validated) - return True + start, expiration, user = cached_auth_data + if time() - start <= expiration: + return user + with Timeout(self.timeout): + conn = http_connect(self.auth_host, self.auth_port, 'GET', + '/token/%s' % token, ssl=self.ssl) + resp = conn.getresponse() + resp.read() + conn.close() + if resp.status == 204: + expiration = float(resp.getheader('x-auth-ttl')) + user = resp.getheader('x-auth-user') + memcache_client.set(key, (time(), expiration, user), + timeout=expiration) + return user + return None + + +class DevChallenger(object): + + def __init__(self, conf): + self.conf = conf + + def challenge(self, env, status, app_headers, forget_headers): + def no_challenge(env, start_response): + start_response(str(status), []) + return [] + return no_challenge + + +class DevGroupSourceAdapter(BaseSourceAdapter): + + def __init__(self, *args, **kwargs): + super(DevGroupSourceAdapter, self).__init__(*args, **kwargs) + self.sections = {} + + def _get_all_sections(self): + return self.sections + + def _get_section_items(self, section): + return self.sections[section] + + def _find_sections(self, credentials): + creds = credentials['repoze.what.userid'].split(':') + if len(creds) != 3: + return set() + rv = set([creds[0], ':'.join(creds[:2]), creds[2]]) + return rv + + def _include_items(self, section, items): + self.sections[section] |= items + + def _exclude_items(self, section, items): + for item in items: + self.sections[section].remove(item) + + def _item_is_included(self, section, item): + return item in self.sections[section] + + def _create_section(self, section): + self.sections[section] = set() + + def _edit_section(self, section, new_section): + self.sections[new_section] = self.sections[section] + del self.sections[section] + + def _delete_section(self, section): + del self.sections[section] + + def _section_exists(self, section): + return self.sections.has_key(section) + + +class DevPermissionSourceAdapter(BaseSourceAdapter): + + def __init__(self, *args, **kwargs): + super(DevPermissionSourceAdapter, self).__init__(*args, **kwargs) + self.sections = {} + + def _get_all_sections(self): + return self.sections + + def _get_section_items(self, section): + return self.sections[section] + + def _find_sections(self, group_name): + return set([n for (n, p) in self.sections.items() + if group_name in p]) + + def _include_items(self, section, items): + self.sections[section] |= items + + def _exclude_items(self, section, items): + for item in items: + self.sections[section].remove(item) + + def _item_is_included(self, section, item): + return item in self.sections[section] + + def _create_section(self, section): + self.sections[section] = set() + + def _edit_section(self, section, new_section): + self.sections[new_section] = self.sections[section] + del self.sections[section] + + def _delete_section(self, section): + del self.sections[section] + + def _section_exists(self, section): + return self.sections.has_key(section) + def filter_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): - return DevAuthMiddleware(app, conf) + return setup_auth(DevAuthorization(app, conf), + group_adapters={'all_groups': DevGroupSourceAdapter()}, + permission_adapters={'all_perms': DevPermissionSourceAdapter()}, + identifiers=[('devauth', DevIdentifier(conf))], + authenticators=[('devauth', DevAuthenticator(conf))], + challengers=[('devauth', DevChallenger(conf))]) return auth_filter diff --git a/swift/container/server.py b/swift/container/server.py index 23a9d0b71c..63a44fcabc 100644 --- a/swift/container/server.py +++ b/swift/container/server.py @@ -44,6 +44,9 @@ DATADIR = 'containers' class ContainerController(object): """WSGI Controller for the container server.""" + # Ensure these are all lowercase + save_headers = ['x-container-read', 'x-container-write'] + def __init__(self, conf): self.logger = get_logger(conf) self.root = conf.get('devices', '/srv/node/') @@ -192,7 +195,8 @@ class ContainerController(object): metadata = {} metadata.update((key, (value, timestamp)) for key, value in req.headers.iteritems() - if key.lower().startswith('x-container-meta-')) + if key.lower() in self.save_headers or + key.lower().startswith('x-container-meta-')) if metadata: broker.update_metadata(metadata) resp = self.account_update(req, account, container, broker) @@ -373,7 +377,8 @@ class ContainerController(object): metadata = {} metadata.update((key, (value, timestamp)) for key, value in req.headers.iteritems() - if key.lower().startswith('x-container-meta-')) + if key.lower() in self.save_headers or + key.lower().startswith('x-container-meta-')) if metadata: broker.update_metadata(metadata) return HTTPNoContent(request=req) diff --git a/swift/proxy/server.py b/swift/proxy/server.py index 140bc5dbc4..aea7b618bd 100644 --- a/swift/proxy/server.py +++ b/swift/proxy/server.py @@ -17,6 +17,7 @@ from __future__ import with_statement import mimetypes import os import time +import traceback from ConfigParser import ConfigParser from urllib import unquote, quote import uuid @@ -73,6 +74,22 @@ def public(func): return wrapped +def delay_denial(func): + """ + Decorator to declare which methods should have any swift.authorize call + delayed. This is so the method can load the Request object up with + additional information that may be needed by the authorization system. + + :param func: function to delay authorization on + """ + func.delay_denial = True + + @functools.wraps(func) + def wrapped(*a, **kw): + return func(*a, **kw) + return wrapped + + class Controller(object): """Base WSGI controller class for the proxy""" @@ -206,19 +223,28 @@ class Controller(object): :param account: account name for the container :param container: container name to look up - :returns: tuple of (container partition, container nodes) or - (None, None) if the container does not exist + :returns: tuple of (container partition, container nodes, container + read acl, container write acl) or (None, None, None, None) if + the container does not exist """ partition, nodes = self.app.container_ring.get_nodes( account, container) path = '/%s/%s' % (account, container) cache_key = 'container%s' % path + # Older memcache values (should be treated as if they aren't there): # 0 = no responses, 200 = found, 404 = not found, -1 = mixed responses - if self.app.memcache.get(cache_key) == 200: - return partition, nodes + # Newer memcache values: + # [older status value from above, read acl, write acl] + cache_value = self.app.memcache.get(cache_key) + if hasattr(cache_value, '__iter__'): + status, read_acl, write_acl = cache_value + if status == 200: + return partition, nodes, read_acl, write_acl if not self.account_info(account)[1]: - return (None, None) + return (None, None, None, None) result_code = 0 + read_acl = None + write_acl = None attempts_left = self.app.container_ring.replica_count headers = {'x-cf-trans-id': self.trans_id} for node in self.iter_nodes(partition, nodes, self.app.container_ring): @@ -233,6 +259,8 @@ class Controller(object): body = resp.read() if 200 <= resp.status <= 299: result_code = 200 + read_acl = resp.getheader('x-container-read') + write_acl = resp.getheader('x-container-write') break elif resp.status == 404: result_code = 404 if not result_code else -1 @@ -251,10 +279,11 @@ class Controller(object): cache_timeout = self.app.recheck_container_existence else: cache_timeout = self.app.recheck_container_existence * 0.1 - self.app.memcache.set(cache_key, result_code, timeout=cache_timeout) + self.app.memcache.set(cache_key, (result_code, read_acl, write_acl), + timeout=cache_timeout) if result_code == 200: - return partition, nodes - return (None, None) + return partition, nodes, read_acl, write_acl + return (None, None, None, None) def iter_nodes(self, partition, nodes, ring): """ @@ -474,6 +503,12 @@ class ObjectController(Controller): def GETorHEAD(self, req): """Handle HTTP GET or HEAD requests.""" + if 'swift.authorize' in req.environ: + req.acl = \ + self.container_info(self.account_name, self.container_name)[2] + aresp = req.environ['swift.authorize'](req) + if aresp: + return aresp partition, nodes = self.app.object_ring.get_nodes( self.account_name, self.container_name, self.object_name) return self.GETorHEAD_base(req, 'Object', partition, @@ -481,13 +516,30 @@ class ObjectController(Controller): req.path_info, self.app.object_ring.replica_count) @public + @delay_denial + def GET(self, req): + """Handler for HTTP GET requests.""" + return self.GETorHEAD(req) + + @public + @delay_denial + def HEAD(self, req): + """Handler for HTTP HEAD requests.""" + return self.GETorHEAD(req) + + @public + @delay_denial def POST(self, req): """HTTP POST request handler.""" error_response = check_metadata(req, 'object') if error_response: return error_response - container_partition, containers = \ + container_partition, containers, _, req.acl = \ self.container_info(self.account_name, self.container_name) + if 'swift.authorize' in req.environ: + aresp = req.environ['swift.authorize'](req) + if aresp: + return aresp if not containers: return HTTPNotFound(request=req) containers = self.get_update_nodes(container_partition, containers, @@ -521,10 +573,15 @@ class ObjectController(Controller): bodies, 'Object POST') @public + @delay_denial def PUT(self, req): """HTTP PUT request handler.""" - container_partition, containers = \ + container_partition, containers, _, req.acl = \ self.container_info(self.account_name, self.container_name) + if 'swift.authorize' in req.environ: + aresp = req.environ['swift.authorize'](req) + if aresp: + return aresp if not containers: return HTTPNotFound(request=req) containers = self.get_update_nodes(container_partition, containers, @@ -701,10 +758,15 @@ class ObjectController(Controller): return resp @public + @delay_denial def DELETE(self, req): """HTTP DELETE request handler.""" - container_partition, containers = \ + container_partition, containers, _, req.acl = \ self.container_info(self.account_name, self.container_name) + if 'swift.authorize' in req.environ: + aresp = req.environ['swift.authorize'](req) + if aresp: + return aresp if not containers: return HTTPNotFound(request=req) containers = self.get_update_nodes(container_partition, containers, @@ -771,11 +833,25 @@ class ObjectController(Controller): class ContainerController(Controller): """WSGI controller for container requests""" + # Ensure these are all lowercase + pass_through_headers = ['x-container-read', 'x-container-write'] + def __init__(self, app, account_name, container_name, **kwargs): Controller.__init__(self, app) self.account_name = unquote(account_name) self.container_name = unquote(container_name) + def clean_acls(self, req): + if 'swift.clean_acl' in req.environ: + for header in ('x-container-read', 'x-container-write'): + if header in req.headers: + try: + req.headers[header] = \ + req.environ['swift.clean_acl'](header, + req.headers[header]) + except ValueError, err: + return HTTPBadRequest(request=req, body=str(err)) + def GETorHEAD(self, req): """Handler for HTTP GET/HEAD requests.""" if not self.account_info(self.account_name)[1]: @@ -784,8 +860,25 @@ class ContainerController(Controller): self.account_name, self.container_name) resp = self.GETorHEAD_base(req, 'Container', part, nodes, req.path_info, self.app.container_ring.replica_count) + if 'swift.authorize' in req.environ: + req.acl = resp.headers.get('x-container-read') + aresp = req.environ['swift.authorize'](req) + if aresp: + return aresp return resp + @public + @delay_denial + def GET(self, req): + """Handler for HTTP GET requests.""" + return self.GETorHEAD(req) + + @public + @delay_denial + def HEAD(self, req): + """Handler for HTTP HEAD requests.""" + return self.GETorHEAD(req) + @public def PUT(self, req): """HTTP PUT request handler.""" @@ -806,8 +899,10 @@ class ContainerController(Controller): self.account_name, self.container_name) headers = {'X-Timestamp': normalize_timestamp(time.time()), 'x-cf-trans-id': self.trans_id} + self.clean_acls(req) headers.update(value for value in req.headers.iteritems() - if value[0].lower().startswith('x-container-meta-')) + if value[0].lower() in self.pass_through_headers or + value[0].lower().startswith('x-container-meta-')) statuses = [] reasons = [] bodies = [] @@ -863,8 +958,10 @@ class ContainerController(Controller): self.account_name, self.container_name) headers = {'X-Timestamp': normalize_timestamp(time.time()), 'x-cf-trans-id': self.trans_id} + self.clean_acls(req) headers.update(value for value in req.headers.iteritems() - if value[0].lower().startswith('x-container-meta-')) + if value[0].lower() in self.pass_through_headers or + value[0].lower().startswith('x-container-meta-')) statuses = [] reasons = [] bodies = [] @@ -1118,7 +1215,8 @@ class BaseApplication(object): self.posthooklogger(env, req) return response except: - print "EXCEPTION IN __call__: %s" % env + print "EXCEPTION IN __call__: %s: %s" % \ + (traceback.format_exc(), env) start_response('500 Server Error', [('Content-Type', 'text/plain')]) return ['Internal server error.\n'] @@ -1160,12 +1258,28 @@ class BaseApplication(object): controller.trans_id = req.headers.get('x-cf-trans-id', '-') try: handler = getattr(controller, req.method) - if getattr(handler, 'publicly_accessible'): - if path_parts['version']: - req.path_info_pop() - return handler(req) + if not getattr(handler, 'publicly_accessible'): + handler = None except AttributeError: + handler = None + if not handler: return HTTPMethodNotAllowed(request=req) + if path_parts['version']: + req.path_info_pop() + if 'swift.authorize' in req.environ: + # We call authorize before the handler, always. If authorized, + # we remove the swift.authorize hook so isn't ever called + # again. If not authorized, we return the denial unless the + # controller's method indicates it'd like to gather more + # information and try again later. + resp = req.environ['swift.authorize'](req) + if resp: + if not getattr(handler, 'delay_denial', None) and \ + 'swift.authorize' in req.environ: + return resp + else: + del req.environ['swift.authorize'] + return handler(req) except Exception: self.logger.exception('ERROR Unhandled exception in request') return HTTPServerError(request=req) @@ -1187,7 +1301,9 @@ class Application(BaseApplication): return req.response def posthooklogger(self, env, req): - response = req.response + response = getattr(req, 'response', None) + if not response: + return trans_time = '%.4f' % (time.time() - req.start_time) the_request = quote(unquote(req.path)) if req.query_string: @@ -1215,7 +1331,8 @@ class Application(BaseApplication): status_int, req.referer or '-', req.user_agent or '-', - req.headers.get('x-auth-token', '-'), + '%s:%s' % (req.remote_user or '', + req.headers.get('x-auth-token', '-')), getattr(req, 'bytes_transferred', 0) or '-', getattr(response, 'bytes_transferred', 0) or '-', req.headers.get('etag', '-'), diff --git a/test/functional/tests.py b/test/functional/tests.py index 3a1f35fc36..ae396f3fb0 100644 --- a/test/functional/tests.py +++ b/test/functional/tests.py @@ -106,9 +106,12 @@ class Base(unittest.TestCase): self.assert_(response_body == body, 'Body returned: %s' % (response_body)) - def assert_status(self, status): - self.assert_(self.env.conn.response.status == status, - 'Status returned: %d' % (self.env.conn.response.status)) + def assert_status(self, status_or_statuses): + self.assert_(self.env.conn.response.status == status_or_statuses or + (hasattr(status_or_statuses, '__iter__') and + self.env.conn.response.status in status_or_statuses), + 'Status returned: %d Expected: %s' % + (self.env.conn.response.status, status_or_statuses)) class Base2(object): def setUp(self): @@ -148,11 +151,11 @@ class TestAccount(Base): def testNoAuthToken(self): self.assertRaises(ResponseError, self.env.account.info, cfg={'no_auth_token':True}) - self.assert_status(412) + self.assert_status([401, 412]) self.assertRaises(ResponseError, self.env.account.containers, cfg={'no_auth_token':True}) - self.assert_status(412) + self.assert_status([401, 412]) def testInvalidUTF8Path(self): invalid_utf8 = Utils.create_utf8_name()[::-1] @@ -1123,7 +1126,8 @@ class TestFile(Base): self.assert_status(400) # bad request types - for req in ('LICK', 'GETorHEAD_base', 'container_info', 'best_response'): + #for req in ('LICK', 'GETorHEAD_base', 'container_info', 'best_response'): + for req in ('LICK', 'GETorHEAD_base'): self.env.account.conn.make_request(req) self.assert_status(405) diff --git a/test/functionalnosetests/swift_testing.py b/test/functionalnosetests/swift_testing.py index 1c805f854d..8bd46b462b 100644 --- a/test/functionalnosetests/swift_testing.py +++ b/test/functionalnosetests/swift_testing.py @@ -10,11 +10,11 @@ from swift.common.client import get_auth, http_connection swift_test_auth = os.environ.get('SWIFT_TEST_AUTH') -swift_test_user = os.environ.get('SWIFT_TEST_USER') -swift_test_key = os.environ.get('SWIFT_TEST_KEY') +swift_test_user = [os.environ.get('SWIFT_TEST_USER'), None, None] +swift_test_key = [os.environ.get('SWIFT_TEST_KEY'), None, None] # If no environment set, fall back to old school conf file -if not all([swift_test_auth, swift_test_user, swift_test_key]): +if not all([swift_test_auth, swift_test_user[0], swift_test_key[0]]): conf = ConfigParser() class Sectionizer(object): def __init__(self, fp): @@ -32,16 +32,36 @@ if not all([swift_test_auth, swift_test_user, swift_test_key]): if conf.get('auth_ssl', 'no').lower() in ('yes', 'true', 'on', '1'): swift_test_auth = 'https' swift_test_auth += '://%(auth_host)s:%(auth_port)s/v1.0' % conf - swift_test_user = '%(account)s:%(username)s' % conf - swift_test_key = conf['password'] + swift_test_user[0] = '%(account)s:%(username)s' % conf + swift_test_key[0] = conf['password'] + try: + swift_test_user[1] = '%(account2)s:%(username2)s' % conf + swift_test_key[1] = conf['password2'] + except KeyError, err: + pass # old conf, no second account tests can be run + try: + swift_test_user[2] = '%(account)s:%(username3)s' % conf + swift_test_key[2] = conf['password3'] + except KeyError, err: + pass # old conf, no third account tests can be run except IOError, err: if err.errno != errno.ENOENT: raise -skip = not all([swift_test_auth, swift_test_user, swift_test_key]) +skip = not all([swift_test_auth, swift_test_user[0], swift_test_key[0]]) if skip: print >>sys.stderr, 'SKIPPING FUNCTIONAL TESTS DUE TO NO CONFIG' +skip2 = not all([not skip, swift_test_user[1], swift_test_key[1]]) +if not skip and skip2: + print >>sys.stderr, \ + 'SKIPPING SECOND ACCOUNT FUNCTIONAL TESTS DUE TO NO CONFIG FOR THEM' + +skip3 = not all([not skip, swift_test_user[2], swift_test_key[2]]) +if not skip and skip3: + print >>sys.stderr, \ + 'SKIPPING THIRD ACCOUNT FUNCTIONAL TESTS DUE TO NO CONFIG FOR THEM' + class AuthError(Exception): pass @@ -51,29 +71,44 @@ class InternalServerError(Exception): pass -url = token = parsed = conn = None +url = [None, None, None] +token = [None, None, None] +parsed = [None, None, None] +conn = [None, None, None] def retry(func, *args, **kwargs): + """ + You can use the kwargs to override the 'retries' (default: 5) and + 'use_account' (default: 1). + """ global url, token, parsed, conn retries = kwargs.get('retries', 5) + use_account = 1 + if 'use_account' in kwargs: + use_account = kwargs['use_account'] + del kwargs['use_account'] + use_account -= 1 attempts = 0 backoff = 1 while attempts <= retries: attempts += 1 try: - if not url or not token: - url, token = \ - get_auth(swift_test_auth, swift_test_user, swift_test_key) - parsed = conn = None - if not parsed or not conn: - parsed, conn = http_connection(url) - return func(url, token, parsed, conn, *args, **kwargs) + if not url[use_account] or not token[use_account]: + url[use_account], token[use_account] = \ + get_auth(swift_test_auth, swift_test_user[use_account], + swift_test_key[use_account]) + parsed[use_account] = conn[use_account] = None + if not parsed[use_account] or not conn[use_account]: + parsed[use_account], conn[use_account] = \ + http_connection(url[use_account]) + return func(url[use_account], token[use_account], + parsed[use_account], conn[use_account], *args, **kwargs) except (socket.error, HTTPException): if attempts > retries: raise - parsed = conn = None + parsed[use_account] = conn[use_account] = None except AuthError, err: - url = token = None + url[use_account] = token[use_account] = None continue except InternalServerError, err: pass diff --git a/test/functionalnosetests/test_container.py b/test/functionalnosetests/test_container.py index 9c36b460b8..15bee7fbdb 100755 --- a/test/functionalnosetests/test_container.py +++ b/test/functionalnosetests/test_container.py @@ -1,12 +1,14 @@ #!/usr/bin/python +import json import unittest from uuid import uuid4 from swift.common.constraints import MAX_META_COUNT, MAX_META_NAME_LENGTH, \ MAX_META_OVERALL_SIZE, MAX_META_VALUE_LENGTH -from swift_testing import check_response, retry, skip +from swift_testing import check_response, retry, skip, skip2, skip3, \ + swift_test_user class TestContainer(unittest.TestCase): @@ -26,6 +28,26 @@ class TestContainer(unittest.TestCase): def tearDown(self): if skip: return + def get(url, token, parsed, conn): + conn.request('GET', parsed.path + '/' + self.name + '?format=json', + '', {'X-Auth-Token': token}) + return check_response(conn) + def delete(url, token, parsed, conn, obj): + conn.request('DELETE', + '/'.join([parsed.path, self.name, obj['name']]), '', + {'X-Auth-Token': token}) + return check_response(conn) + while True: + resp = retry(get) + body = resp.read() + self.assert_(resp.status // 100 == 2, resp.status) + objs = json.loads(body) + if not objs: + break + for obj in objs: + resp = retry(delete, obj) + resp.read() + self.assertEquals(resp.status, 204) def delete(url, token, parsed, conn): conn.request('DELETE', parsed.path + '/' + self.name, '', {'X-Auth-Token': token}) @@ -297,6 +319,206 @@ class TestContainer(unittest.TestCase): resp.read() self.assertEquals(resp.status, 400) + def test_public_container(self): + if skip: + return + def get(url, token, parsed, conn): + conn.request('GET', parsed.path + '/' + self.name) + return check_response(conn) + try: + resp = retry(get) + raise Exception('Should not have been able to GET') + except Exception, err: + self.assert_(str(err).startswith('No result after '), err) + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token, + 'X-Container-Read': '.ref:any'}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + resp = retry(get) + resp.read() + self.assertEquals(resp.status, 204) + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token, 'X-Container-Read': ''}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + try: + resp = retry(get) + raise Exception('Should not have been able to GET') + except Exception, err: + self.assert_(str(err).startswith('No result after '), err) + + def test_cross_account_container(self): + if skip or skip2: + return + # Obtain the first account's string + first_account = ['unknown'] + def get1(url, token, parsed, conn): + first_account[0] = parsed.path + conn.request('HEAD', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(get1) + resp.read() + # Ensure we can't access the container with the second account + def get2(url, token, parsed, conn): + conn.request('GET', first_account[0] + '/' + self.name, '', + {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(get2, use_account=2) + resp.read() + self.assertEquals(resp.status, 403) + # Make the container accessible by the second account + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token, + 'X-Container-Read': 'test2', + 'X-Container-Write': 'test2'}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + # Ensure we can now use the container with the second account + resp = retry(get2, use_account=2) + resp.read() + self.assertEquals(resp.status, 204) + # Make the container private again + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token, 'X-Container-Read': '', + 'X-Container-Write': ''}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + # Ensure we can't access the container with the second account again + resp = retry(get2, use_account=2) + resp.read() + self.assertEquals(resp.status, 403) + + def test_cross_account_public_container(self): + if skip or skip2: + return + # Obtain the first account's string + first_account = ['unknown'] + def get1(url, token, parsed, conn): + first_account[0] = parsed.path + conn.request('HEAD', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(get1) + resp.read() + # Ensure we can't access the container with the second account + def get2(url, token, parsed, conn): + conn.request('GET', first_account[0] + '/' + self.name, '', + {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(get2, use_account=2) + resp.read() + self.assertEquals(resp.status, 403) + # Make the container completely public + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token, + 'X-Container-Read': '.ref:any'}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + # Ensure we can now read the container with the second account + resp = retry(get2, use_account=2) + resp.read() + self.assertEquals(resp.status, 204) + # But we shouldn't be able to write with the second account + def put2(url, token, parsed, conn): + conn.request('PUT', first_account[0] + '/' + self.name + '/object', + 'test object', {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(put2, use_account=2) + resp.read() + self.assertEquals(resp.status, 403) + # Now make the container also writeable by the second account + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token, + 'X-Container-Write': 'test2'}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + # Ensure we can still read the container with the second account + resp = retry(get2, use_account=2) + resp.read() + self.assertEquals(resp.status, 204) + # And that we can now write with the second account + resp = retry(put2, use_account=2) + resp.read() + self.assertEquals(resp.status, 201) + + def test_noaccess_user(self): + if skip or skip3: + return + # Obtain the first account's string + first_account = ['unknown'] + def get1(url, token, parsed, conn): + first_account[0] = parsed.path + conn.request('HEAD', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(get1) + resp.read() + # Ensure we can't access the container with the third account + def get3(url, token, parsed, conn): + conn.request('GET', first_account[0] + '/' + self.name, '', + {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(get3, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + # Make the container accessible by the third account + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token, 'X-Container-Read': swift_test_user[2]}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + # Ensure we can now read the container with the third account + resp = retry(get3, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + # But we shouldn't be able to write with the third account + def put3(url, token, parsed, conn): + conn.request('PUT', first_account[0] + '/' + self.name + '/object', + 'test object', {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(put3, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + # Now make the container also writeable by the third account + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.name, '', + {'X-Auth-Token': token, + 'X-Container-Write': swift_test_user[2]}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + # Ensure we can still read the container with the third account + resp = retry(get3, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + # And that we can now write with the third account + resp = retry(put3, use_account=3) + resp.read() + self.assertEquals(resp.status, 201) + if __name__ == '__main__': unittest.main() diff --git a/test/functionalnosetests/test_object.py b/test/functionalnosetests/test_object.py new file mode 100644 index 0000000000..256068d766 --- /dev/null +++ b/test/functionalnosetests/test_object.py @@ -0,0 +1,90 @@ +#!/usr/bin/python + +import unittest +from uuid import uuid4 + +from swift.common.constraints import MAX_META_COUNT, MAX_META_NAME_LENGTH, \ + MAX_META_OVERALL_SIZE, MAX_META_VALUE_LENGTH + +from swift_testing import check_response, retry, skip + + +class TestObject(unittest.TestCase): + + def setUp(self): + if skip: + return + self.container = uuid4().hex + def put(url, token, parsed, conn): + conn.request('PUT', parsed.path + '/' + self.container, '', + {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(put) + resp.read() + self.assertEquals(resp.status, 201) + self.obj = uuid4().hex + def put(url, token, parsed, conn): + conn.request('PUT', '%s/%s/%s' % (parsed.path, self.container, + self.obj), 'test', {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(put) + resp.read() + self.assertEquals(resp.status, 201) + + def tearDown(self): + if skip: + return + def delete(url, token, parsed, conn): + conn.request('DELETE', '%s/%s/%s' % (parsed.path, self.container, + self.obj), '', {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(delete) + resp.read() + self.assertEquals(resp.status, 204) + def delete(url, token, parsed, conn): + conn.request('DELETE', parsed.path + '/' + self.container, '', + {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(delete) + resp.read() + self.assertEquals(resp.status, 204) + + def test_public_object(self): + if skip: + return + def get(url, token, parsed, conn): + conn.request('GET', + '%s/%s/%s' % (parsed.path, self.container, self.obj)) + return check_response(conn) + try: + resp = retry(get) + raise Exception('Should not have been able to GET') + except Exception, err: + self.assert_(str(err).startswith('No result after ')) + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.container, '', + {'X-Auth-Token': token, + 'X-Container-Read': '.ref:any'}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + resp = retry(get) + resp.read() + self.assertEquals(resp.status, 200) + def post(url, token, parsed, conn): + conn.request('POST', parsed.path + '/' + self.container, '', + {'X-Auth-Token': token, 'X-Container-Read': ''}) + return check_response(conn) + resp = retry(post) + resp.read() + self.assertEquals(resp.status, 204) + try: + resp = retry(get) + raise Exception('Should not have been able to GET') + except Exception, err: + self.assert_(str(err).startswith('No result after ')) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/unit/auth/test_server.py b/test/unit/auth/test_server.py index 31495401f2..cdaffc33e5 100644 --- a/test/unit/auth/test_server.py +++ b/test/unit/auth/test_server.py @@ -113,20 +113,7 @@ class TestAuthServer(unittest.TestCase): headers={'X-Storage-User': 'tester', 'X-Storage-Pass': 'testing'})) token = res.headers['x-storage-token'] - self.assertEquals(self.controller.validate_token(token + 'bad', - cfaccount), False) - - def test_validate_token_non_existant_cfaccount(self): - auth_server.http_connect = fake_http_connect(201, 201, 201) - cfaccount = self.controller.create_account( - 'test', 'tester', 'testing').split('/')[-1] - res = self.controller.handle_auth(Request.blank('/v1/test/auth', - environ={'REQUEST_METHOD': 'GET'}, - headers={'X-Storage-User': 'tester', - 'X-Storage-Pass': 'testing'})) - token = res.headers['x-storage-token'] - self.assertEquals(self.controller.validate_token(token, - cfaccount + 'bad'), False) + self.assertEquals(self.controller.validate_token(token + 'bad'), False) def test_validate_token_good(self): auth_server.http_connect = fake_http_connect(201, 201, 201) @@ -137,7 +124,7 @@ class TestAuthServer(unittest.TestCase): headers={'X-Storage-User': 'tester', 'X-Storage-Pass': 'testing'})) token = res.headers['x-storage-token'] - ttl = self.controller.validate_token(token, cfaccount) + ttl = self.controller.validate_token(token) self.assert_(ttl > 0, repr(ttl)) def test_validate_token_expired(self): @@ -152,12 +139,10 @@ class TestAuthServer(unittest.TestCase): headers={'X-Storage-User': 'tester', 'X-Storage-Pass': 'testing'})) token = res.headers['x-storage-token'] - ttl = self.controller.validate_token( - token, cfaccount) + ttl = self.controller.validate_token(token) self.assert_(ttl > 0, repr(ttl)) auth_server.time = lambda: 1 + self.controller.token_life - self.assertEquals(self.controller.validate_token( - token, cfaccount), False) + self.assertEquals(self.controller.validate_token(token), False) finally: auth_server.time = orig_time @@ -244,7 +229,7 @@ class TestAuthServer(unittest.TestCase): rv = self.controller.recreate_accounts() self.assertEquals(rv.split()[0], '4', repr(rv)) failed = rv.split('[', 1)[-1][:-1].split(', ') - self.assertEquals(failed, [repr(a) for a in cfaccounts]) + self.assertEquals(set(failed), set(repr(a) for a in cfaccounts)) def test_recreate_accounts_several_fail_some(self): auth_server.http_connect = fake_http_connect(201, 201, 201) @@ -266,11 +251,8 @@ class TestAuthServer(unittest.TestCase): rv = self.controller.recreate_accounts() self.assertEquals(rv.split()[0], '4', repr(rv)) failed = rv.split('[', 1)[-1][:-1].split(', ') - expected = [] - for i, value in enumerate(cfaccounts): - if not i % 2: - expected.append(repr(value)) - self.assertEquals(failed, expected) + self.assertEquals( + len(set(repr(a) for a in cfaccounts) - set(failed)), 2) def test_auth_bad_path(self): self.assertRaises(ValueError, self.controller.handle_auth, @@ -349,7 +331,7 @@ class TestAuthServer(unittest.TestCase): headers={'X-Storage-User': 'tester', 'X-Storage-Pass': 'testing'})) token = res.headers['x-storage-token'] - ttl = self.controller.validate_token(token, cfaccount) + ttl = self.controller.validate_token(token) self.assert_(ttl > 0, repr(ttl)) def test_auth_SOSO_good_Mosso_headers(self): @@ -361,7 +343,7 @@ class TestAuthServer(unittest.TestCase): headers={'X-Auth-User': 'test:tester', 'X-Auth-Key': 'testing'})) token = res.headers['x-storage-token'] - ttl = self.controller.validate_token(token, cfaccount) + ttl = self.controller.validate_token(token) self.assert_(ttl > 0, repr(ttl)) def test_auth_SOSO_bad_Mosso_headers(self): @@ -469,7 +451,7 @@ class TestAuthServer(unittest.TestCase): headers={'X-Auth-User': 'test:tester', 'X-Auth-Key': 'testing'})) token = res.headers['x-storage-token'] - ttl = self.controller.validate_token(token, cfaccount) + ttl = self.controller.validate_token(token) self.assert_(ttl > 0, repr(ttl)) def test_auth_Mosso_good_SOSO_header_names(self): @@ -481,7 +463,7 @@ class TestAuthServer(unittest.TestCase): headers={'X-Storage-User': 'test:tester', 'X-Storage-Pass': 'testing'})) token = res.headers['x-storage-token'] - ttl = self.controller.validate_token(token, cfaccount) + ttl = self.controller.validate_token(token) self.assert_(ttl > 0, repr(ttl)) def test_basic_logging(self): @@ -493,8 +475,8 @@ class TestAuthServer(unittest.TestCase): auth_server.http_connect = fake_http_connect(201, 201, 201) url = self.controller.create_account('test', 'tester', 'testing') self.assertEquals(log.getvalue().rsplit(' ', 1)[0], - "auth SUCCESS create_account('test', 'tester', _) = %s" % - repr(url)) + "auth SUCCESS create_account('test', 'tester', _, False) = %s" + % repr(url)) log.truncate(0) def start_response(*args): pass diff --git a/test/unit/common/middleware/test_auth.py b/test/unit/common/middleware/test_auth.py index cd5a02c91d..08f8898d1b 100644 --- a/test/unit/common/middleware/test_auth.py +++ b/test/unit/common/middleware/test_auth.py @@ -100,77 +100,58 @@ def start_response(*args): pass class TestAuth(unittest.TestCase): + # TODO: With the auth refactor, these tests have to be refactored as well. + # I brought some over from another refactor I've been trying, but these + # also need work. - def setUp(self): - self.test_auth = auth.DevAuthMiddleware( - FakeApp(), {}, FakeMemcache(), Logger()) + def test_clean_acl(self): + devauth = auth.DevAuthorization(None, None) + value = devauth.clean_acl('header', '.ref:any') + self.assertEquals(value, '.ref:any') + value = devauth.clean_acl('header', '.ref:specific.host') + self.assertEquals(value, '.ref:specific.host') + value = devauth.clean_acl('header', '.ref:.ending.with') + self.assertEquals(value, '.ref:.ending.with') + value = devauth.clean_acl('header', '.ref:one,.ref:two') + self.assertEquals(value, '.ref:one,.ref:two') + value = devauth.clean_acl('header', '.ref:any,.ref:-specific.host') + self.assertEquals(value, '.ref:any,.ref:-specific.host') + value = devauth.clean_acl('header', '.ref:any,.ref:-.ending.with') + self.assertEquals(value, '.ref:any,.ref:-.ending.with') + value = devauth.clean_acl('header', '.ref:one,.ref:-two') + self.assertEquals(value, '.ref:one,.ref:-two') + value = devauth.clean_acl('header', + ' .ref : one , ,, .ref:two , .ref : - three ') + self.assertEquals(value, '.ref:one,.ref:two,.ref:-three') + self.assertRaises(ValueError, devauth.clean_acl, 'header', '.ref:') + self.assertRaises(ValueError, devauth.clean_acl, 'header', ' .ref : ') + self.assertRaises(ValueError, devauth.clean_acl, 'header', + 'user , .ref : ') + self.assertRaises(ValueError, devauth.clean_acl, 'header', '.ref:-') + self.assertRaises(ValueError, devauth.clean_acl, 'header', + ' .ref : - ') + self.assertRaises(ValueError, devauth.clean_acl, 'header', + 'user , .ref : - ') - def test_auth_fail(self): - old_http_connect = auth.http_connect - try: - auth.http_connect = mock_http_connect(404) - self.assertFalse(self.test_auth.auth('a','t')) - finally: - auth.http_connect = old_http_connect - - def test_auth_success(self): - old_http_connect = auth.http_connect - try: - auth.http_connect = mock_http_connect(204, {'x-auth-ttl':'1234'}) - self.assertTrue(self.test_auth.auth('a','t')) - finally: - auth.http_connect = old_http_connect - - def test_auth_memcache(self): - old_http_connect = auth.http_connect - try: - auth.http_connect = mock_http_connect(204, {'x-auth-ttl':'1234'}) - self.assertTrue(self.test_auth.auth('a','t')) - auth.http_connect = mock_http_connect(404) - # Should still be in memcache - self.assertTrue(self.test_auth.auth('a','t')) - finally: - auth.http_connect = old_http_connect - - def test_middleware_success(self): - old_http_connect = auth.http_connect - try: - auth.http_connect = mock_http_connect(204, {'x-auth-ttl':'1234'}) - req = Request.blank('/v/a/c/o', headers={'x-auth-token':'t'}) - resp = self.test_auth(req.environ, start_response) - self.assertEquals(resp, 'OK') - finally: - auth.http_connect = old_http_connect - - def test_middleware_no_header(self): - old_http_connect = auth.http_connect - try: - auth.http_connect = mock_http_connect(204, {'x-auth-ttl':'1234'}) - req = Request.blank('/v/a/c/o') - resp = self.test_auth(req.environ, start_response) - self.assertEquals(resp, ['Missing Auth Token']) - finally: - auth.http_connect = old_http_connect - - def test_middleware_storage_token(self): - old_http_connect = auth.http_connect - try: - auth.http_connect = mock_http_connect(204, {'x-auth-ttl':'1234'}) - req = Request.blank('/v/a/c/o', headers={'x-storage-token':'t'}) - resp = self.test_auth(req.environ, start_response) - self.assertEquals(resp, 'OK') - finally: - auth.http_connect = old_http_connect - - def test_middleware_only_version(self): - old_http_connect = auth.http_connect - try: - auth.http_connect = mock_http_connect(204, {'x-auth-ttl':'1234'}) - req = Request.blank('/v', headers={'x-auth-token':'t'}) - resp = self.test_auth(req.environ, start_response) - self.assertEquals(resp, ['Bad URL']) - finally: - auth.http_connect = old_http_connect + def test_parse_acl(self): + devauth = auth.DevAuthorization(None, None) + self.assertEquals(devauth.parse_acl(None), None) + self.assertEquals(devauth.parse_acl(''), None) + self.assertEquals(devauth.parse_acl('.ref:ref1'), + (['ref1'], [], [])) + self.assertEquals(devauth.parse_acl('.ref:-ref1'), + (['-ref1'], [], [])) + self.assertEquals(devauth.parse_acl('account:user'), + ([], [], ['account:user'])) + self.assertEquals(devauth.parse_acl('account'), + ([], ['account'], [])) + self.assertEquals( + devauth.parse_acl('acc1,acc2:usr2,.ref:ref3,.ref:-ref4'), + (['ref3', '-ref4'], ['acc1'], ['acc2:usr2'])) + self.assertEquals(devauth.parse_acl( + 'acc1,acc2:usr2,.ref:ref3,acc3,acc4:usr4,.ref:ref5,.ref:-ref6'), + (['ref3', 'ref5', '-ref6'], ['acc1', 'acc3'], + ['acc2:usr2', 'acc4:usr4'])) if __name__ == '__main__': diff --git a/test/unit/container/test_server.py b/test/unit/container/test_server.py index 639bc4ec5c..dfa2ef2d57 100644 --- a/test/unit/container/test_server.py +++ b/test/unit/container/test_server.py @@ -55,6 +55,51 @@ class TestContainerController(unittest.TestCase): """ Tear down for testing swift.object_server.ObjectController """ rmtree(self.testdir, ignore_errors=1) + def test_acl_container(self): + # Ensure no acl by default + req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'PUT'}, + headers={'X-Timestamp': '0'}) + self.controller.PUT(req) + req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) + response = self.controller.HEAD(req) + self.assert_(response.status.startswith('204')) + self.assert_('x-container-read' not in response.headers) + self.assert_('x-container-write' not in response.headers) + # Ensure POSTing acls works + req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'POST'}, + headers={'X-Timestamp': '1', 'X-Container-Read': '.ref:any', + 'X-Container-Write': 'account:user'}) + self.controller.POST(req) + req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) + response = self.controller.HEAD(req) + self.assert_(response.status.startswith('204')) + self.assertEquals(response.headers.get('x-container-read'), + '.ref:any') + self.assertEquals(response.headers.get('x-container-write'), + 'account:user') + # Ensure we can clear acls on POST + req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'POST'}, + headers={'X-Timestamp': '3', 'X-Container-Read': '', + 'X-Container-Write': ''}) + self.controller.POST(req) + req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) + response = self.controller.HEAD(req) + self.assert_(response.status.startswith('204')) + self.assert_('x-container-read' not in response.headers) + self.assert_('x-container-write' not in response.headers) + # Ensure PUTing acls works + req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'}, + headers={'X-Timestamp': '4', 'X-Container-Read': '.ref:any', + 'X-Container-Write': 'account:user'}) + self.controller.PUT(req) + req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'HEAD'}) + response = self.controller.HEAD(req) + self.assert_(response.status.startswith('204')) + self.assertEquals(response.headers.get('x-container-read'), + '.ref:any') + self.assertEquals(response.headers.get('x-container-write'), + 'account:user') + def test_HEAD(self): req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '0'}) diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index ab56dc740a..ce8d1ee4cb 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -203,7 +203,7 @@ class TestProxyServer(unittest.TestCase): app = MyApp(None, FakeMemcache(), account_ring=FakeRing(), container_ring=FakeRing(), object_ring=FakeRing()) req = Request.blank('/account', environ={'REQUEST_METHOD': 'HEAD'}) - req.account = 'account' + app.update_request(req) resp = app.handle_request(req) self.assertEquals(resp.status_int, 500) @@ -224,14 +224,14 @@ class TestObjectController(unittest.TestCase): self.app.memcache.store = {} req = Request.blank('/a/c/o', headers={'Content-Length': '0', 'Content-Type': 'text/plain'}) - req.account = 'a' + self.app.update_request(req) res = method(req) self.assertEquals(res.status_int, expected) proxy_server.http_connect = fake_http_connect(*statuses, **kwargs) self.app.memcache.store = {} req = Request.blank('/a/c/o', headers={'Content-Length': '0', 'Content-Type': 'text/plain'}) - req.account = 'a' + self.app.update_request(req) res = method(req) self.assertEquals(res.status_int, expected) @@ -244,7 +244,7 @@ class TestObjectController(unittest.TestCase): give_content_type=lambda content_type: self.assertEquals(content_type, expected.next())) req = Request.blank('/a/c/%s' % filename, {}) - req.account = 'a' + self.app.update_request(req) res = controller.PUT(req) test_content_type('test.jpg', iter(['', '', '', 'image/jpeg', 'image/jpeg', 'image/jpeg'])) @@ -261,7 +261,7 @@ class TestObjectController(unittest.TestCase): proxy_server.http_connect = fake_http_connect(*statuses) req = Request.blank('/a/c/o.jpg', {}) req.content_length = 0 - req.account = 'a' + self.app.update_request(req) self.app.memcache.store = {} res = controller.PUT(req) expected = str(expected) @@ -296,7 +296,7 @@ class TestObjectController(unittest.TestCase): self.app.memcache.store = {} req = Request.blank('/a/c/o.jpg', {}) req.content_length = 0 - req.account = 'a' + self.app.update_request(req) res = controller.PUT(req) expected = str(expected) self.assertEquals(res.status[:len(expected)], expected) @@ -330,7 +330,7 @@ class TestObjectController(unittest.TestCase): self.app.memcache.store = {} proxy_server.http_connect = mock_http_connect(*statuses) req = Request.blank('/a/c/o.jpg', {}) - req.account = 'a' + self.app.update_request(req) req.body_file = StringIO('some data') res = controller.PUT(req) expected = str(expected) @@ -347,7 +347,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', {}, headers={ 'Content-Length': str(MAX_FILE_SIZE + 1), 'Content-Type': 'foo/bar'}) - req.account = 'a' + self.app.update_request(req) res = controller.PUT(req) self.assertEquals(res.status_int, 413) @@ -379,7 +379,7 @@ class TestObjectController(unittest.TestCase): proxy_server.http_connect = mock_http_connect(*statuses) req = Request.blank('/a/c/o.jpg', {}) req.content_length = 0 - req.account = 'a' + self.app.update_request(req) res = controller.PUT(req) expected = str(expected) self.assertEquals(res.status[:len(str(expected))], @@ -397,7 +397,7 @@ class TestObjectController(unittest.TestCase): self.app.memcache.store = {} req = Request.blank('/a/c/o', {}, headers={ 'Content-Type': 'foo/bar'}) - req.account = 'a' + self.app.update_request(req) res = controller.POST(req) expected = str(expected) self.assertEquals(res.status[:len(expected)], expected) @@ -417,7 +417,7 @@ class TestObjectController(unittest.TestCase): proxy_server.http_connect = fake_http_connect(*statuses) self.app.memcache.store = {} req = Request.blank('/a/c/o', {}) - req.account = 'a' + self.app.update_request(req) res = controller.DELETE(req) self.assertEquals(res.status[:len(str(expected))], str(expected)) @@ -436,7 +436,7 @@ class TestObjectController(unittest.TestCase): proxy_server.http_connect = fake_http_connect(*statuses) self.app.memcache.store = {} req = Request.blank('/a/c/o', {}) - req.account = 'a' + self.app.update_request(req) res = controller.HEAD(req) self.assertEquals(res.status[:len(str(expected))], str(expected)) @@ -460,14 +460,14 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', {}, headers={ 'Content-Type': 'foo/bar', 'X-Object-Meta-Foo': 'x'*256}) - req.account = 'a' + self.app.update_request(req) res = controller.POST(req) self.assertEquals(res.status_int, 202) proxy_server.http_connect = fake_http_connect(202, 202, 202) req = Request.blank('/a/c/o', {}, headers={ 'Content-Type': 'foo/bar', 'X-Object-Meta-Foo': 'x'*257}) - req.account = 'a' + self.app.update_request(req) res = controller.POST(req) self.assertEquals(res.status_int, 400) @@ -481,14 +481,14 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', {}, headers={ 'Content-Type': 'foo/bar', ('X-Object-Meta-'+'x'*128): 'x'}) - req.account = 'a' + self.app.update_request(req) res = controller.POST(req) self.assertEquals(res.status_int, 202) proxy_server.http_connect = fake_http_connect(202, 202, 202) req = Request.blank('/a/c/o', {}, headers={ 'Content-Type': 'foo/bar', ('X-Object-Meta-'+'x'*129): 'x'}) - req.account = 'a' + self.app.update_request(req) res = controller.POST(req) self.assertEquals(res.status_int, 400) @@ -500,7 +500,7 @@ class TestObjectController(unittest.TestCase): headers.update({'Content-Type': 'foo/bar'}) proxy_server.http_connect = fake_http_connect(202, 202, 202) req = Request.blank('/a/c/o', {}, headers=headers) - req.account = 'a' + self.app.update_request(req) res = controller.POST(req) self.assertEquals(res.status_int, 400) @@ -512,7 +512,7 @@ class TestObjectController(unittest.TestCase): headers.update({'Content-Type': 'foo/bar'}) proxy_server.http_connect = fake_http_connect(202, 202, 202) req = Request.blank('/a/c/o', {}, headers=headers) - req.account = 'a' + self.app.update_request(req) res = controller.POST(req) self.assertEquals(res.status_int, 400) @@ -542,7 +542,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT', 'wsgi.input': SlowBody()}, headers={'Content-Length': '4', 'Content-Type': 'text/plain'}) - req.account = 'account' + self.app.update_request(req) controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') proxy_server.http_connect = \ @@ -554,7 +554,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT', 'wsgi.input': SlowBody()}, headers={'Content-Length': '4', 'Content-Type': 'text/plain'}) - req.account = 'account' + self.app.update_request(req) proxy_server.http_connect = \ fake_http_connect(201, 201, 201) # obj obj obj @@ -583,7 +583,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT', 'wsgi.input': SlowBody()}, headers={'Content-Length': '4', 'Content-Type': 'text/plain'}) - req.account = 'account' + self.app.update_request(req) controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') proxy_server.http_connect = \ @@ -607,7 +607,7 @@ class TestObjectController(unittest.TestCase): dev['ip'] = '127.0.0.1' dev['port'] = 1 req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'GET'}) - req.account = 'account' + self.app.update_request(req) controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') proxy_server.http_connect = \ @@ -649,7 +649,7 @@ class TestObjectController(unittest.TestCase): environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '4', 'Content-Type': 'text/plain'}, body=' ') - req.account = 'account' + self.app.update_request(req) controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') proxy_server.http_connect = \ @@ -663,7 +663,7 @@ class TestObjectController(unittest.TestCase): environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '4', 'Content-Type': 'text/plain'}, body=' ') - req.account = 'account' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 503) @@ -708,7 +708,7 @@ class TestObjectController(unittest.TestCase): def test_proxy_passes_content_type(self): with save_globals(): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'GET'}) - req.account = 'account' + self.app.update_request(req) controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') proxy_server.http_connect = fake_http_connect(200, 200, 200) @@ -728,7 +728,7 @@ class TestObjectController(unittest.TestCase): def test_proxy_passes_content_length_on_head(self): with save_globals(): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'HEAD'}) - req.account = 'account' + self.app.update_request(req) controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') proxy_server.http_connect = fake_http_connect(200, 200, 200) @@ -777,7 +777,7 @@ class TestObjectController(unittest.TestCase): proxy_server.http_connect = \ fake_http_connect(200, 200, 200, 200, 200, 200) req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'DELETE'}) - req.account = 'a' + self.app.update_request(req) resp = getattr(controller, 'DELETE')(req) self.assertEquals(resp.status_int, 200) @@ -853,7 +853,7 @@ class TestObjectController(unittest.TestCase): proxy_server.http_connect = \ fake_http_connect(404, 404, 404, 200, 200, 200) req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 404) @@ -861,7 +861,7 @@ class TestObjectController(unittest.TestCase): fake_http_connect(404, 404, 404, 200, 200, 200) req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'POST'}, headers={'Content-Type': 'text/plain'}) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 404) @@ -874,7 +874,7 @@ class TestObjectController(unittest.TestCase): # acct cont obj obj obj req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '0'}) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) @@ -883,7 +883,7 @@ class TestObjectController(unittest.TestCase): headers={'Content-Length': '0', 'X-Object-Meta-' + ('a' * MAX_META_NAME_LENGTH) : 'v'}) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) proxy_server.http_connect = fake_http_connect(201, 201, 201) @@ -891,7 +891,7 @@ class TestObjectController(unittest.TestCase): headers={'Content-Length': '0', 'X-Object-Meta-' + ('a' * (MAX_META_NAME_LENGTH + 1)) : 'v'}) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 400) @@ -900,7 +900,7 @@ class TestObjectController(unittest.TestCase): headers={'Content-Length': '0', 'X-Object-Meta-Too-Long': 'a' * MAX_META_VALUE_LENGTH}) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) proxy_server.http_connect = fake_http_connect(201, 201, 201) @@ -908,7 +908,7 @@ class TestObjectController(unittest.TestCase): headers={'Content-Length': '0', 'X-Object-Meta-Too-Long': 'a' * (MAX_META_VALUE_LENGTH + 1)}) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 400) @@ -918,7 +918,7 @@ class TestObjectController(unittest.TestCase): headers['X-Object-Meta-%d' % x] = 'v' req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers=headers) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) proxy_server.http_connect = fake_http_connect(201, 201, 201) @@ -927,7 +927,7 @@ class TestObjectController(unittest.TestCase): headers['X-Object-Meta-%d' % x] = 'v' req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers=headers) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 400) @@ -946,7 +946,7 @@ class TestObjectController(unittest.TestCase): 'a' * (MAX_META_OVERALL_SIZE - size - 1) req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers=headers) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 201) proxy_server.http_connect = fake_http_connect(201, 201, 201) @@ -954,7 +954,7 @@ class TestObjectController(unittest.TestCase): 'a' * (MAX_META_OVERALL_SIZE - size) req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers=headers) - req.account = 'a' + self.app.update_request(req) resp = controller.PUT(req) self.assertEquals(resp.status_int, 400) @@ -964,7 +964,7 @@ class TestObjectController(unittest.TestCase): 'container', 'object') req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '0'}) - req.account = 'a' + self.app.update_request(req) proxy_server.http_connect = \ fake_http_connect(200, 200, 201, 201, 201) # acct cont obj obj obj @@ -974,7 +974,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '0', 'X-Copy-From': 'c/o'}) - req.account = 'a' + self.app.update_request(req) proxy_server.http_connect = \ fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) # acct cont acct cont objc obj obj obj @@ -986,7 +986,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '0', 'X-Copy-From': '/c/o'}) - req.account = 'a' + self.app.update_request(req) proxy_server.http_connect = \ fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) # acct cont acct cont objc obj obj obj @@ -998,7 +998,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '0', 'X-Copy-From': '/c/o'}) - req.account = 'a' + self.app.update_request(req) proxy_server.http_connect = \ fake_http_connect(200, 200, 503, 503, 503) # acct cont objc objc objc @@ -1009,7 +1009,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '0', 'X-Copy-From': '/c/o'}) - req.account = 'a' + self.app.update_request(req) proxy_server.http_connect = \ fake_http_connect(200, 200, 404, 404, 404) # acct cont objc objc objc @@ -1020,7 +1020,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '0', 'X-Copy-From': '/c/o'}) - req.account = 'a' + self.app.update_request(req) proxy_server.http_connect = \ fake_http_connect(200, 200, 404, 404, 200, 201, 201, 201) # acct cont objc objc objc obj obj obj @@ -1032,7 +1032,7 @@ class TestObjectController(unittest.TestCase): headers={'Content-Length': '0', 'X-Copy-From': '/c/o', 'X-Object-Meta-Ours': 'okay'}) - req.account = 'a' + self.app.update_request(req) proxy_server.http_connect = \ fake_http_connect(200, 200, 200, 201, 201, 201) # acct cont objc obj obj obj @@ -1434,7 +1434,7 @@ class TestObjectController(unittest.TestCase): 'container', 'object') req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '0'}) - req.account = 'a' + self.app.update_request(req) proxy_server.http_connect = fake_http_connect(200, 201, 201, 201, etags=[None, '68b329da9893e34099c7d8ad5cb9c940', @@ -1452,7 +1452,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '10'}, body='1234567890') - req.account = 'a' + self.app.update_request(req) res = controller.PUT(req) self.assert_(hasattr(req, 'bytes_transferred')) self.assertEquals(req.bytes_transferred, 10) @@ -1464,7 +1464,7 @@ class TestObjectController(unittest.TestCase): controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') req = Request.blank('/a/c/o') - req.account = 'a' + self.app.update_request(req) res = controller.GET(req) res.body self.assert_(hasattr(res, 'bytes_transferred')) @@ -1479,7 +1479,7 @@ class TestObjectController(unittest.TestCase): req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '10'}, body='12345') - req.account = 'a' + self.app.update_request(req) res = controller.PUT(req) self.assertEquals(req.bytes_transferred, 5) self.assert_(hasattr(req, 'client_disconnect')) @@ -1492,7 +1492,7 @@ class TestObjectController(unittest.TestCase): controller = proxy_server.ObjectController(self.app, 'account', 'container', 'object') req = Request.blank('/a/c/o') - req.account = 'a' + self.app.update_request(req) orig_object_chunk_size = self.app.object_chunk_size try: self.app.object_chunk_size = 5 @@ -1528,14 +1528,14 @@ class TestContainerController(unittest.TestCase): self.app.memcache.store = {} req = Request.blank('/a/c', headers={'Content-Length': '0', 'Content-Type': 'text/plain'}) - req.account = 'a' + self.app.update_request(req) res = method(req) self.assertEquals(res.status_int, expected) proxy_server.http_connect = fake_http_connect(*statuses, **kwargs) self.app.memcache.store = {} req = Request.blank('/a/c/', headers={'Content-Length': '0', 'Content-Type': 'text/plain'}) - req.account = 'a' + self.app.update_request(req) res = method(req) self.assertEquals(res.status_int, expected) @@ -1547,7 +1547,7 @@ class TestContainerController(unittest.TestCase): proxy_server.http_connect = fake_http_connect(*statuses, **kwargs) self.app.memcache.store = {} req = Request.blank('/a/c', {}) - req.account = 'a' + self.app.update_request(req) res = controller.HEAD(req) self.assertEquals(res.status[:len(str(expected))], str(expected)) @@ -1570,7 +1570,7 @@ class TestContainerController(unittest.TestCase): self.app.memcache.store = {} req = Request.blank('/a/c', {}) req.content_length = 0 - req.account = 'a' + self.app.update_request(req) res = controller.PUT(req) expected = str(expected) self.assertEquals(res.status[:len(expected)], expected) @@ -1613,7 +1613,7 @@ class TestContainerController(unittest.TestCase): fake_http_connect(200, 200, 200, 200) self.app.memcache.store = {} req = Request.blank('/a/c', environ={'REQUEST_METHOD': meth}) - req.account = 'a' + self.app.update_request(req) resp = getattr(controller, meth)(req) self.assertEquals(resp.status_int, 200) @@ -1657,7 +1657,7 @@ class TestContainerController(unittest.TestCase): self.app.memcache = MockMemcache(allow_lock=True) proxy_server.http_connect = fake_http_connect(200, 200, 200, 201, 201, 201, missing_container=True) req = Request.blank('/a/c', environ={'REQUEST_METHOD': 'PUT'}) - req.account = 'a' + self.app.update_request(req) res = controller.PUT(req) self.assertEquals(res.status_int, 201) @@ -1703,7 +1703,7 @@ class TestContainerController(unittest.TestCase): controller = proxy_server.ContainerController(self.app, 'account', 'container') req = Request.blank('/a/c?format=json') - req.account = 'a' + self.app.update_request(req) res = controller.GET(req) res.body self.assert_(hasattr(res, 'bytes_transferred')) @@ -1715,7 +1715,7 @@ class TestContainerController(unittest.TestCase): controller = proxy_server.ContainerController(self.app, 'account', 'container') req = Request.blank('/a/c?format=json') - req.account = 'a' + self.app.update_request(req) orig_object_chunk_size = self.app.object_chunk_size try: self.app.object_chunk_size = 1 @@ -1760,8 +1760,7 @@ class TestContainerController(unittest.TestCase): 201, give_connect=test_connect) req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}, headers={test_header: test_value}) - req.account = 'a' - req.container = 'c' + self.app.update_request(req) res = getattr(controller, method)(req) self.assertEquals(test_errors, []) @@ -1776,7 +1775,7 @@ class TestContainerController(unittest.TestCase): controller = proxy_server.ContainerController(self.app, 'a', 'c') proxy_server.http_connect = fake_http_connect(200, 201, 201, 201) req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}) - req.account = 'a' + self.app.update_request(req) resp = getattr(controller, method)(req) self.assertEquals(resp.status_int, 201) @@ -1784,16 +1783,14 @@ class TestContainerController(unittest.TestCase): req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}, headers={'X-Container-Meta-' + ('a' * MAX_META_NAME_LENGTH): 'v'}) - req.account = 'a' - req.container = 'c' + self.app.update_request(req) resp = getattr(controller, method)(req) self.assertEquals(resp.status_int, 201) proxy_server.http_connect = fake_http_connect(201, 201, 201) req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}, headers={'X-Container-Meta-' + ('a' * (MAX_META_NAME_LENGTH + 1)): 'v'}) - req.account = 'a' - req.container = 'c' + self.app.update_request(req) resp = getattr(controller, method)(req) self.assertEquals(resp.status_int, 400) @@ -1801,16 +1798,14 @@ class TestContainerController(unittest.TestCase): req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}, headers={'X-Container-Meta-Too-Long': 'a' * MAX_META_VALUE_LENGTH}) - req.account = 'a' - req.container = 'c' + self.app.update_request(req) resp = getattr(controller, method)(req) self.assertEquals(resp.status_int, 201) proxy_server.http_connect = fake_http_connect(201, 201, 201) req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}, headers={'X-Container-Meta-Too-Long': 'a' * (MAX_META_VALUE_LENGTH + 1)}) - req.account = 'a' - req.container = 'c' + self.app.update_request(req) resp = getattr(controller, method)(req) self.assertEquals(resp.status_int, 400) @@ -1820,8 +1815,7 @@ class TestContainerController(unittest.TestCase): headers['X-Container-Meta-%d' % x] = 'v' req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}, headers=headers) - req.account = 'a' - req.container = 'c' + self.app.update_request(req) resp = getattr(controller, method)(req) self.assertEquals(resp.status_int, 201) proxy_server.http_connect = fake_http_connect(201, 201, 201) @@ -1830,8 +1824,7 @@ class TestContainerController(unittest.TestCase): headers['X-Container-Meta-%d' % x] = 'v' req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}, headers=headers) - req.account = 'a' - req.container = 'c' + self.app.update_request(req) resp = getattr(controller, method)(req) self.assertEquals(resp.status_int, 400) @@ -1849,8 +1842,7 @@ class TestContainerController(unittest.TestCase): 'a' * (MAX_META_OVERALL_SIZE - size - 1) req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}, headers=headers) - req.account = 'a' - req.container = 'c' + self.app.update_request(req) resp = getattr(controller, method)(req) self.assertEquals(resp.status_int, 201) proxy_server.http_connect = fake_http_connect(201, 201, 201) @@ -1858,8 +1850,7 @@ class TestContainerController(unittest.TestCase): 'a' * (MAX_META_OVERALL_SIZE - size) req = Request.blank('/a/c', environ={'REQUEST_METHOD': method}, headers=headers) - req.account = 'a' - req.container = 'c' + self.app.update_request(req) resp = getattr(controller, method)(req) self.assertEquals(resp.status_int, 400) @@ -1875,12 +1866,12 @@ class TestAccountController(unittest.TestCase): with save_globals(): proxy_server.http_connect = fake_http_connect(*statuses) req = Request.blank('/a', {}) - req.account = 'a' + self.app.update_request(req) res = method(req) self.assertEquals(res.status_int, expected) proxy_server.http_connect = fake_http_connect(*statuses) req = Request.blank('/a/', {}) - req.account = 'a' + self.app.update_request(req) res = method(req) self.assertEquals(res.status_int, expected) @@ -1930,7 +1921,7 @@ class TestAccountController(unittest.TestCase): dev['port'] = 1 ## can't connect on this port controller = proxy_server.AccountController(self.app, 'account') req = Request.blank('/account', environ={'REQUEST_METHOD': 'HEAD'}) - req.account = 'account' + self.app.update_request(req) resp = controller.HEAD(req) self.assertEquals(resp.status_int, 503) @@ -1941,7 +1932,7 @@ class TestAccountController(unittest.TestCase): dev['port'] = -1 ## invalid port number controller = proxy_server.AccountController(self.app, 'account') req = Request.blank('/account', environ={'REQUEST_METHOD': 'HEAD'}) - req.account = 'account' + self.app.update_request(req) resp = controller.HEAD(req) self.assertEquals(resp.status_int, 503) @@ -1950,7 +1941,7 @@ class TestAccountController(unittest.TestCase): proxy_server.http_connect = fake_http_connect(200, 200, body='{}') controller = proxy_server.AccountController(self.app, 'account') req = Request.blank('/a?format=json') - req.account = 'a' + self.app.update_request(req) res = controller.GET(req) res.body self.assert_(hasattr(res, 'bytes_transferred')) @@ -1961,7 +1952,7 @@ class TestAccountController(unittest.TestCase): proxy_server.http_connect = fake_http_connect(200, 200, body='{}') controller = proxy_server.AccountController(self.app, 'account') req = Request.blank('/a?format=json') - req.account = 'a' + self.app.update_request(req) orig_object_chunk_size = self.app.object_chunk_size try: self.app.object_chunk_size = 1 @@ -1999,7 +1990,7 @@ class TestAccountController(unittest.TestCase): give_connect=test_connect) req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}, headers={test_header: test_value}) - req.account = 'a' + self.app.update_request(req) res = controller.POST(req) self.assertEquals(test_errors, []) @@ -2008,7 +1999,7 @@ class TestAccountController(unittest.TestCase): controller = proxy_server.AccountController(self.app, 'a') proxy_server.http_connect = fake_http_connect(204, 204, 204) req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 204) @@ -2016,14 +2007,14 @@ class TestAccountController(unittest.TestCase): req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}, headers={'X-Account-Meta-' + ('a' * MAX_META_NAME_LENGTH): 'v'}) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 204) proxy_server.http_connect = fake_http_connect(204, 204, 204) req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}, headers={'X-Account-Meta-' + ('a' * (MAX_META_NAME_LENGTH + 1)): 'v'}) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 400) @@ -2031,14 +2022,14 @@ class TestAccountController(unittest.TestCase): req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}, headers={'X-Account-Meta-Too-Long': 'a' * MAX_META_VALUE_LENGTH}) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 204) proxy_server.http_connect = fake_http_connect(204, 204, 204) req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}, headers={'X-Account-Meta-Too-Long': 'a' * (MAX_META_VALUE_LENGTH + 1)}) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 400) @@ -2048,7 +2039,7 @@ class TestAccountController(unittest.TestCase): headers['X-Account-Meta-%d' % x] = 'v' req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}, headers=headers) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 204) proxy_server.http_connect = fake_http_connect(204, 204, 204) @@ -2057,7 +2048,7 @@ class TestAccountController(unittest.TestCase): headers['X-Account-Meta-%d' % x] = 'v' req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}, headers=headers) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 400) @@ -2075,7 +2066,7 @@ class TestAccountController(unittest.TestCase): 'a' * (MAX_META_OVERALL_SIZE - size - 1) req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}, headers=headers) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 204) proxy_server.http_connect = fake_http_connect(204, 204, 204) @@ -2083,7 +2074,7 @@ class TestAccountController(unittest.TestCase): 'a' * (MAX_META_OVERALL_SIZE - size) req = Request.blank('/a', environ={'REQUEST_METHOD': 'POST'}, headers=headers) - req.account = 'a' + self.app.update_request(req) resp = controller.POST(req) self.assertEquals(resp.status_int, 400)