-
Notifications
You must be signed in to change notification settings - Fork 276
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
Allow customising access control allow headers #305
Conversation
73691e4
to
c9907db
Compare
c9907db
to
bb00c35
Compare
There was a problem hiding this 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.
http/src/handler.rs
Outdated
@@ -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, |
There was a problem hiding this comment.
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.
http/src/handler.rs
Outdated
])); | ||
|
||
if let AllowHeaders::Ok(cors_allow_headers) = cors_allow_headers { | ||
if cors_allow_headers.len() > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!....is_empty()
?
server-utils/src/cors.rs
Outdated
// if they can be used. | ||
|
||
let echo = AllowHeaders::Ok( | ||
header::AccessControlAllowHeaders(requested.deref().clone()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.deref()
most likely unneessary
There was a problem hiding this comment.
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`
There was a problem hiding this comment.
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.
server-utils/src/cors.rs
Outdated
}, | ||
|
||
AccessControlAllowHeaders::Only(only) => { | ||
let mut allowed_headers = only.clone(); |
There was a problem hiding this comment.
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));
There was a problem hiding this comment.
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.
server-utils/src/cors.rs
Outdated
} | ||
|
||
/// Returns headers which are always allowed. | ||
pub fn get_always_allowed_headers() -> Vec<Ascii<String>> { |
There was a problem hiding this comment.
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>>
There was a problem hiding this comment.
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>>
.
bb00c35
to
7e25946
Compare
@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 |
7e25946
to
56bd7fd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few more grumbles
http/examples/http_meta.rs
Outdated
use jsonrpc_http_server::jsonrpc_core::*; | ||
use self::hyper::header; | ||
use self::unicase::Ascii; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
server-utils/src/cors.rs
Outdated
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())) |
There was a problem hiding this comment.
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?
server-utils/src/cors.rs
Outdated
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())) || |
There was a problem hiding this comment.
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>
?
There was a problem hiding this comment.
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.
server-utils/src/cors.rs
Outdated
// if they can be used. | ||
|
||
let echo = AllowHeaders::Ok( | ||
header::AccessControlAllowHeaders(requested.deref().clone()) |
There was a problem hiding this comment.
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.
server-utils/src/cors.rs
Outdated
let are_all_allowed = requested.iter() | ||
.all(|header| { | ||
let name = &Ascii::new(header.as_ref()); | ||
only.contains(header) || get_always_allowed_headers().contains(name) |
There was a problem hiding this comment.
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.
server-utils/src/cors.rs
Outdated
} | ||
} | ||
|
||
/// Returns headers which are always allowed. | ||
pub fn get_always_allowed_headers() -> Vec<Ascii<&'static str>> { |
There was a problem hiding this comment.
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.
6bb4348
to
7210d57
Compare
@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.).
7210d57
to
0ead1d8
Compare
Thanks for the comments! I have added a number of commits labelled |
0ead1d8
to
573c43c
Compare
@cmichi Perfect, thanks! |
* 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)
* 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)
* 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)
* 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)
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
andAny()
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 whenAccessControlAllowHeaders::Only
is used there are still some fields which are always allowed (Host
etc.).