Allow all headers requested for CORS.

- We allow all headers requested in preflight request. The CORS
  specification does leave the door open for this, as mentioned in
  http://www.w3.org/TR/cors/#resource-preflight-requests
  Note: Since the list of headers can be unbounded
  simply returning headers can be enough.
- This is a followup to review:
  https://review.openstack.org/#/c/24415/.
- Fixes bug 1155034.

Change-Id: If7b8f2f3a581c5209892d1ccc9f06ddb8fac92dd
This commit is contained in:
Chmouel Boudjnah 2013-03-16 09:40:38 +01:00 committed by Adrian Smith
parent 85b7346808
commit c687f6956c
3 changed files with 23 additions and 29 deletions

View File

@ -22,10 +22,6 @@ The supported headers are,
|X-Container-Meta-Access-Control-Max-Age | Max age for the Origin to | |X-Container-Meta-Access-Control-Max-Age | Max age for the Origin to |
| | hold the preflight results. | | | hold the preflight results. |
+----------------------------------------------+------------------------------+ +----------------------------------------------+------------------------------+
|X-Container-Meta-Access-Control-Allow-Headers | Headers to be allowed in |
| | actual request by browser, |
| | space separated. |
+----------------------------------------------+------------------------------+
|X-Container-Meta-Access-Control-Expose-Headers| Headers exposed to the user | |X-Container-Meta-Access-Control-Expose-Headers| Headers exposed to the user |
| | agent (e.g. browser) in the | | | agent (e.g. browser) in the |
| | the actual request response. | | | the actual request response. |

View File

@ -34,7 +34,7 @@ from eventlet.timeout import Timeout
from swift.common.wsgi import make_pre_authed_request from swift.common.wsgi import make_pre_authed_request
from swift.common.utils import normalize_timestamp, config_true_value, \ from swift.common.utils import normalize_timestamp, config_true_value, \
public, split_path, cache_from_env public, split_path, cache_from_env, list_from_csv
from swift.common.bufferedhttp import http_connect from swift.common.bufferedhttp import http_connect
from swift.common.constraints import MAX_ACCOUNT_NAME_LENGTH from swift.common.constraints import MAX_ACCOUNT_NAME_LENGTH
from swift.common.exceptions import ChunkReadTimeout, ConnectionTimeout from swift.common.exceptions import ChunkReadTimeout, ConnectionTimeout
@ -129,8 +129,6 @@ def headers_to_container_info(headers, status_int=HTTP_OK):
'cors': { 'cors': {
'allow_origin': headers.get( 'allow_origin': headers.get(
'x-container-meta-access-control-allow-origin'), 'x-container-meta-access-control-allow-origin'),
'allow_headers': headers.get(
'x-container-meta-access-control-allow-headers'),
'expose_headers': headers.get( 'expose_headers': headers.get(
'x-container-meta-access-control-expose-headers'), 'x-container-meta-access-control-expose-headers'),
'max_age': headers.get( 'max_age': headers.get(
@ -905,15 +903,15 @@ class Controller(object):
resp.status = HTTP_UNAUTHORIZED resp.status = HTTP_UNAUTHORIZED
return resp return resp
# Always allow the x-auth-token header. This ensures # Allow all headers requested in the request. The CORS
# clients can always make a request to the resource. # specification does leave the door open for this, as mentioned in
# http://www.w3.org/TR/cors/#resource-preflight-requests
# Note: Since the list of headers can be unbounded
# simply returning headers can be enough.
allow_headers = set() allow_headers = set()
if cors.get('allow_headers'): if req.headers.get('Access-Control-Request-Headers'):
allow_headers.update( allow_headers.update(
[a.strip() list_from_csv(req.headers['Access-Control-Request-Headers']))
for a in cors['allow_headers'].split(' ')
if a.strip()])
allow_headers.add('x-auth-token')
# Populate the response with the CORS preflight headers # Populate the response with the CORS preflight headers
headers['access-control-allow-origin'] = req_origin_value headers['access-control-allow-origin'] = req_origin_value
@ -921,7 +919,8 @@ class Controller(object):
headers['access-control-max-age'] = cors.get('max_age') headers['access-control-max-age'] = cors.get('max_age')
headers['access-control-allow-methods'] = \ headers['access-control-allow-methods'] = \
', '.join(self.allowed_methods) ', '.join(self.allowed_methods)
headers['access-control-allow-headers'] = ', '.join(allow_headers) if allow_headers:
headers['access-control-allow-headers'] = ', '.join(allow_headers)
resp.headers = headers resp.headers = headers
return resp return resp

View File

@ -3999,7 +3999,6 @@ class TestObjectController(unittest.TestCase):
return { return {
'cors': { 'cors': {
'allow_origin': 'http://foo.bar:8080 https://foo.bar', 'allow_origin': 'http://foo.bar:8080 https://foo.bar',
'allow_headers': 'x-foo',
'max_age': '999', 'max_age': '999',
} }
} }
@ -4022,9 +4021,6 @@ class TestObjectController(unittest.TestCase):
len(resp.headers['access-control-allow-methods'].split(', ')), len(resp.headers['access-control-allow-methods'].split(', ')),
7) 7)
self.assertEquals('999', resp.headers['access-control-max-age']) self.assertEquals('999', resp.headers['access-control-max-age'])
self.assertEquals(
'x-auth-token, x-foo',
sortHeaderNames(resp.headers['access-control-allow-headers']))
req = Request.blank( req = Request.blank(
'/a/c/o.jpg', '/a/c/o.jpg',
{'REQUEST_METHOD': 'OPTIONS'}, {'REQUEST_METHOD': 'OPTIONS'},
@ -4059,7 +4055,6 @@ class TestObjectController(unittest.TestCase):
return { return {
'cors': { 'cors': {
'allow_origin': '*', 'allow_origin': '*',
'allow_headers': 'x-foo',
'max_age': '999', 'max_age': '999',
} }
} }
@ -4082,9 +4077,6 @@ class TestObjectController(unittest.TestCase):
len(resp.headers['access-control-allow-methods'].split(', ')), len(resp.headers['access-control-allow-methods'].split(', ')),
7) 7)
self.assertEquals('999', resp.headers['access-control-max-age']) self.assertEquals('999', resp.headers['access-control-max-age'])
self.assertEquals(
'x-auth-token, x-foo',
sortHeaderNames(resp.headers['access-control-allow-headers']))
def test_CORS_valid(self): def test_CORS_valid(self):
with save_globals(): with save_globals():
@ -4827,7 +4819,6 @@ class TestContainerController(unittest.TestCase):
return { return {
'cors': { 'cors': {
'allow_origin': 'http://foo.bar:8080 https://foo.bar', 'allow_origin': 'http://foo.bar:8080 https://foo.bar',
'allow_headers': 'x-foo',
'max_age': '999', 'max_age': '999',
} }
} }
@ -4850,9 +4841,6 @@ class TestContainerController(unittest.TestCase):
len(resp.headers['access-control-allow-methods'].split(', ')), len(resp.headers['access-control-allow-methods'].split(', ')),
6) 6)
self.assertEquals('999', resp.headers['access-control-max-age']) self.assertEquals('999', resp.headers['access-control-max-age'])
self.assertEquals(
'x-auth-token, x-foo',
sortHeaderNames(resp.headers['access-control-allow-headers']))
req = Request.blank( req = Request.blank(
'/a/c', '/a/c',
{'REQUEST_METHOD': 'OPTIONS'}, {'REQUEST_METHOD': 'OPTIONS'},
@ -4888,7 +4876,6 @@ class TestContainerController(unittest.TestCase):
return { return {
'cors': { 'cors': {
'allow_origin': '*', 'allow_origin': '*',
'allow_headers': 'x-foo',
'max_age': '999', 'max_age': '999',
} }
} }
@ -4911,8 +4898,20 @@ class TestContainerController(unittest.TestCase):
len(resp.headers['access-control-allow-methods'].split(', ')), len(resp.headers['access-control-allow-methods'].split(', ')),
6) 6)
self.assertEquals('999', resp.headers['access-control-max-age']) self.assertEquals('999', resp.headers['access-control-max-age'])
req = Request.blank(
'/a/c/o.jpg',
{'REQUEST_METHOD': 'OPTIONS'},
headers={'Origin': 'https://bar.baz',
'Access-Control-Request-Headers':
'x-foo, x-bar, x-auth-token',
'Access-Control-Request-Method': 'GET'}
)
req.content_length = 0
resp = controller.OPTIONS(req)
self.assertEquals(200, resp.status_int)
self.assertEquals( self.assertEquals(
'x-auth-token, x-foo', sortHeaderNames('x-foo, x-bar, x-auth-token'),
sortHeaderNames(resp.headers['access-control-allow-headers'])) sortHeaderNames(resp.headers['access-control-allow-headers']))
def test_CORS_valid(self): def test_CORS_valid(self):