Adding CORS support

Change-Id: I894473994cdfea0996ad16e7619aff421f604abc
This commit is contained in:
Scott Simpson 2012-10-11 16:52:26 -05:00
parent 8cacf5aaf8
commit 74b27d504d
10 changed files with 341 additions and 68 deletions

View File

@ -529,6 +529,13 @@ cert_file Path to the ssl .crt. This
key_file Path to the ssl .key. This key_file Path to the ssl .key. This
should be enabled for testing should be enabled for testing
purposes only. purposes only.
cors_allow_origin This is a list of hosts that
are included with any CORS
request by default and
returned with the
Access-Control-Allow-Origin
header in addition to what
the container has set.
============================ =============== ============================= ============================ =============== =============================
[proxy-server] [proxy-server]

View File

@ -482,3 +482,12 @@ folks a start on their own code if they want to use repoze.what::
authenticators=[('devauth', DevAuthenticator(conf))], authenticators=[('devauth', DevAuthenticator(conf))],
challengers=[('devauth', DevChallenger(conf))]) challengers=[('devauth', DevChallenger(conf))])
return auth_filter return auth_filter
-----------------------
Allowing CORS with Auth
-----------------------
Cross Origin RequestS require that the auth system allow the OPTIONS method to
pass through without a token. The preflight request will make an OPTIONS call
against the object or container and will not work if the auth system stops it.
See TempAuth for an example of how OPTIONS requests are handled.

View File

@ -171,3 +171,35 @@ Proxy Logging
.. automodule:: swift.common.middleware.proxy_logging .. automodule:: swift.common.middleware.proxy_logging
:members: :members:
:show-inheritance: :show-inheritance:
CORS Headers
============
Cross Origin RequestS or CORS allows the browser to make requests against
Swift from another origin via the browser. This enables the use of HTML5
forms and javascript uploads to swift. The owner of a container can set
three headers:
+---------------------------------------------+-------------------------------+
|Metadata | Use |
+=============================================+===============================+
|X-Container-Meta-Access-Control-Allow-Origin | Origins to be allowed to |
| | make Cross Origin Requests, |
| | space separated |
+---------------------------------------------+-------------------------------+
|X-Container-Meta-Access-Control-Max-Age | Max age for the Origin to |
| | hold the preflight results. |
+---------------------------------------------+-------------------------------+
|X-Container-Meta-Access-Control-Allow-Headers| Headers to be allowed in |
| | actual request by browser. |
+---------------------------------------------+-------------------------------+
When the browser does a request it can issue a preflight request. The
preflight request is the OPTIONS call that verifies the Origin is allowed
to make the request.
* Browser makes OPTIONS request to Swift
* Swift returns 200/401 to browser based on allowed origins
* If 200, browser makes PUT, POST, DELETE, HEAD, GET request to Swift
CORS should be used in conjunction with TempURL and FormPost.

View File

@ -39,6 +39,8 @@ Additionally, if the auth system sets the request environ's swift_owner key to
True, the proxy will return additional header information in some requests, True, the proxy will return additional header information in some requests,
such as the X-Container-Sync-Key for a container GET or HEAD. such as the X-Container-Sync-Key for a container GET or HEAD.
TempAuth will now allow OPTIONS requests to go through without a token.
The user starts a session by sending a ReST request to the auth system to The user starts a session by sending a ReST request to the auth system to
receive the auth token and a URL to the Swift system. receive the auth token and a URL to the Swift system.

View File

@ -27,6 +27,8 @@
# log_statsd_port = 8125 # log_statsd_port = 8125
# log_statsd_default_sample_rate = 1 # log_statsd_default_sample_rate = 1
# log_statsd_metric_prefix = # log_statsd_metric_prefix =
# Use a comma separated list of full url (http://foo.bar:1234,https://foo.bar)
# cors_allow_origin =
[pipeline:main] [pipeline:main]
pipeline = catch_errors healthcheck cache ratelimit tempauth proxy-logging proxy-server pipeline = catch_errors healthcheck cache ratelimit tempauth proxy-logging proxy-server

View File

@ -84,7 +84,8 @@ class TempAuth(object):
if self.auth_prefix[-1] != '/': if self.auth_prefix[-1] != '/':
self.auth_prefix += '/' self.auth_prefix += '/'
self.token_life = int(conf.get('token_life', 86400)) self.token_life = int(conf.get('token_life', 86400))
self.allowed_sync_hosts = [h.strip() self.allowed_sync_hosts = [
h.strip()
for h in conf.get('allowed_sync_hosts', '127.0.0.1').split(',') for h in conf.get('allowed_sync_hosts', '127.0.0.1').split(',')
if h.strip()] if h.strip()]
self.allow_overrides = \ self.allow_overrides = \
@ -244,6 +245,7 @@ class TempAuth(object):
Returns None if the request is authorized to continue or a standard Returns None if the request is authorized to continue or a standard
WSGI response callable if not. WSGI response callable if not.
""" """
try: try:
version, account, container, obj = split_path(req.path, 1, 4, True) version, account, container, obj = split_path(req.path, 1, 4, True)
except ValueError: except ValueError:
@ -270,6 +272,9 @@ class TempAuth(object):
(req.remote_addr in self.allowed_sync_hosts or (req.remote_addr in self.allowed_sync_hosts or
get_remote_client(req) in self.allowed_sync_hosts)): get_remote_client(req) in self.allowed_sync_hosts)):
return None return None
if req.method == 'OPTIONS':
#allow OPTIONS requests to proceed as normal
return None
referrers, groups = parse_acl(getattr(req, 'acl', None)) referrers, groups = parse_acl(getattr(req, 'acl', None))
if referrer_allowed(req.referer, referrers): if referrer_allowed(req.referer, referrers):
if obj or '.rlistings' in groups: if obj or '.rlistings' in groups:
@ -341,8 +346,11 @@ class TempAuth(object):
req.start_time = time() req.start_time = time()
handler = None handler = None
try: try:
version, account, user, _junk = split_path(req.path_info, version, account, user, _junk = split_path(
minsegs=1, maxsegs=4, rest_with_last=True) req.path_info,
minsegs=1,
maxsegs=4,
rest_with_last=True)
except ValueError: except ValueError:
self.logger.increment('errors') self.logger.increment('errors')
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
@ -464,8 +472,10 @@ class TempAuth(object):
memcache_client.set(memcache_user_key, token, memcache_client.set(memcache_user_key, token,
timeout=float(expires - time())) timeout=float(expires - time()))
return Response(request=req, return Response(request=req,
headers={'x-auth-token': token, 'x-storage-token': token, headers={
'x-storage-url': self.users[account_user]['url']}) 'x-auth-token': token,
'x-storage-token': token,
'x-storage-url': self.users[account_user]['url']})
def posthooklogger(self, env, req): def posthooklogger(self, env, req):
if not req.path.startswith(self.auth_prefix): if not req.path.startswith(self.auth_prefix):
@ -490,12 +500,13 @@ class TempAuth(object):
if getattr(req, 'client_disconnect', False) or \ if getattr(req, 'client_disconnect', False) or \
getattr(response, 'client_disconnect', False): getattr(response, 'client_disconnect', False):
status_int = HTTP_CLIENT_CLOSED_REQUEST status_int = HTTP_CLIENT_CLOSED_REQUEST
self.logger.info(' '.join(quote(str(x)) for x in (client or '-', self.logger.info(
' '.join(quote(str(x)) for x in (client or '-',
req.remote_addr or '-', strftime('%d/%b/%Y/%H/%M/%S', gmtime()), req.remote_addr or '-', strftime('%d/%b/%Y/%H/%M/%S', gmtime()),
req.method, the_request, req.environ['SERVER_PROTOCOL'], req.method, the_request, req.environ['SERVER_PROTOCOL'],
status_int, req.referer or '-', req.user_agent or '-', status_int, req.referer or '-', req.user_agent or '-',
req.headers.get('x-auth-token', req.headers.get('x-auth-token',
req.headers.get('x-auth-admin-user', '-')), req.headers.get('x-auth-admin-user', '-')),
getattr(req, 'bytes_transferred', 0) or '-', getattr(req, 'bytes_transferred', 0) or '-',
getattr(response, 'bytes_transferred', 0) or '-', getattr(response, 'bytes_transferred', 0) or '-',
req.headers.get('etag', '-'), req.headers.get('etag', '-'),

View File

@ -278,6 +278,25 @@ class Controller(object):
return partition, nodes, container_count return partition, nodes, container_count
return None, None, None return None, None, None
def headers_to_container_info(self, headers):
headers = dict(headers)
return {
'read_acl': headers.get('x-container-read'),
'write_acl': headers.get('x-container-write'),
'sync_key': headers.get('x-container-sync-key'),
'count': headers.get('x-container-object-count'),
'bytes': headers.get('x-container-bytes-used'),
'versions': headers.get('x-versions-location'),
'cors': {
'allow_origin': headers.get(
'x-container-meta-access-control-allow-origin'),
'allow_headers': headers.get(
'x-container-meta-access-control-allow-headers'),
'max_age': headers.get(
'x-container-meta-access-control-max-age')
}
}
def container_info(self, account, container, account_autocreate=False): def container_info(self, account, container, account_autocreate=False):
""" """
Get container information and thusly verify container existance. Get container information and thusly verify container existance.
@ -324,14 +343,9 @@ class Controller(object):
resp = conn.getresponse() resp = conn.getresponse()
body = resp.read() body = resp.read()
if is_success(resp.status): if is_success(resp.status):
container_info.update({ container_info.update(
'status': HTTP_OK, self.headers_to_container_info(resp.getheaders()))
'read_acl': resp.getheader('x-container-read'), container_info['status'] = HTTP_OK
'write_acl': resp.getheader('x-container-write'),
'sync_key': resp.getheader('x-container-sync-key'),
'count': resp.getheader('x-container-object-count'),
'bytes': resp.getheader('x-container-bytes-used'),
'versions': resp.getheader('x-versions-location')})
break break
elif resp.status == HTTP_NOT_FOUND: elif resp.status == HTTP_NOT_FOUND:
container_info['status'] = HTTP_NOT_FOUND container_info['status'] = HTTP_NOT_FOUND
@ -661,3 +675,37 @@ class Controller(object):
return res return res
return self.best_response(req, statuses, reasons, bodies, return self.best_response(req, statuses, reasons, bodies,
'%s %s' % (server_type, req.method)) '%s %s' % (server_type, req.method))
def OPTIONS_base(self, req):
"""
Base handler for OPTIONS requests
:param req: swob.Request object
:returns: swob.Response object
"""
container_info = \
self.container_info(self.account_name, self.container_name)
cors = container_info.get('cors', {})
allowed_origins = set()
if cors.get('allow_origin'):
allowed_origins.update(cors['allow_origin'].split(' '))
if self.app.cors_allow_origin:
allowed_origins.update(self.app.cors_allow_origin)
if not allowed_origins:
return Response(status=401, request=req)
headers = {}
if req.headers.get('Origin') in allowed_origins \
or '*' in allowed_origins:
headers['access-control-allow-origin'] = ' '.join(allowed_origins)
headers['access-control-max-age'] = cors.get('max_age')
headers['access-control-allow-methods'] = \
'GET, POST, PUT, DELETE, HEAD'
headers['access-control-allow-headers'] = \
cors.get('allow_headers')
return Response(status=200, headers=headers, request=req)
else:
return Response(status=401, request=req)
@public
def OPTIONS(self, req):
return self.OPTIONS_base(req)

View File

@ -79,9 +79,9 @@ class Application(object):
self.resellers_conf.read(os.path.join(swift_dir, 'resellers.conf')) self.resellers_conf.read(os.path.join(swift_dir, 'resellers.conf'))
self.object_ring = object_ring or Ring(swift_dir, ring_name='object') self.object_ring = object_ring or Ring(swift_dir, ring_name='object')
self.container_ring = container_ring or Ring(swift_dir, self.container_ring = container_ring or Ring(swift_dir,
ring_name='container') ring_name='container')
self.account_ring = account_ring or Ring(swift_dir, self.account_ring = account_ring or Ring(swift_dir,
ring_name='account') ring_name='account')
self.memcache = memcache self.memcache = memcache
mimetypes.init(mimetypes.knownfiles + mimetypes.init(mimetypes.knownfiles +
[os.path.join(swift_dir, 'mime.types')]) [os.path.join(swift_dir, 'mime.types')])
@ -94,10 +94,12 @@ class Application(object):
int(conf.get('expiring_objects_container_divisor') or 86400) int(conf.get('expiring_objects_container_divisor') or 86400)
self.max_containers_per_account = \ self.max_containers_per_account = \
int(conf.get('max_containers_per_account') or 0) int(conf.get('max_containers_per_account') or 0)
self.max_containers_whitelist = [a.strip() self.max_containers_whitelist = [
a.strip()
for a in conf.get('max_containers_whitelist', '').split(',') for a in conf.get('max_containers_whitelist', '').split(',')
if a.strip()] if a.strip()]
self.deny_host_headers = [host.strip() for host in self.deny_host_headers = [
host.strip() for host in
conf.get('deny_host_headers', '').split(',') if host.strip()] conf.get('deny_host_headers', '').split(',') if host.strip()]
self.rate_limit_after_segment = \ self.rate_limit_after_segment = \
int(conf.get('rate_limit_after_segment', 10)) int(conf.get('rate_limit_after_segment', 10))
@ -105,6 +107,10 @@ class Application(object):
int(conf.get('rate_limit_segments_per_sec', 1)) int(conf.get('rate_limit_segments_per_sec', 1))
self.log_handoffs = \ self.log_handoffs = \
conf.get('log_handoffs', 'true').lower() in TRUE_VALUES conf.get('log_handoffs', 'true').lower() in TRUE_VALUES
self.cors_allow_origin = [
a.strip()
for a in conf.get('cors_allow_origin', '').split(',')
if a.strip()]
def get_controller(self, path): def get_controller(self, path):
""" """
@ -117,9 +123,9 @@ class Application(object):
""" """
version, account, container, obj = split_path(path, 1, 4, True) version, account, container, obj = split_path(path, 1, 4, True)
d = dict(version=version, d = dict(version=version,
account_name=account, account_name=account,
container_name=container, container_name=container,
object_name=obj) object_name=obj)
if obj and container and account: if obj and container and account:
return ObjectController, d return ObjectController, d
elif container and account: elif container and account:
@ -146,7 +152,7 @@ class Application(object):
return err(env, start_response) return err(env, start_response)
except (Exception, Timeout): except (Exception, Timeout):
start_response('500 Server Error', start_response('500 Server Error',
[('Content-Type', 'text/plain')]) [('Content-Type', 'text/plain')])
return ['Internal server error.\n'] return ['Internal server error.\n']
def update_request(self, req): def update_request(self, req):

View File

@ -13,13 +13,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
try:
import simplejson as json
except ImportError:
import json
import unittest import unittest
from contextlib import contextmanager from contextlib import contextmanager
from time import time
from base64 import b64encode from base64 import b64encode
from swift.common.middleware import tempauth as auth from swift.common.middleware import tempauth as auth
@ -181,7 +176,7 @@ class TestAuth(unittest.TestCase):
def test_auth_deny_non_reseller_prefix(self): def test_auth_deny_non_reseller_prefix(self):
req = self._make_request('/v1/BLAH_account', req = self._make_request('/v1/BLAH_account',
headers={'X-Auth-Token': 'BLAH_t'}) headers={'X-Auth-Token': 'BLAH_t'})
resp = req.get_response(self.test_auth) resp = req.get_response(self.test_auth)
self.assertEquals(resp.status_int, 401) self.assertEquals(resp.status_int, 401)
self.assertEquals(req.environ['swift.authorize'], self.assertEquals(req.environ['swift.authorize'],
@ -190,9 +185,9 @@ class TestAuth(unittest.TestCase):
def test_auth_deny_non_reseller_prefix_no_override(self): def test_auth_deny_non_reseller_prefix_no_override(self):
fake_authorize = lambda x: Response(status='500 Fake') fake_authorize = lambda x: Response(status='500 Fake')
req = self._make_request('/v1/BLAH_account', req = self._make_request('/v1/BLAH_account',
headers={'X-Auth-Token': 'BLAH_t'}, headers={'X-Auth-Token': 'BLAH_t'},
environ={'swift.authorize': fake_authorize} environ={'swift.authorize': fake_authorize}
) )
resp = req.get_response(self.test_auth) resp = req.get_response(self.test_auth)
self.assertEquals(resp.status_int, 500) self.assertEquals(resp.status_int, 500)
self.assertEquals(req.environ['swift.authorize'], fake_authorize) self.assertEquals(req.environ['swift.authorize'], fake_authorize)
@ -204,7 +199,7 @@ class TestAuth(unittest.TestCase):
local_app = FakeApp() local_app = FakeApp()
local_auth = auth.filter_factory({'reseller_prefix': ''})(local_app) local_auth = auth.filter_factory({'reseller_prefix': ''})(local_app)
req = self._make_request('/v1/account', req = self._make_request('/v1/account',
headers={'X-Auth-Token': 't'}) headers={'X-Auth-Token': 't'})
resp = req.get_response(local_auth) resp = req.get_response(local_auth)
self.assertEquals(resp.status_int, 401) self.assertEquals(resp.status_int, 401)
self.assertEquals(local_app.calls, 1) self.assertEquals(local_app.calls, 1)
@ -226,13 +221,14 @@ class TestAuth(unittest.TestCase):
auth.filter_factory({'reseller_prefix': ''})(FakeApp()) auth.filter_factory({'reseller_prefix': ''})(FakeApp())
local_authorize = lambda req: Response('test') local_authorize = lambda req: Response('test')
req = self._make_request('/v1/account', environ={'swift.authorize': req = self._make_request('/v1/account', environ={'swift.authorize':
local_authorize}) local_authorize})
resp = req.get_response(local_auth) resp = req.get_response(local_auth)
self.assertEquals(resp.status_int, 200) self.assertEquals(resp.status_int, 200)
self.assertEquals(req.environ['swift.authorize'], local_authorize) self.assertEquals(req.environ['swift.authorize'], local_authorize)
def test_auth_fail(self): def test_auth_fail(self):
resp = self._make_request('/v1/AUTH_cfa', resp = self._make_request(
'/v1/AUTH_cfa',
headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth)
self.assertEquals(resp.status_int, 401) self.assertEquals(resp.status_int, 401)
@ -331,26 +327,26 @@ class TestAuth(unittest.TestCase):
def test_account_put_permissions(self): def test_account_put_permissions(self):
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'PUT'}) environ={'REQUEST_METHOD': 'PUT'})
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp.status_int, 403) self.assertEquals(resp.status_int, 403)
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'PUT'}) environ={'REQUEST_METHOD': 'PUT'})
req.remote_user = 'act:usr,act,AUTH_other' req.remote_user = 'act:usr,act,AUTH_other'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp.status_int, 403) self.assertEquals(resp.status_int, 403)
# Even PUTs to your own account as account admin should fail # Even PUTs to your own account as account admin should fail
req = self._make_request('/v1/AUTH_old', req = self._make_request('/v1/AUTH_old',
environ={'REQUEST_METHOD': 'PUT'}) environ={'REQUEST_METHOD': 'PUT'})
req.remote_user = 'act:usr,act,AUTH_old' req.remote_user = 'act:usr,act,AUTH_old'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp.status_int, 403) self.assertEquals(resp.status_int, 403)
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'PUT'}) environ={'REQUEST_METHOD': 'PUT'})
req.remote_user = 'act:usr,act,.reseller_admin' req.remote_user = 'act:usr,act,.reseller_admin'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp, None) self.assertEquals(resp, None)
@ -358,33 +354,33 @@ class TestAuth(unittest.TestCase):
# .super_admin is not something the middleware should ever see or care # .super_admin is not something the middleware should ever see or care
# about # about
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'PUT'}) environ={'REQUEST_METHOD': 'PUT'})
req.remote_user = 'act:usr,act,.super_admin' req.remote_user = 'act:usr,act,.super_admin'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp.status_int, 403) self.assertEquals(resp.status_int, 403)
def test_account_delete_permissions(self): def test_account_delete_permissions(self):
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'DELETE'}) environ={'REQUEST_METHOD': 'DELETE'})
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp.status_int, 403) self.assertEquals(resp.status_int, 403)
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'DELETE'}) environ={'REQUEST_METHOD': 'DELETE'})
req.remote_user = 'act:usr,act,AUTH_other' req.remote_user = 'act:usr,act,AUTH_other'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp.status_int, 403) self.assertEquals(resp.status_int, 403)
# Even DELETEs to your own account as account admin should fail # Even DELETEs to your own account as account admin should fail
req = self._make_request('/v1/AUTH_old', req = self._make_request('/v1/AUTH_old',
environ={'REQUEST_METHOD': 'DELETE'}) environ={'REQUEST_METHOD': 'DELETE'})
req.remote_user = 'act:usr,act,AUTH_old' req.remote_user = 'act:usr,act,AUTH_old'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp.status_int, 403) self.assertEquals(resp.status_int, 403)
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'DELETE'}) environ={'REQUEST_METHOD': 'DELETE'})
req.remote_user = 'act:usr,act,.reseller_admin' req.remote_user = 'act:usr,act,.reseller_admin'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp, None) self.assertEquals(resp, None)
@ -392,7 +388,7 @@ class TestAuth(unittest.TestCase):
# .super_admin is not something the middleware should ever see or care # .super_admin is not something the middleware should ever see or care
# about # about
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'DELETE'}) environ={'REQUEST_METHOD': 'DELETE'})
req.remote_user = 'act:usr,act,.super_admin' req.remote_user = 'act:usr,act,.super_admin'
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertEquals(resp.status_int, 403) self.assertEquals(resp.status_int, 403)
@ -400,41 +396,48 @@ class TestAuth(unittest.TestCase):
def test_get_token_fail(self): def test_get_token_fail(self):
resp = self._make_request('/auth/v1.0').get_response(self.test_auth) resp = self._make_request('/auth/v1.0').get_response(self.test_auth)
self.assertEquals(resp.status_int, 401) self.assertEquals(resp.status_int, 401)
resp = self._make_request('/auth/v1.0', resp = self._make_request(
'/auth/v1.0',
headers={'X-Auth-User': 'act:usr', headers={'X-Auth-User': 'act:usr',
'X-Auth-Key': 'key'}).get_response(self.test_auth) 'X-Auth-Key': 'key'}).get_response(self.test_auth)
self.assertEquals(resp.status_int, 401) self.assertEquals(resp.status_int, 401)
def test_get_token_fail_invalid_x_auth_user_format(self): def test_get_token_fail_invalid_x_auth_user_format(self):
resp = self._make_request('/auth/v1/act/auth', resp = self._make_request(
'/auth/v1/act/auth',
headers={'X-Auth-User': 'usr', headers={'X-Auth-User': 'usr',
'X-Auth-Key': 'key'}).get_response(self.test_auth) 'X-Auth-Key': 'key'}).get_response(self.test_auth)
self.assertEquals(resp.status_int, 401) self.assertEquals(resp.status_int, 401)
def test_get_token_fail_non_matching_account_in_request(self): def test_get_token_fail_non_matching_account_in_request(self):
resp = self._make_request('/auth/v1/act/auth', resp = self._make_request(
'/auth/v1/act/auth',
headers={'X-Auth-User': 'act2:usr', headers={'X-Auth-User': 'act2:usr',
'X-Auth-Key': 'key'}).get_response(self.test_auth) 'X-Auth-Key': 'key'}).get_response(self.test_auth)
self.assertEquals(resp.status_int, 401) self.assertEquals(resp.status_int, 401)
def test_get_token_fail_bad_path(self): def test_get_token_fail_bad_path(self):
resp = self._make_request('/auth/v1/act/auth/invalid', resp = self._make_request(
'/auth/v1/act/auth/invalid',
headers={'X-Auth-User': 'act:usr', headers={'X-Auth-User': 'act:usr',
'X-Auth-Key': 'key'}).get_response(self.test_auth) 'X-Auth-Key': 'key'}).get_response(self.test_auth)
self.assertEquals(resp.status_int, 400) self.assertEquals(resp.status_int, 400)
def test_get_token_fail_missing_key(self): def test_get_token_fail_missing_key(self):
resp = self._make_request('/auth/v1/act/auth', resp = self._make_request(
'/auth/v1/act/auth',
headers={'X-Auth-User': 'act:usr'}).get_response(self.test_auth) headers={'X-Auth-User': 'act:usr'}).get_response(self.test_auth)
self.assertEquals(resp.status_int, 401) self.assertEquals(resp.status_int, 401)
def test_allowed_sync_hosts(self): def test_allowed_sync_hosts(self):
a = auth.filter_factory({'super_admin_key': 'supertest'})(FakeApp()) a = auth.filter_factory({'super_admin_key': 'supertest'})(FakeApp())
self.assertEquals(a.allowed_sync_hosts, ['127.0.0.1']) self.assertEquals(a.allowed_sync_hosts, ['127.0.0.1'])
a = auth.filter_factory({'super_admin_key': 'supertest', a = auth.filter_factory(
'allowed_sync_hosts': {'super_admin_key': 'supertest',
'allowed_sync_hosts':
'1.1.1.1,2.1.1.1, 3.1.1.1 , 4.1.1.1,, , 5.1.1.1'})(FakeApp()) '1.1.1.1,2.1.1.1, 3.1.1.1 , 4.1.1.1,, , 5.1.1.1'})(FakeApp())
self.assertEquals(a.allowed_sync_hosts, self.assertEquals(
a.allowed_sync_hosts,
['1.1.1.1', '2.1.1.1', '3.1.1.1', '4.1.1.1', '5.1.1.1']) ['1.1.1.1', '2.1.1.1', '3.1.1.1', '4.1.1.1', '5.1.1.1'])
def test_reseller_admin_is_owner(self): def test_reseller_admin_is_owner(self):
@ -449,7 +452,7 @@ class TestAuth(unittest.TestCase):
self.test_auth.authorize = mitm_authorize self.test_auth.authorize = mitm_authorize
req = self._make_request('/v1/AUTH_cfa', req = self._make_request('/v1/AUTH_cfa',
headers={'X-Auth-Token': 'AUTH_t'}) headers={'X-Auth-Token': 'AUTH_t'})
req.remote_user = '.reseller_admin' req.remote_user = '.reseller_admin'
self.test_auth.authorize(req) self.test_auth.authorize(req)
self.assertEquals(owner_values, [True]) self.assertEquals(owner_values, [True])
@ -465,8 +468,9 @@ class TestAuth(unittest.TestCase):
self.test_auth.authorize = mitm_authorize self.test_auth.authorize = mitm_authorize
req = self._make_request('/v1/AUTH_cfa', req = self._make_request(
headers={'X-Auth-Token': 'AUTH_t'}) '/v1/AUTH_cfa',
headers={'X-Auth-Token': 'AUTH_t'})
req.remote_user = 'AUTH_cfa' req.remote_user = 'AUTH_cfa'
self.test_auth.authorize(req) self.test_auth.authorize(req)
self.assertEquals(owner_values, [True]) self.assertEquals(owner_values, [True])
@ -482,8 +486,9 @@ class TestAuth(unittest.TestCase):
self.test_auth.authorize = mitm_authorize self.test_auth.authorize = mitm_authorize
req = self._make_request('/v1/AUTH_cfa/c', req = self._make_request(
headers={'X-Auth-Token': 'AUTH_t'}) '/v1/AUTH_cfa/c',
headers={'X-Auth-Token': 'AUTH_t'})
req.remote_user = 'act:usr' req.remote_user = 'act:usr'
self.test_auth.authorize(req) self.test_auth.authorize(req)
self.assertEquals(owner_values, [False]) self.assertEquals(owner_values, [False])
@ -491,7 +496,8 @@ class TestAuth(unittest.TestCase):
def test_sync_request_success(self): def test_sync_request_success(self):
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
sync_key='secret') sync_key='secret')
req = self._make_request('/v1/AUTH_cfa/c/o', req = self._make_request(
'/v1/AUTH_cfa/c/o',
environ={'REQUEST_METHOD': 'DELETE'}, environ={'REQUEST_METHOD': 'DELETE'},
headers={'x-container-sync-key': 'secret', headers={'x-container-sync-key': 'secret',
'x-timestamp': '123.456'}) 'x-timestamp': '123.456'})
@ -502,7 +508,8 @@ class TestAuth(unittest.TestCase):
def test_sync_request_fail_key(self): def test_sync_request_fail_key(self):
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
sync_key='secret') sync_key='secret')
req = self._make_request('/v1/AUTH_cfa/c/o', req = self._make_request(
'/v1/AUTH_cfa/c/o',
environ={'REQUEST_METHOD': 'DELETE'}, environ={'REQUEST_METHOD': 'DELETE'},
headers={'x-container-sync-key': 'wrongsecret', headers={'x-container-sync-key': 'wrongsecret',
'x-timestamp': '123.456'}) 'x-timestamp': '123.456'})
@ -512,7 +519,8 @@ class TestAuth(unittest.TestCase):
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
sync_key='othersecret') sync_key='othersecret')
req = self._make_request('/v1/AUTH_cfa/c/o', req = self._make_request(
'/v1/AUTH_cfa/c/o',
environ={'REQUEST_METHOD': 'DELETE'}, environ={'REQUEST_METHOD': 'DELETE'},
headers={'x-container-sync-key': 'secret', headers={'x-container-sync-key': 'secret',
'x-timestamp': '123.456'}) 'x-timestamp': '123.456'})
@ -522,7 +530,8 @@ class TestAuth(unittest.TestCase):
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
sync_key=None) sync_key=None)
req = self._make_request('/v1/AUTH_cfa/c/o', req = self._make_request(
'/v1/AUTH_cfa/c/o',
environ={'REQUEST_METHOD': 'DELETE'}, environ={'REQUEST_METHOD': 'DELETE'},
headers={'x-container-sync-key': 'secret', headers={'x-container-sync-key': 'secret',
'x-timestamp': '123.456'}) 'x-timestamp': '123.456'})
@ -533,7 +542,8 @@ class TestAuth(unittest.TestCase):
def test_sync_request_fail_no_timestamp(self): def test_sync_request_fail_no_timestamp(self):
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
sync_key='secret') sync_key='secret')
req = self._make_request('/v1/AUTH_cfa/c/o', req = self._make_request(
'/v1/AUTH_cfa/c/o',
environ={'REQUEST_METHOD': 'DELETE'}, environ={'REQUEST_METHOD': 'DELETE'},
headers={'x-container-sync-key': 'secret'}) headers={'x-container-sync-key': 'secret'})
req.remote_addr = '127.0.0.1' req.remote_addr = '127.0.0.1'
@ -543,7 +553,8 @@ class TestAuth(unittest.TestCase):
def test_sync_request_fail_sync_host(self): def test_sync_request_fail_sync_host(self):
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
sync_key='secret') sync_key='secret')
req = self._make_request('/v1/AUTH_cfa/c/o', req = self._make_request(
'/v1/AUTH_cfa/c/o',
environ={'REQUEST_METHOD': 'DELETE'}, environ={'REQUEST_METHOD': 'DELETE'},
headers={'x-container-sync-key': 'secret', headers={'x-container-sync-key': 'secret',
'x-timestamp': '123.456'}) 'x-timestamp': '123.456'})
@ -554,7 +565,8 @@ class TestAuth(unittest.TestCase):
def test_sync_request_success_lb_sync_host(self): def test_sync_request_success_lb_sync_host(self):
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
sync_key='secret') sync_key='secret')
req = self._make_request('/v1/AUTH_cfa/c/o', req = self._make_request(
'/v1/AUTH_cfa/c/o',
environ={'REQUEST_METHOD': 'DELETE'}, environ={'REQUEST_METHOD': 'DELETE'},
headers={'x-container-sync-key': 'secret', headers={'x-container-sync-key': 'secret',
'x-timestamp': '123.456', 'x-timestamp': '123.456',
@ -565,7 +577,8 @@ class TestAuth(unittest.TestCase):
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
sync_key='secret') sync_key='secret')
req = self._make_request('/v1/AUTH_cfa/c/o', req = self._make_request(
'/v1/AUTH_cfa/c/o',
environ={'REQUEST_METHOD': 'DELETE'}, environ={'REQUEST_METHOD': 'DELETE'},
headers={'x-container-sync-key': 'secret', headers={'x-container-sync-key': 'secret',
'x-timestamp': '123.456', 'x-timestamp': '123.456',
@ -574,6 +587,12 @@ class TestAuth(unittest.TestCase):
resp = req.get_response(self.test_auth) resp = req.get_response(self.test_auth)
self.assertEquals(resp.status_int, 204) self.assertEquals(resp.status_int, 204)
def test_options_call(self):
req = self._make_request('/v1/AUTH_cfa/c/o',
environ={'REQUEST_METHOD': 'OPTIONS'})
resp = self.test_auth.authorize(req)
self.assertEquals(resp, None)
class TestParseUserCreation(unittest.TestCase): class TestParseUserCreation(unittest.TestCase):
def test_parse_user_creation(self): def test_parse_user_creation(self):
@ -607,11 +626,11 @@ class TestParseUserCreation(unittest.TestCase):
'user64_%s_%s' % ( 'user64_%s_%s' % (
b64encode('test').rstrip('='), b64encode('test').rstrip('='),
b64encode('tester3').rstrip('=')): b64encode('tester3').rstrip('=')):
'testing .reseller_admin', 'testing .reseller_admin',
'user64_%s_%s' % ( 'user64_%s_%s' % (
b64encode('user_foo').rstrip('='), b64encode('user_foo').rstrip('='),
b64encode('ab').rstrip('=')): b64encode('ab').rstrip('=')):
'urlly .admin http://a.b/v1/DEF_has', 'urlly .admin http://a.b/v1/DEF_has',
})(FakeApp()) })(FakeApp())
self.assertEquals(auth_filter.users, { self.assertEquals(auth_filter.users, {
'test:tester3': { 'test:tester3': {

View File

@ -3403,6 +3403,75 @@ class TestObjectController(unittest.TestCase):
sock.close() sock.close()
self.assertEquals(before_request_instances, _request_instances) self.assertEquals(before_request_instances, _request_instances)
def test_OPTIONS(self):
with save_globals():
controller = proxy_server.ObjectController(self.app, 'a',
'c', 'o.jpg')
def my_empty_container_info(*args):
return {}
controller.container_info = my_empty_container_info
req = Request.blank(
'/a/c/o.jpg',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'http://foo.com'})
resp = controller.OPTIONS(req)
self.assertEquals(401, resp.status_int)
def my_empty_origin_container_info(*args):
return {'cors': {'allow_origin': None}}
controller.container_info = my_empty_origin_container_info
req = Request.blank(
'/a/c/o.jpg',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'http://foo.com'})
resp = controller.OPTIONS(req)
self.assertEquals(401, resp.status_int)
def my_container_info(*args):
return {
'cors': {
'allow_origin': 'http://foo.bar:8080 https://foo.bar',
'allow_headers': 'x-foo',
'max_age': 999,
}
}
controller.container_info = my_container_info
req = Request.blank(
'/a/c/o.jpg',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'https://foo.bar'})
req.content_length = 0
resp = controller.OPTIONS(req)
self.assertEquals(200, resp.status_int)
self.assertEquals(
set(['http://foo.bar:8080', 'https://foo.bar']),
set(resp.headers['access-control-allow-origin'].split()))
self.assertEquals(
'GET, POST, PUT, DELETE, HEAD',
resp.headers['access-control-allow-methods'])
self.assertEquals('999', resp.headers['access-control-max-age'])
self.assertEquals(
'x-foo',
resp.headers['access-control-allow-headers'])
req = Request.blank('/a/c/o.jpg', {'REQUEST_METHOD': 'OPTIONS'})
req.content_length = 0
resp = controller.OPTIONS(req)
self.assertEquals(401, resp.status_int)
req = Request.blank(
'/a/c/o.jpg',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'http://foo.com'})
resp = controller.OPTIONS(req)
self.assertEquals(401, resp.status_int)
req = Request.blank(
'/a/c/o.jpg',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'http://foo.bar'})
controller.app.cors_allow_origin = ['http://foo.bar', ]
resp = controller.OPTIONS(req)
self.assertEquals(200, resp.status_int)
class TestContainerController(unittest.TestCase): class TestContainerController(unittest.TestCase):
"Test swift.proxy_server.ContainerController" "Test swift.proxy_server.ContainerController"
@ -3892,6 +3961,74 @@ class TestContainerController(unittest.TestCase):
res = controller.HEAD(req) res = controller.HEAD(req)
self.assert_(called[0]) self.assert_(called[0])
def test_OPTIONS(self):
with save_globals():
controller = proxy_server.ContainerController(self.app, 'a', 'c')
def my_empty_container_info(*args):
return {}
controller.container_info = my_empty_container_info
req = Request.blank(
'/a/c',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'http://foo.com'})
resp = controller.OPTIONS(req)
self.assertEquals(401, resp.status_int)
def my_empty_origin_container_info(*args):
return {'cors': {'allow_origin': None}}
controller.container_info = my_empty_origin_container_info
req = Request.blank(
'/a/c',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'http://foo.com'})
resp = controller.OPTIONS(req)
self.assertEquals(401, resp.status_int)
def my_container_info(*args):
return {
'cors': {
'allow_origin': 'http://foo.bar:8080 https://foo.bar',
'allow_headers': 'x-foo',
'max_age': 999,
}
}
controller.container_info = my_container_info
req = Request.blank(
'/a/c',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'https://foo.bar'})
req.content_length = 0
resp = controller.OPTIONS(req)
self.assertEquals(200, resp.status_int)
self.assertEquals(
set(['http://foo.bar:8080', 'https://foo.bar']),
set(resp.headers['access-control-allow-origin'].split()))
self.assertEquals(
'GET, POST, PUT, DELETE, HEAD',
resp.headers['access-control-allow-methods'])
self.assertEquals('999', resp.headers['access-control-max-age'])
self.assertEquals(
'x-foo',
resp.headers['access-control-allow-headers'])
req = Request.blank('/a/c', {'REQUEST_METHOD': 'OPTIONS'})
req.content_length = 0
resp = controller.OPTIONS(req)
self.assertEquals(401, resp.status_int)
req = Request.blank(
'/a/c',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'http://foo.bar'})
resp = controller.OPTIONS(req)
self.assertEquals(401, resp.status_int)
req = Request.blank(
'/a/c',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'http://foo.bar'})
controller.app.cors_allow_origin = ['http://foo.bar', ]
resp = controller.OPTIONS(req)
self.assertEquals(200, resp.status_int)
class TestAccountController(unittest.TestCase): class TestAccountController(unittest.TestCase):