staticweb: work in progress

This commit is contained in:
gholt 2011-02-19 12:35:58 -08:00
parent 6639b0ec0c
commit 469c287b5b
3 changed files with 85 additions and 80 deletions

View File

@ -58,6 +58,10 @@ def clean_acl(name, value):
.r:
.r:-
By default, allowing read access via .r will allow listing objects in the
container as well as retrieving objects from the container. To turn off
listings, use the .rnolisting directive.
Also, .r designations aren't allowed in headers whose names include the
word 'write'.
@ -71,6 +75,7 @@ def clean_acl(name, value):
``bob,,,sue`` ``bob,sue``
``.referrer : *`` ``.r:*``
``.ref:*.example.com`` ``.r:.example.com``
``.r:*, .rnolisting`` ``.r:*,.rnolisting``
====================== ======================
:param name: The name of the header being cleaned, such as X-Container-Read

View File

@ -21,36 +21,37 @@ except ImportError:
import cgi
import urllib
from webob import Response
from webob import Response, Request
from webob.exc import HTTPMovedPermanently, HTTPNotFound, HTTPUnauthorized
from swift.common.utils import split_path
from swift.common.utils import split_path, TRUE_VALUES
# To use:
# Put the staticweb filter just after the auth filter.
# Make the container publicly readable:
# st post -r '.r:*' container
# You should be able to get objects now, but not do listings.
# Set an index file:
# You should be able to get objects and do direct container listings
# now, though they'll be in the REST API format.
# Set an index file directive:
# st post -m 'index:index.html' container
# You should be able to hit path's that have an index.html without needing to
# type the index.html part, but still not do listings.
# Turn on listings:
# st post -m 'index:allow_listings,index.html' container
# You should be able to hit paths that have an index.html without
# needing to type the index.html part and listings will now be HTML.
# Turn off listings:
# st post -r '.r:*,.rnolisting' container
# Set an error file:
# st post -m 'error:error.html' container
# Now 404's should load 404error.html
# TODO: Tests
# TODO: Docs
# TODO: Make a disallow_listings to restrict direct container listings. These
# have to stay on by default because of swauth and any other similar
# middleware. The lower level indirect listings can stay disabled by
# default.
# TODO: Accept header negotiation: make static web work with authed as well
# TODO: get_container_info can be memcached
# TODO: Blueprint
# Now 401's should load s 401error.html, 404's should load
# 404error.html, etc.
#
# This mode is normally only active for anonymous requests. If you
# want to use it with authenticated requests, set the X-Web-Mode:
# true header.
#
# TODO: Tests.
# TODO: Docs.
# TODO: get_container_info can be memcached.
# TODO: Blueprint.
class StaticWeb(object):
@ -68,12 +69,22 @@ class StaticWeb(object):
start_response(self.response_status, self.response_headers,
self.response_exc_info)
return response
save_response_status = self.response_status
save_response_headers = self.response_headers
save_response_exc_info = self.response_exc_info
tmp_env = dict(env)
self.strip_ifs(tmp_env)
tmp_env['PATH_INFO'] = '/%s/%s/%s/%s%s' % (self.version, self.account,
self.container, self.get_status_int(), self.error)
tmp_env['REQUEST_METHOD'] = 'GET'
return self.app(tmp_env, start_response)
resp = self.app(tmp_env, self.start_response)
if self.get_status_int() // 100 == 2:
start_response(self.response_status, self.response_headers,
self.response_exc_info)
return resp
start_response(save_response_status, save_response_headers,
save_response_exc_info)
return response
def get_status_int(self):
return int(self.response_status.split(' ', 1)[0])
@ -90,26 +101,16 @@ class StaticWeb(object):
def get_container_info(self, env, start_response):
self.index = self.error = None
self.index_allow_listings = False
tmp_env = dict(env)
self.strip_ifs(tmp_env)
tmp_env['REQUEST_METHOD'] = 'HEAD'
tmp_env['PATH_INFO'] = \
'/%s/%s/%s' % (self.version, self.account, self.container)
resp = self.app(tmp_env, self.start_response)
if self.get_status_int() // 100 != 2:
return
self.index = self.get_header(self.response_headers,
'x-container-meta-index', '').strip()
self.error = self.get_header(self.response_headers,
'x-container-meta-error', '').strip()
if not self.index:
return
if self.index.lower() == 'allow_listings':
self.index_allow_listings = True
elif self.index.lower().startswith('allow_listings,'):
self.index = self.index[len('allow_listings,'):]
self.index_allow_listings = True
tmp_env = {'REQUEST_METHOD': 'HEAD', 'HTTP_USER_AGENT': 'StaticWeb'}
for name in ('swift.cache', 'HTTP_X_CF_TRANS_ID'):
if name in env:
tmp_env[name] = env[name]
req = Request.blank('/%s/%s/%s' % (self.version, self.account,
self.container), environ=tmp_env)
resp = req.get_response(self.app)
if resp.status_int // 100 == 2:
self.index = resp.headers.get('x-container-meta-index', '').strip()
self.error = resp.headers.get('x-container-meta-error', '').strip()
def listing(self, env, start_response, prefix=None):
tmp_env = dict(env)
@ -152,18 +153,16 @@ class StaticWeb(object):
def handle_container(self, env, start_response):
self.get_container_info(env, start_response)
if not self.index and not self.index_allow_listings:
if not self.index:
return self.app(env, start_response)
if env['PATH_INFO'][-1] != '/':
return HTTPMovedPermanently(
location=env['PATH_INFO'] + '/')(env, start_response)
if not self.index and self.index_allow_listings:
return self.listing(env, start_response)
tmp_env = dict(env)
tmp_env['PATH_INFO'] += self.index
resp = self.app(tmp_env, self.start_response)
status_int = self.get_status_int()
if status_int == 404 and self.index_allow_listings:
if status_int == 404:
return self.listing(env, start_response)
elif self.get_status_int() // 100 not in (2, 3):
return self.error_response(resp, env, start_response)
@ -182,44 +181,43 @@ class StaticWeb(object):
if status_int != 404:
return self.error_response(resp, env, start_response)
self.get_container_info(env, start_response)
if not self.index and not self.index_allow_listings:
if not self.index:
return self.app(env, start_response)
if self.index:
tmp_env = dict(env)
if tmp_env['PATH_INFO'][-1] != '/':
tmp_env['PATH_INFO'] += '/'
tmp_env['PATH_INFO'] += self.index
resp = self.app(tmp_env, self.start_response)
status_int = self.get_status_int()
if status_int // 100 in (2, 3):
if env['PATH_INFO'][-1] != '/':
return HTTPMovedPermanently(
location=env['PATH_INFO'] + '/')(env, start_response)
start_response(self.response_status, self.response_headers,
self.response_exc_info)
return resp
elif status_int == 404 and self.index_allow_listings:
if env['PATH_INFO'][-1] != '/':
tmp_env = dict(env)
self.strip_ifs(tmp_env)
tmp_env['REQUEST_METHOD'] = 'GET'
tmp_env['PATH_INFO'] = '/%s/%s/%s' % (self.version,
self.account, self.container)
tmp_env['QUERY_STRING'] = 'limit=1&format=json&delimiter' \
'=/&limit=1&prefix=%s' % urllib.quote(self.obj + '/')
resp = self.app(tmp_env, self.start_response)
if self.get_status_int() // 100 != 2 or \
not json.loads(''.join(resp)):
resp = HTTPNotFound()(env, self.start_response)
return self.error_response(resp, env, start_response)
return HTTPMovedPermanently(location=env['PATH_INFO'] +
'/')(env, start_response)
return self.listing(env, start_response, self.obj)
return self.app(env, start_response)
tmp_env = dict(env)
if tmp_env['PATH_INFO'][-1] != '/':
tmp_env['PATH_INFO'] += '/'
tmp_env['PATH_INFO'] += self.index
resp = self.app(tmp_env, self.start_response)
status_int = self.get_status_int()
if status_int // 100 in (2, 3):
if env['PATH_INFO'][-1] != '/':
return HTTPMovedPermanently(
location=env['PATH_INFO'] + '/')(env, start_response)
start_response(self.response_status, self.response_headers,
self.response_exc_info)
return resp
elif status_int == 404:
if env['PATH_INFO'][-1] != '/':
tmp_env = dict(env)
self.strip_ifs(tmp_env)
tmp_env['REQUEST_METHOD'] = 'GET'
tmp_env['PATH_INFO'] = '/%s/%s/%s' % (self.version,
self.account, self.container)
tmp_env['QUERY_STRING'] = 'limit=1&format=json&delimiter' \
'=/&limit=1&prefix=%s' % urllib.quote(self.obj + '/')
resp = self.app(tmp_env, self.start_response)
if self.get_status_int() // 100 != 2 or \
not json.loads(''.join(resp)):
resp = HTTPNotFound()(env, self.start_response)
return self.error_response(resp, env, start_response)
return HTTPMovedPermanently(location=env['PATH_INFO'] +
'/')(env, start_response)
return self.listing(env, start_response, self.obj)
def __call__(self, env, start_response):
if env.get('REMOTE_USER') or \
env['REQUEST_METHOD'] not in ('HEAD', 'GET'):
if env['REQUEST_METHOD'] not in ('HEAD', 'GET') or \
(env.get('REMOTE_USER') and
not env.get('HTTP_X_WEB_MODE', '') in TRUE_VALUES):
return self.app(env, start_response)
(self.version, self.account, self.container, self.obj) = \
split_path(env['PATH_INFO'], 2, 4, True)

View File

@ -277,6 +277,8 @@ class Swauth(object):
return None
referrers, groups = parse_acl(getattr(req, 'acl', None))
if referrer_allowed(req.referer, referrers):
if not obj and '.rnolisting' in groups:
return HTTPUnauthorized(request=req)
return None
if not req.remote_user:
return self.denied_response(req)
@ -1179,7 +1181,7 @@ class Swauth(object):
:returns: webob.Request object
"""
newenv = {'REQUEST_METHOD': method}
newenv = {'REQUEST_METHOD': method, 'HTTP_USER_AGENT': 'Swauth'}
for name in ('swift.cache', 'HTTP_X_CF_TRANS_ID'):
if name in env:
newenv[name] = env[name]