Refactored auth and adding ACLs using repoze.what

This commit is contained in:
gholt 2010-09-02 21:50:16 -07:00
parent acf3a430b6
commit cfcfdd3de1
14 changed files with 1087 additions and 369 deletions

View File

@ -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 <new_account> <new_user> <new_password> [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 <new_account> <new_user> <new_password> [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')

View File

@ -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: <token-from-x-auth-token-above>' <url-from-x-storage-url-above>``
#. 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`

View File

@ -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/<account-hash>/<token>
* GET /token/<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,

View File

@ -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

View File

@ -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)

View File

@ -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', '-'),

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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__':

View File

@ -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'})

View File

@ -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)