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

[CORS] Adds W3C Cors Specification Support #665

Merged
merged 5 commits into from
Mar 9, 2018
Merged

[CORS] Adds W3C Cors Specification Support #665

merged 5 commits into from
Mar 9, 2018

Conversation

eliasjpr
Copy link
Contributor

@eliasjpr eliasjpr commented Feb 25, 2018

The current CORS implementation is lacking some functionality and is failing to meet W3C specifications. The changes presented here provides facilities for dealing with CORS requests and responses while staying complaint to W3C specs.

ISSUE #240

How It Works

This handler is compliant with the W3C CORS specification. As per this specification, It doesn’t put any CORS response headers in a connection that holds an invalid CORS request. To know what “invalid” CORS request means, have a look at the “Validity of CORS requests” section below.

When some options that are not mandatory and have no default value (such :max_age) at intialization, the relative header will often not be sent at all. This is compliant with the specification and at the same time it reduces the size of the response, even if just by a handful of bytes.

The following is a list of all the CORS response headers supported by the handler:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Credentials
  • Access-Control-Expose-Headers
  • Access-Control-Max-Age

Validity of CORS requests

“Invalid CORS request” can mean that a request doesn’t have an Origin header (so it’s not a CORS request at all) or that it’s a CORS request but:

  • The Origin request header doesn’t match any of the allowed origins
  • The request is a preflight request but it requests to use a method
    or some headers that are not allowed (via the Access-Control-Request-Method
    and Access-Control-Request-Headers headers)

Responding to preflight requests

When the request is a preflight request and is a valid one (valid origin, valid request method, and valid request headers), The CORS handler directly sends a response to that request instead of just adding headers to the connection. To do this, the handler halts the connection and sends a response.

Requesting for inputs on the following

  • Cleanup CORS::OriginType to support String | Array(String) | Regex
  • POssibly implement Forbidden Response for failed CORS validity, this is a little subjective since the W3C specs does not state to set a forbidden response for invalid requests, while other frameworks do implements a 403 Forbidden Response

@eliasjpr eliasjpr added the WIP label Feb 25, 2018
@eliasjpr eliasjpr requested review from a team February 25, 2018 16:25
@eliasjpr eliasjpr changed the title [WIP] [CORS] Adds W3C Cors Specification Support [CORS] Adds W3C Cors Specification Support Feb 28, 2018
@eliasjpr eliasjpr removed the WIP label Feb 28, 2018
Copy link
Member

@elorest elorest left a comment

Choose a reason for hiding this comment

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

I haven't looked into the deep magic of cores much but on the surface this looks fine to me. I'll defer to @drujensen or @fridgerator on this one.

Copy link
Member

@robacarp robacarp left a comment

Choose a reason for hiding this comment

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

couple nitpicky comments, but I like this

if allow_credentials
context.response.headers["Access-Control-Allow-credentials"] = "true"
end
private def put_response_headers(response)
Copy link
Member

Choose a reason for hiding this comment

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

these put_ method names are inconsistent with set_preflight_headers below, one pattern or the other should be consistent throughout the class

end
end

struct Origin
Copy link
Member

Choose a reason for hiding this comment

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

Above OriginType is defined, but it's not used at all in this struct. Should it be?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was updated

origins << "example.com"
context = cors_context(
"OPTIONS",
"Origin": "example.com",
Copy link
Member

Choose a reason for hiding this comment

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

should this and line 92 above point to a shared variable?

"OPTIONS",
"Origin": "example.com",
"Access-Control-Request-Method": "PUT",
"Access-Control-Request-Headers": "Accept"
Copy link
Member

Choose a reason for hiding this comment

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

this could use the header constants

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How would you do this in crystal? The arguments are passed as a NamedTuple. I am not sure how to accomplish what you are asking.

origin_header.should_not be_nil
end

def assert_cors_not_success(context)
Copy link
Member

Choose a reason for hiding this comment

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

this english of this method name is a little unexpected, how about refute_cors_success?

context.response.headers["Access-Control-Allow-Origin"] = allow_origin
def forbidden(context)
context.response.headers["Content-Type"] = "text/plain"
context.response.respond_with_error FORBIDDEN, 403
Copy link
Member

Choose a reason for hiding this comment

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

How will this interact with the Error pipe?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This writes to the response without raising an error. Should we raise an error here?

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 so, that way it can be caught by the errors pipe and rendered nicely

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So we do not need to raise error here just respond with 403 according to the CORS specs since the Pref-light request is done behind the scenes by the browser

@eliasjpr eliasjpr force-pushed the ep/cors branch 8 times, most recently from 99a4c83 to 95e21ec Compare March 8, 2018 17:24
eliasjpr added 2 commits March 8, 2018 12:30
Provides facilities for dealing with CORS requests and responses

- A Plug (Handler) that handles CORS request and responses.

\## How It Works

This handler is compliant with the W3C CORS specification. As per
this specification, It doesn’t put any CORS response headers
in a connection that holds an invalid CORS request. To know what
“invalid” CORS request means, have a look at the
“Validity of CORS requests” section below.

When some options that are not mandatory and have no default value
(such :max_age) at intialization, the relative header will often not be
sent at all. This is compliant with the specification and at the
same time it reduces the size of the response, even if just by a handful
of bytes.

The following is a list of all the CORS response headers supported by
the handler:

- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- Access-Control-Allow-Credentials
- Access-Control-Expose-Headers
- Access-Control-Max-Age

\## Validity of CORS requests

“Invalid CORS request” can mean that a request doesn’t have an Origin
header (so it’s not a CORS request at all) or that it’s a CORS request but:

  - The Origin request header doesn’t match any of the allowed origins
  - The request is a preflight request but it requests to use a method
    or some headers that are not allowed (via the Access-Control-Request-Method
    and Access-Control-Request-Headers headers)

\## Responding to preflight requests

When the request is a preflight request and is a valid one (valid origin, valid
request method, and valid request headers), The CORS handler directly sends a
response to that request instead of just adding headers to the connection.
To do this, the handler halts the connection and sends a response.
@eliasjpr eliasjpr force-pushed the ep/cors branch 3 times, most recently from 0a3709e to 58bcae1 Compare March 8, 2018 17:42
@eliasjpr eliasjpr merged commit a8cda7a into master Mar 9, 2018
@eliasjpr eliasjpr deleted the ep/cors branch March 9, 2018 17:10
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