Documentation of the new auth and acls middleware modules and bugfixes
This commit is contained in:
parent
bb01c22440
commit
65eb19f103
@ -42,6 +42,15 @@ Auth
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _acls:
|
||||
|
||||
ACLs
|
||||
====
|
||||
|
||||
.. automodule:: swift.common.middleware.acl
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _wsgi:
|
||||
|
||||
WSGI
|
||||
|
@ -14,37 +14,101 @@
|
||||
# limitations under the License.
|
||||
|
||||
def clean_acl(name, value):
|
||||
"""
|
||||
Returns a cleaned ACL header value, validating that it meets the formatting
|
||||
requirements for standard Swift ACL strings.
|
||||
|
||||
The ACL format is::
|
||||
|
||||
[item[,item...]]
|
||||
|
||||
Each item can be a group name to give access to or a referrer designation
|
||||
to grant or deny based on the HTTP Referer header.
|
||||
|
||||
The referrer designation format is::
|
||||
|
||||
.ref:[-]value
|
||||
|
||||
The value can be "any" to specify any referrer host is allowed access, a
|
||||
specific host name like "www.example.com", or if it has a leading period
|
||||
"." it is a domain name specification, like ".example.com". The leading
|
||||
minus sign "-" indicates referrer hosts that should be denied access.
|
||||
|
||||
Referrer access is applied in the order they are specified. For example,
|
||||
.ref:.example.com,.ref:-thief.example.com would allow all hosts ending with
|
||||
.example.com except for the specific host thief.example.com.
|
||||
|
||||
Example valid ACLs::
|
||||
|
||||
.ref:any
|
||||
.ref:any,.ref:-.thief.com
|
||||
.ref:any,.ref:-.thief.com,bobs_account,sues_account:sue
|
||||
bobs_account,sues_account:sue
|
||||
|
||||
Example invalid ACLs::
|
||||
|
||||
.ref:
|
||||
.ref:-
|
||||
|
||||
Also, .ref designations aren't allowed in headers whose names include the
|
||||
word 'write'.
|
||||
|
||||
ACLs that are "messy" will be cleaned up. Examples:
|
||||
|
||||
====================== ======================
|
||||
Original Cleaned
|
||||
---------------------- ----------------------
|
||||
bob, sue bob,sue
|
||||
bob , sue bob,sue
|
||||
bob,,,sue bob,sue
|
||||
.ref : any .ref:any
|
||||
====================== ======================
|
||||
|
||||
:param name: The name of the header being cleaned, such as X-Container-Read
|
||||
or X-Container-Write.
|
||||
:param value: The value of the header being cleaned.
|
||||
:returns: The value, cleaned of extraneous formatting.
|
||||
:raises ValueError: If the value does not meet the ACL formatting
|
||||
requirements; the error message will indicate why.
|
||||
"""
|
||||
values = []
|
||||
for raw_value in value.lower().split(','):
|
||||
raw_value = raw_value.strip()
|
||||
if raw_value:
|
||||
if ':' in raw_value:
|
||||
if ':' not in raw_value:
|
||||
values.append(raw_value)
|
||||
else:
|
||||
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 name:
|
||||
if first != '.ref':
|
||||
values.append(raw_value)
|
||||
elif 'write' in name:
|
||||
raise ValueError('Referrers not allowed in write ACLs: %s'
|
||||
% repr(raw_value))
|
||||
if second:
|
||||
if first == '.ref' and second[0] == '-':
|
||||
elif not second:
|
||||
raise ValueError('No value after referrer designation in '
|
||||
'%s' % repr(raw_value))
|
||||
else:
|
||||
if 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(acl_string):
|
||||
"""
|
||||
Parses a standard Swift ACL string into a referrers list and groups list.
|
||||
|
||||
See :func:`clean_acl` for documentation of the standard Swift ACL format.
|
||||
|
||||
:param acl_string: The standard Swift ACL string to parse.
|
||||
:returns: A tuple of (referrers, groups) where referrers is a list of
|
||||
referrer designations (without the leading .ref:) and groups is a
|
||||
list of groups to allow access.
|
||||
"""
|
||||
referrers = []
|
||||
groups = []
|
||||
if acl_string:
|
||||
@ -56,15 +120,29 @@ def parse_acl(acl_string):
|
||||
return referrers, groups
|
||||
|
||||
|
||||
def referrer_allowed(req, referrers):
|
||||
def referrer_allowed(referrer, referrer_acl):
|
||||
"""
|
||||
Returns True if the referrer should be allowed based on the referrer_acl
|
||||
list (as returned by :func:`parse_acl`).
|
||||
|
||||
See :func:`clean_acl` for documentation of the standard Swift ACL format.
|
||||
|
||||
:param referrer: The value of the HTTP Referer header.
|
||||
:param referrer_acl: The list of referrer designations as returned by
|
||||
:func:`parse_acl`.
|
||||
:returns: True if the referrer should be allowed; False if not.
|
||||
"""
|
||||
allow = False
|
||||
if referrers:
|
||||
parts = req.referer.split('//', 1)
|
||||
if referrer_acl:
|
||||
if not referrer:
|
||||
rhost = 'unknown'
|
||||
else:
|
||||
parts = referrer.split('//', 1)
|
||||
if len(parts) == 2:
|
||||
rhost = parts[1].split('/', 1)[0].split(':', 1)[0].lower()
|
||||
else:
|
||||
rhost = 'unknown'
|
||||
for mhost in referrers:
|
||||
for mhost in referrer_acl:
|
||||
if mhost[0] == '-':
|
||||
mhost = mhost[1:]
|
||||
if mhost == rhost or \
|
||||
|
@ -24,6 +24,7 @@ from swift.common.utils import cache_from_env, split_path
|
||||
|
||||
|
||||
class DevAuth(object):
|
||||
"""Auth Middleware that uses the dev auth server."""
|
||||
|
||||
def __init__(self, app, conf):
|
||||
self.app = app
|
||||
@ -35,6 +36,11 @@ class DevAuth(object):
|
||||
self.timeout = int(conf.get('node_timeout', 10))
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
"""
|
||||
Accepts a standard WSGI application call, authenticating the request
|
||||
and installing callback hooks for authorization and ACL header
|
||||
validation.
|
||||
"""
|
||||
user = None
|
||||
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
|
||||
if token:
|
||||
@ -64,13 +70,17 @@ class DevAuth(object):
|
||||
return self.app(env, start_response)
|
||||
|
||||
def authorize(self, req):
|
||||
"""
|
||||
Returns None if the request is authorized to continue or a standard
|
||||
WSGI response callable if not.
|
||||
"""
|
||||
version, account, container, obj = split_path(req.path, 1, 4, True)
|
||||
if not account:
|
||||
return self.denied_response(req)
|
||||
if req.remote_user and account in req.remote_user.split(','):
|
||||
return None
|
||||
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
||||
if referrer_allowed(req, referrers):
|
||||
if referrer_allowed(req.referer, referrers):
|
||||
return None
|
||||
if not req.remote_user:
|
||||
return self.denied_response(req)
|
||||
@ -80,6 +90,10 @@ class DevAuth(object):
|
||||
return self.denied_response(req)
|
||||
|
||||
def denied_response(self, req):
|
||||
"""
|
||||
Returns a standard WSGI response callable with the status of 403 or 401
|
||||
depending on whether the REMOTE_USER is set or not.
|
||||
"""
|
||||
if req.remote_user:
|
||||
return HTTPForbidden(request=req)
|
||||
else:
|
||||
@ -87,6 +101,7 @@ class DevAuth(object):
|
||||
|
||||
|
||||
def filter_factory(global_conf, **local_conf):
|
||||
"""Returns a WSGI filter app for use with paste.deploy."""
|
||||
conf = global_conf.copy()
|
||||
conf.update(local_conf)
|
||||
def auth_filter(app):
|
||||
|
Loading…
Reference in New Issue
Block a user