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
should be enabled for testing
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]

View File

@ -482,3 +482,12 @@ folks a start on their own code if they want to use repoze.what::
authenticators=[('devauth', DevAuthenticator(conf))],
challengers=[('devauth', DevChallenger(conf))])
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
:members:
: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,
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
receive the auth token and a URL to the Swift system.

View File

@ -27,6 +27,8 @@
# log_statsd_port = 8125
# log_statsd_default_sample_rate = 1
# 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 = catch_errors healthcheck cache ratelimit tempauth proxy-logging proxy-server

View File

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

View File

@ -278,6 +278,25 @@ class Controller(object):
return partition, nodes, container_count
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):
"""
Get container information and thusly verify container existance.
@ -324,14 +343,9 @@ class Controller(object):
resp = conn.getresponse()
body = resp.read()
if is_success(resp.status):
container_info.update({
'status': HTTP_OK,
'read_acl': resp.getheader('x-container-read'),
'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')})
container_info.update(
self.headers_to_container_info(resp.getheaders()))
container_info['status'] = HTTP_OK
break
elif resp.status == HTTP_NOT_FOUND:
container_info['status'] = HTTP_NOT_FOUND
@ -661,3 +675,37 @@ class Controller(object):
return res
return self.best_response(req, statuses, reasons, bodies,
'%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.object_ring = object_ring or Ring(swift_dir, ring_name='object')
self.container_ring = container_ring or Ring(swift_dir,
ring_name='container')
ring_name='container')
self.account_ring = account_ring or Ring(swift_dir,
ring_name='account')
ring_name='account')
self.memcache = memcache
mimetypes.init(mimetypes.knownfiles +
[os.path.join(swift_dir, 'mime.types')])
@ -94,10 +94,12 @@ class Application(object):
int(conf.get('expiring_objects_container_divisor') or 86400)
self.max_containers_per_account = \
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(',')
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()]
self.rate_limit_after_segment = \
int(conf.get('rate_limit_after_segment', 10))
@ -105,6 +107,10 @@ class Application(object):
int(conf.get('rate_limit_segments_per_sec', 1))
self.log_handoffs = \
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):
"""
@ -117,9 +123,9 @@ class Application(object):
"""
version, account, container, obj = split_path(path, 1, 4, True)
d = dict(version=version,
account_name=account,
container_name=container,
object_name=obj)
account_name=account,
container_name=container,
object_name=obj)
if obj and container and account:
return ObjectController, d
elif container and account:
@ -146,7 +152,7 @@ class Application(object):
return err(env, start_response)
except (Exception, Timeout):
start_response('500 Server Error',
[('Content-Type', 'text/plain')])
[('Content-Type', 'text/plain')])
return ['Internal server error.\n']
def update_request(self, req):

View File

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

View File

@ -3403,6 +3403,75 @@ class TestObjectController(unittest.TestCase):
sock.close()
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):
"Test swift.proxy_server.ContainerController"
@ -3892,6 +3961,74 @@ class TestContainerController(unittest.TestCase):
res = controller.HEAD(req)
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):