Simply auth middleware and document how to make your own
This commit is contained in:
449
doc/source/development_auth.rst
Normal file
449
doc/source/development_auth.rst
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
===============
|
||||||
|
Auth Middleware
|
||||||
|
===============
|
||||||
|
|
||||||
|
---------------------------------
|
||||||
|
Creating Your Own Auth Middleware
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
The included swift/common/middleware/auth.py is a good minimal example of how
|
||||||
|
to create auth middleware. The main points are that the auth middleware can
|
||||||
|
reject requests up front, before they ever get to the Swift Proxy application,
|
||||||
|
and afterwards when the proxy issues callbacks to verify authorization.
|
||||||
|
|
||||||
|
It's generally good to separate the authentication and authorization
|
||||||
|
procedures. Authentication verifies that a request actually comes from who it
|
||||||
|
says it does. Authorization verifies the 'who' has access to the resource(s)
|
||||||
|
the request wants.
|
||||||
|
|
||||||
|
Authentication is performed on the request before it ever gets to the Swift
|
||||||
|
Proxy application. The identity information is gleaned from the request,
|
||||||
|
validated in some way, and the validation information is added to the WSGI
|
||||||
|
environment as needed by the future authorization procedure. What exactly is
|
||||||
|
added to the WSGI environment is solely dependent on what the installed
|
||||||
|
authorization procedures need; the Swift Proxy application itself needs no
|
||||||
|
specific information, it just passes it along. Convention has
|
||||||
|
environ['REMOTE_USER'] set to the authenticated user string but often more
|
||||||
|
information is needed than just that.
|
||||||
|
|
||||||
|
Authorization is performed through callbacks by the Swift Proxy server to the
|
||||||
|
WSGI environment's swift.authorize value, if one is set. The swift.authorize
|
||||||
|
value should simply be a function that takes a webob.Request as an argument and
|
||||||
|
returns None if access is granted or returns a callable(environ,
|
||||||
|
start_response) if access is denied. This callable is a standard WSGI callable.
|
||||||
|
Generally, you should return 403 Forbidden for requests by an authenticated
|
||||||
|
user and 401 Unauthorized for an unauthenticated request. For example, here's
|
||||||
|
an authorize function that only allows GETs (in this case you'd probably return
|
||||||
|
405 Method Not Allowed, but ignore that for the moment).::
|
||||||
|
|
||||||
|
from webob import HTTPForbidden, HTTPUnauthorized
|
||||||
|
|
||||||
|
|
||||||
|
def authorize(req):
|
||||||
|
if req.method == 'GET':
|
||||||
|
return None
|
||||||
|
if req.remote_user:
|
||||||
|
return HTTPForbidden(request=req)
|
||||||
|
else:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
Adding the swift.authorize callback is often done by the authentication
|
||||||
|
middleware as authentication and authorization are often paired together. But,
|
||||||
|
you could create separate authorization middleware that simply sets the
|
||||||
|
callback before passing on the request. To continue our example above::
|
||||||
|
|
||||||
|
from webob import HTTPForbidden, HTTPUnauthorized
|
||||||
|
|
||||||
|
|
||||||
|
class Authorization(object):
|
||||||
|
|
||||||
|
def __init__(self, app, conf):
|
||||||
|
self.app = app
|
||||||
|
self.conf = conf
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
environ['swift.authorize'] = self.authorize
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
def authorize(self, req):
|
||||||
|
if req.method == 'GET':
|
||||||
|
return None
|
||||||
|
if req.remote_user:
|
||||||
|
return HTTPForbidden(request=req)
|
||||||
|
else:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_factory(global_conf, **local_conf):
|
||||||
|
conf = global_conf.copy()
|
||||||
|
conf.update(local_conf)
|
||||||
|
def auth_filter(app):
|
||||||
|
return Authorization(app, conf)
|
||||||
|
return auth_filter
|
||||||
|
|
||||||
|
The Swift Proxy server will call swift.authorize after some initial work, but
|
||||||
|
before truly trying to process the request. Positive authorization at this
|
||||||
|
point will cause the request to be fully processed immediately. A denial at
|
||||||
|
this point will immediately send the denial response for most operations.
|
||||||
|
|
||||||
|
But for some operations that might be approved with more information, the
|
||||||
|
additional information will be gathered and added to the WSGI environment and
|
||||||
|
then swift.authorize will be called once more. These are called delay_denial
|
||||||
|
requests and currently include container read requests and object read and
|
||||||
|
write requests. For these requests, the read or write access control string
|
||||||
|
(X-Container-Read and X-Container-Write) will be fetched and set as the 'acl'
|
||||||
|
attribute in the webob.Request passed to swift.authorize.
|
||||||
|
|
||||||
|
The delay_denial procedures allow skipping possibly expensive access control
|
||||||
|
string retrievals for requests that can be approved without that information,
|
||||||
|
such as administrator or account owner requests.
|
||||||
|
|
||||||
|
To further our example, we now will approve all requests that have the access
|
||||||
|
control string set to same value as the authenticated user string. Note that
|
||||||
|
you probably wouldn't do this exactly as the access control string represents a
|
||||||
|
list rather than a single user, but it'll suffice for this example::
|
||||||
|
|
||||||
|
from webob import HTTPForbidden, HTTPUnauthorized
|
||||||
|
|
||||||
|
|
||||||
|
class Authorization(object):
|
||||||
|
|
||||||
|
def __init__(self, app, conf):
|
||||||
|
self.app = app
|
||||||
|
self.conf = conf
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
environ['swift.authorize'] = self.authorize
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
def authorize(self, req):
|
||||||
|
# Allow anyone to perform GET requests
|
||||||
|
if req.method == 'GET':
|
||||||
|
return None
|
||||||
|
# Allow any request where the acl equals the authenticated user
|
||||||
|
if getattr(req, 'acl', None) == req.remote_user:
|
||||||
|
return None
|
||||||
|
if req.remote_user:
|
||||||
|
return HTTPForbidden(request=req)
|
||||||
|
else:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_factory(global_conf, **local_conf):
|
||||||
|
conf = global_conf.copy()
|
||||||
|
conf.update(local_conf)
|
||||||
|
def auth_filter(app):
|
||||||
|
return Authorization(app, conf)
|
||||||
|
return auth_filter
|
||||||
|
|
||||||
|
The access control string has a standard format included with Swift, though
|
||||||
|
this can be overridden if desired. The standard format can be parsed with
|
||||||
|
swift.common.middleware.acl.parse_acl which converts the string into two arrays
|
||||||
|
of strings: (referrers, groups). The referrers allow comparing the request's
|
||||||
|
Referer header to control access. The groups allow comparing the
|
||||||
|
request.remote_user (or other sources of group information) to control access.
|
||||||
|
Checking referrer access can be accomplished by using the
|
||||||
|
swift.common.middleware.acl.referrer_allowed function. Checking group access is
|
||||||
|
usually a simple string comparison.
|
||||||
|
|
||||||
|
Let's continue our example to use parse_acl and referrer_allowed. Now we'll
|
||||||
|
only allow GETs after a referrer check and any requests after a group check::
|
||||||
|
|
||||||
|
from swift.common.middleware.acl import parse_acl, referrer_allowed
|
||||||
|
from webob import HTTPForbidden, HTTPUnauthorized
|
||||||
|
|
||||||
|
|
||||||
|
class Authorization(object):
|
||||||
|
|
||||||
|
def __init__(self, app, conf):
|
||||||
|
self.app = app
|
||||||
|
self.conf = conf
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
environ['swift.authorize'] = self.authorize
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
def authorize(self, req):
|
||||||
|
if hasattr(req, 'acl'):
|
||||||
|
referrers, groups = parse_acl(req.acl)
|
||||||
|
if req.method == 'GET' and referrer_allowed(req, referrers):
|
||||||
|
return None
|
||||||
|
if req.remote_user and groups and req.remote_user in groups:
|
||||||
|
return None
|
||||||
|
if req.remote_user:
|
||||||
|
return HTTPForbidden(request=req)
|
||||||
|
else:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_factory(global_conf, **local_conf):
|
||||||
|
conf = global_conf.copy()
|
||||||
|
conf.update(local_conf)
|
||||||
|
def auth_filter(app):
|
||||||
|
return Authorization(app, conf)
|
||||||
|
return auth_filter
|
||||||
|
|
||||||
|
The access control strings are set with PUTs and POSTs to containers with the
|
||||||
|
X-Container-Read and X-Container-Write headers. Swift allows these strings to
|
||||||
|
be set to any value, though it's very useful to validate the strings meet the
|
||||||
|
desired format and return a useful error to the user if they don't.
|
||||||
|
|
||||||
|
To support this validation, the Swift Proxy application will call the WSGI
|
||||||
|
environment's swift.clean_acl callback whenever one of these headers is to be
|
||||||
|
written. The callback should take a header name and value as its arguments. It
|
||||||
|
should return the cleaned value to save if valid or raise a ValueError with a
|
||||||
|
reasonable error message if not.
|
||||||
|
|
||||||
|
There is an included swift.common.middleware.acl.clean_acl that validates the
|
||||||
|
standard Swift format. Let's improve our example by making use of that::
|
||||||
|
|
||||||
|
from swift.common.middleware.acl import \
|
||||||
|
clean_acl, parse_acl, referrer_allowed
|
||||||
|
from webob import HTTPForbidden, HTTPUnauthorized
|
||||||
|
|
||||||
|
|
||||||
|
class Authorization(object):
|
||||||
|
|
||||||
|
def __init__(self, app, conf):
|
||||||
|
self.app = app
|
||||||
|
self.conf = conf
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
environ['swift.authorize'] = self.authorize
|
||||||
|
environ['swift.clean_acl'] = clean_acl
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
def authorize(self, req):
|
||||||
|
if hasattr(req, 'acl'):
|
||||||
|
referrers, groups = parse_acl(req.acl)
|
||||||
|
if req.method == 'GET' and referrer_allowed(req, referrers):
|
||||||
|
return None
|
||||||
|
if req.remote_user and groups and req.remote_user in groups:
|
||||||
|
return None
|
||||||
|
if req.remote_user:
|
||||||
|
return HTTPForbidden(request=req)
|
||||||
|
else:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_factory(global_conf, **local_conf):
|
||||||
|
conf = global_conf.copy()
|
||||||
|
conf.update(local_conf)
|
||||||
|
def auth_filter(app):
|
||||||
|
return Authorization(app, conf)
|
||||||
|
return auth_filter
|
||||||
|
|
||||||
|
Now, if you want to override the format for access control strings you'll have
|
||||||
|
to provide your own clean_acl function and you'll have to do your own parsing
|
||||||
|
and authorization checking for that format. It's highly recommended you use the
|
||||||
|
standard format simply to support the widest range of external tools, but
|
||||||
|
sometimes that's less important than meeting certain ACL requirements.
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------
|
||||||
|
Integrating With repoze.what
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Here's an example of integration with repoze.what, though honestly it just does
|
||||||
|
what the default swift/common/middleware/auth.py does in a slightly different
|
||||||
|
way. I'm no repoze.what expert by any stretch; this is just included here to
|
||||||
|
hopefully give folks a start on their own code if they want to use
|
||||||
|
repoze.what::
|
||||||
|
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
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 swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||||
|
from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
|
||||||
|
from swift.common.utils import cache_from_env, split_path
|
||||||
|
from webob.exc import HTTPForbidden, HTTPUnauthorized
|
||||||
|
|
||||||
|
|
||||||
|
class DevAuthorization(object):
|
||||||
|
|
||||||
|
def __init__(self, app, conf):
|
||||||
|
self.app = app
|
||||||
|
self.conf = conf
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
environ['swift.authorize'] = self.authorize
|
||||||
|
environ['swift.clean_acl'] = 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)
|
||||||
|
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
||||||
|
if referrer_allowed(req, referrers):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
in_any_group(account, *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:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
||||||
|
self.ssl = \
|
||||||
|
conf.get('ssl', 'false').lower() in ('true', 'on', '1', 'yes')
|
||||||
|
self.timeout = int(conf.get('node_timeout', 10))
|
||||||
|
|
||||||
|
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, 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):
|
||||||
|
return credentials['repoze.what.userid'].split(',')
|
||||||
|
|
||||||
|
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 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
|
@@ -32,6 +32,7 @@ Development:
|
|||||||
|
|
||||||
development_guidelines
|
development_guidelines
|
||||||
development_saio
|
development_saio
|
||||||
|
development_auth
|
||||||
|
|
||||||
Deployment:
|
Deployment:
|
||||||
|
|
||||||
|
@@ -353,8 +353,10 @@ class AuthController(object):
|
|||||||
validation = self.validate_token(token)
|
validation = self.validate_token(token)
|
||||||
if not validation:
|
if not validation:
|
||||||
return HTTPNotFound()
|
return HTTPNotFound()
|
||||||
|
# X-Auth-User: account:user,account,cfaccount
|
||||||
return HTTPNoContent(headers={'X-Auth-TTL': validation[0],
|
return HTTPNoContent(headers={'X-Auth-TTL': validation[0],
|
||||||
'X-Auth-User': ':'.join(validation[1:])})
|
'X-Auth-User': '%s:%s,%s,%s' %
|
||||||
|
(validation[1], validation[2], validation[1], validation[3])})
|
||||||
|
|
||||||
def handle_account_create(self, request):
|
def handle_account_create(self, request):
|
||||||
"""
|
"""
|
||||||
|
76
swift/common/middleware/acl.py
Normal file
76
swift/common/middleware/acl.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Copyright (c) 2010 OpenStack, LLC.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
def clean_acl(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 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(acl_string):
|
||||||
|
referrers = []
|
||||||
|
groups = []
|
||||||
|
if acl_string:
|
||||||
|
for value in acl_string.split(','):
|
||||||
|
if value.startswith('.ref:'):
|
||||||
|
referrers.append(value[len('.ref:'):])
|
||||||
|
else:
|
||||||
|
groups.append(value)
|
||||||
|
return referrers, groups
|
||||||
|
|
||||||
|
|
||||||
|
def referrer_allowed(req, referrers):
|
||||||
|
allow = False
|
||||||
|
if referrers:
|
||||||
|
parts = req.referer.split('//', 1)
|
||||||
|
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
|
||||||
|
return allow
|
@@ -16,268 +16,79 @@
|
|||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from eventlet.timeout import Timeout
|
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 webob.exc import HTTPForbidden, HTTPUnauthorized
|
||||||
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||||
|
from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
|
||||||
from swift.common.utils import cache_from_env, split_path
|
from swift.common.utils import cache_from_env, split_path
|
||||||
|
|
||||||
|
|
||||||
class DevAuthorization(object):
|
class DevAuth(object):
|
||||||
|
|
||||||
def __init__(self, app, conf):
|
def __init__(self, app, conf):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.conf = 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:
|
|
||||||
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_host = conf.get('ip', '127.0.0.1')
|
||||||
self.auth_port = int(conf.get('port', 11000))
|
self.auth_port = int(conf.get('port', 11000))
|
||||||
self.ssl = \
|
self.ssl = \
|
||||||
conf.get('ssl', 'false').lower() in ('true', 'on', '1', 'yes')
|
conf.get('ssl', 'false').lower() in ('true', 'on', '1', 'yes')
|
||||||
self.timeout = int(conf.get('node_timeout', 10))
|
self.timeout = int(conf.get('node_timeout', 10))
|
||||||
|
|
||||||
def authenticate(self, env, identity):
|
def __call__(self, env, start_response):
|
||||||
token = identity.get('token')
|
user = None
|
||||||
if not token:
|
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
|
||||||
return None
|
if token:
|
||||||
memcache_client = cache_from_env(env)
|
memcache_client = cache_from_env(env)
|
||||||
key = 'devauth/%s' % token
|
key = 'devauth/%s' % token
|
||||||
cached_auth_data = memcache_client.get(key)
|
cached_auth_data = memcache_client.get(key)
|
||||||
if cached_auth_data:
|
if cached_auth_data:
|
||||||
start, expiration, user = cached_auth_data
|
start, expiration, user = cached_auth_data
|
||||||
if time() - start <= expiration:
|
if time() - start > expiration:
|
||||||
return user
|
user = None
|
||||||
|
if not user:
|
||||||
with Timeout(self.timeout):
|
with Timeout(self.timeout):
|
||||||
conn = http_connect(self.auth_host, self.auth_port, 'GET',
|
conn = http_connect(self.auth_host, self.auth_port, 'GET',
|
||||||
'/token/%s' % token, ssl=self.ssl)
|
'/token/%s' % token, ssl=self.ssl)
|
||||||
resp = conn.getresponse()
|
resp = conn.getresponse()
|
||||||
resp.read()
|
resp.read()
|
||||||
conn.close()
|
conn.close()
|
||||||
if resp.status == 204:
|
if resp.status // 100 != 2:
|
||||||
|
return HTTPUnauthorized()(env, start_response)
|
||||||
expiration = float(resp.getheader('x-auth-ttl'))
|
expiration = float(resp.getheader('x-auth-ttl'))
|
||||||
user = resp.getheader('x-auth-user')
|
user = resp.getheader('x-auth-user')
|
||||||
memcache_client.set(key, (time(), expiration, user),
|
memcache_client.set(key, (time(), expiration, user),
|
||||||
timeout=expiration)
|
timeout=expiration)
|
||||||
return user
|
env['REMOTE_USER'] = user
|
||||||
|
env['swift.authorize'] = self.authorize
|
||||||
|
env['swift.clean_acl'] = clean_acl
|
||||||
|
return self.app(env, 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)
|
||||||
|
if req.remote_user and account in req.remote_user.split(','):
|
||||||
return None
|
return None
|
||||||
|
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
||||||
|
if referrer_allowed(req, referrers):
|
||||||
|
return None
|
||||||
|
if not req.remote_user:
|
||||||
|
return self.denied_response(req)
|
||||||
|
for user_group in req.remote_user.split(','):
|
||||||
|
if user_group in groups:
|
||||||
|
return None
|
||||||
|
return self.denied_response(req)
|
||||||
|
|
||||||
|
def denied_response(self, req):
|
||||||
class DevChallenger(object):
|
if req.remote_user:
|
||||||
|
return HTTPForbidden(request=req)
|
||||||
def __init__(self, conf):
|
else:
|
||||||
self.conf = conf
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
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):
|
def filter_factory(global_conf, **local_conf):
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
def auth_filter(app):
|
def auth_filter(app):
|
||||||
return setup_auth(DevAuthorization(app, conf),
|
return DevAuth(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
|
return auth_filter
|
||||||
|
150
test/unit/common/middleware/test_acl.py
Normal file
150
test/unit/common/middleware/test_acl.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# Copyright (c) 2010 OpenStack, LLC.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
from webob import Request
|
||||||
|
|
||||||
|
from swift.common.middleware import acl
|
||||||
|
|
||||||
|
# mocks
|
||||||
|
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
|
||||||
|
|
||||||
|
class FakeMemcache(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.store = {}
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
return self.store.get(key)
|
||||||
|
|
||||||
|
def set(self, key, value, timeout=0):
|
||||||
|
self.store[key] = value
|
||||||
|
return True
|
||||||
|
|
||||||
|
def incr(self, key, timeout=0):
|
||||||
|
self.store[key] = self.store.setdefault(key, 0) + 1
|
||||||
|
return self.store[key]
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def soft_lock(self, key, timeout=0, retries=5):
|
||||||
|
yield True
|
||||||
|
|
||||||
|
def delete(self, key):
|
||||||
|
try:
|
||||||
|
del self.store[key]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def mock_http_connect(response, headers=None, with_exc=False):
|
||||||
|
class FakeConn(object):
|
||||||
|
def __init__(self, status, headers, with_exc):
|
||||||
|
self.status = status
|
||||||
|
self.reason = 'Fake'
|
||||||
|
self.host = '1.2.3.4'
|
||||||
|
self.port = '1234'
|
||||||
|
self.with_exc = with_exc
|
||||||
|
self.headers = headers
|
||||||
|
if self.headers is None:
|
||||||
|
self.headers = {}
|
||||||
|
def getresponse(self):
|
||||||
|
if self.with_exc:
|
||||||
|
raise Exception('test')
|
||||||
|
return self
|
||||||
|
def getheader(self, header):
|
||||||
|
return self.headers[header]
|
||||||
|
def read(self, amt=None):
|
||||||
|
return ''
|
||||||
|
def close(self):
|
||||||
|
return
|
||||||
|
return lambda *args, **kwargs: FakeConn(response, headers, with_exc)
|
||||||
|
|
||||||
|
|
||||||
|
class Logger(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.error_value = None
|
||||||
|
self.exception_value = None
|
||||||
|
def error(self, msg, *args, **kwargs):
|
||||||
|
self.error_value = (msg, args, kwargs)
|
||||||
|
def exception(self, msg, *args, **kwargs):
|
||||||
|
_, exc, _ = sys.exc_info()
|
||||||
|
self.exception_value = (msg,
|
||||||
|
'%s %s' % (exc.__class__.__name__, str(exc)), args, kwargs)
|
||||||
|
# tests
|
||||||
|
|
||||||
|
class FakeApp(object):
|
||||||
|
def __call__(self, env, start_response):
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
def start_response(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TestAuth(unittest.TestCase):
|
||||||
|
# I brought these over from another refactor I've been trying, but they
|
||||||
|
# need work.
|
||||||
|
|
||||||
|
def test_clean_acl(self):
|
||||||
|
value = acl.clean_acl('header', '.ref:any')
|
||||||
|
self.assertEquals(value, '.ref:any')
|
||||||
|
value = acl.clean_acl('header', '.ref:specific.host')
|
||||||
|
self.assertEquals(value, '.ref:specific.host')
|
||||||
|
value = acl.clean_acl('header', '.ref:.ending.with')
|
||||||
|
self.assertEquals(value, '.ref:.ending.with')
|
||||||
|
value = acl.clean_acl('header', '.ref:one,.ref:two')
|
||||||
|
self.assertEquals(value, '.ref:one,.ref:two')
|
||||||
|
value = acl.clean_acl('header', '.ref:any,.ref:-specific.host')
|
||||||
|
self.assertEquals(value, '.ref:any,.ref:-specific.host')
|
||||||
|
value = acl.clean_acl('header', '.ref:any,.ref:-.ending.with')
|
||||||
|
self.assertEquals(value, '.ref:any,.ref:-.ending.with')
|
||||||
|
value = acl.clean_acl('header', '.ref:one,.ref:-two')
|
||||||
|
self.assertEquals(value, '.ref:one,.ref:-two')
|
||||||
|
value = acl.clean_acl('header',
|
||||||
|
' .ref : one , ,, .ref:two , .ref : - three ')
|
||||||
|
self.assertEquals(value, '.ref:one,.ref:two,.ref:-three')
|
||||||
|
self.assertRaises(ValueError, acl.clean_acl, 'header', '.ref:')
|
||||||
|
self.assertRaises(ValueError, acl.clean_acl, 'header', ' .ref : ')
|
||||||
|
self.assertRaises(ValueError, acl.clean_acl, 'header',
|
||||||
|
'user , .ref : ')
|
||||||
|
self.assertRaises(ValueError, acl.clean_acl, 'header', '.ref:-')
|
||||||
|
self.assertRaises(ValueError, acl.clean_acl, 'header', ' .ref : - ')
|
||||||
|
self.assertRaises(ValueError, acl.clean_acl, 'header',
|
||||||
|
'user , .ref : - ')
|
||||||
|
|
||||||
|
def test_parse_acl(self):
|
||||||
|
self.assertEquals(acl.parse_acl(None), ([], []))
|
||||||
|
self.assertEquals(acl.parse_acl(''), ([], []))
|
||||||
|
self.assertEquals(acl.parse_acl('.ref:ref1'), (['ref1'], []))
|
||||||
|
self.assertEquals(acl.parse_acl('.ref:-ref1'), (['-ref1'], []))
|
||||||
|
self.assertEquals(acl.parse_acl('account:user'),
|
||||||
|
([], ['account:user']))
|
||||||
|
self.assertEquals(acl.parse_acl('account'), ([], ['account']))
|
||||||
|
self.assertEquals(acl.parse_acl('acc1,acc2:usr2,.ref:ref3,.ref:-ref4'),
|
||||||
|
(['ref3', '-ref4'], ['acc1', 'acc2:usr2']))
|
||||||
|
self.assertEquals(acl.parse_acl(
|
||||||
|
'acc1,acc2:usr2,.ref:ref3,acc3,acc4:usr4,.ref:ref5,.ref:-ref6'),
|
||||||
|
(['ref3', 'ref5', '-ref6'],
|
||||||
|
['acc1', 'acc2:usr2', 'acc3', 'acc4:usr4']))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@@ -101,57 +101,7 @@ def start_response(*args):
|
|||||||
|
|
||||||
class TestAuth(unittest.TestCase):
|
class TestAuth(unittest.TestCase):
|
||||||
# TODO: With the auth refactor, these tests have to be refactored as well.
|
# 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
|
pass
|
||||||
# also need work.
|
|
||||||
|
|
||||||
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_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__':
|
if __name__ == '__main__':
|
||||||
|
Reference in New Issue
Block a user