Merge "Adding CORS support"
This commit is contained in:
commit
1c05d62bed
@ -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]
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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', '-'),
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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': {
|
||||
|
@ -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):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user