Skip to content

Commit a3baf24

Browse files
committed
[issue2551263] expose headers to rest clients
Expose headers for with rate limiting (X-RateLimiting*, Retry-After), marking obsolete api endpoints (Sunset), and listing methods available on an endpoint (Allow).
1 parent 5220f36 commit a3baf24

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ Fixed:
6868
- Allow '*' and explicit origins in allowed_api_origins. Only return
6969
'Access-Control-Allow-Credentials' when not matching '*'. Fixes
7070
security issue with rest when using '*'.
71+
- issue2551263: In REST response expose rate limiting, sunset, allow
72+
HTTP headers to calling javascript.
7173

7274
Features:
7375

roundup/rest.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2194,6 +2194,24 @@ def dispatch(self, method, uri, input):
21942194
# response may change based on Origin value.
21952195
self.client.setVary("Origin")
21962196

2197+
# expose these headers to rest clients. Otherwise they can't
2198+
# respond to:
2199+
# rate limiting (*RateLimit*, Retry-After)
2200+
# obsolete API endpoint (Sunset)
2201+
# options request to discover supported methods (Allow)
2202+
self.client.setHeader(
2203+
"Access-Control-Expose-Headers",
2204+
", ".join( [
2205+
"X-RateLimit-Limit",
2206+
"X-RateLimit-Remaining",
2207+
"X-RateLimit-Reset",
2208+
"X-RateLimit-Limit-Period",
2209+
"Retry-After",
2210+
"Sunset",
2211+
"Allow",
2212+
] )
2213+
)
2214+
21972215
# Allow-Origin must match origin supplied by client. '*' doesn't
21982216
# work for authenticated requests.
21992217
self.client.setHeader(

test/rest_common.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3468,6 +3468,49 @@ def testPatchRemove(self):
34683468
self.assertEqual(len(results['attributes']['nosy']), 0)
34693469
self.assertListEqual(results['attributes']['nosy'], [])
34703470

3471+
def testRestExposeHeaders(self):
3472+
3473+
local_client = self.server.client
3474+
body = b'{ "data": "Joe Doe 1" }'
3475+
env = { "CONTENT_TYPE": "application/json",
3476+
"CONTENT_LENGTH": len(body),
3477+
"REQUEST_METHOD": "PUT",
3478+
"HTTP_ORIGIN": "http://tracker.example"
3479+
}
3480+
local_client.env.update(env)
3481+
3482+
local_client.db.config["WEB_ALLOWED_API_ORIGINS"] = " * "
3483+
3484+
headers={"accept": "application/json; version=1",
3485+
"content-type": env['CONTENT_TYPE'],
3486+
"content-length": env['CONTENT_LENGTH'],
3487+
"origin": env['HTTP_ORIGIN']
3488+
}
3489+
self.headers=headers
3490+
# we need to generate a FieldStorage the looks like
3491+
# FieldStorage(None, None, 'string') rather than
3492+
# FieldStorage(None, None, [])
3493+
body_file=BytesIO(body) # FieldStorage needs a file
3494+
form = client.BinaryFieldStorage(body_file,
3495+
headers=headers,
3496+
environ=env)
3497+
local_client.request.headers.get=self.get_header
3498+
results = self.server.dispatch('PUT',
3499+
"/rest/data/user/%s/realname"%self.joeid,
3500+
form)
3501+
3502+
for header in [ "X-RateLimit-Limit",
3503+
"X-RateLimit-Remaining",
3504+
"X-RateLimit-Reset",
3505+
"X-RateLimit-Limit-Period",
3506+
"Retry-After",
3507+
"Sunset",
3508+
"Allow",
3509+
]:
3510+
self.assertIn(
3511+
header,
3512+
self.server.client.additional_headers[
3513+
"Access-Control-Expose-Headers"])
34713514

34723515
def testRestMatchWildcardOrigin(self):
34733516
# cribbed from testDispatch #1

0 commit comments

Comments
 (0)