[api][cors] Add CORS configuration
CORS can be enabled in zuul.conf (disabled by default). If enabled, zuul-web will check the Origin header if presented by the requester. A white list of allowed request origins can be defined in zuul.conf. Co-Authored-By: Matthieu Huin <mhuin@redhat.com> Change-Id: If9cf545dab3c72a2a4e46058eee076af3409ee1b
This commit is contained in:
parent
a2b8b975d0
commit
578fd9223b
|
@ -1041,6 +1041,18 @@ sections of ``zuul.conf`` are used by the web server:
|
|||
The Cache-Control max-age response header value for static files served
|
||||
by the zuul-web. Set to 0 during development to disable Cache-Control.
|
||||
|
||||
.. attr:: enable_cors
|
||||
:default: false
|
||||
|
||||
Whether or not CORS shall be enabled. If true, CORS checks will be strictly enforced,
|
||||
ie clients must set the "Origin" header in their queries.
|
||||
|
||||
.. attr:: allowed_origins
|
||||
:default: localhost
|
||||
|
||||
A comma-separated list of origins to be allowed when enabling CORS. Use '*' to enable
|
||||
CORS but allow any origin.
|
||||
|
||||
.. _web-server-tenant-scoped-api:
|
||||
|
||||
Enabling tenant-scoped access to privileged actions
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
[gearman]
|
||||
server=127.0.0.1
|
||||
|
||||
[scheduler]
|
||||
tenant_config=main.yaml
|
||||
relative_priority=true
|
||||
|
||||
[merger]
|
||||
git_dir=/tmp/zuul-test/merger-git
|
||||
git_user_email=zuul@example.com
|
||||
git_user_name=zuul
|
||||
|
||||
[executor]
|
||||
git_dir=/tmp/zuul-test/executor-git
|
||||
|
||||
[connection gerrit]
|
||||
driver=gerrit
|
||||
server=review.example.com
|
||||
user=jenkins
|
||||
sshkey=fake_id_rsa_path
|
||||
|
||||
[web]
|
||||
static_cache_expiry=1200
|
||||
enable_cors=true
|
||||
allowed_origins=foo.zuul,bar.zuul
|
||||
|
||||
[auth zuul_operator]
|
||||
driver=HS256
|
||||
allow_authz_override=true
|
||||
realm=zuul.example.com
|
||||
client_id=zuul.example.com
|
||||
issuer_id=zuul_operator
|
||||
secret=NoDanaOnlyZuul
|
||||
|
||||
[database]
|
||||
dburi=$MYSQL_FIXTURE_DBURI$
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
[gearman]
|
||||
server=127.0.0.1
|
||||
|
||||
[scheduler]
|
||||
tenant_config=main.yaml
|
||||
relative_priority=true
|
||||
|
||||
[merger]
|
||||
git_dir=/tmp/zuul-test/merger-git
|
||||
git_user_email=zuul@example.com
|
||||
git_user_name=zuul
|
||||
|
||||
[executor]
|
||||
git_dir=/tmp/zuul-test/executor-git
|
||||
|
||||
[connection gerrit]
|
||||
driver=gerrit
|
||||
server=review.example.com
|
||||
user=jenkins
|
||||
sshkey=fake_id_rsa_path
|
||||
|
||||
[web]
|
||||
static_cache_expiry=1200
|
||||
enable_cors=true
|
||||
allowed_origins=*
|
||||
|
||||
[auth zuul_operator]
|
||||
driver=HS256
|
||||
allow_authz_override=true
|
||||
realm=zuul.example.com
|
||||
client_id=zuul.example.com
|
||||
issuer_id=zuul_operator
|
||||
secret=NoDanaOnlyZuul
|
||||
|
||||
[database]
|
||||
dburi=$MYSQL_FIXTURE_DBURI$
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
[gearman]
|
||||
server=127.0.0.1
|
||||
|
||||
[scheduler]
|
||||
tenant_config=main.yaml
|
||||
|
||||
[merger]
|
||||
git_dir=/tmp/zuul-test/merger-git
|
||||
git_user_email=zuul@example.com
|
||||
git_user_name=zuul
|
||||
|
||||
[executor]
|
||||
git_dir=/tmp/zuul-test/executor-git
|
||||
|
||||
[connection gerrit]
|
||||
driver=gerrit
|
||||
server=review.example.com
|
||||
user=jenkins
|
||||
sshkey=fake_id_rsa1
|
||||
|
||||
[database]
|
||||
dburi=$MYSQL_FIXTURE_DBURI$
|
||||
|
||||
[connection resultsdb_failures]
|
||||
driver=sql
|
||||
dburi=$MYSQL_FIXTURE_DBURI$
|
||||
|
||||
[web]
|
||||
enable_cors=true
|
||||
allowed_origins=foo.zuul,bar.zuul
|
|
@ -137,7 +137,6 @@ class TestWeb(BaseTestWeb):
|
|||
self.assertIn('Content-Type', resp.headers)
|
||||
self.assertEqual(
|
||||
'application/json; charset=utf-8', resp.headers['Content-Type'])
|
||||
self.assertIn('Access-Control-Allow-Origin', resp.headers)
|
||||
self.assertIn('Cache-Control', resp.headers)
|
||||
self.assertIn('Last-Modified', resp.headers)
|
||||
self.assertTrue(resp.headers['Last-Modified'].endswith(' GMT'))
|
||||
|
@ -1431,6 +1430,7 @@ class TestArtifacts(BaseTestWeb, AnsibleZuulTestCase):
|
|||
|
||||
class TestTenantScopedWebApi(BaseTestWeb):
|
||||
config_file = 'zuul-admin-web.conf'
|
||||
extra_headers = {}
|
||||
|
||||
def test_admin_routes_no_token(self):
|
||||
resp = self.post_url(
|
||||
|
@ -1438,13 +1438,15 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
json={'job': 'project-test1',
|
||||
'count': 1,
|
||||
'reason': 'because',
|
||||
'node_hold_expiration': 36000})
|
||||
'node_hold_expiration': 36000},
|
||||
headers=self.extra_headers or None)
|
||||
self.assertEqual(401, resp.status_code)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/enqueue",
|
||||
json={'trigger': 'gerrit',
|
||||
'change': '2,1',
|
||||
'pipeline': 'check'})
|
||||
'pipeline': 'check'},
|
||||
headers=self.extra_headers or None)
|
||||
self.assertEqual(401, resp.status_code)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/enqueue",
|
||||
|
@ -1452,7 +1454,8 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'ref': 'abcd',
|
||||
'newrev': 'aaaa',
|
||||
'oldrev': 'bbbb',
|
||||
'pipeline': 'check'})
|
||||
'pipeline': 'check'},
|
||||
headers=self.extra_headers or None)
|
||||
self.assertEqual(401, resp.status_code)
|
||||
|
||||
def test_bad_key_JWT_token(self):
|
||||
|
@ -1465,9 +1468,11 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'exp': time.time() + 3600}
|
||||
token = jwt.encode(authz, key='OnlyZuulNoDana',
|
||||
algorithm='HS256')
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/autohold",
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json={'job': 'project-test1',
|
||||
'count': 1,
|
||||
'reason': 'because',
|
||||
|
@ -1475,14 +1480,14 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
self.assertEqual(401, resp.status_code)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/enqueue",
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json={'trigger': 'gerrit',
|
||||
'change': '2,1',
|
||||
'pipeline': 'check'})
|
||||
self.assertEqual(401, resp.status_code)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/enqueue",
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json={'trigger': 'gerrit',
|
||||
'ref': 'abcd',
|
||||
'newrev': 'aaaa',
|
||||
|
@ -1500,9 +1505,11 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'exp': time.time() - 3600}
|
||||
token = jwt.encode(authz, key='NoDanaOnlyZuul',
|
||||
algorithm='HS256')
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/autohold",
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json={'job': 'project-test1',
|
||||
'count': 1,
|
||||
'reason': 'because',
|
||||
|
@ -1510,14 +1517,14 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
self.assertEqual(401, resp.status_code)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/enqueue",
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json={'trigger': 'gerrit',
|
||||
'change': '2,1',
|
||||
'pipeline': 'check'})
|
||||
self.assertEqual(401, resp.status_code)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/enqueue",
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json={'trigger': 'gerrit',
|
||||
'ref': 'abcd',
|
||||
'newrev': 'aaaa',
|
||||
|
@ -1535,9 +1542,11 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'exp': time.time() + 3600}
|
||||
token = jwt.encode(authz, key='NoDanaOnlyZuul',
|
||||
algorithm='HS256')
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/autohold",
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json={'job': 'project-test1',
|
||||
'count': 1,
|
||||
'reason': 'because',
|
||||
|
@ -1545,14 +1554,14 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
self.assertEqual(403, resp.status_code)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/enqueue",
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json={'trigger': 'gerrit',
|
||||
'change': '2,1',
|
||||
'pipeline': 'check'})
|
||||
self.assertEqual(403, resp.status_code)
|
||||
resp = self.post_url(
|
||||
"api/tenant/tenant-one/project/org/project/enqueue",
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json={'trigger': 'gerrit',
|
||||
'ref': 'abcd',
|
||||
'newrev': 'aaaa',
|
||||
|
@ -1575,9 +1584,11 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'node_hold_expiration': None}
|
||||
good_token = jwt.encode(good_authz, key='NoDanaOnlyZuul',
|
||||
algorithm='HS256')
|
||||
headers = {'Authorization': 'Bearer %s' % good_token}
|
||||
headers.update(self.extra_headers)
|
||||
req = self.post_url(
|
||||
'api/tenant/tenant-one/project/org/project/autohold',
|
||||
headers={'Authorization': 'Bearer %s' % good_token},
|
||||
headers=headers,
|
||||
json=args)
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
client = zuul.rpcclient.RPCClient('127.0.0.1',
|
||||
|
@ -1587,9 +1598,11 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
self.assertNotEqual([], autohold_requests)
|
||||
self.assertEqual(1, len(autohold_requests))
|
||||
request = autohold_requests[0]
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
resp = self.delete_url(
|
||||
"api/tenant/tenant-one/autohold/%s" % request['id'],
|
||||
headers={'Authorization': 'Bearer %s' % token})
|
||||
headers=headers)
|
||||
self.assertEqual(403, resp.status_code)
|
||||
|
||||
def test_autohold(self):
|
||||
|
@ -1609,10 +1622,16 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'exp': time.time() + 3600}
|
||||
token = jwt.encode(authz, key='NoDanaOnlyZuul',
|
||||
algorithm='HS256')
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
req = self.post_url(
|
||||
'api/tenant/tenant-one/project/org/project/autohold',
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json=args)
|
||||
# Expected failure if testing CORS with a bad origin
|
||||
if self.extra_headers.get('Origin') == 'bad.zuul':
|
||||
self.assertEqual(400, req.status_code, req.text)
|
||||
return
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
data = req.json()
|
||||
self.assertEqual(True, data)
|
||||
|
@ -1643,7 +1662,6 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
"", "", "reason text", 1)
|
||||
self.assertTrue(r)
|
||||
|
||||
# Use autohold-list API to retrieve request ID
|
||||
resp = self.get_url(
|
||||
"api/tenant/tenant-one/autohold")
|
||||
self.assertEqual(200, resp.status_code, resp.text)
|
||||
|
@ -1673,9 +1691,15 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'exp': time.time() + 3600}
|
||||
bad_token = jwt.encode(bad_authz, key='NoDanaOnlyZuul',
|
||||
algorithm='HS256')
|
||||
headers = {'Authorization': 'Bearer %s' % bad_token}
|
||||
headers.update(self.extra_headers)
|
||||
resp = self.delete_url(
|
||||
"api/tenant/tenant-one/autohold/%s" % request_id,
|
||||
headers={'Authorization': 'Bearer %s' % bad_token})
|
||||
headers=headers)
|
||||
# Expected failure if testing CORS with a bad origin
|
||||
if self.extra_headers.get('Origin') == 'bad.zuul':
|
||||
self.assertEqual(400, resp.status_code, resp.text)
|
||||
return
|
||||
# Throw a "Forbidden" error, because user is authenticated but not
|
||||
# authorized for tenant-one
|
||||
self.assertEqual(403, resp.status_code, resp.text)
|
||||
|
@ -1692,9 +1716,15 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
},
|
||||
'exp': time.time() + 3600}
|
||||
client, request_id, token = self._init_autohold_delete(authz)
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
resp = self.delete_url(
|
||||
"api/tenant/tenant-one/autohold/%s" % request_id,
|
||||
headers={'Authorization': 'Bearer %s' % token})
|
||||
headers=headers)
|
||||
# Expected failure if testing CORS with a bad origin
|
||||
if self.extra_headers.get('Origin') == 'bad.zuul':
|
||||
self.assertEqual(400, resp.status_code, resp.text)
|
||||
return
|
||||
self.assertEqual(204, resp.status_code, resp.text)
|
||||
# autohold-list should be empty now
|
||||
resp = self.get_url(
|
||||
|
@ -1724,9 +1754,15 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'pipeline': 'gate', }
|
||||
if use_trigger:
|
||||
change['trigger'] = 'gerrit'
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
req = self.post_url(path % enqueue_args,
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json=change)
|
||||
# Expected failure if testing CORS with a bad origin
|
||||
if self.extra_headers.get('Origin') == 'bad.zuul':
|
||||
self.assertEqual(400, req.status_code, req.text)
|
||||
return
|
||||
# The JSON returned is the same as the client's output
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
data = req.json()
|
||||
|
@ -1769,9 +1805,14 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'exp': time.time() + 3600}
|
||||
token = jwt.encode(authz, key='NoDanaOnlyZuul',
|
||||
algorithm='HS256')
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
req = self.post_url(path % enqueue_args,
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json=ref)
|
||||
if self.extra_headers.get('Origin') == 'bad.zuul':
|
||||
self.assertEqual(400, req.status_code, req.text)
|
||||
return
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
# The JSON returned is the same as the client's output
|
||||
data = req.json()
|
||||
|
@ -1813,14 +1854,19 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'exp': time.time() + 3600}
|
||||
token = jwt.encode(authz, key='NoDanaOnlyZuul',
|
||||
algorithm='HS256')
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
path = "api/tenant/%(tenant)s/project/%(project)s/dequeue"
|
||||
dequeue_args = {'tenant': 'tenant-one',
|
||||
'project': 'org/project', }
|
||||
change = {'ref': 'refs/heads/stable',
|
||||
'pipeline': 'periodic', }
|
||||
req = self.post_url(path % dequeue_args,
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json=change)
|
||||
if self.extra_headers.get('Origin') == 'bad.zuul':
|
||||
self.assertEqual(400, req.status_code, req.text)
|
||||
return
|
||||
# The JSON returned is the same as the client's output
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
data = req.json()
|
||||
|
@ -1841,6 +1887,8 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
properly"""
|
||||
# Note that %tenant, %project are not relevant here. The client is
|
||||
# just checking what the endpoint allows.
|
||||
web_config = self.config.get('web', {})
|
||||
cors_enabled = web_config.get('enable_cors', False)
|
||||
endpoints = [
|
||||
{'action': 'promote',
|
||||
'path': 'api/tenant/my-tenant/promote',
|
||||
|
@ -1868,19 +1916,31 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'path': 'api/user/authorizations',
|
||||
'allowed_methods': ['GET', ]},
|
||||
]
|
||||
headers = {'Access-Control-Request-Method': 'GET',
|
||||
'Access-Control-Request-Headers': 'Authorization',
|
||||
'Origin': 'test.zuul'}
|
||||
headers.update(self.extra_headers)
|
||||
for endpoint in endpoints:
|
||||
preflight = self.options_url(
|
||||
endpoint['path'],
|
||||
headers={'Access-Control-Request-Method': 'GET',
|
||||
'Access-Control-Request-Headers': 'Authorization'})
|
||||
headers=headers)
|
||||
if self.extra_headers.get('Origin') == 'bad.zuul':
|
||||
self.assertEqual(400, preflight.status_code, preflight.text)
|
||||
continue
|
||||
self.assertEqual(
|
||||
204,
|
||||
preflight.status_code,
|
||||
"%s failed: %s" % (endpoint['action'], preflight.text))
|
||||
self.assertEqual(
|
||||
'*',
|
||||
preflight.headers.get('Access-Control-Allow-Origin'),
|
||||
"%s failed: %s" % (endpoint['action'], preflight.headers))
|
||||
if cors_enabled:
|
||||
self.assertEqual(
|
||||
self.extra_headers.get('Origin'),
|
||||
preflight.headers.get('Access-Control-Allow-Origin'),
|
||||
"%s failed: %s" % (endpoint['action'], preflight.headers))
|
||||
else:
|
||||
self.assertEqual(
|
||||
'*',
|
||||
preflight.headers.get('Access-Control-Allow-Origin'),
|
||||
"%s failed: %s" % (endpoint['action'], preflight.headers))
|
||||
self.assertEqual(
|
||||
'Authorization, Content-Type',
|
||||
preflight.headers.get('Access-Control-Allow-Headers'),
|
||||
|
@ -1935,10 +1995,15 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'iat': time.time()}
|
||||
token = jwt.encode(authz, key='NoDanaOnlyZuul',
|
||||
algorithm='HS256')
|
||||
headers = {'Authorization': 'Bearer %s' % token}
|
||||
headers.update(self.extra_headers)
|
||||
req = self.post_url(
|
||||
'api/tenant/tenant-one/promote',
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
headers=headers,
|
||||
json=args)
|
||||
if self.extra_headers.get('Origin') == 'bad.zuul':
|
||||
self.assertEqual(400, req.status_code, req.text)
|
||||
return
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
data = req.json()
|
||||
self.assertEqual(True, data)
|
||||
|
@ -1988,6 +2053,44 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
self.assertEqual(C.reported, 2)
|
||||
|
||||
|
||||
class TestTenantScopedWebAPICORSEnabledMultiOrigins(TestTenantScopedWebApi):
|
||||
config_file = 'zuul-admin-web-CORS-multiple-origins.conf'
|
||||
extra_headers = {'Origin': 'foo.zuul'}
|
||||
|
||||
|
||||
class TestTenantScopedWebAPICORSEnabledDirectCalls(TestTenantScopedWebApi):
|
||||
"""Test that non-CORS calls work with CORS enabled."""
|
||||
config_file = 'zuul-admin-web-CORS-multiple-origins.conf'
|
||||
|
||||
def test_OPTIONS(self):
|
||||
self.skipTest('this tests a CORS-only call')
|
||||
|
||||
|
||||
class TestTenantScopedWebAPICORSEnabledBadOrigin(TestTenantScopedWebApi):
|
||||
config_file = 'zuul-admin-web-CORS-multiple-origins.conf'
|
||||
extra_headers = {'Origin': 'bad.zuul'}
|
||||
|
||||
def test_admin_routes_no_token(self):
|
||||
self.skipTest('N/A')
|
||||
|
||||
def test_bad_key_JWT_token(self):
|
||||
self.skipTest('N/A')
|
||||
|
||||
def test_expired_JWT_token(self):
|
||||
self.skipTest('N/A')
|
||||
|
||||
def test_valid_JWT_bad_tenants(self):
|
||||
self.skipTest('N/A')
|
||||
|
||||
def test_autohold_delete_wrong_tenant(self):
|
||||
self.skipTest('N/A')
|
||||
|
||||
|
||||
class TestTenantScopedWebAPICORSEnabledWildcard(TestTenantScopedWebApi):
|
||||
config_file = 'zuul-admin-web-CORS-wildcard.conf'
|
||||
extra_headers = {'Origin': 'nodanaonly.zuul'}
|
||||
|
||||
|
||||
class TestTenantScopedWebApiWithAuthRules(BaseTestWeb):
|
||||
config_file = 'zuul-admin-web-no-override.conf'
|
||||
tenant_config_file = 'config/authorization/single-tenant/main.yaml'
|
||||
|
|
|
@ -419,6 +419,19 @@ class TestZuulClientAdmin(BaseTestWeb):
|
|||
self.assertEqual(C.reported, 2)
|
||||
|
||||
|
||||
class TestZuulClientAdminCORSEnabled(TestZuulClientAdmin):
|
||||
"""Test the admin commands of zuul-client, CORS enabled"""
|
||||
config_file = 'zuul-admin-web-CORS-multiple-origins.conf'
|
||||
|
||||
|
||||
class TestZuulClientAdminCORSEnabledWildCard(TestZuulClientAdmin):
|
||||
"""
|
||||
Test the admin commands of zuul-client, CORS enabled with origin
|
||||
wildcard
|
||||
"""
|
||||
config_file = 'zuul-admin-web-CORS-wildcard.conf'
|
||||
|
||||
|
||||
class TestZuulClientQueryData(BaseTestWeb):
|
||||
"""Test that zuul-client can fetch builds"""
|
||||
config_file = 'zuul-sql-driver-mysql.conf'
|
||||
|
@ -578,6 +591,11 @@ class TestZuulClientBuilds(TestZuulClientQueryData,
|
|||
results)
|
||||
|
||||
|
||||
class TestZuulClientBuildsCORSEnabled(TestZuulClientBuilds):
|
||||
"""Test that zuul-client can fetch builds, CORS enabled"""
|
||||
config_file = 'zuul-sql-driver-mysql-CORS-enabled.conf'
|
||||
|
||||
|
||||
class TestZuulClientBuildInfo(TestZuulClientQueryData,
|
||||
AnsibleZuulTestCase):
|
||||
"""Test that zuul-client can fetch a build's details"""
|
||||
|
@ -631,3 +649,8 @@ class TestZuulClientBuildInfo(TestZuulClientQueryData,
|
|||
x['url'] == 'http://example.com/docs'
|
||||
for x in artifacts),
|
||||
output)
|
||||
|
||||
|
||||
class TestZuulClientBuildInfoCORSEnabled(TestZuulClientBuildInfo):
|
||||
"""Test that zuul-client can fetch builds, CORS enabled"""
|
||||
config_file = 'zuul-sql-driver-mysql-CORS-enabled.conf'
|
||||
|
|
|
@ -72,8 +72,58 @@ class SaveParamsTool(cherrypy.Tool):
|
|||
cherrypy.tools.save_params = SaveParamsTool()
|
||||
|
||||
|
||||
def handle_options(allowed_methods=None):
|
||||
if cherrypy.request.method == 'OPTIONS':
|
||||
class CORSTool(cherrypy.Tool):
|
||||
"""
|
||||
Handle CORS headers and preflight exchanges.
|
||||
"""
|
||||
|
||||
def __init__(self, CORS_enabled=None, allowed_origins=None):
|
||||
cherrypy.Tool.__init__(self, 'on_start_resource',
|
||||
self.handle_CORS)
|
||||
self.CORS_enabled = CORS_enabled
|
||||
self.allowed_origins = allowed_origins
|
||||
|
||||
def handle_CORS(self, allowed_methods=None):
|
||||
|
||||
if cherrypy.request.method == 'OPTIONS':
|
||||
self.handle_OPTIONS(allowed_methods)
|
||||
|
||||
resp = cherrypy.response
|
||||
origin = cherrypy.request.headers.get('Origin', None)
|
||||
# CORS queries occur within specific conditions:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS and
|
||||
# https://fetch.spec.whatwg.org/#cors-safelisted-request-header
|
||||
# For simplicity's sake we will just enforce CORS if the
|
||||
# "Origin" header is present in the request. This way calls
|
||||
# to the API issued without a browser (zuul-client for example)
|
||||
# won't run through this check.
|
||||
if self.CORS_enabled and origin is not None:
|
||||
if origin in self.allowed_origins:
|
||||
resp.headers['Access-Control-Allow-Origin'] = origin
|
||||
if len(self.allowed_origins) > 1:
|
||||
resp.headers['Vary'] = 'Origin'
|
||||
elif '*' in self.allowed_origins:
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
else:
|
||||
# Pick arbitrarily the first allowed origin
|
||||
resp.headers['Access-Control-Allow-Origin'] =\
|
||||
self.allowed_origins[0]
|
||||
if len(self.allowed_origins) > 1:
|
||||
resp.headers['Vary'] = 'Origin'
|
||||
error_message = ('Cross-Origin Request blocked: '
|
||||
'access not allowed '
|
||||
'from origin "%s"' % origin)
|
||||
raise cherrypy.HTTPError(
|
||||
400,
|
||||
error_message)
|
||||
|
||||
else:
|
||||
# Be polite to the client if it sent a CORS header.
|
||||
if origin is not None:
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
|
||||
def handle_OPTIONS(self, allowed_methods):
|
||||
"""Specific logic for handling CORS preflight"""
|
||||
methods = allowed_methods or ['GET', 'OPTIONS']
|
||||
if allowed_methods and 'OPTIONS' not in allowed_methods:
|
||||
methods = methods + ['OPTIONS']
|
||||
|
@ -82,7 +132,6 @@ def handle_options(allowed_methods=None):
|
|||
request.handler = None
|
||||
# Set CORS response headers
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
resp.headers['Access-Control-Allow-Headers'] =\
|
||||
', '.join(['Authorization', 'Content-Type'])
|
||||
resp.headers['Access-Control-Allow-Methods'] =\
|
||||
|
@ -92,8 +141,8 @@ def handle_options(allowed_methods=None):
|
|||
resp.status = 204
|
||||
|
||||
|
||||
cherrypy.tools.handle_options = cherrypy.Tool('on_start_resource',
|
||||
handle_options)
|
||||
# this will be overridden at ZuulWeb instantiation
|
||||
cherrypy.tools.handle_CORS = CORSTool()
|
||||
|
||||
|
||||
class ChangeFilter(object):
|
||||
|
@ -284,7 +333,7 @@ class ZuulWebAPI(object):
|
|||
@cherrypy.expose
|
||||
@cherrypy.tools.json_in()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_options(allowed_methods=['POST', ])
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['POST', ])
|
||||
def dequeue(self, tenant, project):
|
||||
basic_error = self._basic_auth_header_check()
|
||||
if basic_error is not None:
|
||||
|
@ -312,8 +361,6 @@ class ZuulWebAPI(object):
|
|||
'change': body.get('change', None),
|
||||
'ref': body.get('ref', None)})
|
||||
result = not job.failure
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return result
|
||||
else:
|
||||
raise cherrypy.HTTPError(400,
|
||||
|
@ -322,7 +369,7 @@ class ZuulWebAPI(object):
|
|||
@cherrypy.expose
|
||||
@cherrypy.tools.json_in()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_options(allowed_methods=['POST', ])
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['POST', ])
|
||||
def enqueue(self, tenant, project):
|
||||
basic_error = self._basic_auth_header_check()
|
||||
if basic_error is not None:
|
||||
|
@ -356,8 +403,6 @@ class ZuulWebAPI(object):
|
|||
'project': project,
|
||||
'change': change, })
|
||||
result = not job.failure
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return result
|
||||
|
||||
def _enqueue_ref(self, tenant, project, ref,
|
||||
|
@ -370,14 +415,12 @@ class ZuulWebAPI(object):
|
|||
'oldrev': oldrev,
|
||||
'newrev': newrev, })
|
||||
result = not job.failure
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return result
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_in()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_options(allowed_methods=['POST', ])
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['POST', ])
|
||||
def promote(self, tenant):
|
||||
basic_error = self._basic_auth_header_check()
|
||||
if basic_error is not None:
|
||||
|
@ -406,12 +449,11 @@ class ZuulWebAPI(object):
|
|||
'change_ids': changes,
|
||||
})
|
||||
result = not job.failure
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return result
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def autohold_list(self, tenant, *args, **kwargs):
|
||||
# we don't use json_in because a payload is not mandatory with GET
|
||||
if cherrypy.request.method != 'GET':
|
||||
|
@ -422,7 +464,7 @@ class ZuulWebAPI(object):
|
|||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_options(allowed_methods=['GET', 'POST', ])
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', 'POST', ])
|
||||
def autohold(self, tenant, project=None):
|
||||
# we don't use json_in because a payload is not mandatory with GET
|
||||
# Note: GET handling is redundant with autohold_list
|
||||
|
@ -499,13 +541,11 @@ class ZuulWebAPI(object):
|
|||
'expired': request['expired'],
|
||||
'nodes': request['nodes']
|
||||
})
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return result
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_options(allowed_methods=['GET', 'DELETE', ])
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', 'DELETE', ])
|
||||
def autohold_by_request_id(self, tenant, request_id):
|
||||
if cherrypy.request.method == 'GET':
|
||||
return self._autohold_info(tenant, request_id)
|
||||
|
@ -528,8 +568,6 @@ class ZuulWebAPI(object):
|
|||
# return 404 rather than 403 to avoid leaking tenant info
|
||||
raise cherrypy.HTTPError(
|
||||
404, 'Hold request %s not found.' % request_id)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return {
|
||||
'id': request['id'],
|
||||
'tenant': request['tenant'],
|
||||
|
@ -626,9 +664,10 @@ class ZuulWebAPI(object):
|
|||
return self._handleInfo(info)
|
||||
|
||||
def _handleInfo(self, info):
|
||||
ret = {'info': info.toDict()}
|
||||
ret = {
|
||||
'info': info.toDict(),
|
||||
}
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
if self.static_cache_expiry:
|
||||
resp.headers['Cache-Control'] = "public, max-age=%d" % \
|
||||
self.static_cache_expiry
|
||||
|
@ -653,7 +692,7 @@ class ZuulWebAPI(object):
|
|||
# TODO(mhu) deprecated, remove next version
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_options(allowed_methods=['GET', ])
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def authorizations(self):
|
||||
basic_error = self._basic_auth_header_check()
|
||||
if basic_error is not None:
|
||||
|
@ -675,7 +714,7 @@ class ZuulWebAPI(object):
|
|||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_options(allowed_methods=['GET', ])
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def tenant_authorizations(self, tenant):
|
||||
basic_error = self._basic_auth_header_check()
|
||||
if basic_error is not None:
|
||||
|
@ -714,19 +753,17 @@ class ZuulWebAPI(object):
|
|||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def tenants(self):
|
||||
ret = self._tenants()
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def connections(self):
|
||||
job = self.rpc.submitJob('zuul:connection_list', {})
|
||||
ret = json.loads(job.data[0])
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
def _getStatus(self, tenant):
|
||||
|
@ -746,18 +783,19 @@ class ZuulWebAPI(object):
|
|||
last_modified = datetime.utcfromtimestamp(self.cache_time[tenant])
|
||||
last_modified_header = last_modified.strftime('%a, %d %b %Y %X GMT')
|
||||
resp.headers["Last-modified"] = last_modified_header
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return payload
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def status(self, tenant):
|
||||
return self._getStatus(tenant)
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def status_change(self, tenant, change):
|
||||
payload = self._getStatus(tenant)
|
||||
result_filter = ChangeFilter(change)
|
||||
|
@ -766,56 +804,53 @@ class ZuulWebAPI(object):
|
|||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def jobs(self, tenant):
|
||||
job = self.rpc.submitJob('zuul:job_list', {'tenant': tenant})
|
||||
ret = json.loads(job.data[0])
|
||||
if ret is None:
|
||||
raise cherrypy.HTTPError(404, 'Tenant %s does not exist.' % tenant)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def config_errors(self, tenant):
|
||||
config_errors = self.rpc.submitJob(
|
||||
'zuul:config_errors_list', {'tenant': tenant})
|
||||
ret = json.loads(config_errors.data[0])
|
||||
if ret is None:
|
||||
raise cherrypy.HTTPError(404, 'Tenant %s does not exist.' % tenant)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def job(self, tenant, job_name):
|
||||
job = self.rpc.submitJob(
|
||||
'zuul:job_get', {'tenant': tenant, 'job': job_name})
|
||||
ret = json.loads(job.data[0])
|
||||
if not ret:
|
||||
raise cherrypy.HTTPError(404, 'Job %s does not exist.' % job_name)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def projects(self, tenant):
|
||||
job = self.rpc.submitJob('zuul:project_list', {'tenant': tenant})
|
||||
ret = json.loads(job.data[0])
|
||||
if ret is None:
|
||||
raise cherrypy.HTTPError(404, 'Tenant %s does not exist.' % tenant)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def project(self, tenant, project):
|
||||
job = self.rpc.submitJob(
|
||||
'zuul:project_get', {'tenant': tenant, 'project': project})
|
||||
|
@ -825,25 +860,23 @@ class ZuulWebAPI(object):
|
|||
if not ret:
|
||||
raise cherrypy.HTTPError(
|
||||
404, 'Project %s does not exist.' % project)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def pipelines(self, tenant):
|
||||
job = self.rpc.submitJob('zuul:pipeline_list', {'tenant': tenant})
|
||||
ret = json.loads(job.data[0])
|
||||
if ret is None:
|
||||
raise cherrypy.HTTPError(404, 'Tenant %s does not exist.' % tenant)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def labels(self, tenant):
|
||||
job = self.rpc.submitJob('zuul:allowed_labels_get', {'tenant': tenant})
|
||||
data = json.loads(job.data[0])
|
||||
|
@ -863,13 +896,12 @@ class ZuulWebAPI(object):
|
|||
launcher.supported_labels,
|
||||
allowed_labels, disallowed_labels))
|
||||
ret = [{'name': label} for label in sorted(labels)]
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def nodes(self, tenant):
|
||||
ret = []
|
||||
for node in self.zk_nodepool.nodeIterator():
|
||||
|
@ -878,12 +910,11 @@ class ZuulWebAPI(object):
|
|||
"provider", "state", "state_time", "comment"):
|
||||
node_data[key] = node.get(key)
|
||||
ret.append(node_data)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def key(self, tenant, project):
|
||||
job = self.rpc.submitJob('zuul:key_get', {'tenant': tenant,
|
||||
'project': project,
|
||||
|
@ -892,12 +923,12 @@ class ZuulWebAPI(object):
|
|||
raise cherrypy.HTTPError(
|
||||
404, 'Project %s does not exist.' % project)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
resp.headers['Content-Type'] = 'text/plain'
|
||||
return job.data[0]
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def project_ssh_key(self, tenant, project):
|
||||
job = self.rpc.submitJob('zuul:key_get', {'tenant': tenant,
|
||||
'project': project,
|
||||
|
@ -906,7 +937,6 @@ class ZuulWebAPI(object):
|
|||
raise cherrypy.HTTPError(
|
||||
404, 'Project %s does not exist.' % project)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
resp.headers['Content-Type'] = 'text/plain'
|
||||
return job.data[0] + '\n'
|
||||
|
||||
|
@ -978,6 +1008,7 @@ class ZuulWebAPI(object):
|
|||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def builds(self, tenant, project=None, pipeline=None, change=None,
|
||||
branch=None, patchset=None, ref=None, newrev=None,
|
||||
uuid=None, job_name=None, voting=None, nodeset=None,
|
||||
|
@ -1002,13 +1033,12 @@ class ZuulWebAPI(object):
|
|||
result=result, final=final, held=held, complete=complete,
|
||||
limit=limit, offset=skip)
|
||||
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return [self.buildToDict(b, b.buildset) for b in builds]
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def build(self, tenant, uuid):
|
||||
connection = self._get_connection()
|
||||
|
||||
|
@ -1016,8 +1046,6 @@ class ZuulWebAPI(object):
|
|||
if not data:
|
||||
raise cherrypy.HTTPError(404, "Build not found")
|
||||
data = self.buildToDict(data[0], data[0].buildset)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return data
|
||||
|
||||
def buildsetToDict(self, buildset, builds=[]):
|
||||
|
@ -1067,6 +1095,7 @@ class ZuulWebAPI(object):
|
|||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def buildsets(self, tenant, project=None, pipeline=None, change=None,
|
||||
branch=None, patchset=None, ref=None, newrev=None,
|
||||
uuid=None, result=None, complete=None, limit=50, skip=0):
|
||||
|
@ -1081,13 +1110,12 @@ class ZuulWebAPI(object):
|
|||
uuid=uuid, result=result, complete=complete,
|
||||
limit=limit, offset=skip)
|
||||
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return [self.buildsetToDict(b) for b in buildsets]
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def buildset(self, tenant, uuid):
|
||||
connection = self._get_connection()
|
||||
|
||||
|
@ -1095,8 +1123,6 @@ class ZuulWebAPI(object):
|
|||
if not data:
|
||||
raise cherrypy.HTTPError(404, "Buildset not found")
|
||||
data = self.buildsetToDict(data, data.builds)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return data
|
||||
|
||||
@cherrypy.expose
|
||||
|
@ -1108,6 +1134,7 @@ class ZuulWebAPI(object):
|
|||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def project_freeze_jobs(self, tenant, pipeline, project, branch):
|
||||
job = self.rpc.submitJob(
|
||||
'zuul:project_freeze_jobs',
|
||||
|
@ -1121,13 +1148,12 @@ class ZuulWebAPI(object):
|
|||
ret = json.loads(job.data[0])
|
||||
if not ret:
|
||||
raise cherrypy.HTTPError(404)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return ret
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_CORS(allowed_methods=['GET', ])
|
||||
def project_freeze_job(self, tenant, pipeline, project, branch, job):
|
||||
# TODO(jhesketh): Allow a canonical change/item to be passed in which
|
||||
# would return the job with any in-change modifications.
|
||||
|
@ -1262,6 +1288,14 @@ class ZuulWeb(object):
|
|||
ssl_key = get_default(self.config, 'gearman', 'ssl_key')
|
||||
ssl_cert = get_default(self.config, 'gearman', 'ssl_cert')
|
||||
ssl_ca = get_default(self.config, 'gearman', 'ssl_ca')
|
||||
enable_cors = get_default(self.config, 'web', 'enable_cors',
|
||||
False)
|
||||
allowed_origins = get_default(
|
||||
self.config, 'web',
|
||||
'allowed_origins', "localhost").split(',')
|
||||
self.CORS_config = {'CORS_enabled': enable_cors,
|
||||
'allowed_origins': allowed_origins}
|
||||
cherrypy.tools.handle_CORS = CORSTool(**self.CORS_config)
|
||||
|
||||
# instanciate handlers
|
||||
self.rpc = zuul.rpcclient.RPCClient(gear_server, gear_port,
|
||||
|
@ -1399,6 +1433,7 @@ class ZuulWeb(object):
|
|||
'request.dispatch': route_map
|
||||
}
|
||||
}
|
||||
|
||||
cherrypy.config.update({
|
||||
'global': {
|
||||
'environment': 'production',
|
||||
|
|
Loading…
Reference in New Issue