Allow specifying extra cors headers for dapps (#4710)

This commit is contained in:
Tomasz Drwięga 2017-03-07 17:33:28 +01:00 committed by Arkadiy Paronyan
parent ae3f85bd5b
commit 4868f758bf
8 changed files with 76 additions and 10 deletions

View File

@ -39,7 +39,11 @@ pub struct RestApi {
impl RestApi { impl RestApi {
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> { pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
Box::new(RestApi { 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, endpoints: endpoints,
fetcher: fetcher, fetcher: fetcher,
}) })

View File

@ -111,6 +111,7 @@ pub struct ServerBuilder<T: Fetch = FetchClient> {
web_proxy_tokens: Arc<WebProxyTokens>, web_proxy_tokens: Arc<WebProxyTokens>,
signer_address: Option<(String, u16)>, signer_address: Option<(String, u16)>,
allowed_hosts: Option<Vec<String>>, allowed_hosts: Option<Vec<String>>,
extra_cors: Option<Vec<String>>,
remote: Remote, remote: Remote,
fetch: Option<T>, fetch: Option<T>,
} }
@ -126,6 +127,7 @@ impl ServerBuilder {
web_proxy_tokens: Arc::new(|_| false), web_proxy_tokens: Arc::new(|_| false),
signer_address: None, signer_address: None,
allowed_hosts: Some(vec![]), allowed_hosts: Some(vec![]),
extra_cors: None,
remote: remote, remote: remote,
fetch: None, fetch: None,
} }
@ -143,6 +145,7 @@ impl<T: Fetch> ServerBuilder<T> {
web_proxy_tokens: self.web_proxy_tokens, web_proxy_tokens: self.web_proxy_tokens,
signer_address: self.signer_address, signer_address: self.signer_address,
allowed_hosts: self.allowed_hosts, allowed_hosts: self.allowed_hosts,
extra_cors: self.extra_cors,
remote: self.remote, remote: self.remote,
fetch: Some(fetch), fetch: Some(fetch),
} }
@ -174,6 +177,13 @@ impl<T: Fetch> ServerBuilder<T> {
self self
} }
/// Extra cors headers.
/// `None` - no additional CORS URLs
pub fn extra_cors_headers(mut self, cors: Option<Vec<String>>) -> Self {
self.extra_cors = cors;
self
}
/// Change extra dapps paths (apart from `dapps_path`) /// Change extra dapps paths (apart from `dapps_path`)
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self { pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect(); self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
@ -187,6 +197,7 @@ impl<T: Fetch> ServerBuilder<T> {
Server::start_http( Server::start_http(
addr, addr,
self.allowed_hosts, self.allowed_hosts,
self.extra_cors,
NoAuth, NoAuth,
handler, handler,
self.dapps_path, self.dapps_path,
@ -207,6 +218,7 @@ impl<T: Fetch> ServerBuilder<T> {
Server::start_http( Server::start_http(
addr, addr,
self.allowed_hosts, self.allowed_hosts,
self.extra_cors,
HttpBasicAuth::single_user(username, password), HttpBasicAuth::single_user(username, password),
handler, handler,
self.dapps_path, self.dapps_path,
@ -251,8 +263,8 @@ impl Server {
} }
/// Returns a list of CORS domains for API endpoint. /// Returns a list of CORS domains for API endpoint.
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> { fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option<Vec<String>>) -> Vec<String> {
match signer_address { let basic_cors = match signer_address {
Some(signer_address) => vec![ Some(signer_address) => vec![
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), 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),
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
format!("https://{}", address(&signer_address)), format!("https://{}", address(&signer_address)),
], ],
None => vec![], None => vec![],
};
match extra_cors {
None => basic_cors,
Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(),
} }
} }
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>( fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
addr: &SocketAddr, addr: &SocketAddr,
hosts: Option<Vec<String>>, hosts: Option<Vec<String>>,
extra_cors: Option<Vec<String>>,
authorization: A, authorization: A,
handler: RpcHandler<Metadata, T>, handler: RpcHandler<Metadata, T>,
dapps_path: PathBuf, dapps_path: PathBuf,
@ -297,7 +314,7 @@ impl Server {
remote.clone(), remote.clone(),
fetch.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 special = Arc::new({
let mut special = HashMap::new(); let mut special = HashMap::new();
@ -413,8 +430,9 @@ mod util_tests {
// given // given
// when // when
let none = Server::cors_domains(None); let none = Server::cors_domains(None, None);
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180))); 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 // then
assert_eq!(none, Vec::<String>::new()); assert_eq!(none, Vec::<String>::new());
@ -425,7 +443,7 @@ mod util_tests {
"https://parity.web3.site".into(), "https://parity.web3.site".into(),
"https://parity.web3.site:18180".into(), "https://parity.web3.site:18180".into(),
"https://127.0.0.1:18180".into() "https://127.0.0.1:18180".into()
]); ]);
assert_eq!(extra, vec!["all".to_owned()]);
} }
} }

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
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] #[test]
fn should_return_error() { 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");
}

View File

@ -109,6 +109,10 @@ pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0 init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0
} }
pub fn serve_extra_cors(extra_cors: Option<Vec<String>>) -> 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<FakeRegistrar>) { pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync()) init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync())
} }

View File

@ -181,6 +181,8 @@ usage! {
or |c: &Config| otry!(c.dapps).interface.clone(), or |c: &Config| otry!(c.dapps).interface.clone(),
flag_dapps_hosts: String = "none", flag_dapps_hosts: String = "none",
or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| vec.join(",")), or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| vec.join(",")),
flag_dapps_cors: Option<String> = None,
or |c: &Config| otry!(c.dapps).cors.clone().map(Some),
flag_dapps_path: String = "$BASE/dapps", flag_dapps_path: String = "$BASE/dapps",
or |c: &Config| otry!(c.dapps).path.clone(), or |c: &Config| otry!(c.dapps).path.clone(),
flag_dapps_user: Option<String> = None, flag_dapps_user: Option<String> = None,
@ -428,6 +430,7 @@ struct Dapps {
port: Option<u16>, port: Option<u16>,
interface: Option<String>, interface: Option<String>,
hosts: Option<Vec<String>>, hosts: Option<Vec<String>>,
cors: Option<String>,
path: Option<String>, path: Option<String>,
user: Option<String>, user: Option<String>,
pass: Option<String>, pass: Option<String>,
@ -674,6 +677,7 @@ mod tests {
flag_dapps_port: 8080u16, flag_dapps_port: 8080u16,
flag_dapps_interface: "local".into(), flag_dapps_interface: "local".into(),
flag_dapps_hosts: "none".into(), flag_dapps_hosts: "none".into(),
flag_dapps_cors: None,
flag_dapps_path: "$HOME/.parity/dapps".into(), flag_dapps_path: "$HOME/.parity/dapps".into(),
flag_dapps_user: Some("test_user".into()), flag_dapps_user: Some("test_user".into()),
flag_dapps_pass: Some("test_pass".into()), flag_dapps_pass: Some("test_pass".into()),
@ -873,6 +877,7 @@ mod tests {
path: None, path: None,
interface: None, interface: None,
hosts: None, hosts: None,
cors: None,
user: Some("username".into()), user: Some("username".into()),
pass: Some("password".into()) pass: Some("password".into())
}), }),

View File

@ -164,6 +164,8 @@ API and Console Options:
is additional security against some attack is additional security against some attack
vectors. Special options: "all", "none", vectors. Special options: "all", "none",
(default: {flag_dapps_hosts}). (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 --dapps-user USERNAME Specify username for Dapps server. It will be
used in HTTP Basic Authentication Scheme. used in HTTP Basic Authentication Scheme.
If --dapps-pass is not specified you will be If --dapps-pass is not specified you will be

View File

@ -546,6 +546,7 @@ impl Configuration {
interface: self.dapps_interface(), interface: self.dapps_interface(),
port: self.args.flag_dapps_port, port: self.args.flag_dapps_port,
hosts: self.dapps_hosts(), hosts: self.dapps_hosts(),
cors: self.dapps_cors(),
user: self.args.flag_dapps_user.clone(), user: self.args.flag_dapps_user.clone(),
pass: self.args.flag_dapps_pass.clone(), pass: self.args.flag_dapps_pass.clone(),
dapps_path: PathBuf::from(self.directories().dapps), dapps_path: PathBuf::from(self.directories().dapps),
@ -722,6 +723,10 @@ impl Configuration {
Self::cors(self.args.flag_ipfs_api_cors.as_ref()) Self::cors(self.args.flag_ipfs_api_cors.as_ref())
} }
fn dapps_cors(&self) -> Option<Vec<String>> {
Self::cors(self.args.flag_dapps_cors.as_ref())
}
fn hosts(hosts: &str) -> Option<Vec<String>> { fn hosts(hosts: &str) -> Option<Vec<String>> {
match hosts { match hosts {
"none" => return Some(Vec::new()), "none" => return Some(Vec::new()),

View File

@ -33,6 +33,7 @@ pub struct Configuration {
pub interface: String, pub interface: String,
pub port: u16, pub port: u16,
pub hosts: Option<Vec<String>>, pub hosts: Option<Vec<String>>,
pub cors: Option<Vec<String>>,
pub user: Option<String>, pub user: Option<String>,
pub pass: Option<String>, pub pass: Option<String>,
pub dapps_path: PathBuf, pub dapps_path: PathBuf,
@ -48,6 +49,7 @@ impl Default for Configuration {
interface: "127.0.0.1".into(), interface: "127.0.0.1".into(),
port: 8080, port: 8080,
hosts: Some(Vec::new()), hosts: Some(Vec::new()),
cors: None,
user: None, user: None,
pass: None, pass: None,
dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), dapps_path: replace_home(&data_dir, "$BASE/dapps").into(),
@ -93,6 +95,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<We
configuration.extra_dapps, configuration.extra_dapps,
&addr, &addr,
configuration.hosts, configuration.hosts,
configuration.cors,
auth, auth,
configuration.all_apis, configuration.all_apis,
)?)) )?))
@ -114,6 +117,7 @@ mod server {
_extra_dapps: Vec<PathBuf>, _extra_dapps: Vec<PathBuf>,
_url: &SocketAddr, _url: &SocketAddr,
_allowed_hosts: Option<Vec<String>>, _allowed_hosts: Option<Vec<String>>,
_cors: Option<Vec<String>>,
_auth: Option<(String, String)>, _auth: Option<(String, String)>,
_all_apis: bool, _all_apis: bool,
) -> Result<WebappServer, String> { ) -> Result<WebappServer, String> {
@ -147,6 +151,7 @@ mod server {
extra_dapps: Vec<PathBuf>, extra_dapps: Vec<PathBuf>,
url: &SocketAddr, url: &SocketAddr,
allowed_hosts: Option<Vec<String>>, allowed_hosts: Option<Vec<String>>,
cors: Option<Vec<String>>,
auth: Option<(String, String)>, auth: Option<(String, String)>,
all_apis: bool, all_apis: bool,
) -> Result<WebappServer, String> { ) -> Result<WebappServer, String> {
@ -167,7 +172,8 @@ mod server {
.web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token))) .web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token)))
.extra_dapps(&extra_dapps) .extra_dapps(&extra_dapps)
.signer_address(deps.signer.address()) .signer_address(deps.signer.address())
.allowed_hosts(allowed_hosts); .allowed_hosts(allowed_hosts)
.extra_cors_headers(cors);
let api_set = if all_apis { let api_set = if all_apis {
warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Dapps with all APIs exposed.")); warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Dapps with all APIs exposed."));