Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ISSUE: 3030. CORS Expose Headers. #3043

Merged
merged 3 commits into from
Nov 21, 2017

Conversation

McMutton
Copy link
Contributor

Access-Control-Expose-Headers is now sent in the response, allowing the client to access certain response headers.

With this PR, I took the liberty to add some improvements to our logic with respect to the CORS specification: We are no longer adding any CORS headers to the response if the client fails to add Origin to the request or the Origin value does not match the one allowed by Orion.

next release details added
@@ -41,6 +41,7 @@ HTTP/1.1 200 OK
Content-Length: 0
Access-Control-Max-Age: 600
Access-Control-Allow-Headers: REGEX(.*)
Access-Control-Expose-Headers: REGEX(.*)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I see, the list is explicit (define CORS_EXPOSED_HEADERS FIWARE_CORRELATOR", "FIWARE_TOTAL_COUNT", "RESOURCE_LOCATION). Thus, I'd suggest to use the actual headers instead a generic regex, in order to make the test stronger.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fact.. would it make sense to to the same for Access-Control-Allow-Headers, I mean, to pass from REGEX(.*) to the actual list of headers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fgalan, thank you for this comment. This made me realize I have skipped an important step in testing. Let me explain : )

My initial idea was to have a separate test file focusing on each CORS header and avoid failing of a specific test just because another CORS header has a wrong value.

For example, here in max_age.test we are focusing on the Access-Control-Max-Age header and this test should not fail in case there is something wrong with Access-Control-Allow-Headers. A specific test or a set of tests should be focusing on that header.

The reality is, there are no tests focusing on Access-Control-Allow-Headers or Access-Control-Expose-Headers. I'll go ahead and add them quickly to the set of CORS tests. Please check if the tests make sense as a whole then and we can discuss if we need any further improvements.

Copy link
Member

@fgalan fgalan Nov 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me check if I'm undersetanding your point before providing feedback

You mean having 3 .test files, each one focusing on each header, thus:

  • In the first .test Access-Control-Max-Age: has particular values but Access-Control-Allow-Headers and Access-Control-Expose-Headers use REGEX(.*)
  • In the second .test Access-Control-Allow-Headers: has particular values but Access-Control-Max-Age and Access-Control-Expose-Headers use REGEX(.*)
  • In the third .test Access-Control-Expose-Headers: has particular values but Access-Control-Max-Age and Access-Control-Allow-Headers use REGEX(.*)

Is this correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's the idea. We also have the Access-Control-Allow-Methods and Access-Control-Allow-Origin headers but they already have their own dedicated test files.

Copy link
Member

@fgalan fgalan Nov 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The general rule we use in the .test is try to be as specific as possible, that is, to avoid the usage of REGEX() (and specially, REGEX(.*)) as much as possible.

Having said that, I think that in this particular case it makes sense to do an exception to the general rule and proceed as you suggest. However, I'd suggest to use the following in the preamble of the involved .test in order to be clear:

This test is focused in header Access-Control-Max-Age. Thus, other CORS related headers use REGEX(.*) as value. However, note that those specific headers are tested with specific headers values in other .test files in this same directory

or something similar

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3e6e50a

@@ -179,16 +181,27 @@ void restReply(ConnectionInfo* ciP, const std::string& _answer)
{
MHD_add_response_header(response, ACCESS_CONTROL_ALLOW_ORIGIN, corsOrigin);
}
}
// If there is no match, originAllowed flag is set to false
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: { in next line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3e6e50a

*
* CORS Exposed Headers -
*/
#define CORS_EXPOSED_HEADERS FIWARE_CORRELATOR", "FIWARE_TOTAL_COUNT", "RESOURCE_LOCATION
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the three exposed headers be documented as part of Orion documentation? I mean, is this list something implementation-specific (so it should) or derived directly from CORS specification without ambiguity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, these are the headers we put in Context Broker responses that the client could read. In the context of CORS, we need to have these headers listed in Access-Control-Expose-Headers in our responses so that the client can access and read them.

Since we are not limiting or extending this set for CORS, I would say documenting might not be crucial but won't hurt either : )

By the way, if I have missed any headers that we put in the responses and expect it to be client accessible please let me know.

Copy link
Member

@fgalan fgalan Nov 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are not limiting or extending this set for CORS, I would say documenting might not be crucial but won't hurt either : )

I agree.

Could you write a little cors.md piece of documentation about this (and any other CORS specific implementation aspects to mention) in doc/manuals/user to be included in this PR, please? Don't worty too much about how the material fits in the overall manual, I'll deal with it once the PR gets merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3e6e50a

@@ -3,3 +3,4 @@
- Fix: case-sensitive header duplication (e.g. "Content-Type" and "Content-type") in custom notifications (#2893)
- Fix: bug in GTE and LTE operations in query filters (q/mq), both for GET operations and subscriptions (#2995)
- Fix: Wrong "max one service-path allowed for subscriptions" in NGSIv2 subscription operation (#2948)
- Fix: Add support for accessing response headers when making CORS requests (#3030)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be considered part of "CORS Preflight Requests support for all NGSIv2 resources"? Or it is better to consider it a separate item of work?

Copy link
Contributor Author

@McMutton McMutton Nov 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it is not directly related with Preflight requests but since we are touching those parts in this PR as well, I can change the line below

Add: CORS Preflight Requests support for all NGSIv2 resources, -corsMaxAge switch (#501)

to

Add: CORS Preflight Requests support for all NGSIv2 resources, -corsMaxAge switch, CORS exposed headers (#501)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, with a small ammedment to include the number of the issue about exposed headers:

Add: CORS Preflight Requests support for all NGSIv2 resources, -corsMaxAge switch, CORS exposed headers (#501, #3030)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3e6e50a

@kzangeli
Copy link
Member

LGTM

ciP->httpHeaderValue.push_back("GET, PATCH, POST, PUT, OPTIONS");
if ( (ciP->httpHeaders.origin != "") && ((strcmp(corsOrigin, "__ALL") == 0) || (strcmp(ciP->httpHeaders.origin.c_str(), corsOrigin) == 0)) )
{
ciP->httpHeader.push_back("Access-Control-Allow-Methods");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use ACCESS_CONTROL_ALLOW_METHODS.

(Similar cases along others serviceRoutinesV2/ files in this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3e6e50a

@@ -45,8 +46,11 @@ std::string optionsPostOnly
ParseData* parseDataP
)
{
ciP->httpHeader.push_back("Access-Control-Allow-Methods");
ciP->httpHeaderValue.push_back("POST, OPTIONS");
if ( (ciP->httpHeaders.origin != "") && ((strcmp(corsOrigin, "__ALL") == 0) || (strcmp(ciP->httpHeaders.origin.c_str(), corsOrigin) == 0)) )
Copy link
Member

@fgalan fgalan Nov 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is done many times... Maybe it should be moved to a common function.

Copy link
Contributor Author

@McMutton McMutton Nov 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which file do you think should host the function? Is rest.cpp or RestService.cpp are good candidates? Or should we have another, CORS specific file?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rest.cpp (along with extern declaration in rest.h) sounds good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3e6e50a

Copy link
Member

@fgalan fgalan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work @McMutton! Only some minor comments added to the PR

@@ -45,8 +47,11 @@ std::string optionsAllNotDelete
ParseData* parseDataP
)
{
ciP->httpHeader.push_back("Access-Control-Allow-Methods");
ciP->httpHeaderValue.push_back("GET, PATCH, POST, PUT, OPTIONS");
if ( isOriginAllowedForCORS(ciP->httpHeaders.origin) )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is should be

if (isOriginAllowedForCORS(ciP->httpHeaders.origin))

per M11 style rule

Also, after ( and before ), spaces MUST NOT be present.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Seen in other part of the PR also)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in da358b6

*/
bool isOriginAllowedForCORS(std::string requestOrigin)
{
return ( (requestOrigin != "") && ((strcmp(corsOrigin, "__ALL") == 0) || (strcmp(requestOrigin.c_str(), corsOrigin) == 0)) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If think it should change: return ( ( .. ) ); -> return ( ) per M11 style rule

Also, after ( and before ), spaces MUST NOT be present.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in da358b6

* isOriginAllowedForCORS - checks the Origin header of the request and returns
* true if that Origin is allowed to make a CORS request
*/
bool isOriginAllowedForCORS(std::string requestOrigin)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be const std::string&, see https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/contribution_guidelines.md#programming-patterns:

if the function should not be able to modify the object (i.e. "read only"), then const references should be used, e.g. const BigFish& bf

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing out these mistakes @fgalan, I will fix them quickly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They aren't big issues, but I think is good to fix them so you get familiar with style aspects ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we need mistakes to memorize style guidelines :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in da358b6

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to add some clarification here ...
This is a bit more than just 'style guide' (understanding style guide as a "the way I prefer to see the code, my taste").
This is about avoiding to copy an entire object on the stack for every call to the function, and instead just passing the object by reference.

The style guide says something like:

"An entire object should never ever be sent over the stack."
"If the object is allowed to be altered by the function, use a C pointer"
"If not, use a C++ reference"

The reasoning behind this rule is that the ampersand in the call:
funcCall(&v):
shows that the variable v may be altered, while
funcCall(v)
does not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, this is more than style. Note it is in the section about Programming Patterns of the Contribution Guidelines, not in the sections for Must/should Style Rules.

@fgalan
Copy link
Member

fgalan commented Nov 21, 2017

LGTM. Thanks!

@fgalan fgalan merged commit dfc83ad into telefonicaid:master Nov 21, 2017
@McMutton McMutton deleted the cors_expose_headers branch November 30, 2017 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants