diff --git a/swift/common/middleware/acl.py b/swift/common/middleware/acl.py index f08780eedb..e31ebda743 100644 --- a/swift/common/middleware/acl.py +++ b/swift/common/middleware/acl.py @@ -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 diff --git a/swift/common/middleware/staticweb.py b/swift/common/middleware/staticweb.py index 8936728b04..d9fdc196af 100644 --- a/swift/common/middleware/staticweb.py +++ b/swift/common/middleware/staticweb.py @@ -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) diff --git a/swift/common/middleware/swauth.py b/swift/common/middleware/swauth.py index 68b0d7afaf..86cdcac07c 100644 --- a/swift/common/middleware/swauth.py +++ b/swift/common/middleware/swauth.py @@ -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]