diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index b521c0ba1..9106e0d70 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -39,7 +39,11 @@ pub struct RestApi { impl RestApi { pub fn new(cors_domains: Vec, endpoints: Arc, fetcher: Arc) -> Box { Box::new(RestApi { - cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()), + cors_domains: Some(cors_domains.into_iter().map(|domain| match domain.as_ref() { + "all" | "*" | "any" => AccessControlAllowOrigin::Any, + "null" => AccessControlAllowOrigin::Null, + other => AccessControlAllowOrigin::Value(other.into()), + }).collect()), endpoints: endpoints, fetcher: fetcher, }) diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 3f26e82a9..30c62a031 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -111,6 +111,7 @@ pub struct ServerBuilder { web_proxy_tokens: Arc, signer_address: Option<(String, u16)>, allowed_hosts: Option>, + extra_cors: Option>, remote: Remote, fetch: Option, } @@ -126,6 +127,7 @@ impl ServerBuilder { web_proxy_tokens: Arc::new(|_| false), signer_address: None, allowed_hosts: Some(vec![]), + extra_cors: None, remote: remote, fetch: None, } @@ -143,6 +145,7 @@ impl ServerBuilder { web_proxy_tokens: self.web_proxy_tokens, signer_address: self.signer_address, allowed_hosts: self.allowed_hosts, + extra_cors: self.extra_cors, remote: self.remote, fetch: Some(fetch), } @@ -174,6 +177,13 @@ impl ServerBuilder { self } + /// Extra cors headers. + /// `None` - no additional CORS URLs + pub fn extra_cors_headers(mut self, cors: Option>) -> Self { + self.extra_cors = cors; + self + } + /// Change extra dapps paths (apart from `dapps_path`) pub fn extra_dapps>(mut self, extra_dapps: &[P]) -> Self { self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect(); @@ -187,6 +197,7 @@ impl ServerBuilder { Server::start_http( addr, self.allowed_hosts, + self.extra_cors, NoAuth, handler, self.dapps_path, @@ -207,6 +218,7 @@ impl ServerBuilder { Server::start_http( addr, self.allowed_hosts, + self.extra_cors, HttpBasicAuth::single_user(username, password), handler, self.dapps_path, @@ -251,8 +263,8 @@ impl Server { } /// Returns a list of CORS domains for API endpoint. - fn cors_domains(signer_address: Option<(String, u16)>) -> Vec { - match signer_address { + fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option>) -> Vec { + let basic_cors = match signer_address { Some(signer_address) => vec![ format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), @@ -260,15 +272,20 @@ impl Server { format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), format!("https://{}", address(&signer_address)), - ], None => vec![], + }; + + match extra_cors { + None => basic_cors, + Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(), } } fn start_http>( addr: &SocketAddr, hosts: Option>, + extra_cors: Option>, authorization: A, handler: RpcHandler, dapps_path: PathBuf, @@ -297,7 +314,7 @@ impl Server { remote.clone(), fetch.clone(), )); - let cors_domains = Self::cors_domains(signer_address.clone()); + let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors); let special = Arc::new({ let mut special = HashMap::new(); @@ -413,8 +430,9 @@ mod util_tests { // given // when - let none = Server::cors_domains(None); - let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180))); + let none = Server::cors_domains(None, None); + let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None); + let extra = Server::cors_domains(None, Some(vec!["all".to_owned()])); // then assert_eq!(none, Vec::::new()); @@ -425,7 +443,7 @@ mod util_tests { "https://parity.web3.site".into(), "https://parity.web3.site:18180".into(), "https://127.0.0.1:18180".into() - ]); + assert_eq!(extra, vec!["all".to_owned()]); } } diff --git a/dapps/src/tests/api.rs b/dapps/src/tests/api.rs index 0930aa0ce..1b9f64b7f 100644 --- a/dapps/src/tests/api.rs +++ b/dapps/src/tests/api.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers}; +use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, request, assert_security_headers}; #[test] fn should_return_error() { @@ -212,3 +212,25 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() { ); } +#[test] +fn should_return_extra_cors_headers() { + // given + let server = serve_extra_cors(Some(vec!["all".to_owned()])); + + // when + let response = request(server, + "\ + POST /api/ping HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Origin: http://somedomain.io\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + response.assert_status("HTTP/1.1 200 OK"); + response.assert_header("Access-Control-Allow-Origin", "http://somedomain.io"); +} + diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index 9df98c343..d1a1e9900 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -109,6 +109,10 @@ pub fn serve_hosts(hosts: Option>) -> ServerLoop { init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0 } +pub fn serve_extra_cors(extra_cors: Option>) -> ServerLoop { + init_server(|builder| builder.allowed_hosts(None).extra_cors_headers(extra_cors), Default::default(), Remote::new_sync()).0 +} + pub fn serve_with_registrar() -> (ServerLoop, Arc) { init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync()) } diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 5f062d425..cb256f0b7 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -181,6 +181,8 @@ usage! { or |c: &Config| otry!(c.dapps).interface.clone(), flag_dapps_hosts: String = "none", or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| vec.join(",")), + flag_dapps_cors: Option = None, + or |c: &Config| otry!(c.dapps).cors.clone().map(Some), flag_dapps_path: String = "$BASE/dapps", or |c: &Config| otry!(c.dapps).path.clone(), flag_dapps_user: Option = None, @@ -428,6 +430,7 @@ struct Dapps { port: Option, interface: Option, hosts: Option>, + cors: Option, path: Option, user: Option, pass: Option, @@ -674,6 +677,7 @@ mod tests { flag_dapps_port: 8080u16, flag_dapps_interface: "local".into(), flag_dapps_hosts: "none".into(), + flag_dapps_cors: None, flag_dapps_path: "$HOME/.parity/dapps".into(), flag_dapps_user: Some("test_user".into()), flag_dapps_pass: Some("test_pass".into()), @@ -873,6 +877,7 @@ mod tests { path: None, interface: None, hosts: None, + cors: None, user: Some("username".into()), pass: Some("password".into()) }), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index a36d0a68c..da7a72db9 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -164,6 +164,8 @@ API and Console Options: is additional security against some attack vectors. Special options: "all", "none", (default: {flag_dapps_hosts}). + --dapps-cors URL Specify CORS headers for Dapps server APIs. + (default: {flag_dapps_cors:?}) --dapps-user USERNAME Specify username for Dapps server. It will be used in HTTP Basic Authentication Scheme. If --dapps-pass is not specified you will be diff --git a/parity/configuration.rs b/parity/configuration.rs index b50816482..c0756a771 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -546,6 +546,7 @@ impl Configuration { interface: self.dapps_interface(), port: self.args.flag_dapps_port, hosts: self.dapps_hosts(), + cors: self.dapps_cors(), user: self.args.flag_dapps_user.clone(), pass: self.args.flag_dapps_pass.clone(), dapps_path: PathBuf::from(self.directories().dapps), @@ -722,6 +723,10 @@ impl Configuration { Self::cors(self.args.flag_ipfs_api_cors.as_ref()) } + fn dapps_cors(&self) -> Option> { + Self::cors(self.args.flag_dapps_cors.as_ref()) + } + fn hosts(hosts: &str) -> Option> { match hosts { "none" => return Some(Vec::new()), diff --git a/parity/dapps.rs b/parity/dapps.rs index 572d32b48..b9094c16d 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -33,6 +33,7 @@ pub struct Configuration { pub interface: String, pub port: u16, pub hosts: Option>, + pub cors: Option>, pub user: Option, pub pass: Option, pub dapps_path: PathBuf, @@ -48,6 +49,7 @@ impl Default for Configuration { interface: "127.0.0.1".into(), port: 8080, hosts: Some(Vec::new()), + cors: None, user: None, pass: None, dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), @@ -93,6 +95,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result, _url: &SocketAddr, _allowed_hosts: Option>, + _cors: Option>, _auth: Option<(String, String)>, _all_apis: bool, ) -> Result { @@ -147,6 +151,7 @@ mod server { extra_dapps: Vec, url: &SocketAddr, allowed_hosts: Option>, + cors: Option>, auth: Option<(String, String)>, all_apis: bool, ) -> Result { @@ -167,7 +172,8 @@ mod server { .web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token))) .extra_dapps(&extra_dapps) .signer_address(deps.signer.address()) - .allowed_hosts(allowed_hosts); + .allowed_hosts(allowed_hosts) + .extra_cors_headers(cors); let api_set = if all_apis { warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Dapps with all APIs exposed."));