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

Allow customising access control allow headers #305

Conversation

cmichi
Copy link
Contributor

@cmichi cmichi commented Sep 3, 2018

This should implement #114.

I also discovered that @tomusdrw made a comment at openethereum/parity-ethereum#6616 which seems to refer to the same issue. So I think that issue would be solved through this PR as well. I implemented this PR in the way suggested in that issue (with Only and Any() enums).

A thing that I am a bit unsure about is the set of header fields which are always allowed (get_always_allowed_headers() in the PR). This is something we might need to discuss. The behavior of this PR is that even when AccessControlAllowHeaders::Only is used there are still some fields which are always allowed (Host etc.).

@cmichi cmichi force-pushed the allow-customising-access-control-allow-headers branch 2 times, most recently from 73691e4 to c9907db Compare September 7, 2018 08:13
@cmichi cmichi force-pushed the allow-customising-access-control-allow-headers branch from c9907db to bb00c35 Compare September 7, 2018 08:13
Copy link
Contributor

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

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

Looks solid, couple of minor issues.

@@ -33,6 +36,7 @@ impl<M: Metadata, S: Middleware<M>> ServerHandler<M, S> {
jsonrpc_handler: Rpc<M, S>,
cors_domains: CorsDomains,
cors_max_age: Option<u32>,
allowed_headers: AccessControlAllowHeaders,
Copy link
Contributor

Choose a reason for hiding this comment

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

Please refer to the type in a consistent way (either cors::AccessControlAllowHeaders or directly). TBH I prefer the former.

]));

if let AllowHeaders::Ok(cors_allow_headers) = cors_allow_headers {
if cors_allow_headers.len() > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

!....is_empty()?

// if they can be used.

let echo = AllowHeaders::Ok(
header::AccessControlAllowHeaders(requested.deref().clone())
Copy link
Contributor

Choose a reason for hiding this comment

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

.deref() most likely unneessary

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I dereferenced here to extract the vec, if I leave it away the compiler complains about:

expected struct `std::vec::Vec`, found struct `cors::hyper::header::AccessControlRequestHeaders`

Copy link
Contributor

Choose a reason for hiding this comment

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

(*requested).clone() should work and doesn't require Deref to be imported. I think it's also more idiomatic.

},

AccessControlAllowHeaders::Only(only) => {
let mut allowed_headers = only.clone();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to clone it with every request? Maybe just do two checks instead of extending and doing one?

let are_all_allowed = requested.iter().all(|header| only.contains(header) || always_allowed.contains(header));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yeah, you're right, the cloning is not necessary. I replaced it with your suggestion.

}

/// Returns headers which are always allowed.
pub fn get_always_allowed_headers() -> Vec<Ascii<String>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make it a static array? You should be able to use either Ascii<&str> or Ascii<Cow<str>>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I went for Vec<Ascii<&'static str>>.

@cmichi cmichi force-pushed the allow-customising-access-control-allow-headers branch from bb00c35 to 7e25946 Compare September 10, 2018 19:06
@cmichi
Copy link
Contributor Author

cmichi commented Sep 10, 2018

@tomusdrw Thanks for the feedback! I addressed your comments and amended the last commit. Can you please take a look again?

Do you have any thoughts on the default set of headers which should be returned by get_always_allowed_headers()?

@cmichi cmichi force-pushed the allow-customising-access-control-allow-headers branch from 7e25946 to 56bd7fd Compare September 10, 2018 20:32
Copy link
Contributor

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

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

Few more grumbles

use jsonrpc_http_server::jsonrpc_core::*;
use self::hyper::header;
use self::unicase::Ascii;
Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO we should use re-exported value - actually user doesn't really care that it's Ascii internally, can't we do like this:

AccessControlAllowHeaders::Only(vec![
  "Authorization",
])

and convert inside the builder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yeah, I agree, the API should only expose what is necessary.

I added a commit which introduces a struct AccessControlAllowHeadersUnicase which is a HashSet<Ascii<&'static str>> and is used internally now. I added an Into trait which converts the exposed AccessControlAllowHeaders to this internal type, so it's still possible to use a Vector for setting everything up.

let are_all_allowed = request_headers.iter()
.all(|header| {
only.contains(&Ascii::new(header.name().to_owned())) ||
get_always_allowed_headers().contains(&Ascii::new(header.name()))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we alloc always_allowed_headers on every iteration? Maybe just create it once and cache?

if let AccessControlAllowHeaders::Only(only) = cors_allow_headers {
let are_all_allowed = request_headers.iter()
.all(|header| {
only.contains(&Ascii::new(header.name().to_owned())) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Would also be good to avoid copying the header here, Can't we compare Ascii<String> with Ascii<Cow> or Ascii<&'a str>?

Copy link
Contributor Author

@cmichi cmichi Sep 21, 2018

Choose a reason for hiding this comment

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

I changed the type of only to HashSet<Ascii<&'static str>>, so it's no longer necessary to copy the header.

// if they can be used.

let echo = AllowHeaders::Ok(
header::AccessControlAllowHeaders(requested.deref().clone())
Copy link
Contributor

Choose a reason for hiding this comment

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

(*requested).clone() should work and doesn't require Deref to be imported. I think it's also more idiomatic.

let are_all_allowed = requested.iter()
.all(|header| {
let name = &Ascii::new(header.as_ref());
only.contains(header) || get_always_allowed_headers().contains(name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above - create once, not in every iteration.

}
}

/// Returns headers which are always allowed.
pub fn get_always_allowed_headers() -> Vec<Ascii<&'static str>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be better to do a HashSet here instead of doing linear search every time. The list of headers looks good.

@cmichi cmichi force-pushed the allow-customising-access-control-allow-headers branch 2 times, most recently from 6bb4348 to 7210d57 Compare September 20, 2018 07:27
@tomusdrw
Copy link
Contributor

@cmichi could you rebase on latest master to fix the build?

This commit adds the capability to customise the header fields
which are allowed.

A CORS preflight request uses the `Access-Control-Request-Headers`
header to inquire about header fields which are allowed to be used.

The response is either `403 Forbidden` or `200 OK` with the header
`Access-Control-Allow-Headers` and the list of fields which were
supplied in the `Access-Control-Request-Headers`.

This list is not necessarily a complete list of all allowed fields.
Rather certain fields are always allowed and don't need to be listed.
Also `Access-Control-Request-Headers` might not contain header fields
which would in fact be allowed to send; these fields are currently
not exposed -- only fields which were in `Access-Control-Request-Headers`
are containted in the responding `Access-Control-Allow-Headers`.

The API can be used with two enums:

	`cors_allow_headers(AccessControlAllowHeaders::Only(Vec))`
	`cors_allow_headers(AccessControlAllowHeaders::Any)`

The default is `Any`, as to not break the existing behavior.

If `Only` is chosen there will still be some default headers which
are always allowed (`Content-Type`, etc.).
@cmichi cmichi force-pushed the allow-customising-access-control-allow-headers branch from 7210d57 to 0ead1d8 Compare September 21, 2018 11:38
@cmichi
Copy link
Contributor Author

cmichi commented Sep 21, 2018

Thanks for the comments!

I have added a number of commits labelled (…) to the PR to make it easier reviewing them. Before merging I would squash them into the main Support Access-Control-Request-Headers in http-server commit though.

@cmichi cmichi force-pushed the allow-customising-access-control-allow-headers branch from 0ead1d8 to 573c43c Compare September 21, 2018 11:53
@tomusdrw tomusdrw merged commit 53f814e into paritytech:master Sep 24, 2018
@tomusdrw
Copy link
Contributor

@cmichi Perfect, thanks!

@cmichi cmichi deleted the allow-customising-access-control-allow-headers branch September 24, 2018 15:30
CriesofCarrots pushed a commit to CriesofCarrots/jsonrpc that referenced this pull request Sep 28, 2018
* Fix typo

* Refactor CorsHeader to AllowOrigin

* Support Access-Control-Request-Headers in http-server

This commit adds the capability to customise the header fields
which are allowed.

A CORS preflight request uses the `Access-Control-Request-Headers`
header to inquire about header fields which are allowed to be used.

The response is either `403 Forbidden` or `200 OK` with the header
`Access-Control-Allow-Headers` and the list of fields which were
supplied in the `Access-Control-Request-Headers`.

This list is not necessarily a complete list of all allowed fields.
Rather certain fields are always allowed and don't need to be listed.
Also `Access-Control-Request-Headers` might not contain header fields
which would in fact be allowed to send; these fields are currently
not exposed -- only fields which were in `Access-Control-Request-Headers`
are containted in the responding `Access-Control-Allow-Headers`.

The API can be used with two enums:

	`cors_allow_headers(AccessControlAllowHeaders::Only(Vec))`
	`cors_allow_headers(AccessControlAllowHeaders::Any)`

The default is `Any`, as to not break the existing behavior.

If `Only` is chosen there will still be some default headers which
are always allowed (`Content-Type`, etc.).

* (cache always allowed headers and use HashSet for them)

* (get rid of Deref import)

* (do not expose Ascii)

* (convert Vector to HashSet for allowed headers set by user)

* (change to non-owned str to prevent copying the header when testing containment)
CriesofCarrots pushed a commit to CriesofCarrots/jsonrpc that referenced this pull request Sep 28, 2018
* Fix typo

* Refactor CorsHeader to AllowOrigin

* Support Access-Control-Request-Headers in http-server

This commit adds the capability to customise the header fields
which are allowed.

A CORS preflight request uses the `Access-Control-Request-Headers`
header to inquire about header fields which are allowed to be used.

The response is either `403 Forbidden` or `200 OK` with the header
`Access-Control-Allow-Headers` and the list of fields which were
supplied in the `Access-Control-Request-Headers`.

This list is not necessarily a complete list of all allowed fields.
Rather certain fields are always allowed and don't need to be listed.
Also `Access-Control-Request-Headers` might not contain header fields
which would in fact be allowed to send; these fields are currently
not exposed -- only fields which were in `Access-Control-Request-Headers`
are containted in the responding `Access-Control-Allow-Headers`.

The API can be used with two enums:

	`cors_allow_headers(AccessControlAllowHeaders::Only(Vec))`
	`cors_allow_headers(AccessControlAllowHeaders::Any)`

The default is `Any`, as to not break the existing behavior.

If `Only` is chosen there will still be some default headers which
are always allowed (`Content-Type`, etc.).

* (cache always allowed headers and use HashSet for them)

* (get rid of Deref import)

* (do not expose Ascii)

* (convert Vector to HashSet for allowed headers set by user)

* (change to non-owned str to prevent copying the header when testing containment)
CriesofCarrots pushed a commit to solana-labs/jsonrpc that referenced this pull request Sep 28, 2018
* Fix typo

* Refactor CorsHeader to AllowOrigin

* Support Access-Control-Request-Headers in http-server

This commit adds the capability to customise the header fields
which are allowed.

A CORS preflight request uses the `Access-Control-Request-Headers`
header to inquire about header fields which are allowed to be used.

The response is either `403 Forbidden` or `200 OK` with the header
`Access-Control-Allow-Headers` and the list of fields which were
supplied in the `Access-Control-Request-Headers`.

This list is not necessarily a complete list of all allowed fields.
Rather certain fields are always allowed and don't need to be listed.
Also `Access-Control-Request-Headers` might not contain header fields
which would in fact be allowed to send; these fields are currently
not exposed -- only fields which were in `Access-Control-Request-Headers`
are containted in the responding `Access-Control-Allow-Headers`.

The API can be used with two enums:

	`cors_allow_headers(AccessControlAllowHeaders::Only(Vec))`
	`cors_allow_headers(AccessControlAllowHeaders::Any)`

The default is `Any`, as to not break the existing behavior.

If `Only` is chosen there will still be some default headers which
are always allowed (`Content-Type`, etc.).

* (cache always allowed headers and use HashSet for them)

* (get rid of Deref import)

* (do not expose Ascii)

* (convert Vector to HashSet for allowed headers set by user)

* (change to non-owned str to prevent copying the header when testing containment)
CriesofCarrots pushed a commit to solana-labs/jsonrpc that referenced this pull request Sep 28, 2018
* Fix typo

* Refactor CorsHeader to AllowOrigin

* Support Access-Control-Request-Headers in http-server

This commit adds the capability to customise the header fields
which are allowed.

A CORS preflight request uses the `Access-Control-Request-Headers`
header to inquire about header fields which are allowed to be used.

The response is either `403 Forbidden` or `200 OK` with the header
`Access-Control-Allow-Headers` and the list of fields which were
supplied in the `Access-Control-Request-Headers`.

This list is not necessarily a complete list of all allowed fields.
Rather certain fields are always allowed and don't need to be listed.
Also `Access-Control-Request-Headers` might not contain header fields
which would in fact be allowed to send; these fields are currently
not exposed -- only fields which were in `Access-Control-Request-Headers`
are containted in the responding `Access-Control-Allow-Headers`.

The API can be used with two enums:

	`cors_allow_headers(AccessControlAllowHeaders::Only(Vec))`
	`cors_allow_headers(AccessControlAllowHeaders::Any)`

The default is `Any`, as to not break the existing behavior.

If `Only` is chosen there will still be some default headers which
are always allowed (`Content-Type`, etc.).

* (cache always allowed headers and use HashSet for them)

* (get rid of Deref import)

* (do not expose Ascii)

* (convert Vector to HashSet for allowed headers set by user)

* (change to non-owned str to prevent copying the header when testing containment)
@tomusdrw tomusdrw mentioned this pull request Jan 28, 2019
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.

None yet

2 participants