diff -r bd4097fa0671 roundup/cgi/client.py --- a/roundup/cgi/client.py Wed May 04 15:28:30 2022 -0400 +++ b/roundup/cgi/client.py Sun May 08 19:21:49 2022 -0400 @@ -620,7 +620,18 @@ self.write(output) return - if not self.db.security.hasPermission('Rest Access', self.userid): + # allow preflight request even if unauthenticated + if ( self.env['REQUEST_METHOD'] == "OPTIONS" + and self.request.headers.get ("Access-Control-Request-Headers") + and self.request.headers.get ("Access-Control-Request-Method") + and self.request.headers.get ("Origin") + ): + # Call rest library to handle the pre-flight request + handler = rest.RestfulInstance(self, self.db) + output = handler.dispatch(self.env['REQUEST_METHOD'], + self.path, self.form) + + elif not self.db.security.hasPermission('Rest Access', self.userid): self.response_code = 403 output = s2b('{ "error": { "status": 403, "msg": "Forbidden." } }') self.setHeader("Content-Length", str(len(output))) diff -r bd4097fa0671 roundup/rest.py --- a/roundup/rest.py Wed May 04 15:28:30 2022 -0400 +++ b/roundup/rest.py Sun May 08 19:21:49 2022 -0400 @@ -1751,6 +1751,10 @@ "Allow", "OPTIONS, GET, PUT, DELETE, PATCH" ) + self.client.setHeader( + "Access-Control-Allow-Methods", + "OPTIONS, GET, PUT, DELETE, PATCH" + ) return 204, "" @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'OPTIONS') @@ -1774,12 +1778,20 @@ "Allow", "OPTIONS, GET, PUT, DELETE, PATCH" ) + self.client.setHeader( + "Access-Control-Allow-Methods", + "OPTIONS, GET, PUT, DELETE, PATCH" + ) elif attr_name in class_obj.getprops(protected=True): # It must match a protected prop. These can't be written. self.client.setHeader( "Allow", "OPTIONS, GET" ) + self.client.setHeader( + "Access-Control-Allow-Methods", + "OPTIONS, GET" + ) else: raise NotFound('Attribute %s not valid for Class %s' % ( attr_name, class_name)) @@ -1910,6 +1922,10 @@ "Allow", "OPTIONS, GET" ) + self.client.setHeader( + "Access-Control-Allow-Methods", + "OPTIONS, GET" + ) return 204, "" @Routing.route("/data") @@ -1939,6 +1955,10 @@ "Allow", "OPTIONS, GET" ) + self.client.setHeader( + "Access-Control-Allow-Methods", + "OPTIONS, GET" + ) return 204, "" @Routing.route("/summary") @@ -2161,20 +2181,41 @@ # with invalid values. data_type = ext_type or accept_type or headers.get('Accept') or "invalid" - # add access-control-allow-* to support CORS - self.client.setHeader("Access-Control-Allow-Origin", "*") - self.client.setHeader( - "Access-Control-Allow-Headers", - "Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override" - ) - self.client.setHeader( - "Allow", - "OPTIONS, GET, POST, PUT, DELETE, PATCH" - ) - self.client.setHeader( - "Access-Control-Allow-Methods", - "HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH" - ) + if method.upper() == 'OPTIONS': + # add access-control-allow-* access-control-max-age to support CORS + # Allow-Origin must match origin supplied by client. '*' doesn't + # work for authenticated requests. + self.client.setHeader( + "Access-Control-Allow-Origin", + self.client.request.headers.get ("Origin") + ) + self.client.setHeader( + "Access-Control-Allow-Headers", + "Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override" + ) + # can be overridden by options handlers to provide supported + # methods for endpoint + self.client.setHeader( + "Access-Control-Allow-Methods", + "HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH" + ) + # allow credentials + self.client.setHeader( + "Access-Control-Allow-Credentials", + "true" + ) + # cache the Access headings for a week. Allows one CORS pre-flight + # request to be reused again and again. + self.client.setHeader("Access-Control-Max-Age", "86400") + + # set allow header in case of error. 405 handlers below should + # replace it with a custom version as will OPTIONS handler + # doing CORS. + self.client.setHeader( + "Allow", + "OPTIONS, GET, POST, PUT, DELETE, PATCH" + ) + # Is there an input.value with format json data? # If so turn it into an object that emulates enough # of the FieldStorge methods/props to allow a response.