Merge pull request #5992 from paritytech/csp-fix

Add missing CSP for web3.site
This commit is contained in:
Robert Habermeier 2017-07-11 16:12:03 +02:00 committed by GitHub
commit 02f2c611d4
20 changed files with 183 additions and 111 deletions

View File

@ -25,6 +25,7 @@ use util::sha3::sha3;
use page::{LocalPageEndpoint, PageCache}; use page::{LocalPageEndpoint, PageCache};
use handlers::{ContentValidator, ValidatorResponse}; use handlers::{ContentValidator, ValidatorResponse};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
use Embeddable;
type OnDone = Box<Fn(Option<LocalPageEndpoint>) + Send>; type OnDone = Box<Fn(Option<LocalPageEndpoint>) + Send>;
@ -116,16 +117,16 @@ pub struct Dapp {
id: String, id: String,
dapps_path: PathBuf, dapps_path: PathBuf,
on_done: OnDone, on_done: OnDone,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
} }
impl Dapp { impl Dapp {
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Option<(String, u16)>) -> Self { pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Embeddable) -> Self {
Dapp { Dapp {
id: id, id,
dapps_path: dapps_path, dapps_path,
on_done: on_done, on_done,
embeddable_on: embeddable_on, embeddable_on,
} }
} }

View File

@ -31,7 +31,7 @@ use parity_reactor::Remote;
use hyper; use hyper;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use {SyncStatus, random_filename}; use {Embeddable, SyncStatus, random_filename};
use util::Mutex; use util::Mutex;
use page::LocalPageEndpoint; use page::LocalPageEndpoint;
use handlers::{ContentHandler, ContentFetcherHandler}; use handlers::{ContentHandler, ContentFetcherHandler};
@ -52,7 +52,7 @@ pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + 'static = URLHint
resolver: R, resolver: R,
cache: Arc<Mutex<ContentCache>>, cache: Arc<Mutex<ContentCache>>,
sync: Arc<SyncStatus>, sync: Arc<SyncStatus>,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
remote: Remote, remote: Remote,
fetch: F, fetch: F,
only_content: bool, only_content: bool,
@ -93,22 +93,22 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
self self
} }
pub fn embeddable_on(mut self, embeddable_on: Option<(String, u16)>) -> Self { pub fn embeddable_on(mut self, embeddable_on: Embeddable) -> Self {
self.embeddable_on = embeddable_on; self.embeddable_on = embeddable_on;
self self
} }
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> { fn still_syncing(embeddable: Embeddable) -> Box<Handler> {
Box::new(ContentHandler::error( Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable, StatusCode::ServiceUnavailable,
"Sync In Progress", "Sync In Progress",
"Your node is still syncing. We cannot resolve any content before it's fully synced.", "Your node is still syncing. We cannot resolve any content before it's fully synced.",
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"), Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
address, embeddable,
)) ))
} }
fn dapps_disabled(address: Option<(String, u16)>) -> Box<Handler> { fn dapps_disabled(address: Embeddable) -> Box<Handler> {
Box::new(ContentHandler::error( Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable, StatusCode::ServiceUnavailable,
"Network Dapps Not Available", "Network Dapps Not Available",

View File

@ -22,6 +22,7 @@ use std::path::{Path, PathBuf};
use page::{LocalPageEndpoint, PageCache}; use page::{LocalPageEndpoint, PageCache};
use endpoint::{Endpoint, EndpointInfo}; use endpoint::{Endpoint, EndpointInfo};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
use Embeddable;
struct LocalDapp { struct LocalDapp {
id: String, id: String,
@ -60,14 +61,14 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
/// Returns Dapp Id and Local Dapp Endpoint for given filesystem path. /// Returns Dapp Id and Local Dapp Endpoint for given filesystem path.
/// Parses the path to extract last component (for name). /// Parses the path to extract last component (for name).
/// `None` is returned when path is invalid or non-existent. /// `None` is returned when path is invalid or non-existent.
pub fn local_endpoint<P: AsRef<Path>>(path: P, signer_address: Option<(String, u16)>) -> Option<(String, Box<LocalPageEndpoint>)> { pub fn local_endpoint<P: AsRef<Path>>(path: P, embeddable: Embeddable) -> Option<(String, Box<LocalPageEndpoint>)> {
let path = path.as_ref().to_owned(); let path = path.as_ref().to_owned();
path.canonicalize().ok().and_then(|path| { path.canonicalize().ok().and_then(|path| {
let name = path.file_name().and_then(|name| name.to_str()); let name = path.file_name().and_then(|name| name.to_str());
name.map(|name| { name.map(|name| {
let dapp = local_dapp(name.into(), path.clone()); let dapp = local_dapp(name.into(), path.clone());
(dapp.id, Box::new(LocalPageEndpoint::new( (dapp.id, Box::new(LocalPageEndpoint::new(
dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()) dapp.path, dapp.info, PageCache::Disabled, embeddable.clone())
)) ))
}) })
}) })
@ -86,12 +87,12 @@ fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
/// Returns endpoints for Local Dapps found for given filesystem path. /// Returns endpoints for Local Dapps found for given filesystem path.
/// Scans the directory and collects `LocalPageEndpoints`. /// Scans the directory and collects `LocalPageEndpoints`.
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> BTreeMap<String, Box<Endpoint>> { pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, embeddable: Embeddable) -> BTreeMap<String, Box<Endpoint>> {
let mut pages = BTreeMap::<String, Box<Endpoint>>::new(); let mut pages = BTreeMap::<String, Box<Endpoint>>::new();
for dapp in local_dapps(dapps_path.as_ref()) { for dapp in local_dapps(dapps_path.as_ref()) {
pages.insert( pages.insert(
dapp.id, dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())) Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, embeddable.clone()))
); );
} }
pages pages

View File

@ -26,7 +26,7 @@ use fetch::Fetch;
use parity_dapps::WebApp; use parity_dapps::WebApp;
use parity_reactor::Remote; use parity_reactor::Remote;
use parity_ui; use parity_ui;
use {WebProxyTokens}; use {WebProxyTokens, ParentFrameSettings};
mod app; mod app;
mod cache; mod cache;
@ -52,23 +52,23 @@ pub fn ui() -> Box<Endpoint> {
Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default())) Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default()))
} }
pub fn ui_redirection(ui_address: Option<(String, u16)>) -> Box<Endpoint> { pub fn ui_redirection(embeddable: Option<ParentFrameSettings>) -> Box<Endpoint> {
Box::new(ui::Redirection::new(ui_address)) Box::new(ui::Redirection::new(embeddable))
} }
pub fn all_endpoints<F: Fetch>( pub fn all_endpoints<F: Fetch>(
dapps_path: PathBuf, dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>, extra_dapps: Vec<PathBuf>,
dapps_domain: String, dapps_domain: &str,
ui_address: Option<(String, u16)>, embeddable: Option<ParentFrameSettings>,
web_proxy_tokens: Arc<WebProxyTokens>, web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote, remote: Remote,
fetch: F, fetch: F,
) -> Endpoints { ) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins // fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path, ui_address.clone()); let mut pages = fs::local_endpoints(dapps_path, embeddable.clone());
for path in extra_dapps { for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), ui_address.clone()) { if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), embeddable.clone()) {
pages.insert(id, endpoint); pages.insert(id, endpoint);
} else { } else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display()); warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
@ -76,9 +76,9 @@ pub fn all_endpoints<F: Fetch>(
} }
// NOTE [ToDr] Dapps will be currently embeded on 8180 // NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(ui_address.clone())); insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(embeddable.clone()));
pages.insert("proxy".into(), ProxyPac::boxed(ui_address.clone(), dapps_domain)); pages.insert("proxy".into(), ProxyPac::boxed(embeddable.clone(), dapps_domain.to_owned()));
pages.insert(WEB_PATH.into(), Web::boxed(ui_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone())); pages.insert(WEB_PATH.into(), Web::boxed(embeddable.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
Arc::new(pages) Arc::new(pages)
} }
@ -91,7 +91,7 @@ fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpo
} }
enum Embeddable { enum Embeddable {
Yes(Option<(String, u16)>), Yes(Option<ParentFrameSettings>),
#[allow(dead_code)] #[allow(dead_code)]
No, No,
} }

View File

@ -19,28 +19,28 @@
use hyper::{Control, StatusCode}; use hyper::{Control, StatusCode};
use endpoint::{Endpoint, Handler, EndpointPath}; use endpoint::{Endpoint, Handler, EndpointPath};
use {address, handlers}; use {handlers, Embeddable};
/// Redirection to UI server. /// Redirection to UI server.
pub struct Redirection { pub struct Redirection {
signer_address: Option<(String, u16)>, embeddable_on: Embeddable,
} }
impl Redirection { impl Redirection {
pub fn new( pub fn new(
signer_address: Option<(String, u16)>, embeddable_on: Embeddable,
) -> Self { ) -> Self {
Redirection { Redirection {
signer_address: signer_address, embeddable_on,
} }
} }
} }
impl Endpoint for Redirection { impl Endpoint for Redirection {
fn to_async_handler(&self, _path: EndpointPath, _control: Control) -> Box<Handler> { fn to_async_handler(&self, _path: EndpointPath, _control: Control) -> Box<Handler> {
if let Some(ref signer_address) = self.signer_address { if let Some(ref frame) = self.embeddable_on {
trace!(target: "dapps", "Redirecting to signer interface."); trace!(target: "dapps", "Redirecting to signer interface.");
handlers::Redirection::boxed(&format!("http://{}", address(signer_address))) handlers::Redirection::boxed(&format!("http://{}:{}", &frame.host, frame.port))
} else { } else {
trace!(target: "dapps", "Signer disabled, returning 404."); trace!(target: "dapps", "Signer disabled, returning 404.");
Box::new(handlers::ContentHandler::error( Box::new(handlers::ContentHandler::error(
@ -48,7 +48,7 @@ impl Endpoint for Redirection {
"404 Not Found", "404 Not Found",
"Your homepage is not available when Trusted Signer is disabled.", "Your homepage is not available when Trusted Signer is disabled.",
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."), Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
self.signer_address.clone(), None,
)) ))
} }
} }

View File

@ -24,6 +24,7 @@ use hyper::status::StatusCode;
use util::version; use util::version;
use handlers::add_security_headers; use handlers::add_security_headers;
use Embeddable;
#[derive(Clone)] #[derive(Clone)]
pub struct ContentHandler { pub struct ContentHandler {
@ -31,7 +32,7 @@ pub struct ContentHandler {
content: String, content: String,
mimetype: Mime, mimetype: Mime,
write_pos: usize, write_pos: usize,
safe_to_embed_on: Option<(String, u16)>, safe_to_embed_on: Embeddable,
} }
impl ContentHandler { impl ContentHandler {
@ -39,11 +40,17 @@ impl ContentHandler {
Self::new(StatusCode::Ok, content, mimetype) Self::new(StatusCode::Ok, content, mimetype)
} }
pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self { pub fn html(code: StatusCode, content: String, embeddable_on: Embeddable) -> Self {
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on) Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on)
} }
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self { pub fn error(
code: StatusCode,
title: &str,
message: &str,
details: Option<&str>,
embeddable_on: Embeddable,
) -> Self {
Self::html(code, format!( Self::html(code, format!(
include_str!("../error_tpl.html"), include_str!("../error_tpl.html"),
title=title, title=title,
@ -57,13 +64,18 @@ impl ContentHandler {
Self::new_embeddable(code, content, mimetype, None) Self::new_embeddable(code, content, mimetype, None)
} }
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self { pub fn new_embeddable(
code: StatusCode,
content: String,
mimetype: Mime,
safe_to_embed_on: Embeddable,
) -> Self {
ContentHandler { ContentHandler {
code: code, code,
content: content, content,
mimetype: mimetype, mimetype,
write_pos: 0, write_pos: 0,
safe_to_embed_on: embeddable_on, safe_to_embed_on,
} }
} }
} }
@ -80,7 +92,7 @@ impl server::Handler<HttpStream> for ContentHandler {
fn on_response(&mut self, res: &mut server::Response) -> Next { fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.code); res.set_status(self.code);
res.headers_mut().set(header::ContentType(self.mimetype.clone())); res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write() Next::write()
} }

View File

@ -33,6 +33,7 @@ use hyper::status::StatusCode;
use endpoint::EndpointPath; use endpoint::EndpointPath;
use handlers::{ContentHandler, StreamingHandler}; use handlers::{ContentHandler, StreamingHandler};
use page::{LocalPageEndpoint, PageHandlerWaiting}; use page::{LocalPageEndpoint, PageHandlerWaiting};
use {Embeddable};
const FETCH_TIMEOUT: u64 = 300; const FETCH_TIMEOUT: u64 = 300;
@ -179,7 +180,7 @@ impl server::Handler<HttpStream> for WaitingHandler {
#[derive(Clone)] #[derive(Clone)]
struct Errors { struct Errors {
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
} }
impl Errors { impl Errors {
@ -241,20 +242,20 @@ impl<H: ContentValidator, F: Fetch> ContentFetcherHandler<H, F> {
path: EndpointPath, path: EndpointPath,
control: Control, control: Control,
installer: H, installer: H,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
remote: Remote, remote: Remote,
fetch: F, fetch: F,
) -> Self { ) -> Self {
ContentFetcherHandler { ContentFetcherHandler {
fetch_control: FetchControl::default(), fetch_control: FetchControl::default(),
control: control, control,
remote: remote, remote,
fetch: fetch, fetch,
status: FetchState::NotStarted(url), status: FetchState::NotStarted(url),
installer: Some(installer), installer: Some(installer),
path: path, path,
errors: Errors { errors: Errors {
embeddable_on: embeddable_on, embeddable_on,
}, },
} }
} }

View File

@ -30,22 +30,20 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, Val
pub use self::redirect::Redirection; pub use self::redirect::Redirection;
pub use self::streaming::StreamingHandler; pub use self::streaming::StreamingHandler;
use std::iter;
use util::Itertools;
use url::Url; use url::Url;
use hyper::{server, header, net, uri}; use hyper::{server, header, net, uri};
use address; use {apps, address, Embeddable};
/// Adds security-related headers to the Response. /// Adds security-related headers to the Response.
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) { pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embeddable) {
headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]); headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]);
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]); headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
// Embedding header: // Embedding header:
if let Some(ref embeddable_on) = embeddable_on { if let None = embeddable_on {
headers.set_raw("X-Frame-Options", vec![
format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()
]);
} else {
// TODO [ToDr] Should we be more strict here (DENY?)?
headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]); headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
} }
@ -62,7 +60,7 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option
b"child-src 'self' http: https:;".to_vec(), b"child-src 'self' http: https:;".to_vec(),
// We allow data: blob: and HTTP(s) images. // We allow data: blob: and HTTP(s) images.
// We could get rid of wildcarding HTTP and only allow RPC server URL. // We could get rid of wildcarding HTTP and only allow RPC server URL.
// (http require for local dapps icons) // (http required for local dapps icons)
b"img-src 'self' 'unsafe-inline' data: blob: http: https:;".to_vec(), b"img-src 'self' 'unsafe-inline' data: blob: http: https:;".to_vec(),
// Allow style from data: blob: and HTTPS. // Allow style from data: blob: and HTTPS.
b"style-src 'self' 'unsafe-inline' data: blob: https:;".to_vec(), b"style-src 'self' 'unsafe-inline' data: blob: https:;".to_vec(),
@ -80,10 +78,27 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option
b"block-all-mixed-content;".to_vec(), b"block-all-mixed-content;".to_vec(),
// Specify if the site can be embedded. // Specify if the site can be embedded.
match embeddable_on { match embeddable_on {
Some((ref host, ref port)) if host == "127.0.0.1" => { Some(ref embed) => {
format!("frame-ancestors {} {};", address(&(host.to_owned(), *port)), address(&("localhost".to_owned(), *port))) let std = address(&embed.host, embed.port);
let proxy = format!("{}.{}", apps::HOME_PAGE, embed.dapps_domain);
let domain = format!("*.{}:{}", embed.dapps_domain, embed.port);
let mut ancestors = vec![std, domain, proxy]
.into_iter()
.chain(embed.extra_embed_on
.iter()
.map(|&(ref host, port)| format!("{}:{}", host, port))
);
let ancestors = if embed.host == "127.0.0.1" {
let localhost = address("localhost", embed.port);
ancestors.chain(iter::once(localhost)).join(" ")
} else {
ancestors.join(" ")
};
format!("frame-ancestors {};", ancestors)
}, },
Some(ref embed) => format!("frame-ancestors {};", address(embed)),
None => format!("frame-ancestors 'self';"), None => format!("frame-ancestors 'self';"),
}.into_bytes(), }.into_bytes(),
]); ]);

View File

@ -24,6 +24,7 @@ use hyper::mime::Mime;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use handlers::add_security_headers; use handlers::add_security_headers;
use Embeddable;
const BUFFER_SIZE: usize = 1024; const BUFFER_SIZE: usize = 1024;
@ -33,11 +34,11 @@ pub struct StreamingHandler<R: io::Read> {
status: StatusCode, status: StatusCode,
content: io::BufReader<R>, content: io::BufReader<R>,
mimetype: Mime, mimetype: Mime,
safe_to_embed_on: Option<(String, u16)>, safe_to_embed_on: Embeddable,
} }
impl<R: io::Read> StreamingHandler<R> { impl<R: io::Read> StreamingHandler<R> {
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self { pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Embeddable) -> Self {
StreamingHandler { StreamingHandler {
buffer: [0; BUFFER_SIZE], buffer: [0; BUFFER_SIZE],
buffer_leftover: 0, buffer_leftover: 0,
@ -68,7 +69,7 @@ impl<R: io::Read> server::Handler<HttpStream> for StreamingHandler<R> {
fn on_response(&mut self, res: &mut server::Response) -> Next { fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.status); res.set_status(self.status);
res.headers_mut().set(header::ContentType(self.mimetype.clone())); res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write() Next::write()
} }

View File

@ -175,6 +175,7 @@ impl Middleware {
pool: CpuPool, pool: CpuPool,
remote: Remote, remote: Remote,
ui_address: Option<(String, u16)>, ui_address: Option<(String, u16)>,
extra_embed_on: Vec<(String, u16)>,
dapps_path: PathBuf, dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>, extra_dapps: Vec<PathBuf>,
dapps_domain: &str, dapps_domain: &str,
@ -183,17 +184,18 @@ impl Middleware {
web_proxy_tokens: Arc<WebProxyTokens>, web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F, fetch: F,
) -> Self { ) -> Self {
let embeddable = as_embeddable(ui_address, extra_embed_on, dapps_domain);
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new( let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
hash_fetch::urlhint::URLHintContract::new(registrar), hash_fetch::urlhint::URLHintContract::new(registrar),
sync_status.clone(), sync_status.clone(),
remote.clone(), remote.clone(),
fetch.clone(), fetch.clone(),
).embeddable_on(ui_address.clone()).allow_dapps(true)); ).embeddable_on(embeddable.clone()).allow_dapps(true));
let endpoints = apps::all_endpoints( let endpoints = apps::all_endpoints(
dapps_path, dapps_path,
extra_dapps, extra_dapps,
dapps_domain.to_owned(), dapps_domain,
ui_address.clone(), embeddable.clone(),
web_proxy_tokens, web_proxy_tokens,
remote.clone(), remote.clone(),
fetch.clone(), fetch.clone(),
@ -207,7 +209,10 @@ impl Middleware {
remote.clone(), remote.clone(),
sync_status, sync_status,
); );
special.insert(router::SpecialEndpoint::Home, Some(apps::ui_redirection(ui_address.clone()))); special.insert(
router::SpecialEndpoint::Home,
Some(apps::ui_redirection(embeddable.clone())),
);
special special
}; };
@ -215,7 +220,7 @@ impl Middleware {
content_fetcher, content_fetcher,
Some(endpoints.clone()), Some(endpoints.clone()),
special, special,
ui_address, embeddable,
dapps_domain.to_owned(), dapps_domain.to_owned(),
); );
@ -251,8 +256,21 @@ fn special_endpoints(
special special
} }
fn address(address: &(String, u16)) -> String { fn address(host: &str, port: u16) -> String {
format!("{}:{}", address.0, address.1) format!("{}:{}", host, port)
}
fn as_embeddable(
ui_address: Option<(String, u16)>,
extra_embed_on: Vec<(String, u16)>,
dapps_domain: &str,
) -> Option<ParentFrameSettings> {
ui_address.map(|(host, port)| ParentFrameSettings {
host,
port,
extra_embed_on,
dapps_domain: dapps_domain.to_owned(),
})
} }
/// Random filename /// Random filename
@ -261,3 +279,18 @@ fn random_filename() -> String {
let mut rng = ::rand::OsRng::new().unwrap(); let mut rng = ::rand::OsRng::new().unwrap();
rng.gen_ascii_chars().take(12).collect() rng.gen_ascii_chars().take(12).collect()
} }
type Embeddable = Option<ParentFrameSettings>;
/// Parent frame host and port allowed to embed the content.
#[derive(Debug, Clone)]
pub struct ParentFrameSettings {
/// Hostname
pub host: String,
/// Port
pub port: u16,
/// Additional pages the pages can be embedded on.
pub extra_embed_on: Vec<(String, u16)>,
/// Dapps Domain (web3.site)
pub dapps_domain: String,
}

View File

@ -18,6 +18,7 @@ use page::{handler, PageCache};
use std::sync::Arc; use std::sync::Arc;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use parity_dapps::{WebApp, File, Info}; use parity_dapps::{WebApp, File, Info};
use Embeddable;
pub struct PageEndpoint<T : WebApp + 'static> { pub struct PageEndpoint<T : WebApp + 'static> {
/// Content of the files /// Content of the files
@ -25,7 +26,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
/// Prefix to strip from the path (when `None` deducted from `app_id`) /// Prefix to strip from the path (when `None` deducted from `app_id`)
pub prefix: Option<String>, pub prefix: Option<String>,
/// Safe to be loaded in frame by other origin. (use wisely!) /// Safe to be loaded in frame by other origin. (use wisely!)
safe_to_embed_on: Option<(String, u16)>, safe_to_embed_on: Embeddable,
info: EndpointInfo, info: EndpointInfo,
fallback_to_index_html: bool, fallback_to_index_html: bool,
} }
@ -73,7 +74,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
/// Creates new `PageEndpoint` which can be safely used in iframe /// Creates new `PageEndpoint` which can be safely used in iframe
/// even from different origin. It might be dangerous (clickjacking). /// even from different origin. It might be dangerous (clickjacking).
/// Use wisely! /// Use wisely!
pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self { pub fn new_safe_to_embed(app: T, address: Embeddable) -> Self {
let info = app.info(); let info = app.info();
PageEndpoint { PageEndpoint {
app: Arc::new(app), app: Arc::new(app),

View File

@ -24,6 +24,7 @@ use hyper::status::StatusCode;
use hyper::{Decoder, Encoder, Next}; use hyper::{Decoder, Encoder, Next};
use endpoint::EndpointPath; use endpoint::EndpointPath;
use handlers::{ContentHandler, add_security_headers}; use handlers::{ContentHandler, add_security_headers};
use {Embeddable};
/// Represents a file that can be sent to client. /// Represents a file that can be sent to client.
/// Implementation should keep track of bytes already sent internally. /// Implementation should keep track of bytes already sent internally.
@ -59,7 +60,7 @@ pub enum ServedFile<T: Dapp> {
} }
impl<T: Dapp> ServedFile<T> { impl<T: Dapp> ServedFile<T> {
pub fn new(embeddable_on: Option<(String, u16)>) -> Self { pub fn new(embeddable_on: Embeddable) -> Self {
ServedFile::Error(ContentHandler::error( ServedFile::Error(ContentHandler::error(
StatusCode::NotFound, StatusCode::NotFound,
"404 Not Found", "404 Not Found",
@ -102,7 +103,7 @@ pub struct PageHandler<T: Dapp> {
/// Requested path. /// Requested path.
pub path: EndpointPath, pub path: EndpointPath,
/// Flag indicating if the file can be safely embeded (put in iframe). /// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed_on: Option<(String, u16)>, pub safe_to_embed_on: Embeddable,
/// Cache settings for this page. /// Cache settings for this page.
pub cache: PageCache, pub cache: PageCache,
} }
@ -174,7 +175,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
} }
// Security headers: // Security headers:
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write() Next::write()
}, },
ServedFile::Error(ref mut handler) => { ServedFile::Error(ref mut handler) => {

View File

@ -21,6 +21,7 @@ use std::path::{Path, PathBuf};
use page::handler::{self, PageCache, PageHandlerWaiting}; use page::handler::{self, PageCache, PageHandlerWaiting};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use mime::Mime; use mime::Mime;
use Embeddable;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LocalPageEndpoint { pub struct LocalPageEndpoint {
@ -28,11 +29,11 @@ pub struct LocalPageEndpoint {
mime: Option<Mime>, mime: Option<Mime>,
info: Option<EndpointInfo>, info: Option<EndpointInfo>,
cache: PageCache, cache: PageCache,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
} }
impl LocalPageEndpoint { impl LocalPageEndpoint {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self { pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
LocalPageEndpoint { LocalPageEndpoint {
path: path, path: path,
mime: None, mime: None,

View File

@ -19,27 +19,24 @@
use endpoint::{Endpoint, Handler, EndpointPath}; use endpoint::{Endpoint, Handler, EndpointPath};
use handlers::ContentHandler; use handlers::ContentHandler;
use apps::HOME_PAGE; use apps::HOME_PAGE;
use address; use {address, Embeddable};
pub struct ProxyPac { pub struct ProxyPac {
signer_address: Option<(String, u16)>, embeddable: Embeddable,
dapps_domain: String, dapps_domain: String,
} }
impl ProxyPac { impl ProxyPac {
pub fn boxed(signer_address: Option<(String, u16)>, dapps_domain: String) -> Box<Endpoint> { pub fn boxed(embeddable: Embeddable, dapps_domain: String) -> Box<Endpoint> {
Box::new(ProxyPac { Box::new(ProxyPac { embeddable, dapps_domain })
signer_address: signer_address,
dapps_domain: dapps_domain,
})
} }
} }
impl Endpoint for ProxyPac { impl Endpoint for ProxyPac {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> { fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
let signer = self.signer_address let ui = self.embeddable
.as_ref() .as_ref()
.map(address) .map(|ref parent| address(&parent.host, parent.port))
.unwrap_or_else(|| format!("{}:{}", path.host, path.port)); .unwrap_or_else(|| format!("{}:{}", path.host, path.port));
let content = format!( let content = format!(
@ -58,7 +55,7 @@ function FindProxyForURL(url, host) {{
return "DIRECT"; return "DIRECT";
}} }}
"#, "#,
HOME_PAGE, self.dapps_domain, path.host, path.port, signer); HOME_PAGE, self.dapps_domain, path.host, path.port, ui);
Box::new(ContentHandler::ok(content, mime!(Application/Javascript))) Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
} }

View File

@ -30,6 +30,7 @@ use apps;
use apps::fetcher::Fetcher; use apps::fetcher::Fetcher;
use endpoint::{Endpoint, Endpoints, EndpointPath, Handler}; use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
use handlers; use handlers;
use Embeddable;
/// Special endpoints are accessible on every domain (every dapp) /// Special endpoints are accessible on every domain (every dapp)
#[derive(Debug, PartialEq, Hash, Eq)] #[derive(Debug, PartialEq, Hash, Eq)]
@ -45,7 +46,7 @@ pub struct Router {
endpoints: Option<Endpoints>, endpoints: Option<Endpoints>,
fetch: Arc<Fetcher>, fetch: Arc<Fetcher>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>, special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
dapps_domain: String, dapps_domain: String,
} }
@ -148,7 +149,7 @@ impl Router {
content_fetcher: Arc<Fetcher>, content_fetcher: Arc<Fetcher>,
endpoints: Option<Endpoints>, endpoints: Option<Endpoints>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>, special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
dapps_domain: String, dapps_domain: String,
) -> Self { ) -> Self {
Router { Router {

View File

@ -259,6 +259,7 @@ impl Server {
CpuPool::new(4), CpuPool::new(4),
remote, remote,
signer_address, signer_address,
vec![],
dapps_path, dapps_path,
extra_dapps, extra_dapps,
DAPPS_DOMAIN.into(), DAPPS_DOMAIN.into(),

View File

@ -31,9 +31,7 @@ use handlers::{
StreamingHandler, extract_url, StreamingHandler, extract_url,
}; };
use url::Url; use url::Url;
use WebProxyTokens; use {Embeddable, WebProxyTokens};
pub type Embeddable = Option<(String, u16)>;
pub struct Web<F> { pub struct Web<F> {
embeddable_on: Embeddable, embeddable_on: Embeddable,
@ -43,12 +41,17 @@ pub struct Web<F> {
} }
impl<F: Fetch> Web<F> { impl<F: Fetch> Web<F> {
pub fn boxed(embeddable_on: Embeddable, web_proxy_tokens: Arc<WebProxyTokens>, remote: Remote, fetch: F) -> Box<Endpoint> { pub fn boxed(
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
) -> Box<Endpoint> {
Box::new(Web { Box::new(Web {
embeddable_on: embeddable_on, embeddable_on,
web_proxy_tokens: web_proxy_tokens, web_proxy_tokens,
remote: remote, remote,
fetch: fetch, fetch,
}) })
} }
} }

View File

@ -102,12 +102,7 @@ pub fn request(address: &SocketAddr, request: &str) -> Response {
/// Check if all required security headers are present /// Check if all required security headers are present
pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) { pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
if let Some(port) = port { if let None = port {
assert!(
headers.iter().find(|header| header.as_str() == &format!("X-Frame-Options: ALLOW-FROM http://127.0.0.1:{}", port)).is_some(),
"X-Frame-Options: ALLOW-FROM missing: {:?}", headers
);
} else {
assert!( assert!(
headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(), headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(),
"X-Frame-Options: SAMEORIGIN missing: {:?}", headers "X-Frame-Options: SAMEORIGIN missing: {:?}", headers

View File

@ -573,6 +573,11 @@ impl Configuration {
} else { } else {
vec![] vec![]
}, },
extra_embed_on: if self.args.flag_ui_no_validation {
vec![("localhost".to_owned(), 3000)]
} else {
vec![]
},
} }
} }
@ -778,15 +783,11 @@ impl Configuration {
} }
fn ws_hosts(&self) -> Option<Vec<String>> { fn ws_hosts(&self) -> Option<Vec<String>> {
if self.args.flag_ui_no_validation {
return None;
}
self.hosts(&self.args.flag_ws_hosts, &self.ws_interface()) self.hosts(&self.args.flag_ws_hosts, &self.ws_interface())
} }
fn ws_origins(&self) -> Option<Vec<String>> { fn ws_origins(&self) -> Option<Vec<String>> {
if self.args.flag_unsafe_expose { if self.args.flag_unsafe_expose || self.args.flag_ui_no_validation {
return None; return None;
} }
@ -1522,7 +1523,8 @@ mod tests {
port: 8180, port: 8180,
hosts: Some(vec![]), hosts: Some(vec![]),
}); });
assert_eq!(conf1.ws_config().unwrap().hosts, None); assert_eq!(conf1.dapps_config().extra_embed_on, vec![("localhost".to_owned(), 3000)]);
assert_eq!(conf1.ws_config().unwrap().origins, None);
assert_eq!(conf2.directories().signer, "signer".to_owned()); assert_eq!(conf2.directories().signer, "signer".to_owned());
assert_eq!(conf2.ui_config(), UiConfiguration { assert_eq!(conf2.ui_config(), UiConfiguration {
enabled: true, enabled: true,

View File

@ -39,6 +39,7 @@ pub struct Configuration {
pub ntp_server: String, pub ntp_server: String,
pub dapps_path: PathBuf, pub dapps_path: PathBuf,
pub extra_dapps: Vec<PathBuf>, pub extra_dapps: Vec<PathBuf>,
pub extra_embed_on: Vec<(String, u16)>,
} }
impl Default for Configuration { impl Default for Configuration {
@ -49,6 +50,7 @@ impl Default for Configuration {
ntp_server: "pool.ntp.org:123".into(), ntp_server: "pool.ntp.org:123".into(),
dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), dapps_path: replace_home(&data_dir, "$BASE/dapps").into(),
extra_dapps: vec![], extra_dapps: vec![],
extra_embed_on: vec![],
} }
} }
} }
@ -160,6 +162,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<Mi
configuration.dapps_path, configuration.dapps_path,
configuration.extra_dapps, configuration.extra_dapps,
rpc::DAPPS_DOMAIN, rpc::DAPPS_DOMAIN,
configuration.extra_embed_on,
).map(Some) ).map(Some)
} }
@ -202,6 +205,7 @@ mod server {
_dapps_path: PathBuf, _dapps_path: PathBuf,
_extra_dapps: Vec<PathBuf>, _extra_dapps: Vec<PathBuf>,
_dapps_domain: &str, _dapps_domain: &str,
_extra_embed_on: Vec<(String, u16)>,
) -> Result<Middleware, String> { ) -> Result<Middleware, String> {
Err("Your Parity version has been compiled without WebApps support.".into()) Err("Your Parity version has been compiled without WebApps support.".into())
} }
@ -238,6 +242,7 @@ mod server {
dapps_path: PathBuf, dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>, extra_dapps: Vec<PathBuf>,
dapps_domain: &str, dapps_domain: &str,
extra_embed_on: Vec<(String, u16)>,
) -> Result<Middleware, String> { ) -> Result<Middleware, String> {
let signer = deps.signer; let signer = deps.signer;
let parity_remote = parity_reactor::Remote::new(deps.remote.clone()); let parity_remote = parity_reactor::Remote::new(deps.remote.clone());
@ -248,6 +253,7 @@ mod server {
deps.pool, deps.pool,
parity_remote, parity_remote,
deps.ui_address, deps.ui_address,
extra_embed_on,
dapps_path, dapps_path,
extra_dapps, extra_dapps,
dapps_domain, dapps_domain,