diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bc9d9257db5..c8f5f4d774a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -564,7 +564,7 @@ test-windows: - git submodule update --init --recursive script: - set RUST_BACKTRACE=1 - - echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p parity-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release + - echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p parity-dapps -p parity-rpc -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity-rpc-client -p parity %CARGOFLAGS% --verbose --release tags: - rust-windows allow_failure: true diff --git a/Cargo.lock b/Cargo.lock index 05a44cd6b41..b63349f7735 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -609,26 +609,6 @@ dependencies = [ "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ethcore-signer" -version = "1.7.0" -dependencies = [ - "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.7.0", - "ethcore-io 1.7.0", - "ethcore-util 1.7.0", - "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", - "jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", - "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-rpc 1.7.0", - "parity-ui 1.7.0", - "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "ws 0.5.3 (git+https://github.com/paritytech/ws-rs.git?branch=parity-1.7)", -] - [[package]] name = "ethcore-stratum" version = "1.7.0" @@ -1608,7 +1588,6 @@ dependencies = [ "ethcore-light 1.7.0", "ethcore-logger 1.7.0", "ethcore-secretstore 1.0.0", - "ethcore-signer 1.7.0", "ethcore-stratum 1.7.0", "ethcore-util 1.7.0", "ethkey 0.2.0", @@ -1797,18 +1776,18 @@ dependencies = [ name = "parity-rpc-client" version = "1.4.0" dependencies = [ - "ethcore-signer 1.7.0", "ethcore-util 1.7.0", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", + "jsonrpc-ws-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "parity-rpc 1.7.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ws 0.5.3 (git+https://github.com/paritytech/ws-rs.git?branch=parity-1.7)", ] [[package]] @@ -2385,11 +2364,6 @@ name = "siphasher" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "slab" -version = "0.2.0" -source = "git+https://github.com/carllerche/slab?rev=5476efcafb#5476efcafbc5ef4d7315b1bea3f756d8a1fe975e" - [[package]] name = "slab" version = "0.2.0" @@ -2823,25 +2797,10 @@ name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "ws" -version = "0.5.3" -source = "git+https://github.com/paritytech/ws-rs.git?branch=parity-1.7#30415c17f1bec53b2dcabae5b8b887df75dcbe34" -dependencies = [ - "bytes 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.1 (git+https://github.com/paritytech/mio)", - "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)", - "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ws" version = "0.6.0" -source = "git+https://github.com/tomusdrw/ws-rs#3259e7ca906c848beae109eb32e492871f8f397d" +source = "git+https://github.com/tomusdrw/ws-rs#7f8e416b7f048880228005457e117128be38bf0f" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3082,7 +3041,6 @@ dependencies = [ "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d" "checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd" -"checksum slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)" = "" "checksum slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6dbdd334bd28d328dad1c41b0ea662517883d8880d8533895ef96c8003dec9c4" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" "checksum smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fcc8d19212aacecf95e4a7a2179b26f7aeb9732a915cf01f05b0d3e044865410" @@ -3135,7 +3093,6 @@ dependencies = [ "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum ws 0.5.3 (git+https://github.com/paritytech/ws-rs.git?branch=parity-1.7)" = "" "checksum ws 0.6.0 (git+https://github.com/tomusdrw/ws-rs)" = "" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum xdg 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77b831a5ba77110f438f0ac5583aafeb087f70432998ba6b7dcb1d32185db453" diff --git a/Cargo.toml b/Cargo.toml index c0eccff2836..597766a4992 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,6 @@ ethcore = { path = "ethcore" } ethcore-util = { path = "util" } ethcore-io = { path = "util/io" } ethcore-devtools = { path = "devtools" } -ethcore-signer = { path = "signer" } ethcore-ipc = { path = "ipc/rpc" } ethcore-ipc-nano = { path = "ipc/nano" } ethcore-ipc-hypervisor = { path = "ipc/hypervisor" } @@ -75,17 +74,15 @@ default = ["ui-precompiled"] ui = [ "dapps", "parity-dapps/ui", - "ethcore-signer/ui", ] ui-precompiled = [ "dapps", - "ethcore-signer/ui-precompiled", "parity-dapps/ui-precompiled", ] dapps = ["parity-dapps"] ipc = ["ethcore/ipc", "ethsync/ipc"] jit = ["ethcore/jit"] -dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "parity-rpc/dev", "parity-dapps/dev", "ethcore-signer/dev"] +dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "parity-rpc/dev", "parity-dapps/dev"] json-tests = ["ethcore/json-tests"] test-heavy = ["ethcore/test-heavy"] ethkey-cli = ["ethcore/ethkey-cli"] diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index 064ad6d429a..d377ebe57f1 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -16,42 +16,27 @@ use std::sync::Arc; -use unicase::UniCase; use hyper::{server, net, Decoder, Encoder, Next, Control}; -use hyper::header; use hyper::method::Method; -use api::types::{App, ApiError}; +use api::types::ApiError; use api::response; use apps::fetcher::Fetcher; use handlers::extract_url; -use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; -use jsonrpc_http_server::{self, AccessControlAllowOrigin}; +use endpoint::{Endpoint, Handler, EndpointPath}; #[derive(Clone)] pub struct RestApi { - // TODO [ToDr] cors_domains should be handled by the server to avoid duplicated logic. - // RequestMiddleware should be able to tell that cors headers should be included. - cors_domains: Option>, - apps: Vec, fetcher: Arc, } impl RestApi { - pub fn new(cors_domains: Vec, endpoints: &Endpoints, fetcher: Arc) -> Box { + pub fn new(fetcher: Arc) -> Box { Box::new(RestApi { - cors_domains: Some(cors_domains), - apps: Self::list_apps(endpoints), fetcher: fetcher, }) } - - fn list_apps(endpoints: &Endpoints) -> Vec { - endpoints.iter().filter_map(|(ref k, ref e)| { - e.info().map(|ref info| App::from_info(k, info)) - }).collect() - } } impl Endpoint for RestApi { @@ -62,7 +47,6 @@ impl Endpoint for RestApi { struct RestApiRouter { api: RestApi, - cors_header: Option, path: Option, control: Option, handler: Box, @@ -72,7 +56,6 @@ impl RestApiRouter { fn new(api: RestApi, path: EndpointPath, control: Control) -> Self { RestApiRouter { path: Some(path), - cors_header: None, control: Some(control), api: api, handler: response::as_json_error(&ApiError { @@ -92,35 +75,10 @@ impl RestApiRouter { _ => None } } - - /// Returns basic headers for a response (it may be overwritten by the handler) - fn response_headers(cors_header: Option) -> header::Headers { - let mut headers = header::Headers::new(); - - if let Some(cors_header) = cors_header { - headers.set(header::AccessControlAllowCredentials); - headers.set(header::AccessControlAllowMethods(vec![ - Method::Options, - Method::Post, - Method::Get, - ])); - headers.set(header::AccessControlAllowHeaders(vec![ - UniCase("origin".to_owned()), - UniCase("content-type".to_owned()), - UniCase("accept".to_owned()), - ])); - - headers.set(cors_header); - } - - headers - } } impl server::Handler for RestApiRouter { fn on_request(&mut self, request: server::Request) -> Next { - self.cors_header = jsonrpc_http_server::cors_header(&request, &self.api.cors_domains).into(); - if let Method::Options = *request.method() { self.handler = response::empty(); return Next::write(); @@ -144,7 +102,6 @@ impl server::Handler for RestApiRouter { if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() } let handler = endpoint.and_then(|v| match v { - "apps" => Some(response::as_json(&self.api.apps)), "ping" => Some(response::ping()), "content" => self.resolve_content(hash, path, control), _ => None @@ -163,7 +120,6 @@ impl server::Handler for RestApiRouter { } fn on_response(&mut self, res: &mut server::Response) -> Next { - *res.headers_mut() = Self::response_headers(self.cors_header.take()); self.handler.on_response(res) } diff --git a/dapps/src/api/mod.rs b/dapps/src/api/mod.rs index f04b18878e6..4ffb9f791a7 100644 --- a/dapps/src/api/mod.rs +++ b/dapps/src/api/mod.rs @@ -21,4 +21,3 @@ mod response; mod types; pub use self::api::RestApi; -pub use self::types::App; diff --git a/dapps/src/api/response.rs b/dapps/src/api/response.rs index 380b1f996c8..2da2d0c14bd 100644 --- a/dapps/src/api/response.rs +++ b/dapps/src/api/response.rs @@ -23,12 +23,6 @@ pub fn empty() -> Box { Box::new(ContentHandler::ok("".into(), mime!(Text/Plain))) } -pub fn as_json(val: &T) -> Box { - let json = serde_json::to_string(val) - .expect("serialization to string is infallible; qed"); - Box::new(ContentHandler::ok(json, mime!(Application/Json))) -} - pub fn as_json_error(val: &T) -> Box { let json = serde_json::to_string(val) .expect("serialization to string is infallible; qed"); diff --git a/dapps/src/api/types.rs b/dapps/src/api/types.rs index a690a0b2bbe..549186955f9 100644 --- a/dapps/src/api/types.rs +++ b/dapps/src/api/types.rs @@ -14,46 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use endpoint::EndpointInfo; - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct App { - pub id: String, - pub name: String, - pub description: String, - pub version: String, - pub author: String, - #[serde(rename="iconUrl")] - pub icon_url: String, -} - -impl App { - /// Creates `App` instance from `EndpointInfo` and `id`. - pub fn from_info(id: &str, info: &EndpointInfo) -> Self { - App { - id: id.to_owned(), - name: info.name.to_owned(), - description: info.description.to_owned(), - version: info.version.to_owned(), - author: info.author.to_owned(), - icon_url: info.icon_url.to_owned(), - } - } -} - -impl Into for App { - fn into(self) -> EndpointInfo { - EndpointInfo { - name: self.name, - description: self.description, - version: self.version, - author: self.author, - icon_url: self.icon_url, - } - } -} - #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ApiError { diff --git a/dapps/src/apps/app.rs b/dapps/src/apps/app.rs new file mode 100644 index 00000000000..1d2c9dca6a9 --- /dev/null +++ b/dapps/src/apps/app.rs @@ -0,0 +1,55 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use endpoint::EndpointInfo; + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct App { + pub id: String, + pub name: String, + pub description: String, + pub version: String, + pub author: String, + #[serde(rename="iconUrl")] + pub icon_url: String, +} + +impl App { + /// Creates `App` instance from `EndpointInfo` and `id`. + pub fn from_info(id: &str, info: &EndpointInfo) -> Self { + App { + id: id.to_owned(), + name: info.name.to_owned(), + description: info.description.to_owned(), + version: info.version.to_owned(), + author: info.author.to_owned(), + icon_url: info.icon_url.to_owned(), + } + } +} + +impl Into for App { + fn into(self) -> EndpointInfo { + EndpointInfo { + name: self.name, + description: self.description, + version: self.version, + author: self.author, + icon_url: self.icon_url, + } + } +} diff --git a/dapps/src/apps/fetcher/mod.rs b/dapps/src/apps/fetcher/mod.rs index ec8004b300e..d621042c4a9 100644 --- a/dapps/src/apps/fetcher/mod.rs +++ b/dapps/src/apps/fetcher/mod.rs @@ -55,6 +55,7 @@ pub struct ContentFetcher, remote: Remote, fetch: F, + only_content: bool, } impl Drop for ContentFetcher { @@ -66,7 +67,12 @@ impl Drop for ContentFetcher { impl ContentFetcher { - pub fn new(resolver: R, sync_status: Arc, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self { + pub fn new( + resolver: R, + sync_status: Arc, + remote: Remote, + fetch: F, + ) -> Self { let mut dapps_path = env::temp_dir(); dapps_path.push(random_filename()); @@ -75,12 +81,23 @@ impl ContentFetcher { resolver: resolver, sync: sync_status, cache: Arc::new(Mutex::new(ContentCache::default())), - embeddable_on: embeddable_on, + embeddable_on: None, remote: remote, fetch: fetch, + only_content: true, } } + pub fn allow_dapps(mut self, dapps: bool) -> Self { + self.only_content = !dapps; + self + } + + pub fn embeddable_on(mut self, embeddable_on: Option<(String, u16)>) -> Self { + self.embeddable_on = embeddable_on; + self + } + fn still_syncing(address: Option<(String, u16)>) -> Box { Box::new(ContentHandler::error( StatusCode::ServiceUnavailable, @@ -91,6 +108,16 @@ impl ContentFetcher { )) } + fn dapps_disabled(address: Option<(String, u16)>) -> Box { + Box::new(ContentHandler::error( + StatusCode::ServiceUnavailable, + "Network Dapps Not Available", + "This interface doesn't support network dapps for security reasons.", + None, + address, + )) + } + #[cfg(test)] fn set_status(&self, content_id: &str, status: ContentStatus) { self.cache.lock().insert(content_id.to_owned(), status); @@ -163,6 +190,9 @@ impl Fetcher for ContentFetcher { Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => { (None, Self::still_syncing(self.embeddable_on.clone())) }, + Some(URLHintResult::Dapp(_)) if self.only_content => { + (None, Self::dapps_disabled(self.embeddable_on.clone())) + }, Some(URLHintResult::Dapp(dapp)) => { let handler = ContentFetcherHandler::new( dapp.url(), @@ -254,7 +284,8 @@ mod tests { fn should_true_if_contains_the_app() { // given let path = env::temp_dir(); - let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), None, Remote::new_sync(), Client::new().unwrap()); + let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), Remote::new_sync(), Client::new().unwrap()) + .allow_dapps(true); let handler = LocalPageEndpoint::new(path, EndpointInfo { name: "fake".into(), description: "".into(), diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs index d14f52c690e..8c5c65202ff 100644 --- a/dapps/src/apps/fs.rs +++ b/dapps/src/apps/fs.rs @@ -14,12 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::collections::BTreeMap; use std::io; use std::io::Read; use std::fs; use std::path::{Path, PathBuf}; use page::{LocalPageEndpoint, PageCache}; -use endpoint::{Endpoints, EndpointInfo}; +use endpoint::{Endpoint, EndpointInfo}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest}; struct LocalDapp { @@ -85,8 +86,8 @@ fn local_dapp(name: String, path: PathBuf) -> LocalDapp { /// Returns endpoints for Local Dapps found for given filesystem path. /// Scans the directory and collects `LocalPageEndpoints`. -pub fn local_endpoints>(dapps_path: P, signer_address: Option<(String, u16)>) -> Endpoints { - let mut pages = Endpoints::new(); +pub fn local_endpoints>(dapps_path: P, signer_address: Option<(String, u16)>) -> BTreeMap> { + let mut pages = BTreeMap::>::new(); for dapp in local_dapps(dapps_path.as_ref()) { pages.insert( dapp.id, diff --git a/dapps/src/apps/manifest.rs b/dapps/src/apps/manifest.rs index a40cfb8b980..94611498043 100644 --- a/dapps/src/apps/manifest.rs +++ b/dapps/src/apps/manifest.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use serde_json; -pub use api::App as Manifest; +pub use apps::App as Manifest; pub const MANIFEST_FILENAME: &'static str = "manifest.json"; diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index b85f0dde9a3..b3c5a5cef3b 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -14,8 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::collections::BTreeMap; use std::path::PathBuf; use std::sync::Arc; + use endpoint::{Endpoints, Endpoint}; use page::PageEndpoint; use proxypac::ProxyPac; @@ -23,17 +25,19 @@ use web::Web; use fetch::Fetch; use parity_dapps::WebApp; use parity_reactor::Remote; +use parity_ui; use {WebProxyTokens}; +mod app; mod cache; mod fs; +mod ui; pub mod fetcher; pub mod manifest; -extern crate parity_ui; +pub use self::app::App; -pub const HOME_PAGE: &'static str = "parity"; -pub const DAPPS_DOMAIN: &'static str = ".web3.site"; +pub const HOME_PAGE: &'static str = "home"; pub const RPC_PATH: &'static str = "rpc"; pub const API_PATH: &'static str = "api"; pub const UTILS_PATH: &'static str = "parity-utils"; @@ -44,18 +48,27 @@ pub fn utils() -> Box { Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned())) } +pub fn ui() -> Box { + Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default())) +} + +pub fn ui_redirection(ui_address: Option<(String, u16)>) -> Box { + Box::new(ui::Redirection::new(ui_address)) +} + pub fn all_endpoints( dapps_path: PathBuf, extra_dapps: Vec, - signer_address: Option<(String, u16)>, + dapps_domain: String, + ui_address: Option<(String, u16)>, web_proxy_tokens: Arc, remote: Remote, fetch: F, ) -> Endpoints { // fetch fs dapps at first to avoid overwriting builtins - let mut pages = fs::local_endpoints(dapps_path, signer_address.clone()); + let mut pages = fs::local_endpoints(dapps_path, ui_address.clone()); for path in extra_dapps { - if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), signer_address.clone()) { + if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), ui_address.clone()) { pages.insert(id, endpoint); } else { warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display()); @@ -63,14 +76,14 @@ pub fn all_endpoints( } // NOTE [ToDr] Dapps will be currently embeded on 8180 - insert::(&mut pages, "ui", Embeddable::Yes(signer_address.clone())); - pages.insert("proxy".into(), ProxyPac::boxed(signer_address.clone())); - pages.insert(WEB_PATH.into(), Web::boxed(signer_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone())); + insert::(&mut pages, "ui", Embeddable::Yes(ui_address.clone())); + pages.insert("proxy".into(), ProxyPac::boxed(ui_address.clone(), dapps_domain)); + pages.insert(WEB_PATH.into(), Web::boxed(ui_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone())); - pages + Arc::new(pages) } -fn insert(pages: &mut Endpoints, id: &str, embed_at: Embeddable) { +fn insert(pages: &mut BTreeMap>, id: &str, embed_at: Embeddable) { pages.insert(id.to_owned(), Box::new(match embed_at { Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address), Embeddable::No => PageEndpoint::new(T::default()), diff --git a/dapps/src/apps/ui.rs b/dapps/src/apps/ui.rs new file mode 100644 index 00000000000..d5e7bd5e8f7 --- /dev/null +++ b/dapps/src/apps/ui.rs @@ -0,0 +1,55 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! UI redirections + +use hyper::{Control, StatusCode}; + +use endpoint::{Endpoint, Handler, EndpointPath}; +use {address, handlers}; + +/// Redirection to UI server. +pub struct Redirection { + signer_address: Option<(String, u16)>, +} + +impl Redirection { + pub fn new( + signer_address: Option<(String, u16)>, + ) -> Self { + Redirection { + signer_address: signer_address, + } + } +} + +impl Endpoint for Redirection { + fn to_async_handler(&self, _path: EndpointPath, _control: Control) -> Box { + if let Some(ref signer_address) = self.signer_address { + trace!(target: "dapps", "Redirecting to signer interface."); + handlers::Redirection::boxed(&format!("http://{}", address(signer_address))) + } else { + trace!(target: "dapps", "Signer disabled, returning 404."); + Box::new(handlers::ContentHandler::error( + StatusCode::NotFound, + "404 Not Found", + "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."), + self.signer_address.clone(), + )) + } + } +} diff --git a/dapps/src/endpoint.rs b/dapps/src/endpoint.rs index ea5825b7495..ea8fd0a3842 100644 --- a/dapps/src/endpoint.rs +++ b/dapps/src/endpoint.rs @@ -16,6 +16,7 @@ //! URL Endpoint traits +use std::sync::Arc; use std::collections::BTreeMap; use hyper::{self, server, net}; @@ -38,7 +39,7 @@ pub struct EndpointInfo { pub icon_url: String, } -pub type Endpoints = BTreeMap>; +pub type Endpoints = Arc>>; pub type Handler = server::Handler + Send; pub trait Endpoint : Send + Sync { diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 5f4b833252a..0860f0c1092 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -40,6 +40,7 @@ extern crate fetch; extern crate parity_dapps_glue as parity_dapps; extern crate parity_hash_fetch as hash_fetch; extern crate parity_reactor; +extern crate parity_ui; #[macro_use] extern crate log; @@ -70,7 +71,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::collections::HashMap; -use jsonrpc_http_server::{self as http, hyper, AccessControlAllowOrigin}; +use jsonrpc_http_server::{self as http, hyper}; use fetch::Fetch; use parity_reactor::Remote; @@ -97,18 +98,74 @@ impl WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync { fn is_web_proxy_token_valid(&self, token: &str) -> bool { self(token.to_owned()) } } +/// Current supported endpoints. +pub struct Endpoints { + endpoints: endpoint::Endpoints, +} + +impl Endpoints { + /// Returns a current list of app endpoints. + pub fn list(&self) -> Vec { + self.endpoints.iter().filter_map(|(ref k, ref e)| { + e.info().map(|ref info| apps::App::from_info(k, info)) + }).collect() + } +} + /// Dapps server as `jsonrpc-http-server` request middleware. pub struct Middleware { router: router::Router, + endpoints: endpoint::Endpoints, } impl Middleware { + /// Get local endpoints handle. + pub fn endpoints(&self) -> Endpoints { + Endpoints { + endpoints: self.endpoints.clone(), + } + } + + /// Creates new middleware for UI server. + pub fn ui( + remote: Remote, + registrar: Arc, + sync_status: Arc, + fetch: F, + dapps_domain: String, + ) -> Self { + let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new( + hash_fetch::urlhint::URLHintContract::new(registrar), + sync_status, + remote.clone(), + fetch.clone(), + ).embeddable_on(None).allow_dapps(false)); + let special = { + let mut special = special_endpoints(content_fetcher.clone()); + special.insert(router::SpecialEndpoint::Home, Some(apps::ui())); + special + }; + let router = router::Router::new( + content_fetcher, + None, + special, + None, + dapps_domain, + ); + + Middleware { + router: router, + endpoints: Default::default(), + } + } + /// Creates new Dapps server middleware. - pub fn new( + pub fn dapps( remote: Remote, - signer_address: Option<(String, u16)>, + ui_address: Option<(String, u16)>, dapps_path: PathBuf, extra_dapps: Vec, + dapps_domain: String, registrar: Arc, sync_status: Arc, web_proxy_tokens: Arc, @@ -117,45 +174,36 @@ impl Middleware { let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new( hash_fetch::urlhint::URLHintContract::new(registrar), sync_status, - signer_address.clone(), remote.clone(), fetch.clone(), - )); + ).embeddable_on(ui_address.clone()).allow_dapps(true)); let endpoints = apps::all_endpoints( dapps_path, extra_dapps, - signer_address.clone(), + dapps_domain.clone(), + ui_address.clone(), web_proxy_tokens, remote.clone(), fetch.clone(), ); - let cors_domains = cors_domains(signer_address.clone()); - let special = { - let mut special = HashMap::new(); - special.insert(router::SpecialEndpoint::Rpc, None); - special.insert(router::SpecialEndpoint::Utils, Some(apps::utils())); - special.insert( - router::SpecialEndpoint::Api, - Some(api::RestApi::new( - cors_domains.clone(), - &endpoints, - content_fetcher.clone() - )), - ); + let mut special = special_endpoints(content_fetcher.clone()); + special.insert(router::SpecialEndpoint::Home, Some(apps::ui_redirection(ui_address.clone()))); special }; let router = router::Router::new( - signer_address, content_fetcher, - endpoints, + Some(endpoints.clone()), special, + ui_address, + dapps_domain, ); Middleware { router: router, + endpoints: endpoints, } } } @@ -166,21 +214,12 @@ impl http::RequestMiddleware for Middleware { } } -/// Returns a list of CORS domains for API endpoint. -fn cors_domains(signer_address: Option<(String, u16)>) -> Vec { - use self::apps::{HOME_PAGE, DAPPS_DOMAIN}; - - match signer_address { - Some(signer_address) => [ - format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), - format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), - format!("http://{}", address(&signer_address)), - format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN), - format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), - format!("https://{}", address(&signer_address)), - ].into_iter().map(|val| AccessControlAllowOrigin::Value(val.into())).collect(), - None => vec![], - } +fn special_endpoints(content_fetcher: Arc) -> HashMap>> { + let mut special = HashMap::new(); + special.insert(router::SpecialEndpoint::Rpc, None); + special.insert(router::SpecialEndpoint::Utils, Some(apps::utils())); + special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new(content_fetcher))); + special } fn address(address: &(String, u16)) -> String { @@ -193,29 +232,3 @@ fn random_filename() -> String { let mut rng = ::rand::OsRng::new().unwrap(); rng.gen_ascii_chars().take(12).collect() } - -#[cfg(test)] -mod util_tests { - use super::cors_domains; - use jsonrpc_http_server::AccessControlAllowOrigin; - - #[test] - fn should_return_cors_domains() { - // given - - // when - let none = cors_domains(None); - let some = cors_domains(Some(("127.0.0.1".into(), 18180))); - - // then - assert_eq!(none, Vec::::new()); - assert_eq!(some, vec![ - "http://parity.web3.site".into(), - "http://parity.web3.site:18180".into(), - "http://127.0.0.1:18180".into(), - "https://parity.web3.site".into(), - "https://parity.web3.site:18180".into(), - "https://127.0.0.1:18180".into(), - ]); - } -} diff --git a/dapps/src/page/builtin.rs b/dapps/src/page/builtin.rs index c778b29776e..e93c2953832 100644 --- a/dapps/src/page/builtin.rs +++ b/dapps/src/page/builtin.rs @@ -27,6 +27,7 @@ pub struct PageEndpoint { /// Safe to be loaded in frame by other origin. (use wisely!) safe_to_embed_on: Option<(String, u16)>, info: EndpointInfo, + fallback_to_index_html: bool, } impl PageEndpoint { @@ -38,6 +39,20 @@ impl PageEndpoint { prefix: None, safe_to_embed_on: None, info: EndpointInfo::from(info), + fallback_to_index_html: false, + } + } + + /// Creates a new `PageEndpoint` for builtin (compile time) Dapp. + /// Instead of returning 404 this endpoint will always server index.html. + pub fn with_fallback_to_index(app: T) -> Self { + let info = app.info(); + PageEndpoint { + app: Arc::new(app), + prefix: None, + safe_to_embed_on: None, + info: EndpointInfo::from(info), + fallback_to_index_html: true, } } @@ -51,6 +66,7 @@ impl PageEndpoint { prefix: Some(prefix), safe_to_embed_on: None, info: EndpointInfo::from(info), + fallback_to_index_html: false, } } @@ -64,6 +80,7 @@ impl PageEndpoint { prefix: None, safe_to_embed_on: address, info: EndpointInfo::from(info), + fallback_to_index_html: false, } } } @@ -76,7 +93,7 @@ impl Endpoint for PageEndpoint { fn to_handler(&self, path: EndpointPath) -> Box { Box::new(handler::PageHandler { - app: BuiltinDapp::new(self.app.clone()), + app: BuiltinDapp::new(self.app.clone(), self.fallback_to_index_html), prefix: self.prefix.clone(), path: path, file: handler::ServedFile::new(self.safe_to_embed_on.clone()), @@ -100,12 +117,14 @@ impl From for EndpointInfo { struct BuiltinDapp { app: Arc, + fallback_to_index_html: bool, } impl BuiltinDapp { - fn new(app: Arc) -> Self { + fn new(app: Arc, fallback_to_index_html: bool) -> Self { BuiltinDapp { app: app, + fallback_to_index_html: fallback_to_index_html, } } } @@ -114,13 +133,19 @@ impl handler::Dapp for BuiltinDapp { type DappFile = BuiltinDappFile; fn file(&self, path: &str) -> Option { - self.app.file(path).map(|_| { + let file = |path| self.app.file(path).map(|_| { BuiltinDappFile { app: self.app.clone(), path: path.into(), write_pos: 0, } - }) + }); + let res = file(path); + if self.fallback_to_index_html { + res.or_else(|| file("index.html")) + } else { + res + } } } diff --git a/dapps/src/proxypac.rs b/dapps/src/proxypac.rs index 16459d88e76..13ea4c665e9 100644 --- a/dapps/src/proxypac.rs +++ b/dapps/src/proxypac.rs @@ -18,17 +18,19 @@ use endpoint::{Endpoint, Handler, EndpointPath}; use handlers::ContentHandler; -use apps::{HOME_PAGE, DAPPS_DOMAIN}; +use apps::HOME_PAGE; use address; pub struct ProxyPac { signer_address: Option<(String, u16)>, + dapps_domain: String, } impl ProxyPac { - pub fn boxed(signer_address: Option<(String, u16)>) -> Box { + pub fn boxed(signer_address: Option<(String, u16)>, dapps_domain: String) -> Box { Box::new(ProxyPac { - signer_address: signer_address + signer_address: signer_address, + dapps_domain: dapps_domain, }) } } @@ -43,12 +45,12 @@ impl Endpoint for ProxyPac { let content = format!( r#" function FindProxyForURL(url, host) {{ - if (shExpMatch(host, "{0}{1}")) + if (shExpMatch(host, "{0}.{1}")) {{ return "PROXY {4}"; }} - if (shExpMatch(host, "*{1}")) + if (shExpMatch(host, "*.{1}")) {{ return "PROXY {2}:{3}"; }} @@ -56,7 +58,7 @@ function FindProxyForURL(url, host) {{ return "DIRECT"; }} "#, - HOME_PAGE, DAPPS_DOMAIN, path.host, path.port, signer); + HOME_PAGE, self.dapps_domain, path.host, path.port, signer); Box::new(ContentHandler::ok(content, mime!(Application/Javascript))) } diff --git a/dapps/src/router.rs b/dapps/src/router.rs index c7b7fb7ffc2..b3454c78274 100644 --- a/dapps/src/router.rs +++ b/dapps/src/router.rs @@ -17,20 +17,19 @@ //! Router implementation //! Dispatch requests to proper application. -use address; use std::cmp; use std::sync::Arc; use std::collections::HashMap; use url::{Url, Host}; -use hyper::{self, server, header, Control, StatusCode}; +use hyper::{self, server, header, Control}; use hyper::net::HttpStream; use jsonrpc_http_server as http; -use apps::{self, DAPPS_DOMAIN}; +use apps; use apps::fetcher::Fetcher; use endpoint::{Endpoint, Endpoints, EndpointPath, Handler}; -use handlers::{self, Redirection, ContentHandler}; +use handlers; /// Special endpoints are accessible on every domain (every dapp) #[derive(Debug, PartialEq, Hash, Eq)] @@ -38,26 +37,28 @@ pub enum SpecialEndpoint { Rpc, Api, Utils, + Home, None, } pub struct Router { - signer_address: Option<(String, u16)>, - endpoints: Endpoints, + endpoints: Option, fetch: Arc, special: HashMap>>, + embeddable_on: Option<(String, u16)>, + dapps_domain: String, } impl http::RequestMiddleware for Router { fn on_request(&self, req: &server::Request, control: &Control) -> http::RequestMiddlewareAction { // Choose proper handler depending on path / domain let url = handlers::extract_url(req); - let endpoint = extract_endpoint(&url); - let referer = extract_referer_endpoint(req); + let endpoint = extract_endpoint(&url, &self.dapps_domain); + let referer = extract_referer_endpoint(req, &self.dapps_domain); let is_utils = endpoint.1 == SpecialEndpoint::Utils; - let is_dapps_domain = endpoint.0.as_ref().map(|endpoint| endpoint.using_dapps_domains).unwrap_or(false); - let is_origin_set = req.headers().get::().is_some(); + let is_origin_set = req.headers().get::().is_some(); let is_get_request = *req.method() == hyper::Method::Get; + let is_head_request = *req.method() == hyper::Method::Head; trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req); @@ -67,7 +68,7 @@ impl http::RequestMiddleware for Router { // Handle invalid web requests that we can recover from (ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url))) if referer.app_id == apps::WEB_PATH - && self.endpoints.contains_key(apps::WEB_PATH) + && self.endpoints.as_ref().map(|ep| ep.contains_key(apps::WEB_PATH)).unwrap_or(false) && !is_web_endpoint(path) => { @@ -75,7 +76,7 @@ impl http::RequestMiddleware for Router { let len = cmp::min(referer_url.path.len(), 2); // /web// let base = referer_url.path[..len].join("/"); let requested = url.map(|u| u.path.join("/")).unwrap_or_default(); - Some(Redirection::boxed(&format!("/{}/{}", base, requested))) + Some(handlers::Redirection::boxed(&format!("/{}/{}", base, requested))) }, // First check special endpoints (ref path, ref endpoint, _) if self.special.contains_key(endpoint) => { @@ -86,9 +87,12 @@ impl http::RequestMiddleware for Router { .map(|special| special.to_async_handler(path.clone().unwrap_or_default(), control)) }, // Then delegate to dapp - (Some(ref path), _, _) if self.endpoints.contains_key(&path.app_id) => { + (Some(ref path), _, _) if self.endpoints.as_ref().map(|ep| ep.contains_key(&path.app_id)).unwrap_or(false) => { trace!(target: "dapps", "Resolving to local/builtin dapp."); - Some(self.endpoints.get(&path.app_id) + Some(self.endpoints + .as_ref() + .expect("endpoints known to be set; qed") + .get(&path.app_id) .expect("endpoints known to contain key; qed") .to_async_handler(path.clone(), control)) }, @@ -97,36 +101,28 @@ impl http::RequestMiddleware for Router { trace!(target: "dapps", "Resolving to fetchable content."); Some(self.fetch.to_async_handler(path.clone(), control)) }, - // NOTE [todr] /home is redirected to home page since some users may have the redirection cached - // (in the past we used 301 instead of 302) - // It should be safe to remove it in (near) future. - // - // 404 for non-existent content - (Some(ref path), _, _) if is_get_request && path.app_id != "home" => { + // 404 for non-existent content (only if serving endpoints and not homepage) + (Some(ref path), _, _) + if (is_get_request || is_head_request) + && self.endpoints.is_some() + && path.app_id != apps::HOME_PAGE + => + { trace!(target: "dapps", "Resolving to 404."); - Some(Box::new(ContentHandler::error( - StatusCode::NotFound, + Some(Box::new(handlers::ContentHandler::error( + hyper::StatusCode::NotFound, "404 Not Found", "Requested content was not found.", None, - self.signer_address.clone(), + self.embeddable_on.clone(), ))) }, - // Redirect any other GET request to signer. - _ if is_get_request => { - if let Some(ref signer_address) = self.signer_address { - trace!(target: "dapps", "Redirecting to signer interface."); - Some(Redirection::boxed(&format!("http://{}", address(signer_address)))) - } else { - trace!(target: "dapps", "Signer disabled, returning 404."); - Some(Box::new(ContentHandler::error( - StatusCode::NotFound, - "404 Not Found", - "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."), - self.signer_address.clone(), - ))) - } + // Any other GET|HEAD requests to home page. + _ if (is_get_request || is_head_request) && self.special.contains_key(&SpecialEndpoint::Home) => { + self.special.get(&SpecialEndpoint::Home) + .expect("special known to contain key; qed") + .as_ref() + .map(|special| special.to_async_handler(Default::default(), control)) }, // RPC by default _ => { @@ -137,7 +133,7 @@ impl http::RequestMiddleware for Router { match handler { Some(handler) => http::RequestMiddlewareAction::Respond { - should_validate_hosts: !(is_utils || is_dapps_domain), + should_validate_hosts: !is_utils, handler: handler, }, None => http::RequestMiddlewareAction::Proceed { @@ -149,16 +145,18 @@ impl http::RequestMiddleware for Router { impl Router { pub fn new( - signer_address: Option<(String, u16)>, content_fetcher: Arc, - endpoints: Endpoints, + endpoints: Option, special: HashMap>>, + embeddable_on: Option<(String, u16)>, + dapps_domain: String, ) -> Self { Router { - signer_address: signer_address, endpoints: endpoints, fetch: content_fetcher, special: special, + embeddable_on: embeddable_on, + dapps_domain: format!(".{}", dapps_domain), } } } @@ -170,19 +168,19 @@ fn is_web_endpoint(path: &Option) -> bool { } } -fn extract_referer_endpoint(req: &server::Request) -> Option<(EndpointPath, Url)> { +fn extract_referer_endpoint(req: &server::Request, dapps_domain: &str) -> Option<(EndpointPath, Url)> { let referer = req.headers().get::(); let url = referer.and_then(|referer| Url::parse(&referer.0).ok()); url.and_then(|url| { let option = Some(url); - extract_url_referer_endpoint(&option).or_else(|| { - extract_endpoint(&option).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed"))) + extract_url_referer_endpoint(&option, dapps_domain).or_else(|| { + extract_endpoint(&option, dapps_domain).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed"))) }) }) } -fn extract_url_referer_endpoint(url: &Option) -> Option<(EndpointPath, Url)> { +fn extract_url_referer_endpoint(url: &Option, dapps_domain: &str) -> Option<(EndpointPath, Url)> { let query = url.as_ref().and_then(|url| url.query.as_ref()); match (url, query) { (&Some(ref url), Some(ref query)) if query.starts_with(apps::URL_REFERER) => { @@ -190,7 +188,7 @@ fn extract_url_referer_endpoint(url: &Option) -> Option<(EndpointPath, Url) debug!(target: "dapps", "Recovering referer from query parameter: {}", referer_url); let referer_url = Url::parse(&referer_url).ok(); - extract_endpoint(&referer_url).0.map(|endpoint| { + extract_endpoint(&referer_url, dapps_domain).0.map(|endpoint| { (endpoint, referer_url.expect("Endpoint returned only when url `is_some`").clone()) }) }, @@ -198,7 +196,7 @@ fn extract_url_referer_endpoint(url: &Option) -> Option<(EndpointPath, Url) } } -fn extract_endpoint(url: &Option) -> (Option, SpecialEndpoint) { +fn extract_endpoint(url: &Option, dapps_domain: &str) -> (Option, SpecialEndpoint) { fn special_endpoint(url: &Url) -> SpecialEndpoint { if url.path.len() <= 1 { return SpecialEndpoint::None; @@ -208,14 +206,15 @@ fn extract_endpoint(url: &Option) -> (Option, SpecialEndpoint apps::RPC_PATH => SpecialEndpoint::Rpc, apps::API_PATH => SpecialEndpoint::Api, apps::UTILS_PATH => SpecialEndpoint::Utils, + apps::HOME_PAGE => SpecialEndpoint::Home, _ => SpecialEndpoint::None, } } match *url { Some(ref url) => match url.host { - Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => { - let id = &domain[0..(domain.len() - DAPPS_DOMAIN.len())]; + Host::Domain(ref domain) if domain.ends_with(dapps_domain) => { + let id = &domain[0..(domain.len() - dapps_domain.len())]; let (id, params) = if let Some(split) = id.rfind('.') { let (params, id) = id.split_at(split); (id[1..].to_owned(), [params.to_owned()].into_iter().chain(&url.path).cloned().collect()) @@ -249,11 +248,12 @@ fn extract_endpoint(url: &Option) -> (Option, SpecialEndpoint #[test] fn should_extract_endpoint() { - assert_eq!(extract_endpoint(&None), (None, SpecialEndpoint::None)); + let dapps_domain = ".web3.site"; + assert_eq!(extract_endpoint(&None, dapps_domain), (None, SpecialEndpoint::None)); // With path prefix assert_eq!( - extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok()), + extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok(), dapps_domain), (Some(EndpointPath { app_id: "status".to_owned(), app_params: vec!["index.html".to_owned()], @@ -265,7 +265,7 @@ fn should_extract_endpoint() { // With path prefix assert_eq!( - extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()), + extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok(), dapps_domain), (Some(EndpointPath { app_id: "rpc".to_owned(), app_params: vec!["".to_owned()], @@ -276,7 +276,7 @@ fn should_extract_endpoint() { ); assert_eq!( - extract_endpoint(&Url::parse("http://my.status.web3.site/parity-utils/inject.js").ok()), + extract_endpoint(&Url::parse("http://my.status.web3.site/parity-utils/inject.js").ok(), dapps_domain), (Some(EndpointPath { app_id: "status".to_owned(), app_params: vec!["my".to_owned(), "parity-utils".into(), "inject.js".into()], @@ -288,7 +288,7 @@ fn should_extract_endpoint() { // By Subdomain assert_eq!( - extract_endpoint(&Url::parse("http://status.web3.site/test.html").ok()), + extract_endpoint(&Url::parse("http://status.web3.site/test.html").ok(), dapps_domain), (Some(EndpointPath { app_id: "status".to_owned(), app_params: vec!["test.html".to_owned()], @@ -300,7 +300,7 @@ fn should_extract_endpoint() { // RPC by subdomain assert_eq!( - extract_endpoint(&Url::parse("http://my.status.web3.site/rpc/").ok()), + extract_endpoint(&Url::parse("http://my.status.web3.site/rpc/").ok(), dapps_domain), (Some(EndpointPath { app_id: "status".to_owned(), app_params: vec!["my".to_owned(), "rpc".into(), "".into()], @@ -312,7 +312,7 @@ fn should_extract_endpoint() { // API by subdomain assert_eq!( - extract_endpoint(&Url::parse("http://my.status.web3.site/api/").ok()), + extract_endpoint(&Url::parse("http://my.status.web3.site/api/").ok(), dapps_domain), (Some(EndpointPath { app_id: "status".to_owned(), app_params: vec!["my".to_owned(), "api".into(), "".into()], diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs deleted file mode 100644 index 74c6d8d8984..00000000000 --- a/dapps/src/rpc.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -use std::sync::Arc; -use hyper; - -use parity_rpc::{Metadata, Origin}; -use jsonrpc_core::{Middleware, MetaIoHandler}; -use jsonrpc_http_server::{self as http, AccessControlAllowOrigin, HttpMetaExtractor}; -use jsonrpc_http_server::tokio_core::reactor::Remote; -use endpoint::{Endpoint, EndpointPath, Handler}; - -pub fn rpc>( - handler: MetaIoHandler, - remote: Remote, - cors_domains: Vec, -) -> Box { - Box::new(RpcEndpoint { - handler: Arc::new(handler), - remote: remote, - meta_extractor: Arc::new(MetadataExtractor), - cors_domain: Some(cors_domains), - // NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router. - allowed_hosts: None, - }) -} - -struct RpcEndpoint> { - handler: Arc>, - remote: Remote, - meta_extractor: Arc>, - cors_domain: Option>, - allowed_hosts: Option>, -} - - -impl> Endpoint for RpcEndpoint { - fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box { - Box::new(http::ServerHandler::new( - http::Rpc { - handler: self.handler.clone(), - remote: self.remote.clone(), - extractor: self.meta_extractor.clone(), - }, - self.cors_domain.clone(), - self.allowed_hosts.clone(), - Arc::new(NoopMiddleware), - control, - )) - } -} - -#[derive(Default)] -struct NoopMiddleware; -impl http::RequestMiddleware for NoopMiddleware { - fn on_request(&self, request: &http::hyper::server::Request, _control: &http::hyper::Control) -> http::RequestMiddlewareAction { - http::RequestMiddlewareAction::Proceed { - should_continue_on_invalid_cors: request.headers().get::().is_none(), - } - } -} - -pub struct MetadataExtractor; -impl HttpMetaExtractor for MetadataExtractor { - fn read_metadata(&self, request: &http::hyper::server::Request) -> Metadata { - let dapp_id = request.headers().get::() - .map(|origin| format!("{}://{}", origin.scheme, origin.host)) - .or_else(|| { - // fallback to custom header, but only if origin is null - request.headers().get_raw("origin") - .and_then(|raw| raw.one()) - .and_then(|raw| if raw == "null".as_bytes() { - request.headers().get_raw("x-parity-origin") - .and_then(|raw| raw.one()) - .map(|raw| String::from_utf8_lossy(raw).into_owned()) - } else { - None - }) - }); - Metadata { - origin: Origin::Dapps(dapp_id.map(Into::into).unwrap_or_default()), - } - } -} diff --git a/dapps/src/tests/api.rs b/dapps/src/tests/api.rs index 04381437734..b75cd25f29e 100644 --- a/dapps/src/tests/api.rs +++ b/dapps/src/tests/api.rs @@ -39,29 +39,6 @@ fn should_return_error() { assert_security_headers(&response.headers); } -#[test] -fn should_serve_apps() { - // given - let server = serve(); - - // when - let response = request(server, - "\ - GET /api/apps HTTP/1.1\r\n\ - Host: 127.0.0.1:8080\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - response.assert_status("HTTP/1.1 200 OK"); - response.assert_header("Content-Type", "application/json"); - assert!(response.body.contains("Parity UI"), response.body); - assert_security_headers(&response.headers); -} - #[test] fn should_handle_ping() { // given @@ -106,92 +83,3 @@ fn should_try_to_resolve_dapp() { assert_eq!(registrar.calls.lock().len(), 2); assert_security_headers(&response.headers); } - -#[test] -fn should_return_signer_port_cors_headers() { - // given - let server = serve(); - - // when - let response = request(server, - "\ - POST /api/ping HTTP/1.1\r\n\ - Host: localhost:8080\r\n\ - Origin: http://127.0.0.1:18180\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://127.0.0.1:18180"); -} - -#[test] -fn should_return_signer_port_cors_headers_for_home_parity() { - // given - let server = serve(); - - // when - let response = request(server, - "\ - POST /api/ping HTTP/1.1\r\n\ - Host: localhost:8080\r\n\ - Origin: http://parity.web3.site\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://parity.web3.site"); -} - - -#[test] -fn should_return_signer_port_cors_headers_for_home_parity_with_https() { - // given - let server = serve(); - - // when - let response = request(server, - "\ - POST /api/ping HTTP/1.1\r\n\ - Host: localhost:8080\r\n\ - Origin: https://parity.web3.site\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - response.assert_status("HTTP/1.1 200 OK"); - response.assert_header("Access-Control-Allow-Origin", "https://parity.web3.site"); -} - -#[test] -fn should_return_signer_port_cors_headers_for_home_parity_with_port() { - // given - let server = serve(); - - // when - let response = request(server, - "\ - POST /api/ping HTTP/1.1\r\n\ - Host: localhost:8080\r\n\ - Origin: http://parity.web3.site:18180\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://parity.web3.site:18180"); -} diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index e6c0325491d..6bc0006ce83 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -26,7 +26,7 @@ use jsonrpc_http_server::{self as http, Host, DomainsValidation}; use devtools::http_client; use hash_fetch::urlhint::ContractClient; use fetch::{Fetch, Client as FetchClient}; -use parity_reactor::{EventLoop, Remote}; +use parity_reactor::Remote; use {Middleware, SyncStatus, WebProxyTokens}; @@ -47,20 +47,7 @@ fn init_logger() { } } -pub struct ServerLoop { - pub server: Server, - pub event_loop: EventLoop, -} - -impl ::std::ops::Deref for ServerLoop { - type Target = Server; - - fn deref(&self) -> &Self::Target { - &self.server - } -} - -pub fn init_server(process: F, io: IoHandler, remote: Remote) -> (ServerLoop, Arc) where +pub fn init_server(process: F, io: IoHandler, remote: Remote) -> (Server, Arc) where F: FnOnce(ServerBuilder) -> ServerBuilder, B: Fetch, { @@ -69,44 +56,41 @@ pub fn init_server(process: F, io: IoHandler, remote: Remote) -> (ServerLo let mut dapps_path = env::temp_dir(); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); - // TODO [ToDr] When https://github.com/paritytech/jsonrpc/issues/26 is resolved - // this additional EventLoop wouldn't be needed, we should be able to re-use remote. - let event_loop = EventLoop::spawn(); let server = process(ServerBuilder::new( &dapps_path, registrar.clone(), remote, )) .signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))) .start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io).unwrap(); ( - ServerLoop { server: server, event_loop: event_loop }, + server, registrar, ) } -pub fn serve_with_rpc(io: IoHandler) -> ServerLoop { +pub fn serve_with_rpc(io: IoHandler) -> Server { init_server(|builder| builder, io, Remote::new_sync()).0 } -pub fn serve_hosts(hosts: Option>) -> ServerLoop { +pub fn serve_hosts(hosts: Option>) -> Server { let hosts = hosts.map(|hosts| hosts.into_iter().map(Into::into).collect()); init_server(|builder| builder.allowed_hosts(hosts.into()), Default::default(), Remote::new_sync()).0 } -pub fn serve_with_registrar() -> (ServerLoop, Arc) { +pub fn serve_with_registrar() -> (Server, Arc) { init_server(|builder| builder, Default::default(), Remote::new_sync()) } -pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc) { +pub fn serve_with_registrar_and_sync() -> (Server, Arc) { init_server(|builder| { builder.sync_status(Arc::new(|| true)) }, Default::default(), Remote::new_sync()) } -pub fn serve_with_registrar_and_fetch() -> (ServerLoop, FakeFetch, Arc) { +pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc) { serve_with_registrar_and_fetch_and_threads(false) } -pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (ServerLoop, FakeFetch, Arc) { +pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Server, FakeFetch, Arc) { let fetch = FakeFetch::default(); let f = fetch.clone(); let (server, reg) = init_server(move |builder| { @@ -116,7 +100,7 @@ pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Serv (server, fetch, reg) } -pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) { +pub fn serve_with_fetch(web_token: &'static str) -> (Server, FakeFetch) { let fetch = FakeFetch::default(); let f = fetch.clone(); let (server, _) = init_server(move |builder| { @@ -128,11 +112,11 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) { (server, fetch) } -pub fn serve() -> ServerLoop { +pub fn serve() -> Server { init_server(|builder| builder, Default::default(), Remote::new_sync()).0 } -pub fn request(server: ServerLoop, request: &str) -> http_client::Response { +pub fn request(server: Server, request: &str) -> http_client::Response { http_client::request(server.addr(), request) } @@ -240,6 +224,7 @@ impl ServerBuilder { } } +const DAPPS_DOMAIN: &'static str = "web3.site"; /// Webapps HTTP server. pub struct Server { @@ -260,19 +245,27 @@ impl Server { remote: Remote, fetch: F, ) -> Result { - let middleware = Middleware::new( + let middleware = Middleware::dapps( remote, signer_address, dapps_path, extra_dapps, + DAPPS_DOMAIN.into(), registrar, sync_status, web_proxy_tokens, fetch, ); + + let mut allowed_hosts: Option> = allowed_hosts.into(); + allowed_hosts.as_mut().map(|mut hosts| { + hosts.push(format!("http://*.{}:*", DAPPS_DOMAIN).into()); + hosts.push(format!("http://*.{}", DAPPS_DOMAIN).into()); + }); + http::ServerBuilder::new(io) .request_middleware(middleware) - .allowed_hosts(allowed_hosts) + .allowed_hosts(allowed_hosts.into()) .cors(http::DomainsValidation::Disabled) .start_http(addr) .map(|server| Server { diff --git a/dapps/src/tests/redirection.rs b/dapps/src/tests/redirection.rs index 4e3fff4dc97..1e9b039e2ea 100644 --- a/dapps/src/tests/redirection.rs +++ b/dapps/src/tests/redirection.rs @@ -37,15 +37,15 @@ fn should_redirect_to_home() { } #[test] -fn should_redirect_to_home_when_trailing_slash_is_missing() { +fn should_redirect_to_home_with_domain() { // given let server = serve(); // when let response = request(server, "\ - GET /app HTTP/1.1\r\n\ - Host: 127.0.0.1:8080\r\n\ + GET / HTTP/1.1\r\n\ + Host: home.web3.site\r\n\ Connection: close\r\n\ \r\n\ " @@ -57,14 +57,14 @@ fn should_redirect_to_home_when_trailing_slash_is_missing() { } #[test] -fn should_redirect_to_home_for_users_with_cached_redirection() { +fn should_redirect_to_home_when_trailing_slash_is_missing() { // given let server = serve(); // when let response = request(server, "\ - GET /home/ HTTP/1.1\r\n\ + GET /app HTTP/1.1\r\n\ Host: 127.0.0.1:8080\r\n\ Connection: close\r\n\ \r\n\ @@ -179,7 +179,7 @@ fn should_serve_proxy_pac() { // then response.assert_status("HTTP/1.1 200 OK"); - assert_eq!(response.body, "DD\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"parity.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned()); + assert_eq!(response.body, "DB\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned()); assert_security_headers(&response.headers); } diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 22b3f97514d..4fdaf5b1beb 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -90,15 +90,14 @@ export default class Parity { .execute('parity_consensusCapability'); } - dappsPort () { + dappsList () { return this._transport - .execute('parity_dappsPort') - .then(outNumber); + .execute('parity_dappsList'); } - dappsInterface () { + dappsUrl () { return this._transport - .execute('parity_dappsInterface'); + .execute('parity_dappsUrl'); } decryptMessage (address, data) { @@ -530,12 +529,6 @@ export default class Parity { .execute('parity_setVaultMeta', vaultName, JSON.stringify(meta)); } - signerPort () { - return this._transport - .execute('parity_signerPort') - .then(outNumber); - } - signMessage (address, password, messageHash) { return this._transport .execute('parity_signMessage', inAddress(address), password, inHex(messageHash)); @@ -567,4 +560,9 @@ export default class Parity { return this._transport .execute('parity_versionInfo'); } + + wsUrl () { + return this._transport + .execute('parity_wsUrl'); + } } diff --git a/js/src/dapps/console/parity.js b/js/src/dapps/console/parity.js index 9fce483e19b..d05cc2350d0 100644 --- a/js/src/dapps/console/parity.js +++ b/js/src/dapps/console/parity.js @@ -19,312 +19,308 @@ import Web3 from 'web3'; const api = window.parent.secureApi; let web3; -Promise - .all([ - api.parity.dappsInterface(), - api.parity.dappsPort() - ]).then((res) => { - web3 = new Web3(new Web3.providers.HttpProvider(`http://${res.join(':')}/rpc/`)); - window.web3 = web3; - - // Usage example: - // web3.eth.traceCall({ - // to: theChicken.address, - // data: theChicken.withdraw.getData(100000000000000000), - // gas: 100000 - // }, - // `["trace", "vmTrace", "stateDiff"] - // ) - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceCall', - call: 'trace_call', - params: 2, - inputFormatter: [web3._extend.formatters.inputCallFormatter, null] - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceSendRawTransaction', - call: 'trace_rawTransaction', - params: 2, - inputFormatter: [null, null] - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceReplayTransaction', - call: 'trace_replayTransaction', - params: 2, - inputFormatter: [null, null] - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'setMode', - call: 'parity_setMode', - params: 1 - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'mode', - call: 'parity_mode', - params: 0 - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceTransaction', - call: 'trace_Transaction', - params: 1, - inputFormatter: [null] - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'gasPriceStatistics', - call: 'parity_gasPriceStatistics', - params: 0, - outputFormatter: function (a) { return a.map(web3.toBigNumber); } - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'registryAddress', - call: 'parity_registryAddress', - params: 0 - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'accountsInfo', - call: 'personal_accountsInfo', - outputFormatter: function (m) { - Object.keys(m).forEach(k => { - m[k].meta = JSON.parse(m[k].meta); - m[k].meta.name = m[k].name; - m[k].meta.uuid = m[k].uuid; - m[k] = m[k].meta; - }); return m; - }, - params: 0 - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'setAccountName', - call: 'personal_setAccountName', - params: 2 - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'setAccountMeta', - call: 'personal_setAccountMeta', - params: 2, - inputFormatter: [a => a, JSON.stringify] - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'postTransaction', - call: 'eth_postTransaction', - params: 1, - inputFormatter: [web3._extend.formatters.inputCallFormatter] - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'postSign', - call: 'eth_postSign', - params: 1 - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'encryptMessage', - call: 'parity_encryptMessage', - params: 2 - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'checkRequest', - call: 'eth_checkRequest', - params: 1 - }) - ] - }); - - web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'listAccounts', - call: 'parity_listAccounts', - params: 0 - }) - ] - }); - - { - let postTransaction = web3.eth.postTransaction.bind(web3.eth); - let sendTransaction = web3.eth.sendTransaction.bind(web3.eth); - - web3.eth.sendTransaction = function (options, f) { - // No callback - do sync API. - if (typeof f !== 'function') { - return sendTransaction(options); - } - // Callback - use async API. - let id = postTransaction(options); - - console.log('Posted trasaction id=' + id); - let timerId = window.setInterval(check, 500); - - function check () { - try { - let r = web3.eth.checkRequest(id); - - if (typeof r === 'string') { - clearInterval(timerId); - if (r === '0x0000000000000000000000000000000000000000000000000000000000000000') { - f('Rejected', r); - } else { - f(null, r); - } - } else if (r !== null) { - console.log('checkRequest returned: ' + r); - } - } catch (e) { - clearInterval(timerId); - f('Rejected', null); - } - } - }; - } +api.parity.dappsUrl().then(url => { + web3 = new Web3(new Web3.providers.HttpProvider(`${window.location.protocol}//${url}/rpc/`)); + window.web3 = web3; + + // Usage example: + // web3.eth.traceCall({ + // to: theChicken.address, + // data: theChicken.withdraw.getData(100000000000000000), + // gas: 100000 + // }, + // `["trace", "vmTrace", "stateDiff"] + // ) + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceCall', + call: 'trace_call', + params: 2, + inputFormatter: [web3._extend.formatters.inputCallFormatter, null] + }) + ] + }); - web3.eth.installInterceptor = function (interceptor) { - let oldSendTransaction = web3.eth.sendTransaction.bind(web3.eth); + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceSendRawTransaction', + call: 'trace_rawTransaction', + params: 2, + inputFormatter: [null, null] + }) + ] + }); - web3.eth.sendTransaction = function (options, f) { - if (!interceptor(options)) { - return '0x0000000000000000000000000000000000000000000000000000000000000000'; - } + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceReplayTransaction', + call: 'trace_replayTransaction', + params: 2, + inputFormatter: [null, null] + }) + ] + }); - return oldSendTransaction(options, f); - }; - }; + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'setMode', + call: 'parity_setMode', + params: 1 + }) + ] + }); - web3.eth.reporter = function (e, r) { - if (e) { - console.log('Error confirming transaction: ' + e); - } else { - let addr = r; - let confirmed = false; - let timerId = window.setInterval(function check () { - let receipt = web3.eth.getTransactionReceipt(addr); - - if (receipt != null) { - if (!confirmed) { - console.log('Transaction confirmed (' + r + '); used ' + receipt.gasUsed + ' gas; left ' + receipt.logs.length + ' logs; mining...'); - confirmed = true; - } - if (typeof receipt.blockHash === 'string') { - clearInterval(timerId); - console.log('Mined into block ' + receipt.blockNumber); + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'mode', + call: 'parity_mode', + params: 0 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceTransaction', + call: 'trace_Transaction', + params: 1, + inputFormatter: [null] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'gasPriceStatistics', + call: 'parity_gasPriceStatistics', + params: 0, + outputFormatter: function (a) { return a.map(web3.toBigNumber); } + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'registryAddress', + call: 'parity_registryAddress', + params: 0 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'accountsInfo', + call: 'personal_accountsInfo', + outputFormatter: function (m) { + Object.keys(m).forEach(k => { + m[k].meta = JSON.parse(m[k].meta); + m[k].meta.name = m[k].name; + m[k].meta.uuid = m[k].uuid; + m[k] = m[k].meta; + }); return m; + }, + params: 0 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'setAccountName', + call: 'personal_setAccountName', + params: 2 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'setAccountMeta', + call: 'personal_setAccountMeta', + params: 2, + inputFormatter: [a => a, JSON.stringify] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'postTransaction', + call: 'eth_postTransaction', + params: 1, + inputFormatter: [web3._extend.formatters.inputCallFormatter] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'postSign', + call: 'eth_postSign', + params: 1 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'encryptMessage', + call: 'parity_encryptMessage', + params: 2 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'checkRequest', + call: 'eth_checkRequest', + params: 1 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'listAccounts', + call: 'parity_listAccounts', + params: 0 + }) + ] + }); + + { + let postTransaction = web3.eth.postTransaction.bind(web3.eth); + let sendTransaction = web3.eth.sendTransaction.bind(web3.eth); + + web3.eth.sendTransaction = function (options, f) { + // No callback - do sync API. + if (typeof f !== 'function') { + return sendTransaction(options); + } + // Callback - use async API. + let id = postTransaction(options); + + console.log('Posted trasaction id=' + id); + let timerId = window.setInterval(check, 500); + + function check () { + try { + let r = web3.eth.checkRequest(id); + + if (typeof r === 'string') { + clearInterval(timerId); + if (r === '0x0000000000000000000000000000000000000000000000000000000000000000') { + f('Rejected', r); + } else { + f(null, r); } + } else if (r !== null) { + console.log('checkRequest returned: ' + r); } - }, 500); + } catch (e) { + clearInterval(timerId); + f('Rejected', null); + } } }; + } - { - let oldSha3 = web3.sha3; + web3.eth.installInterceptor = function (interceptor) { + let oldSendTransaction = web3.eth.sendTransaction.bind(web3.eth); + + web3.eth.sendTransaction = function (options, f) { + if (!interceptor(options)) { + return '0x0000000000000000000000000000000000000000000000000000000000000000'; + } - web3.sha3 = function (data, format) { - if (typeof format !== 'string' || (format !== 'hex' && format !== 'bin')) { - format = data.startsWith('0x') ? 'hex' : 'bin'; + return oldSendTransaction(options, f); + }; + }; + + web3.eth.reporter = function (e, r) { + if (e) { + console.log('Error confirming transaction: ' + e); + } else { + let addr = r; + let confirmed = false; + let timerId = window.setInterval(function check () { + let receipt = web3.eth.getTransactionReceipt(addr); + + if (receipt != null) { + if (!confirmed) { + console.log('Transaction confirmed (' + r + '); used ' + receipt.gasUsed + ' gas; left ' + receipt.logs.length + ' logs; mining...'); + confirmed = true; + } + if (typeof receipt.blockHash === 'string') { + clearInterval(timerId); + console.log('Mined into block ' + receipt.blockNumber); + } } - return oldSha3(data, { encoding: format }); - }; + }, 500); } + }; - { - let Registry = web3.eth.contract([{ 'constant': false, 'inputs': [{ 'name': '_new', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'string' }], 'name': 'confirmReverse', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'reserve', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'bytes32' }], 'name': 'set', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'drop', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'getAddress', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_amount', 'type': 'uint256' }], 'name': 'setFee', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_to', 'type': 'address' }], 'name': 'transfer', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'reserved', 'outputs': [{ 'name': 'reserved', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'drain', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'string' }, { 'name': '_who', 'type': 'address' }], 'name': 'proposeReverse', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'getUint', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'get', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'fee', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '', 'type': 'address' }], 'name': 'reverse', 'outputs': [{ 'name': '', 'type': 'string' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'uint256' }], 'name': 'setUint', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'removeReverse', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'address' }], 'name': 'setAddress', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'name': 'amount', 'type': 'uint256' }], 'name': 'Drained', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'name': 'amount', 'type': 'uint256' }], 'name': 'FeeChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }], 'name': 'Reserved', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'oldOwner', 'type': 'address' }, { 'indexed': true, 'name': 'newOwner', 'type': 'address' }], 'name': 'Transferred', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }], 'name': 'Dropped', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }, { 'indexed': true, 'name': 'key', 'type': 'string' }, { 'indexed': false, 'name': 'plainKey', 'type': 'string' }], 'name': 'DataChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseProposed', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseConfirmed', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseRemoved', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'old', 'type': 'address' }, { 'indexed': true, 'name': 'current', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }]); + { + let oldSha3 = web3.sha3; - web3.eth.registry = Registry.at(web3.eth.registryAddress()); - web3.eth.registry.lookup = (name, field) => web3.eth.registry.get(web3.sha3(name), field); - web3.eth.registry.lookupAddress = (name, field) => web3.eth.registry.getAddress(web3.sha3(name), field); - web3.eth.registry.lookupUint = (name, field) => web3.eth.registry.getUint(web3.sha3(name), field); + web3.sha3 = function (data, format) { + if (typeof format !== 'string' || (format !== 'hex' && format !== 'bin')) { + format = data.startsWith('0x') ? 'hex' : 'bin'; + } + return oldSha3(data, { encoding: format }); + }; + } - let TokenReg = web3.eth.contract([{ 'constant': true, 'inputs': [{ 'name': '_id', 'type': 'uint256' }], 'name': 'token', 'outputs': [{ 'name': 'addr', 'type': 'address' }, { 'name': 'tla', 'type': 'string' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_new', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_addr', 'type': 'address' }, { 'name': '_tla', 'type': 'string' }, { 'name': '_base', 'type': 'uint256' }, { 'name': '_name', 'type': 'string' }], 'name': 'register', 'outputs': [{ 'name': '', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_fee', 'type': 'uint256' }], 'name': 'setFee', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_id', 'type': 'uint256' }, { 'name': '_key', 'type': 'bytes32' }], 'name': 'meta', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_addr', 'type': 'address' }, { 'name': '_tla', 'type': 'string' }, { 'name': '_base', 'type': 'uint256' }, { 'name': '_name', 'type': 'string' }, { 'name': '_owner', 'type': 'address' }], 'name': 'registerAs', 'outputs': [{ 'name': '', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_tla', 'type': 'string' }], 'name': 'fromTLA', 'outputs': [{ 'name': 'id', 'type': 'uint256' }, { 'name': 'addr', 'type': 'address' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'drain', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'tokenCount', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_id', 'type': 'uint256' }], 'name': 'unregister', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_addr', 'type': 'address' }], 'name': 'fromAddress', 'outputs': [{ 'name': 'id', 'type': 'uint256' }, { 'name': 'tla', 'type': 'string' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_id', 'type': 'uint256' }, { 'name': '_key', 'type': 'bytes32' }, { 'name': '_value', 'type': 'bytes32' }], 'name': 'setMeta', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'fee', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'tla', 'type': 'string' }, { 'indexed': true, 'name': 'id', 'type': 'uint256' }, { 'indexed': false, 'name': 'addr', 'type': 'address' }, { 'indexed': false, 'name': 'name', 'type': 'string' }], 'name': 'Registered', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'tla', 'type': 'string' }, { 'indexed': true, 'name': 'id', 'type': 'uint256' }], 'name': 'Unregistered', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'id', 'type': 'uint256' }, { 'indexed': true, 'name': 'key', 'type': 'bytes32' }, { 'indexed': false, 'name': 'value', 'type': 'bytes32' }], 'name': 'MetaChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'old', 'type': 'address' }, { 'indexed': true, 'name': 'current', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }]); + { + let Registry = web3.eth.contract([{ 'constant': false, 'inputs': [{ 'name': '_new', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'string' }], 'name': 'confirmReverse', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'reserve', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'bytes32' }], 'name': 'set', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'drop', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'getAddress', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_amount', 'type': 'uint256' }], 'name': 'setFee', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_to', 'type': 'address' }], 'name': 'transfer', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'reserved', 'outputs': [{ 'name': 'reserved', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'drain', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'string' }, { 'name': '_who', 'type': 'address' }], 'name': 'proposeReverse', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'getUint', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'get', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'fee', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '', 'type': 'address' }], 'name': 'reverse', 'outputs': [{ 'name': '', 'type': 'string' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'uint256' }], 'name': 'setUint', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'removeReverse', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'address' }], 'name': 'setAddress', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'name': 'amount', 'type': 'uint256' }], 'name': 'Drained', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'name': 'amount', 'type': 'uint256' }], 'name': 'FeeChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }], 'name': 'Reserved', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'oldOwner', 'type': 'address' }, { 'indexed': true, 'name': 'newOwner', 'type': 'address' }], 'name': 'Transferred', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }], 'name': 'Dropped', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }, { 'indexed': true, 'name': 'key', 'type': 'string' }, { 'indexed': false, 'name': 'plainKey', 'type': 'string' }], 'name': 'DataChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseProposed', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseConfirmed', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseRemoved', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'old', 'type': 'address' }, { 'indexed': true, 'name': 'current', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }]); - web3.eth.tokenReg = TokenReg.at(web3.eth.registry.lookupAddress('tokenreg', 'A')); - } - }) - .catch((error) => { - console.error(error); - }); + web3.eth.registry = Registry.at(web3.eth.registryAddress()); + web3.eth.registry.lookup = (name, field) => web3.eth.registry.get(web3.sha3(name), field); + web3.eth.registry.lookupAddress = (name, field) => web3.eth.registry.getAddress(web3.sha3(name), field); + web3.eth.registry.lookupUint = (name, field) => web3.eth.registry.getUint(web3.sha3(name), field); + + let TokenReg = web3.eth.contract([{ 'constant': true, 'inputs': [{ 'name': '_id', 'type': 'uint256' }], 'name': 'token', 'outputs': [{ 'name': 'addr', 'type': 'address' }, { 'name': 'tla', 'type': 'string' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_new', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_addr', 'type': 'address' }, { 'name': '_tla', 'type': 'string' }, { 'name': '_base', 'type': 'uint256' }, { 'name': '_name', 'type': 'string' }], 'name': 'register', 'outputs': [{ 'name': '', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_fee', 'type': 'uint256' }], 'name': 'setFee', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_id', 'type': 'uint256' }, { 'name': '_key', 'type': 'bytes32' }], 'name': 'meta', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_addr', 'type': 'address' }, { 'name': '_tla', 'type': 'string' }, { 'name': '_base', 'type': 'uint256' }, { 'name': '_name', 'type': 'string' }, { 'name': '_owner', 'type': 'address' }], 'name': 'registerAs', 'outputs': [{ 'name': '', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_tla', 'type': 'string' }], 'name': 'fromTLA', 'outputs': [{ 'name': 'id', 'type': 'uint256' }, { 'name': 'addr', 'type': 'address' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'drain', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'tokenCount', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_id', 'type': 'uint256' }], 'name': 'unregister', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_addr', 'type': 'address' }], 'name': 'fromAddress', 'outputs': [{ 'name': 'id', 'type': 'uint256' }, { 'name': 'tla', 'type': 'string' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_id', 'type': 'uint256' }, { 'name': '_key', 'type': 'bytes32' }, { 'name': '_value', 'type': 'bytes32' }], 'name': 'setMeta', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'fee', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'tla', 'type': 'string' }, { 'indexed': true, 'name': 'id', 'type': 'uint256' }, { 'indexed': false, 'name': 'addr', 'type': 'address' }, { 'indexed': false, 'name': 'name', 'type': 'string' }], 'name': 'Registered', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'tla', 'type': 'string' }, { 'indexed': true, 'name': 'id', 'type': 'uint256' }], 'name': 'Unregistered', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'id', 'type': 'uint256' }, { 'indexed': true, 'name': 'key', 'type': 'bytes32' }, { 'indexed': false, 'name': 'value', 'type': 'bytes32' }], 'name': 'MetaChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'old', 'type': 'address' }, { 'indexed': true, 'name': 'current', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }]); + + web3.eth.tokenReg = TokenReg.at(web3.eth.registry.lookupAddress('tokenreg', 'A')); + } +}) +.catch((error) => { + console.error(error); +}); window.api = api; window.web3 = web3; diff --git a/js/src/dapps/registry/ui/image.js b/js/src/dapps/registry/ui/image.js index 3f0a90abe76..88cae4e308c 100644 --- a/js/src/dapps/registry/ui/image.js +++ b/js/src/dapps/registry/ui/image.js @@ -16,8 +16,6 @@ import React from 'react'; -import { parityNode } from '../../../environment'; - const styles = { padding: '.5em', border: '1px solid #777' @@ -34,7 +32,7 @@ export default (address) => { return ( { diff --git a/js/src/dapps/tokenreg/Tokens/Token/token.js b/js/src/dapps/tokenreg/Tokens/Token/token.js index 5ea50535c16..9d60d45802c 100644 --- a/js/src/dapps/tokenreg/Tokens/Token/token.js +++ b/js/src/dapps/tokenreg/Tokens/Token/token.js @@ -30,7 +30,6 @@ import styles from './token.css'; import { metaDataKeys } from '../../constants'; import { api } from '../../parity'; -import { parityNode } from '../../../../environment'; export default class Token extends Component { static propTypes = { @@ -312,7 +311,7 @@ export default class Token extends Component { meta-data:

- +
); diff --git a/js/src/embed.js b/js/src/embed.js index 218bb2f4f2a..56532257ccc 100644 --- a/js/src/embed.js +++ b/js/src/embed.js @@ -55,6 +55,9 @@ class FakeTransport { return Promise.reject('not connected'); } + addMiddleware () { + } + on () { } } diff --git a/js/src/environment/index.js b/js/src/environment/index.js index 1123ddd9b83..8eaa6519d35 100644 --- a/js/src/environment/index.js +++ b/js/src/environment/index.js @@ -19,14 +19,4 @@ import './tests'; -const parityNode = ( - process.env.PARITY_URL && `http://${process.env.PARITY_URL}` - ) || ( - process.env.NODE_ENV === 'production' - ? 'http://127.0.0.1:8545' - : '' - ); - -export { - parityNode -}; +export {}; diff --git a/js/src/index.js b/js/src/index.js index 1436f30c06b..9a5c34d3ee0 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -53,8 +53,7 @@ if (process.env.NODE_ENV === 'development') { } const AUTH_HASH = '#/auth?'; -const parityUrl = process.env.PARITY_URL || window.location.host; -const urlScheme = window.location.href.match(/^https/) ? 'wss://' : 'ws://'; +const parityUrl = process.env.PARITY_URL || '127.0.0.1:8546'; let token = null; @@ -62,7 +61,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) { token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token; } -const api = new SecureApi(`${urlScheme}${parityUrl}`, token); +const api = new SecureApi(parityUrl, token); patchApi(api); loadSender(api); diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 6ab5ea212b5..326ca98314a 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -143,25 +143,34 @@ export default { } }, - dappsPort: { - section: SECTION_NODE, - desc: 'Returns the port the dapps are running on, error if not enabled.', + dappsList: { + subdoc: SUBDOC_SET, + desc: 'Returns a list of available local dapps.', params: [], returns: { - type: Quantity, - desc: 'The port number', - example: 8080 + type: Array, + desc: 'The list of dapps', + example: [ + { + author: 'Parity Technologies Ltd', + description: 'A skeleton dapp', + iconUrl: 'title.png', + id: 'skeleton', + name: 'Skeleton', + version: '0.1' + } + ] } }, - dappsInterface: { + dappsUrl: { section: SECTION_NODE, - desc: 'Returns the interface the dapps are running on, error if not enabled.', + desc: 'Returns the hostname and the port of dapps/rpc server, error if not enabled.', params: [], returns: { type: String, - desc: 'The interface', - example: '127.0.0.1' + desc: 'The hostname and port number', + example: 'localhost:8545' } }, @@ -788,17 +797,6 @@ export default { } }, - signerPort: { - section: SECTION_NODE, - desc: 'Returns the port the signer is running on, error if not enabled', - params: [], - returns: { - type: Quantity, - desc: 'The port number', - example: 8180 - } - }, - transactionsLimit: { section: SECTION_MINING, desc: 'Changes limit for transactions in queue.', @@ -1916,6 +1914,17 @@ export default { } }, + wsUrl: { + section: SECTION_NODE, + desc: 'Returns the hostname and the port of WebSockets/Signer server, error if not enabled.', + params: [], + returns: { + type: String, + desc: 'The hostname and port number', + example: 'localhost:8546' + } + }, + composeTransaction: { desc: 'Given partial transaction request produces transaction with all fields filled in. Such transaction can be then signed externally.', params: [ @@ -1997,4 +2006,5 @@ export default { example: 'QmSbFjqjd6nFwNHqsBCC7SK8GShGcayLUEtysJjNGhZAnC' } } + }; diff --git a/js/src/secureApi.js b/js/src/secureApi.js index e19d7ae998c..2fd33fb9b7b 100644 --- a/js/src/secureApi.js +++ b/js/src/secureApi.js @@ -27,21 +27,28 @@ export default class SecureApi extends Api { _needsToken = false; _tokens = []; - _dappsInterface = null; - _dappsPort = 8545; - _signerPort = 8180; + _dappsUrl = null; + _wsUrl = null; - static getTransport (url, sysuiToken) { - return new Api.Transport.Ws(url, sysuiToken, false); + static getTransport (url, sysuiToken, protocol) { + const proto = protocol() === 'https:' ? 'wss:' : 'ws:'; + + return new Api.Transport.Ws(`${proto}//${url}`, sysuiToken, false); + } + + // Returns a protocol with `:` at the end. + static protocol () { + return window.location.protocol; } - constructor (url, nextToken, getTransport = SecureApi.getTransport) { + constructor (url, nextToken, getTransport = SecureApi.getTransport, protocol = SecureApi.protocol) { const sysuiToken = store.get('sysuiToken'); - const transport = getTransport(url, sysuiToken); + const transport = getTransport(url, sysuiToken, protocol); super(transport); - this._url = url; + this._wsUrl = url; + this.protocol = protocol; // Try tokens from localStorage, from hash and 'initial' this._tokens = uniq([sysuiToken, nextToken, 'initial']) .filter((token) => token) @@ -53,12 +60,30 @@ export default class SecureApi extends Api { this.connect(); } + get _dappsAddress () { + if (!this._dappsUrl) { + return { + host: null, + port: 8545 + }; + } + + const [host, port] = this._dappsUrl.split(':'); + + return { + host, + port: parseInt(port, 10) + }; + } + get dappsPort () { - return this._dappsPort; + return this._dappsAddress.port; } get dappsUrl () { - return `http://${this.hostname}:${this.dappsPort}`; + const { port } = this._dappsAddress; + + return `${this.protocol()}//${this.hostname}:${port}`; } get hostname () { @@ -66,15 +91,13 @@ export default class SecureApi extends Api { return 'dapps.parity'; } - if (!this._dappsInterface || this._dappsInterface === '0.0.0.0') { + const { host } = this._dappsAddress; + + if (!host || host === '0.0.0.0') { return window.location.hostname; } - return this._dappsInterface; - } - - get signerPort () { - return this._signerPort; + return host; } get isConnecting () { @@ -98,18 +121,18 @@ export default class SecureApi extends Api { * (`signerPort`, `dappsInterface`, `dappsPort`, ...) */ configure (configuration) { - const { dappsInterface, dappsPort, signerPort } = configuration; + const { dappsInterface, dappsPort, signerPort, wsPort } = configuration; if (dappsInterface) { - this._dappsInterface = dappsInterface; + this._dappsUrl = `${dappsInterface}:${this._dappsAddress.port}`; } if (dappsPort) { - this._dappsPort = dappsPort; + this._dappsUrl = `${this.hostname}:${dappsPort}`; } - if (signerPort) { - this._signerPort = signerPort; + if (signerPort || wsPort) { + this._wsUrl = `${this.hostname}:${signerPort || wsPort}`; } } @@ -166,9 +189,7 @@ export default class SecureApi extends Api { * otherwise (HEAD request to the Node) */ isNodeUp () { - const url = this._url.replace(/wss?/, 'http'); - - return fetch(url, { method: 'HEAD' }) + return fetch(`${this.protocol()}//${this._wsUrl}`, { method: 'HEAD', mode: 'no-cors' }) .then( (r) => r.status === 200, () => false @@ -297,14 +318,12 @@ export default class SecureApi extends Api { _fetchSettings () { return Promise .all([ - this.parity.dappsPort(), - this.parity.dappsInterface(), - this.parity.signerPort() + this.parity.dappsUrl(), + this.parity.wsUrl() ]) - .then(([dappsPort, dappsInterface, signerPort]) => { - this._dappsPort = dappsPort.toNumber(); - this._dappsInterface = dappsInterface; - this._signerPort = signerPort.toNumber(); + .then(([dappsUrl, wsUrl]) => { + this._dappsUrl = dappsUrl; + this._wsUrl = dappsUrl; }); } diff --git a/js/src/util/dapps.js b/js/src/util/dapps.js index 2ca416e1a28..58b33e49e7d 100644 --- a/js/src/util/dapps.js +++ b/js/src/util/dapps.js @@ -25,21 +25,6 @@ import builtinJson from '~/views/Dapps/builtin.json'; const builtinApps = builtinJson.filter((app) => app.id); -function getHost (api) { - const host = process.env.DAPPS_URL || - ( - process.env.NODE_ENV === 'production' - ? api.dappsUrl - : '' - ); - - if (host === '/') { - return ''; - } - - return host; -} - export function subscribeToChanges (api, dappReg, callback) { return dappReg .getContract() @@ -105,12 +90,7 @@ export function fetchBuiltinApps () { } export function fetchLocalApps (api) { - return fetch(`${getHost(api)}/api/apps`) - .then((response) => { - return response.ok - ? response.json() - : []; - }) + return api.parity.dappsList() .then((apps) => { return apps .map((app) => { @@ -195,7 +175,7 @@ export function fetchManifest (api, manifestHash) { } return fetch( - `${getHost(api)}/api/content/${manifestHash}/`, + `/api/content/${manifestHash}/`, { redirect: 'follow', mode: 'cors' } ) .then((response) => { diff --git a/js/src/views/Dapps/dappStore.spec.js b/js/src/views/Dapps/dappStore.spec.js index b08e1f1dcd3..3b4fb0ded62 100644 --- a/js/src/views/Dapps/dappStore.spec.js +++ b/js/src/views/Dapps/dappStore.spec.js @@ -26,17 +26,11 @@ const APPID_DAPPREG = '0x7bbc4f1a27628781b96213e781a1b8eec6982c1db8fac739af6e4c5 const APPID_GHH = '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75'; const APPID_LOCALTX = '0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d'; const APPID_TOKENDEPLOY = '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f'; -const FETCH_OK = { - ok: true, - status: 200 -}; let globalContractsGet; -let globalFetch; function stubGlobals () { globalContractsGet = Contracts.get; - globalFetch = global.fetch; Contracts.get = () => { return { @@ -50,31 +44,21 @@ function stubGlobals () { } }; }; - - global.fetch = (url) => { - switch (url) { - case '/api/apps': - return Promise.resolve(Object.assign({}, FETCH_OK, { - json: sinon.stub().resolves([]) // TODO: Local stubs in here - })); - - default: - console.log('Unknown fetch stub endpoint', url); - return Promise.reject(); - } - }; } function restoreGlobals () { Contracts.get = globalContractsGet; - global.fetch = globalFetch; } let api; let store; function create () { - api = {}; + api = { + parity: { + dappsList: () => Promise.resolve([]) + } + }; store = new Store(api); return store; diff --git a/js/src/views/Web/store.spec.js b/js/src/views/Web/store.spec.js index 58b2f1b3c68..9f7d2e77749 100644 --- a/js/src/views/Web/store.spec.js +++ b/js/src/views/Web/store.spec.js @@ -34,8 +34,8 @@ let store; function createApi () { api = { - dappsPort: 8080, - dappsUrl: 'http://home.web3.site:8080', + dappsPort: 8545, + dappsUrl: 'http://home.web3.site:8545', parity: { listRecentDapps: sinon.stub().resolves(TEST_HISTORY) }, @@ -159,7 +159,7 @@ describe('views/Web/Store', () => { it('encodes current', () => { store.setCurrentUrl(TEST_URL1); expect(store.encodedPath).to.match( - /http:\/\/home\.web3\.site:8080\/web\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\?t=[0-9]*$/ + /http:\/\/home\.web3\.site:8545\/web\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\?t=[0-9]*$/ ); }); }); @@ -167,7 +167,7 @@ describe('views/Web/Store', () => { it('encodes current', () => { store.setCurrentUrl(TEST_URL1); expect(store.encodedUrl).to.match( - /^http:\/\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\.web\.web3\.site:8080\?t=[0-9]*$/ + /^http:\/\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\.web\.web3\.site:8545\?t=[0-9]*$/ ); }); }); diff --git a/js/webpack/build.server.js b/js/webpack/build.server.js index efc8a2cdaa3..486209ccbd0 100644 --- a/js/webpack/build.server.js +++ b/js/webpack/build.server.js @@ -15,26 +15,21 @@ // along with Parity. If not, see . // test only /** - * Run `DAPPS_URL="/" PARITY_URL="127.0.0.1:8180" NODE_ENV="production" npm run build` + * Run `DAPPS_URL="/" PARITY_URL="127.0.0.1:8546" NODE_ENV="production" npm run build` * to build the project ; use this server to test that the minifed * version is working (this is a simple proxy server) */ var express = require('express'); -var proxy = require('http-proxy-middleware'); var Shared = require('./shared'); var app = express(); -var wsProxy = proxy('ws://127.0.0.1:8180', { changeOrigin: true }); Shared.addProxies(app); app.use(express.static('.build')); -app.use(wsProxy); var server = app.listen(process.env.PORT || 3000, function () { console.log('Listening on port', server.address().port); }); - -server.on('upgrade', wsProxy.upgrade); diff --git a/js/webpack/dev.server.js b/js/webpack/dev.server.js index 75ea7703afe..03894501464 100644 --- a/js/webpack/dev.server.js +++ b/js/webpack/dev.server.js @@ -22,7 +22,6 @@ const webpackHotMiddleware = require('webpack-hot-middleware'); const http = require('http'); const express = require('express'); const ProgressBar = require('progress'); -const proxy = require('http-proxy-middleware'); const webpackConfig = require('./app'); const Shared = require('./shared'); @@ -84,18 +83,13 @@ app.use(webpackDevMiddleware(compiler, { } })); -var wsProxy = proxy('ws://127.0.0.1:8180', { changeOrigin: true }); - // Add the dev proxies in the express App Shared.addProxies(app); app.use(express.static(webpackConfig.output.path)); -app.use(wsProxy); const server = http.createServer(app); server.listen(process.env.PORT || 3000, function () { console.log('Listening on port', server.address().port); progressBar = new ProgressBar('[:bar] :percent :etas', { total: 50 }); }); - -server.on('upgrade', wsProxy.upgrade); diff --git a/js/webpack/shared.js b/js/webpack/shared.js index 3e2eef8f12d..ded064642e1 100644 --- a/js/webpack/shared.js +++ b/js/webpack/shared.js @@ -162,16 +162,8 @@ function getDappsEntry () { function addProxies (app) { const proxy = require('http-proxy-middleware'); - app.use(proxy((pathname, req) => { - return pathname === '/' && req.method === 'HEAD'; - }, { - target: 'http://127.0.0.1:8180', - changeOrigin: true, - autoRewrite: true - })); - app.use('/api', proxy({ - target: 'http://127.0.0.1:8545', + target: 'http://127.0.0.1:8180', changeOrigin: true, autoRewrite: true })); diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index b5afb8d0a49..cd8f14add63 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -124,6 +124,8 @@ usage! { or |c: &Config| otry!(c.ui).port.clone(), flag_ui_interface: String = "local", or |c: &Config| otry!(c.ui).interface.clone(), + flag_ui_hosts: String = "none", + or |c: &Config| otry!(c.ui).hosts.as_ref().map(|vec| vec.join(",")), flag_ui_path: String = "$BASE/signer", or |c: &Config| otry!(c.ui).path.clone(), // NOTE [todr] For security reasons don't put this to config files @@ -188,7 +190,7 @@ usage! { or |c: &Config| otry!(c.websockets).interface.clone(), flag_ws_apis: String = "web3,eth,pubsub,net,parity,parity_pubsub,traces,rpc,secretstore", or |c: &Config| otry!(c.websockets).apis.as_ref().map(|vec| vec.join(",")), - flag_ws_origins: String = "none", + flag_ws_origins: String = "chrome-extension://*", or |c: &Config| otry!(c.websockets).origins.as_ref().map(|vec| vec.join(",")), flag_ws_hosts: String = "none", or |c: &Config| otry!(c.websockets).hosts.as_ref().map(|vec| vec.join(",")), @@ -430,6 +432,7 @@ struct Ui { disable: Option, port: Option, interface: Option, + hosts: Option>, path: Option, } @@ -709,6 +712,7 @@ mod tests { flag_no_ui: false, flag_ui_port: 8180u16, flag_ui_interface: "127.0.0.1".into(), + flag_ui_hosts: "none".into(), flag_ui_path: "$HOME/.parity/signer".into(), flag_ui_no_validation: false, @@ -929,6 +933,7 @@ mod tests { disable: Some(true), port: None, interface: None, + hosts: None, path: None, }), network: Some(Network { diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 34352450aab..99f6c7304c5 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -110,6 +110,11 @@ UI Options: --ui-interface IP Specify the hostname portion of the Trusted UI server, IP should be an interface's IP address, or local (default: {flag_ui_interface}). + --ui-hosts HOSTS List of allowed Host header values. This option will + validate the Host header sent by the browser, it + is additional security against some attack + vectors. Special options: "all", "none", + (default: {flag_ui_hosts}). --ui-path PATH Specify directory where Trusted UIs tokens should be stored. (default: {flag_ui_path}) --ui-no-validation Disable Origin and Host headers validation for diff --git a/parity/configuration.rs b/parity/configuration.rs index 7985c9cd3c3..ad1353d097f 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -30,7 +30,7 @@ use ethcore::client::{VMType}; use ethcore::miner::{MinerOptions, Banning, StratumOptions}; use ethcore::verification::queue::VerifierSettings; -use rpc::{IpcConfiguration, HttpConfiguration, WsConfiguration}; +use rpc::{IpcConfiguration, HttpConfiguration, WsConfiguration, UiConfiguration}; use rpc_apis::ApiSet; use parity_rpc::NetworkSettings; use cache::CacheConfig; @@ -41,7 +41,6 @@ use ethcore_logger::Config as LogConfig; use dir::{self, Directories, default_hypervisor_path, default_local_path, default_data_path}; use dapps::Configuration as DappsConfiguration; use ipfs::Configuration as IpfsConfiguration; -use signer::{Configuration as SignerConfiguration}; use secretstore::Configuration as SecretStoreConfiguration; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use run::RunCmd; @@ -50,8 +49,6 @@ use presale::ImportWallet; use account::{AccountCmd, NewAccount, ListAccounts, ImportAccounts, ImportFromGethAccounts}; use snapshot::{self, SnapshotCommand}; -const AUTHCODE_FILENAME: &'static str = "authcodes"; - #[derive(Debug, PartialEq)] pub enum Cmd { Run(RunCmd), @@ -59,7 +56,7 @@ pub enum Cmd { Account(AccountCmd), ImportPresaleWallet(ImportWallet), Blockchain(BlockchainCmd), - SignerToken(SignerConfiguration), + SignerToken(WsConfiguration, UiConfiguration), SignerSign { id: Option, pwfile: Option, @@ -118,6 +115,7 @@ impl Configuration { let http_conf = self.http_config()?; let ipc_conf = self.ipc_config()?; let net_conf = self.net_config()?; + let ui_conf = self.ui_config(); let network_id = self.network_id(); let cache_config = self.cache_config(); let tracing = self.args.flag_tracing.parse()?; @@ -134,10 +132,8 @@ impl Configuration { let public_node = self.args.flag_public_node; let warp_sync = !self.args.flag_no_warp && fat_db != Switch::On && tracing != Switch::On && pruning != Pruning::Specific(Algorithm::Archive); let geth_compatibility = self.args.flag_geth; - let ui_address = self.ui_port().map(|port| (self.ui_interface(), port)); let mut dapps_conf = self.dapps_config(); let ipfs_conf = self.ipfs_config(); - let signer_conf = self.signer_config(); let secretstore_conf = self.secretstore_config()?; let format = self.format()?; @@ -149,11 +145,10 @@ impl Configuration { let cmd = if self.args.flag_version { Cmd::Version } else if self.args.cmd_signer { - let mut authfile = PathBuf::from(signer_conf.signer_path.clone()); - authfile.push(AUTHCODE_FILENAME); + let authfile = ::signer::codes_path(&ws_conf.signer_path); if self.args.cmd_new_token { - Cmd::SignerToken(signer_conf) + Cmd::SignerToken(ws_conf, ui_conf) } else if self.args.cmd_sign { let pwfile = self.args.flag_password.get(0).map(|pwfile| { PathBuf::from(pwfile) @@ -161,18 +156,18 @@ impl Configuration { Cmd::SignerSign { id: self.args.arg_id, pwfile: pwfile, - port: signer_conf.port, + port: ws_conf.port, authfile: authfile, } } else if self.args.cmd_reject { Cmd::SignerReject { id: self.args.arg_id, - port: signer_conf.port, + port: ws_conf.port, authfile: authfile, } } else if self.args.cmd_list { Cmd::SignerList { - port: signer_conf.port, + port: ws_conf.port, authfile: authfile, } } else { @@ -372,11 +367,10 @@ impl Configuration { warp_sync: warp_sync, public_node: public_node, geth_compatibility: geth_compatibility, - ui_address: ui_address, net_settings: self.network_settings()?, dapps_conf: dapps_conf, ipfs_conf: ipfs_conf, - signer_conf: signer_conf, + ui_conf: ui_conf, secretstore_conf: secretstore_conf, dapp: self.dapp_to_open()?, ui: self.args.cmd_ui, @@ -553,13 +547,12 @@ impl Configuration { Ok(options) } - fn signer_config(&self) -> SignerConfiguration { - SignerConfiguration { + fn ui_config(&self) -> UiConfiguration { + UiConfiguration { enabled: self.ui_enabled(), - port: self.args.flag_ports_shift + self.args.flag_ui_port, interface: self.ui_interface(), - signer_path: self.directories().signer, - skip_origin_validation: self.args.flag_unsafe_expose || self.args.flag_ui_no_validation, + port: self.args.flag_ports_shift + self.args.flag_ui_port, + hosts: self.ui_hosts(), } } @@ -768,6 +761,14 @@ impl Configuration { Some(hosts) } + fn ui_hosts(&self) -> Option> { + if self.args.flag_ui_no_validation { + return None; + } + + self.hosts(&self.args.flag_ui_hosts, &self.ui_interface()) + } + fn rpc_hosts(&self) -> Option> { self.hosts(&self.args.flag_jsonrpc_hosts, &self.rpc_interface()) } @@ -825,13 +826,17 @@ impl Configuration { } fn ws_config(&self) -> Result { + let ui = self.ui_config(); + let conf = WsConfiguration { enabled: self.ws_enabled(), interface: self.ws_interface(), port: self.args.flag_ports_shift + self.args.flag_ws_port, apis: self.args.flag_ws_apis.parse()?, hosts: self.ws_hosts(), - origins: self.ws_origins() + origins: self.ws_origins(), + signer_path: self.directories().signer.into(), + ui_address: ui.address(), }; Ok(conf) @@ -928,18 +933,6 @@ impl Configuration { } } - fn ui_port(&self) -> Option { - if !self.ui_enabled() { - None - } else { - Some(self.args.flag_ui_port) - } - } - - fn ui_interface(&self) -> String { - self.interface(&self.args.flag_ui_interface) - } - fn interface(&self, interface: &str) -> String { if self.args.flag_unsafe_expose { return "0.0.0.0".into(); @@ -952,6 +945,11 @@ impl Configuration { }.into() } + + fn ui_interface(&self) -> String { + self.interface(&self.args.flag_ui_interface) + } + fn rpc_interface(&self) -> String { let rpc_interface = self.args.flag_rpcaddr.clone().unwrap_or(self.args.flag_jsonrpc_interface.clone()); self.interface(&rpc_interface) @@ -1050,23 +1048,26 @@ impl Configuration { #[cfg(test)] mod tests { - use super::*; - use cli::Args; - use parity_rpc::NetworkSettings; + use std::io::Write; + use std::fs::{File, create_dir}; + + use devtools::{RandomTempPath}; use ethcore::client::{VMType, BlockId}; use ethcore::miner::{MinerOptions, PrioritizationStrategy}; - use helpers::{default_network_config}; - use run::RunCmd; - use dir::{Directories, default_hypervisor_path}; - use signer::{Configuration as SignerConfiguration}; + use parity_rpc::NetworkSettings; + use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; + + use account::{AccountCmd, NewAccount, ImportAccounts, ListAccounts}; use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, DataFormat, ExportState}; - use presale::ImportWallet; + use cli::Args; + use dir::{Directories, default_hypervisor_path}; + use helpers::{default_network_config}; use params::SpecType; - use account::{AccountCmd, NewAccount, ImportAccounts, ListAccounts}; - use devtools::{RandomTempPath}; - use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; - use std::io::Write; - use std::fs::{File, create_dir}; + use presale::ImportWallet; + use rpc::{WsConfiguration, UiConfiguration}; + use run::RunCmd; + + use super::*; #[derive(Debug, PartialEq)] struct TestPasswordReader(&'static str); @@ -1233,12 +1234,20 @@ mod tests { let args = vec!["parity", "signer", "new-token"]; let conf = parse(&args); let expected = Directories::default().signer; - assert_eq!(conf.into_command().unwrap().cmd, Cmd::SignerToken(SignerConfiguration { + assert_eq!(conf.into_command().unwrap().cmd, Cmd::SignerToken(WsConfiguration { + enabled: true, + interface: "127.0.0.1".into(), + port: 8546, + apis: ApiSet::UnsafeContext, + origins: Some(vec!["chrome-extension://*".into()]), + hosts: Some(vec![]), + signer_path: expected.into(), + ui_address: Some(("127.0.0.1".to_owned(), 8180)), + }, UiConfiguration { enabled: true, - signer_path: expected, interface: "127.0.0.1".into(), port: 8180, - skip_origin_validation: false, + hosts: Some(vec![]), })); } @@ -1273,11 +1282,10 @@ mod tests { wal: true, vm_type: Default::default(), geth_compatibility: false, - ui_address: Some(("127.0.0.1".into(), 8180)), net_settings: Default::default(), dapps_conf: Default::default(), ipfs_conf: Default::default(), - signer_conf: Default::default(), + ui_conf: Default::default(), secretstore_conf: Default::default(), ui: false, dapp: None, @@ -1457,7 +1465,7 @@ mod tests { } #[test] - fn should_parse_signer_configration() { + fn should_parse_ui_configuration() { // given // when @@ -1467,33 +1475,33 @@ mod tests { let conf3 = parse(&["parity", "--ui-path", "signer", "--ui-interface", "test"]); // then - assert_eq!(conf0.signer_config(), SignerConfiguration { + assert_eq!(conf0.directories().signer, "signer".to_owned()); + assert_eq!(conf0.ui_config(), UiConfiguration { enabled: true, - port: 8180, interface: "127.0.0.1".into(), - signer_path: "signer".into(), - skip_origin_validation: false, + port: 8180, + hosts: Some(vec![]), }); - assert_eq!(conf1.signer_config(), SignerConfiguration { + assert_eq!(conf1.directories().signer, "signer".to_owned()); + assert_eq!(conf1.ui_config(), UiConfiguration { enabled: true, - port: 8180, interface: "127.0.0.1".into(), - signer_path: "signer".into(), - skip_origin_validation: true, + port: 8180, + hosts: None, }); - assert_eq!(conf2.signer_config(), SignerConfiguration { + assert_eq!(conf2.directories().signer, "signer".to_owned()); + assert_eq!(conf2.ui_config(), UiConfiguration { enabled: true, - port: 3123, interface: "127.0.0.1".into(), - signer_path: "signer".into(), - skip_origin_validation: false, + port: 3123, + hosts: Some(vec![]), }); - assert_eq!(conf3.signer_config(), SignerConfiguration { + assert_eq!(conf3.directories().signer, "signer".to_owned()); + assert_eq!(conf3.ui_config(), UiConfiguration { enabled: true, - port: 8180, interface: "test".into(), - signer_path: "signer".into(), - skip_origin_validation: false, + port: 8180, + hosts: Some(vec![]), }); } @@ -1551,7 +1559,7 @@ mod tests { assert_eq!(conf0.network_settings().unwrap().rpc_port, 8546); assert_eq!(conf0.http_config().unwrap().port, 8546); assert_eq!(conf0.ws_config().unwrap().port, 8547); - assert_eq!(conf0.signer_config().port, 8181); + assert_eq!(conf0.ui_config().port, 8181); assert_eq!(conf0.secretstore_config().unwrap().port, 8084); assert_eq!(conf0.secretstore_config().unwrap().http_port, 8083); assert_eq!(conf0.ipfs_config().port, 5002); @@ -1563,7 +1571,7 @@ mod tests { assert_eq!(conf1.network_settings().unwrap().rpc_port, 8545); assert_eq!(conf1.http_config().unwrap().port, 8545); assert_eq!(conf1.ws_config().unwrap().port, 8547); - assert_eq!(conf1.signer_config().port, 8181); + assert_eq!(conf1.ui_config().port, 8181); assert_eq!(conf1.secretstore_config().unwrap().port, 8084); assert_eq!(conf1.secretstore_config().unwrap().http_port, 8083); assert_eq!(conf1.ipfs_config().port, 5002); @@ -1582,8 +1590,8 @@ mod tests { assert_eq!(conf0.http_config().unwrap().hosts, None); assert_eq!(&conf0.ws_config().unwrap().interface, "0.0.0.0"); assert_eq!(conf0.ws_config().unwrap().hosts, None); - assert_eq!(&conf0.signer_config().interface, "0.0.0.0"); - assert_eq!(conf0.signer_config().skip_origin_validation, true); + assert_eq!(&conf0.ui_config().interface, "0.0.0.0"); + assert_eq!(conf0.ui_config().hosts, None); assert_eq!(&conf0.secretstore_config().unwrap().interface, "0.0.0.0"); assert_eq!(&conf0.secretstore_config().unwrap().http_interface, "0.0.0.0"); assert_eq!(&conf0.ipfs_config().interface, "0.0.0.0"); diff --git a/parity/dapps.rs b/parity/dapps.rs index 324e4040305..f0ae06c6b30 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -27,6 +27,7 @@ use hash_fetch::urlhint::ContractClient; use helpers::replace_home; use light::client::Client as LightClient; use light::on_demand::{self, OnDemand}; +use rpc; use rpc_apis::SignerService; use parity_reactor; use util::{Bytes, Address}; @@ -49,6 +50,15 @@ impl Default for Configuration { } } +impl Configuration { + pub fn address(&self, address: Option<(String, u16)>) -> Option<(String, u16)> { + match self.enabled { + true => address, + false => None, + } + } +} + /// Registrar implementation of the full client. pub struct FullRegistrar { /// Handle to the full client. @@ -125,35 +135,49 @@ impl ContractClient for LightRegistrar { // TODO: light client implementation forwarding to OnDemand and waiting for future // to resolve. +#[derive(Clone)] pub struct Dependencies { pub sync_status: Arc, pub contract_client: Arc, pub remote: parity_reactor::TokioRemote, pub fetch: FetchClient, pub signer: Arc, + pub ui_address: Option<(String, u16)>, } -pub fn new(configuration: Configuration, deps: Dependencies) - -> Result, String> -{ +pub fn new(configuration: Configuration, deps: Dependencies) -> Result, String> { if !configuration.enabled { return Ok(None); } - dapps_middleware( + server::dapps_middleware( deps, configuration.dapps_path, configuration.extra_dapps, + rpc::DAPPS_DOMAIN.into(), + ).map(Some) +} + +pub fn new_ui(enabled: bool, deps: Dependencies) -> Result, String> { + if !enabled { + return Ok(None); + } + + server::ui_middleware( + deps, + rpc::DAPPS_DOMAIN.into(), ).map(Some) } -pub use self::server::{SyncStatus, Middleware, dapps_middleware}; +pub use self::server::{SyncStatus, Middleware, service}; #[cfg(not(feature = "dapps"))] mod server { use super::Dependencies; + use std::sync::Arc; use std::path::PathBuf; use parity_rpc::{hyper, RequestMiddleware, RequestMiddlewareAction}; + use rpc_apis; pub type SyncStatus = Fn() -> bool; @@ -170,9 +194,21 @@ mod server { _deps: Dependencies, _dapps_path: PathBuf, _extra_dapps: Vec, + _dapps_domain: String, ) -> Result { Err("Your Parity version has been compiled without WebApps support.".into()) } + + pub fn ui_middleware( + _deps: Dependencies, + _dapps_domain: String, + ) -> Result { + Err("Your Parity version has been compiled without UI support.".into()) + } + + pub fn service(_: &Option) -> Option> { + None + } } #[cfg(feature = "dapps")] @@ -180,6 +216,7 @@ mod server { use super::Dependencies; use std::path::PathBuf; use std::sync::Arc; + use rpc_apis; use parity_dapps; use parity_reactor; @@ -191,20 +228,62 @@ mod server { deps: Dependencies, dapps_path: PathBuf, extra_dapps: Vec, + dapps_domain: String, ) -> Result { - let signer = deps.signer.clone(); + let signer = deps.signer; let parity_remote = parity_reactor::Remote::new(deps.remote.clone()); let web_proxy_tokens = Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token)); - Ok(parity_dapps::Middleware::new( + Ok(parity_dapps::Middleware::dapps( parity_remote, - deps.signer.address(), + deps.ui_address, dapps_path, extra_dapps, + dapps_domain, deps.contract_client, deps.sync_status, web_proxy_tokens, - deps.fetch.clone(), + deps.fetch, )) } + + pub fn ui_middleware( + deps: Dependencies, + dapps_domain: String, + ) -> Result { + let parity_remote = parity_reactor::Remote::new(deps.remote.clone()); + Ok(parity_dapps::Middleware::ui( + parity_remote, + deps.contract_client, + deps.sync_status, + deps.fetch, + dapps_domain, + )) + } + + pub fn service(middleware: &Option) -> Option> { + middleware.as_ref().map(|m| Arc::new(DappsServiceWrapper { + endpoints: m.endpoints() + }) as Arc) + } + + pub struct DappsServiceWrapper { + endpoints: parity_dapps::Endpoints, + } + + impl rpc_apis::DappsService for DappsServiceWrapper { + fn list_dapps(&self) -> Vec { + self.endpoints.list() + .into_iter() + .map(|app| rpc_apis::LocalDapp { + id: app.id, + name: app.name, + description: app.description, + version: app.version, + author: app.author, + icon_url: app.icon_url, + }) + .collect() + } + } } diff --git a/parity/main.rs b/parity/main.rs index c61d414bebb..c05c9ba59a3 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -51,7 +51,6 @@ extern crate ethcore_ipc_hypervisor as hypervisor; extern crate ethcore_ipc_nano as nanoipc; extern crate ethcore_light as light; extern crate ethcore_logger; -extern crate ethcore_signer; extern crate ethcore_util as util; extern crate ethkey; extern crate ethsync; @@ -114,9 +113,9 @@ mod presale; mod rpc; mod rpc_apis; mod run; +mod secretstore; mod signer; mod snapshot; -mod secretstore; mod upgrade; mod url; mod user_defaults; @@ -170,7 +169,7 @@ fn execute(command: Execute, can_restart: bool) -> Result account::execute(account_cmd).map(|s| PostExecutionAction::Print(s)), Cmd::ImportPresaleWallet(presale_cmd) => presale::execute(presale_cmd).map(|s| PostExecutionAction::Print(s)), Cmd::Blockchain(blockchain_cmd) => blockchain::execute(blockchain_cmd).map(|_| PostExecutionAction::Quit), - Cmd::SignerToken(signer_cmd) => signer::execute(signer_cmd).map(|s| PostExecutionAction::Print(s)), + Cmd::SignerToken(ws_conf, ui_conf) => signer::execute(ws_conf, ui_conf).map(|s| PostExecutionAction::Print(s)), Cmd::SignerSign { id, pwfile, port, authfile } => rpc_cli::signer_sign(id, pwfile, port, authfile).map(|s| PostExecutionAction::Print(s)), Cmd::SignerList { port, authfile } => rpc_cli::signer_list(port, authfile).map(|s| PostExecutionAction::Print(s)), Cmd::SignerReject { id, port, authfile } => rpc_cli::signer_reject(id, port, authfile).map(|s| PostExecutionAction::Print(s)), diff --git a/parity/rpc.rs b/parity/rpc.rs index ae0e08858e5..66dcbcaf73f 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -16,18 +16,24 @@ use std::io; use std::sync::Arc; +use std::path::PathBuf; +use std::collections::HashSet; use dapps; -use parity_rpc::informant::{RpcStats, Middleware}; -use parity_rpc::{self as rpc, HttpServerError, Metadata, Origin, DomainsValidation}; -use helpers::parity_ipc_path; +use dir::default_data_path; +use helpers::{parity_ipc_path, replace_home}; use jsonrpc_core::MetaIoHandler; use parity_reactor::TokioRemote; +use parity_rpc::informant::{RpcStats, Middleware}; +use parity_rpc::{self as rpc, Metadata, DomainsValidation}; use rpc_apis::{self, ApiSet}; pub use parity_rpc::{IpcServer, HttpServer, RequestMiddleware}; pub use parity_rpc::ws::Server as WsServer; + +pub const DAPPS_DOMAIN: &'static str = "web3.site"; + #[derive(Debug, Clone, PartialEq)] pub struct HttpConfiguration { pub enabled: bool, @@ -39,6 +45,15 @@ pub struct HttpConfiguration { pub threads: Option, } +impl HttpConfiguration { + pub fn address(&self) -> Option<(String, u16)> { + match self.enabled { + true => Some((self.interface.clone(), self.port)), + false => None, + } + } +} + impl Default for HttpConfiguration { fn default() -> Self { HttpConfiguration { @@ -53,6 +68,48 @@ impl Default for HttpConfiguration { } } +#[derive(Debug, PartialEq, Clone)] +pub struct UiConfiguration { + pub enabled: bool, + pub interface: String, + pub port: u16, + pub hosts: Option>, +} + +impl UiConfiguration { + pub fn address(&self) -> Option<(String, u16)> { + match self.enabled { + true => Some((self.interface.clone(), self.port)), + false => None, + } + } +} + +impl From for HttpConfiguration { + fn from(conf: UiConfiguration) -> Self { + HttpConfiguration { + enabled: conf.enabled, + interface: conf.interface, + port: conf.port, + apis: rpc_apis::ApiSet::SafeContext, + cors: None, + hosts: conf.hosts, + threads: None, + } + } +} + +impl Default for UiConfiguration { + fn default() -> Self { + UiConfiguration { + enabled: true, + port: 8180, + interface: "127.0.0.1".into(), + hosts: Some(vec![]), + } + } +} + #[derive(Debug, PartialEq)] pub struct IpcConfiguration { pub enabled: bool, @@ -75,7 +132,7 @@ impl Default for IpcConfiguration { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct WsConfiguration { pub enabled: bool, pub interface: String, @@ -83,81 +140,40 @@ pub struct WsConfiguration { pub apis: ApiSet, pub origins: Option>, pub hosts: Option>, + pub signer_path: PathBuf, + pub ui_address: Option<(String, u16)>, } impl Default for WsConfiguration { fn default() -> Self { + let data_dir = default_data_path(); WsConfiguration { enabled: true, interface: "127.0.0.1".into(), port: 8546, apis: ApiSet::UnsafeContext, - origins: Some(Vec::new()), + origins: Some(vec!["chrome-extension://*".into()]), hosts: Some(Vec::new()), + signer_path: replace_home(&data_dir, "$BASE/signer").into(), + ui_address: Some(("127.0.0.1".to_owned(), 8180)), } } } -pub struct Dependencies { - pub apis: Arc, - pub remote: TokioRemote, - pub stats: Arc, -} - -pub struct RpcExtractor; -impl rpc::HttpMetaExtractor for RpcExtractor { - type Metadata = Metadata; - - fn read_metadata(&self, origin: String, dapps_origin: Option) -> Metadata { - let mut metadata = Metadata::default(); - - metadata.origin = match (origin.as_str(), dapps_origin) { - ("null", Some(dapp)) => Origin::Dapps(dapp.into()), - _ => Origin::Rpc(origin), - }; - - metadata - } -} - -impl rpc::IpcMetaExtractor for RpcExtractor { - fn extract(&self, _req: &rpc::IpcRequestContext) -> Metadata { - let mut metadata = Metadata::default(); - // TODO [ToDr] Extract proper session id when it's available in context. - metadata.origin = Origin::Ipc(1.into()); - metadata - } -} - -struct WsRpcExtractor; -impl rpc::ws::MetaExtractor for WsRpcExtractor { - fn extract(&self, req: &rpc::ws::RequestContext) -> Metadata { - let mut metadata = Metadata::default(); - let id = req.session_id as u64; - metadata.origin = Origin::Ws(id.into()); - metadata.session = Some(Arc::new(rpc::PubSubSession::new(req.sender()))); - metadata - } -} - -struct WsStats { - stats: Arc, -} - -impl rpc::ws::SessionStats for WsStats { - fn open_session(&self, _id: rpc::ws::SessionId) { - self.stats.open_session() - } - fn close_session(&self, _id: rpc::ws::SessionId) { - self.stats.close_session() +impl WsConfiguration { + pub fn address(&self) -> Option<(String, u16)> { + match self.enabled { + true => Some((self.interface.clone(), self.port)), + false => None, + } } } -fn setup_apis(apis: ApiSet, deps: &Dependencies) -> MetaIoHandler> - where D: rpc_apis::Dependencies -{ - rpc_apis::setup_rpc(deps.stats.clone(), &*deps.apis, apis) +pub struct Dependencies { + pub apis: Arc, + pub remote: TokioRemote, + pub stats: Arc, } pub fn new_ws( @@ -168,23 +184,41 @@ pub fn new_ws( return Ok(None); } - let url = format!("{}:{}", conf.interface, conf.port); + let domain = DAPPS_DOMAIN; + let ws_address = (conf.interface, conf.port); + let url = format!("{}:{}", ws_address.0, ws_address.1); let addr = url.parse().map_err(|_| format!("Invalid WebSockets listen host/port given: {}", url))?; - let handler = setup_apis(conf.apis, deps); + + + let full_handler = setup_apis(rpc_apis::ApiSet::SafeContext, deps); + let handler = { + let mut handler = MetaIoHandler::with_middleware(( + rpc::WsDispatcher::new(full_handler), + Middleware::new(deps.stats.clone(), deps.apis.activity_notifier()) + )); + let apis = conf.apis.list_apis().into_iter().collect::>(); + deps.apis.extend_with_set(&mut handler, &apis); + + handler + }; + let remote = deps.remote.clone(); - let allowed_origins = into_domains(conf.origins); - let allowed_hosts = into_domains(conf.hosts); + let ui_address = conf.ui_address.clone(); + let allowed_origins = into_domains(with_domain(conf.origins, domain, &[ui_address])); + let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &[Some(ws_address)])); + let signer_path = conf.signer_path; + let signer_path = conf.ui_address.map(move |_| ::signer::codes_path(&signer_path)); + let path = signer_path.as_ref().map(|p| p.as_path()); let start_result = rpc::start_ws( &addr, handler, remote.clone(), allowed_origins, allowed_hosts, - WsRpcExtractor, - WsStats { - stats: deps.stats.clone(), - }, + rpc::WsExtractor::new(path.clone()), + rpc::WsExtractor::new(path.clone()), + rpc::WsStats::new(deps.stats.clone()), ); match start_result { @@ -197,21 +231,25 @@ pub fn new_ws( } pub fn new_http( + id: &str, + options: &str, conf: HttpConfiguration, deps: &Dependencies, - middleware: Option + middleware: Option, ) -> Result, String> { if !conf.enabled { return Ok(None); } - let url = format!("{}:{}", conf.interface, conf.port); - let addr = url.parse().map_err(|_| format!("Invalid HTTP JSON-RPC listen host/port given: {}", url))?; + let domain = DAPPS_DOMAIN; + let http_address = (conf.interface, conf.port); + let url = format!("{}:{}", http_address.0, http_address.1); + let addr = url.parse().map_err(|_| format!("Invalid {} listen host/port given: {}", id, url))?; let handler = setup_apis(conf.apis, deps); let remote = deps.remote.clone(); let cors_domains = into_domains(conf.cors); - let allowed_hosts = into_domains(conf.hosts); + let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &[Some(http_address)])); let start_result = rpc::start_http( &addr, @@ -219,7 +257,7 @@ pub fn new_http( allowed_hosts, handler, remote, - RpcExtractor, + rpc::RpcExtractor, match (conf.threads, middleware) { (Some(threads), None) => rpc::HttpSettings::Threads(threads), (None, middleware) => rpc::HttpSettings::Dapps(middleware), @@ -231,17 +269,13 @@ pub fn new_http( match start_result { Ok(server) => Ok(Some(server)), - Err(HttpServerError::Io(ref err)) if err.kind() == io::ErrorKind::AddrInUse => Err( - format!("HTTP address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --jsonrpc-port and --jsonrpc-interface options.", url) + Err(rpc::HttpServerError::Io(ref err)) if err.kind() == io::ErrorKind::AddrInUse => Err( + format!("{} address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --{}-port and --{}-interface options.", id, url, options, options) ), - Err(e) => Err(format!("HTTP error: {:?}", e)), + Err(e) => Err(format!("{} error: {:?}", id, e)), } } -fn into_domains>(items: Option>) -> DomainsValidation { - items.map(|vals| vals.into_iter().map(T::from).collect()).into() -} - pub fn new_ipc( conf: IpcConfiguration, dependencies: &Dependencies @@ -252,48 +286,39 @@ pub fn new_ipc( let handler = setup_apis(conf.apis, dependencies); let remote = dependencies.remote.clone(); - let ipc = rpc::start_ipc( - &conf.socket_addr, - handler, - remote, - RpcExtractor, - ); - - match ipc { + match rpc::start_ipc(&conf.socket_addr, handler, remote, rpc::RpcExtractor) { Ok(server) => Ok(Some(server)), Err(io_error) => Err(format!("IPC error: {}", io_error)), } } -#[cfg(test)] -mod tests { - use super::RpcExtractor; - use parity_rpc::{HttpMetaExtractor, Origin}; - - #[test] - fn should_extract_rpc_origin() { - // given - let extractor = RpcExtractor; - - // when - let meta = extractor.read_metadata("http://parity.io".into(), None); - let meta1 = extractor.read_metadata("http://parity.io".into(), Some("ignored".into())); - - // then - assert_eq!(meta.origin, Origin::Rpc("http://parity.io".into())); - assert_eq!(meta1.origin, Origin::Rpc("http://parity.io".into())); - } +fn into_domains>(items: Option>) -> DomainsValidation { + items.map(|vals| vals.into_iter().map(T::from).collect()).into() +} - #[test] - fn should_dapps_origin() { - // given - let extractor = RpcExtractor; - let dapp = "https://wallet.ethereum.org".to_owned(); +fn with_domain(items: Option>, domain: &str, addresses: &[Option<(String, u16)>]) -> Option> { + items.map(move |items| { + let mut items = items.into_iter().collect::>(); + for address in addresses { + if let Some((host, port)) = address.clone() { + items.insert(format!("{}:{}", host, port)); + items.insert(format!("{}:{}", host.replace("127.0.0.1", "localhost"), port)); + items.insert(format!("http://*.{}:{}", domain, port)); + items.insert(format!("http://*.{}", domain)); //proxypac + } + } + items.into_iter().collect() + }) +} - // when - let meta = extractor.read_metadata("null".into(), Some(dapp.clone())); +fn setup_apis(apis: ApiSet, deps: &Dependencies) -> MetaIoHandler> + where D: rpc_apis::Dependencies +{ + let mut handler = MetaIoHandler::with_middleware( + Middleware::new(deps.stats.clone(), deps.apis.activity_notifier()) + ); + let apis = apis.list_apis().into_iter().collect::>(); + deps.apis.extend_with_set(&mut handler, &apis); - // then - assert_eq!(meta.origin, Origin::Dapps(dapp.into())); - } + handler } diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index d456a0fff6e..78f9de03a18 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -20,14 +20,15 @@ use std::collections::HashSet; use std::str::FromStr; use std::sync::{Arc, Weak}; -pub use parity_rpc::SignerService; +pub use parity_rpc::signer::SignerService; +pub use parity_rpc::dapps::{DappsService, LocalDapp}; use ethcore::account_provider::AccountProvider; use ethcore::client::Client; use ethcore::miner::{Miner, ExternalMiner}; use ethcore::snapshot::SnapshotService; use parity_rpc::{Metadata, NetworkSettings}; -use parity_rpc::informant::{ActivityNotifier, Middleware, RpcStats, ClientNotifier}; +use parity_rpc::informant::{ActivityNotifier, ClientNotifier}; use parity_rpc::dispatch::{FullDispatcher, LightDispatcher}; use ethsync::{ManageNetwork, SyncProvider, LightSync}; use hash_fetch::fetch::Client as FetchClient; @@ -183,7 +184,11 @@ pub trait Dependencies { fn activity_notifier(&self) -> Self::Notifier; /// Extend the given I/O handler with endpoints for each API. - fn extend_with_set(&self, handler: &mut MetaIoHandler>, apis: &[Api]); + fn extend_with_set( + &self, + handler: &mut MetaIoHandler, + apis: &[Api], + ) where S: core::Middleware; } /// RPC dependencies for a full node. @@ -201,19 +206,20 @@ pub struct FullDependencies { pub net_service: Arc, pub updater: Arc, pub geth_compatibility: bool, - pub dapps_interface: Option, - pub dapps_port: Option, + pub dapps_service: Option>, + pub dapps_address: Option<(String, u16)>, + pub ws_address: Option<(String, u16)>, pub fetch: FetchClient, pub remote: parity_reactor::Remote, } impl FullDependencies { - fn extend_api>( + fn extend_api( &self, - handler: &mut MetaIoHandler, + handler: &mut MetaIoHandler, apis: &[Api], for_generic_pubsub: bool, - ) { + ) where S: core::Middleware { use parity_rpc::v1::*; macro_rules! add_signing_methods { @@ -288,8 +294,8 @@ impl FullDependencies { self.logger.clone(), self.settings.clone(), signer, - self.dapps_interface.clone(), - self.dapps_port, + self.dapps_address.clone(), + self.ws_address.clone(), ).to_delegate()); if !for_generic_pubsub { @@ -312,6 +318,7 @@ impl FullDependencies { &self.miner, &self.updater, &self.net_service, + self.dapps_service.clone(), self.fetch.clone(), ).to_delegate()) }, @@ -339,7 +346,11 @@ impl Dependencies for FullDependencies { } } - fn extend_with_set(&self, handler: &mut MetaIoHandler>, apis: &[Api]) { + fn extend_with_set( + &self, + handler: &mut MetaIoHandler, + apis: &[Api], + ) where S: core::Middleware { self.extend_api(handler, apis, false) } } @@ -363,8 +374,9 @@ pub struct LightDependencies { pub on_demand: Arc<::light::on_demand::OnDemand>, pub cache: Arc>, pub transaction_queue: Arc>, - pub dapps_interface: Option, - pub dapps_port: Option, + pub dapps_service: Option>, + pub dapps_address: Option<(String, u16)>, + pub ws_address: Option<(String, u16)>, pub fetch: FetchClient, pub geth_compatibility: bool, pub remote: parity_reactor::Remote, @@ -457,8 +469,8 @@ impl LightDependencies { self.logger.clone(), self.settings.clone(), signer, - self.dapps_interface.clone(), - self.dapps_port, + self.dapps_address.clone(), + self.ws_address.clone(), ).to_delegate()); if !for_generic_pubsub { @@ -479,6 +491,7 @@ impl LightDependencies { Api::ParitySet => { handler.extend_with(light::ParitySetClient::new( self.sync.clone(), + self.dapps_service.clone(), self.fetch.clone(), ).to_delegate()) }, @@ -502,7 +515,12 @@ impl Dependencies for LightDependencies { type Notifier = LightClientNotifier; fn activity_notifier(&self) -> Self::Notifier { LightClientNotifier } - fn extend_with_set(&self, handler: &mut MetaIoHandler>, apis: &[Api]) { + + fn extend_with_set( + &self, + handler: &mut MetaIoHandler, + apis: &[Api], + ) where S: core::Middleware { self.extend_api(handler, apis, false) } } @@ -552,15 +570,6 @@ impl ApiSet { } } -pub fn setup_rpc(stats: Arc, deps: &D, apis: ApiSet) -> MetaIoHandler> { - let mut handler = MetaIoHandler::with_middleware(Middleware::new(stats, deps.activity_notifier())); - // it's turned into vector, cause ont of the cases requires &[] - let apis = apis.list_apis().into_iter().collect::>(); - deps.extend_with_set(&mut handler, &apis[..]); - - handler -} - #[cfg(test)] mod test { use super::{Api, ApiSet}; diff --git a/parity/run.rs b/parity/run.rs index 0f45e585ce9..352f11c5d64 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -49,11 +49,11 @@ use cache::CacheConfig; use user_defaults::UserDefaults; use dapps; use ipfs; -use signer; -use secretstore; use modules; -use rpc_apis; use rpc; +use rpc_apis; +use secretstore; +use signer; use url; // how often to take periodic snapshots. @@ -99,11 +99,10 @@ pub struct RunCmd { pub wal: bool, pub vm_type: VMType, pub geth_compatibility: bool, - pub ui_address: Option<(String, u16)>, pub net_settings: NetworkSettings, pub dapps_conf: dapps::Configuration, pub ipfs_conf: ipfs::Configuration, - pub signer_conf: signer::Configuration, + pub ui_conf: rpc::UiConfiguration, pub secretstore_conf: secretstore::Configuration, pub dapp: Option, pub ui: bool, @@ -119,12 +118,12 @@ pub struct RunCmd { pub no_persistent_txqueue: bool, } -pub fn open_ui(signer_conf: &signer::Configuration) -> Result<(), String> { - if !signer_conf.enabled { +pub fn open_ui(ws_conf: &rpc::WsConfiguration, ui_conf: &rpc::UiConfiguration) -> Result<(), String> { + if !ui_conf.enabled { return Err("Cannot use UI command with UI turned off.".into()) } - let token = signer::generate_token_and_url(signer_conf)?; + let token = signer::generate_token_and_url(ws_conf, ui_conf)?; // Open a browser url::open(&token.url); // Print a message @@ -195,7 +194,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, compaction.clone())?; // create dirs used by parity - cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled, cmd.secretstore_conf.enabled)?; + cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.ui_conf.enabled, cmd.secretstore_conf.enabled)?; info!("Starting {}", Colour::White.bold().paint(version())); info!("Running in experimental {} mode.", Colour::Blue.bold().paint("Light Client")); @@ -267,31 +266,47 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> // prepare account provider let account_provider = Arc::new(prepare_account_provider(&cmd.spec, &cmd.dirs, &spec.data_dir, cmd.acc_conf, &passwords)?); let rpc_stats = Arc::new(informant::RpcStats::default()); - let signer_path = cmd.signer_conf.signer_path.clone(); + + // the dapps server + let signer_service = Arc::new(signer::new_service(&cmd.ws_conf, &cmd.ui_conf)); + let dapps_deps = { + let contract_client = Arc::new(::dapps::LightRegistrar { + client: service.client().clone(), + sync: light_sync.clone(), + on_demand: on_demand.clone(), + }); + + let sync = light_sync.clone(); + dapps::Dependencies { + sync_status: Arc::new(move || sync.is_major_importing()), + contract_client: contract_client, + remote: event_loop.raw_remote(), + fetch: fetch.clone(), + signer: signer_service.clone(), + ui_address: cmd.ui_conf.address(), + } + }; + + let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?; + let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?; // start RPCs + let dapps_service = dapps::service(&dapps_middleware); let deps_for_rpc_apis = Arc::new(rpc_apis::LightDependencies { - signer_service: Arc::new(rpc_apis::SignerService::new(move || { - signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e)) - }, cmd.ui_address)), + signer_service: signer_service, client: service.client().clone(), sync: light_sync.clone(), net: light_sync.clone(), secret_store: account_provider, logger: logger, settings: Arc::new(cmd.net_settings), - on_demand: on_demand.clone(), + on_demand: on_demand, cache: cache, transaction_queue: txq, - dapps_interface: match cmd.dapps_conf.enabled { - true => Some(cmd.http_conf.interface.clone()), - false => None, - }, - dapps_port: match cmd.dapps_conf.enabled { - true => Some(cmd.http_conf.port), - false => None, - }, - fetch: fetch.clone(), + dapps_service: dapps_service, + dapps_address: cmd.dapps_conf.address(cmd.http_conf.address()), + ws_address: cmd.ws_conf.address(), + fetch: fetch, geth_compatibility: cmd.geth_compatibility, remote: event_loop.remote(), }); @@ -302,39 +317,11 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> stats: rpc_stats.clone(), }; - // the dapps server - let dapps_deps = { - let contract_client = Arc::new(::dapps::LightRegistrar { - client: service.client().clone(), - sync: light_sync.clone(), - on_demand: on_demand, - }); - - let sync = light_sync.clone(); - dapps::Dependencies { - sync_status: Arc::new(move || sync.is_major_importing()), - contract_client: contract_client, - remote: event_loop.raw_remote(), - fetch: fetch, - signer: deps_for_rpc_apis.signer_service.clone(), - } - }; - - let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?; - // start rpc servers let _ws_server = rpc::new_ws(cmd.ws_conf, &dependencies)?; - let _http_server = rpc::new_http(cmd.http_conf.clone(), &dependencies, dapps_middleware)?; + let _http_server = rpc::new_http("HTTP JSON-RPC", "jsonrpc", cmd.http_conf.clone(), &dependencies, dapps_middleware)?; let _ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?; - - // the signer server - let signer_deps = signer::Dependencies { - apis: deps_for_rpc_apis.clone(), - remote: event_loop.raw_remote(), - rpc_stats: rpc_stats.clone(), - }; - let signing_queue = deps_for_rpc_apis.signer_service.queue(); - let _signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?; + let _ui_server = rpc::new_http("Parity Wallet (UI)", "ui", cmd.ui_conf.clone().into(), &dependencies, ui_middleware)?; // minimal informant thread. Just prints block number every 5 seconds. // TODO: integrate with informant.rs @@ -351,9 +338,9 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> Result<(bool, Option), String> { if cmd.ui && cmd.dapps_conf.enabled { // Check if Parity is already running - let addr = format!("{}:{}", cmd.signer_conf.interface, cmd.signer_conf.port); + let addr = format!("{}:{}", cmd.ui_conf.interface, cmd.ui_conf.port); if !TcpListener::bind(&addr as &str).is_ok() { - return open_ui(&cmd.signer_conf).map(|_| (false, None)); + return open_ui(&cmd.ws_conf, &cmd.ui_conf).map(|_| (false, None)); } } @@ -408,7 +395,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))?; // create dirs used by parity - cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled, cmd.secretstore_conf.enabled)?; + cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.ui_conf.enabled, cmd.secretstore_conf.enabled)?; // run in daemon mode if let Some(pid_file) = cmd.daemon { @@ -620,16 +607,33 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R // set up dependencies for rpc servers let rpc_stats = Arc::new(informant::RpcStats::default()); - let signer_path = cmd.signer_conf.signer_path.clone(); let secret_store = match cmd.public_node { true => None, false => Some(account_provider.clone()) }; + let signer_service = Arc::new(signer::new_service(&cmd.ws_conf, &cmd.ui_conf)); + + // the dapps server + let dapps_deps = { + let (sync, client) = (sync_provider.clone(), client.clone()); + let contract_client = Arc::new(::dapps::FullRegistrar { client: client.clone() }); + + dapps::Dependencies { + sync_status: Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())), + contract_client: contract_client, + remote: event_loop.raw_remote(), + fetch: fetch.clone(), + signer: signer_service.clone(), + ui_address: cmd.ui_conf.address(), + } + }; + let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?; + let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?; + + let dapps_service = dapps::service(&dapps_middleware); let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies { - signer_service: Arc::new(rpc_apis::SignerService::new(move || { - signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e)) - }, cmd.ui_address)), + signer_service: signer_service, snapshot: snapshot_service.clone(), client: client.clone(), sync: sync_provider.clone(), @@ -642,14 +646,9 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R net_service: manage_network.clone(), updater: updater.clone(), geth_compatibility: cmd.geth_compatibility, - dapps_interface: match cmd.dapps_conf.enabled { - true => Some(cmd.http_conf.interface.clone()), - false => None, - }, - dapps_port: match cmd.dapps_conf.enabled { - true => Some(cmd.http_conf.port), - false => None, - }, + dapps_service: dapps_service, + dapps_address: cmd.dapps_conf.address(cmd.http_conf.address()), + ws_address: cmd.ws_conf.address(), fetch: fetch.clone(), remote: event_loop.remote(), }); @@ -660,34 +659,12 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R stats: rpc_stats.clone(), }; - // the dapps server - let dapps_deps = { - let (sync, client) = (sync_provider.clone(), client.clone()); - let contract_client = Arc::new(::dapps::FullRegistrar { client: client.clone() }); - - dapps::Dependencies { - sync_status: Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())), - contract_client: contract_client, - remote: event_loop.raw_remote(), - fetch: fetch.clone(), - signer: deps_for_rpc_apis.signer_service.clone(), - } - }; - let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?; - // start rpc servers - let ws_server = rpc::new_ws(cmd.ws_conf, &dependencies)?; - let http_server = rpc::new_http(cmd.http_conf.clone(), &dependencies, dapps_middleware)?; + let ws_server = rpc::new_ws(cmd.ws_conf.clone(), &dependencies)?; let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?; - - // the signer server - let signer_deps = signer::Dependencies { - apis: deps_for_rpc_apis.clone(), - remote: event_loop.raw_remote(), - rpc_stats: rpc_stats.clone(), - }; - let signing_queue = deps_for_rpc_apis.signer_service.queue(); - let signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?; + let http_server = rpc::new_http("HTTP JSON-RPC", "jsonrpc", cmd.http_conf.clone(), &dependencies, dapps_middleware)?; + // the ui server + let ui_server = rpc::new_http("UI WALLET", "ui", cmd.ui_conf.clone().into(), &dependencies, ui_middleware)?; // secret store key server let secretstore_deps = secretstore::Dependencies { @@ -746,7 +723,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R // start ui if cmd.ui { - open_ui(&cmd.signer_conf)?; + open_ui(&cmd.ws_conf, &cmd.ui_conf)?; } if let Some(dapp) = cmd.dapp { @@ -756,11 +733,11 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R // Handle exit let restart = wait_for_exit(panic_handler, Some(updater), Some(client), can_restart); - // drop this stuff as soon as exit detected. - drop((ws_server, http_server, ipc_server, signer_server, secretstore_key_server, ipfs_server, event_loop)); - info!("Finishing work, please wait..."); + // drop this stuff as soon as exit detected. + drop((ws_server, http_server, ipc_server, ui_server, secretstore_key_server, ipfs_server, event_loop)); + // to make sure timer does not spawn requests while shutdown is in progress informant.shutdown(); // just Arc is dropping here, to allow other reference release in its default time diff --git a/parity/signer.rs b/parity/signer.rs index 7f800f0e055..7a5e9034104 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -15,51 +15,16 @@ // along with Parity. If not, see . use std::io; -use std::path::PathBuf; -use std::sync::Arc; - -pub use ethcore_signer::Server as SignerServer; +use std::path::{Path, PathBuf}; use ansi_term::Colour; -use dir::default_data_path; -use parity_rpc::informant::RpcStats; -use parity_rpc::{self, ConfirmationsQueue}; -use ethcore_signer as signer; -use helpers::replace_home; -use parity_reactor::TokioRemote; +use rpc; use rpc_apis; +use parity_rpc; use path::restrict_permissions_owner; -use util::H256; - -const CODES_FILENAME: &'static str = "authcodes"; -#[derive(Debug, PartialEq, Clone)] -pub struct Configuration { - pub enabled: bool, - pub port: u16, - pub interface: String, - pub signer_path: String, - pub skip_origin_validation: bool, -} -impl Default for Configuration { - fn default() -> Self { - let data_dir = default_data_path(); - Configuration { - enabled: true, - port: 8180, - interface: "127.0.0.1".into(), - signer_path: replace_home(&data_dir, "$BASE/signer"), - skip_origin_validation: false, - } - } -} - -pub struct Dependencies { - pub apis: Arc, - pub remote: TokioRemote, - pub rpc_stats: Arc, -} +pub const CODES_FILENAME: &'static str = "authcodes"; pub struct NewToken { pub token: String, @@ -67,42 +32,29 @@ pub struct NewToken { pub message: String, } -#[derive(Debug, Default, Clone)] -pub struct StandardExtractor; -impl signer::MetaExtractor for StandardExtractor { - fn extract_metadata(&self, session: &H256) -> parity_rpc::Metadata { - let mut metadata = parity_rpc::Metadata::default(); - metadata.origin = parity_rpc::Origin::Signer((*session).into()); - metadata - } -} +pub fn new_service(ws_conf: &rpc::WsConfiguration, ui_conf: &rpc::UiConfiguration) -> rpc_apis::SignerService { + let signer_path = ws_conf.signer_path.clone(); + let signer_enabled = ui_conf.enabled; -pub fn start( - conf: Configuration, - queue: Arc, - deps: Dependencies, -) -> Result, String> { - if !conf.enabled { - Ok(None) - } else { - Ok(Some(do_start(conf, queue, deps)?)) - } + rpc_apis::SignerService::new(move || { + generate_new_token(&signer_path).map_err(|e| format!("{:?}", e)) + }, signer_enabled) } -fn codes_path(path: String) -> PathBuf { - let mut p = PathBuf::from(path); +pub fn codes_path(path: &Path) -> PathBuf { + let mut p = path.to_owned(); p.push(CODES_FILENAME); let _ = restrict_permissions_owner(&p, true, false); p } -pub fn execute(cmd: Configuration) -> Result { - Ok(generate_token_and_url(&cmd)?.message) +pub fn execute(ws_conf: rpc::WsConfiguration, ui_conf: rpc::UiConfiguration) -> Result { + Ok(generate_token_and_url(&ws_conf, &ui_conf)?.message) } -pub fn generate_token_and_url(conf: &Configuration) -> Result { - let code = generate_new_token(conf.signer_path.clone()).map_err(|err| format!("Error generating token: {}", err))?; - let auth_url = format!("http://{}:{}/#/auth?token={}", conf.interface, conf.port, code); +pub fn generate_token_and_url(ws_conf: &rpc::WsConfiguration, ui_conf: &rpc::UiConfiguration) -> Result { + let code = generate_new_token(&ws_conf.signer_path).map_err(|err| format!("Error generating token: {:?}", err))?; + let auth_url = format!("http://{}:{}/#/auth?token={}", ui_conf.interface, ui_conf.port, code); // And print in to the console Ok(NewToken { token: code.clone(), @@ -119,49 +71,12 @@ Or use the generated token: }) } -pub fn generate_new_token(path: String) -> io::Result { +fn generate_new_token(path: &Path) -> io::Result { let path = codes_path(path); - let mut codes = signer::AuthCodes::from_file(&path)?; + let mut codes = parity_rpc::AuthCodes::from_file(&path)?; codes.clear_garbage(); let code = codes.generate_new()?; codes.to_file(&path)?; trace!("New key code created: {}", Colour::White.bold().paint(&code[..])); Ok(code) } - -fn do_start( - conf: Configuration, - queue: Arc, - deps: Dependencies -) -> Result { - let addr = format!("{}:{}", conf.interface, conf.port) - .parse() - .map_err(|_| format!("Invalid port specified: {}", conf.port))?; - - let start_result = { - let server = signer::ServerBuilder::new( - queue, - codes_path(conf.signer_path), - ); - if conf.skip_origin_validation { - warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Trusted Signer with no origin validation.")); - info!("If you do not intend this, exit now."); - } - let server = server.skip_origin_validation(conf.skip_origin_validation); - let server = server.stats(deps.rpc_stats.clone()); - let handler = rpc_apis::setup_rpc(deps.rpc_stats, &*deps.apis, rpc_apis::ApiSet::SafeContext); - let remote = deps.remote.clone(); - server.start_with_extractor(addr, handler, remote, StandardExtractor) - }; - - match start_result { - Err(signer::ServerError::IoError(err)) => match err.kind() { - io::ErrorKind::AddrInUse => Err(format!("Trusted UI address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --ui-port and --ui-interface options.", addr)), - _ => Err(format!("Trusted Signer io error: {}", err)), - }, - Err(e) => Err(format!("Trusted Signer Error: {:?}", e)), - Ok(server) => Ok(server), - } -} - - diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index b1e2d64aaf5..f9b18ef320e 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -8,9 +8,13 @@ authors = ["Parity Technologies "] [lib] [dependencies] +cid = "0.2" futures = "0.1" log = "0.3" +multihash = "0.5" order-stat = "0.1" +rand = "0.3" +rust-crypto = "0.2" rustc-serialize = "0.3" semver = "0.6" serde = "0.9" @@ -19,10 +23,6 @@ serde_json = "0.9" time = "0.1" tokio-timer = "0.1" transient-hashmap = "0.4" -cid = "0.2.1" -multihash = "0.5" -rust-crypto = "0.2.36" -rand = "0.3" jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } diff --git a/rpc/rpctest/Cargo.toml b/rpc/rpctest/Cargo.toml deleted file mode 100644 index 258af2f39cb..00000000000 --- a/rpc/rpctest/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -description = "Rpc test client." -name = "rpctest" -version = "1.7.0" -license = "GPL-3.0" -authors = ["Parity Technologies "] - -[dependencies] -ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } -docopt = "0.7" -ethcore = { path = "../../ethcore" } -ethcore-devtools = { path = "../../devtools" } -ethcore-util = { path = "../../util" } -ethjson = { path = "../../json" } -parity-rpc = { path = ".." } -rustc-serialize = "0.3" -serde_json = "0.8" diff --git a/rpc/rpctest/src/main.rs b/rpc/rpctest/src/main.rs deleted file mode 100644 index d65d2462ae8..00000000000 --- a/rpc/rpctest/src/main.rs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -extern crate ctrlc; -extern crate docopt; -extern crate ethcore; -extern crate ethcore_devtools as devtools; -extern crate ethcore_util as util; -extern crate ethjson; -extern crate parity_rpc as rpc; -extern crate rustc_serialize; -extern crate serde_json; - -use std::collections::HashMap; -use std::sync::{Arc, Mutex, Condvar}; -use std::process; -use std::fs::File; -use std::path::Path; -use docopt::Docopt; -use ctrlc::CtrlC; -use ethcore::spec::Genesis; -use ethcore::pod_state::PodState; -use ethcore::ethereum; -use ethcore::client::{BlockChainClient, Client, ClientConfig}; -use devtools::RandomTempPath; -use util::IoChannel; -use rpc::v1::tests::helpers::{TestSyncProvider, Config as SyncConfig, TestMinerService, TestAccountProvider, TestAccount}; -use rpc::v1::{Eth, EthClient, EthFilter, EthFilterClient}; -use util::panics::MayPanic; -use util::hash::Address; - -const USAGE: &'static str = r#" -Parity rpctest client. - By Wood/Paronyan/Kotewicz/Drwięga/Volf. - Copyright 2015, 2016, 2017 Parity Technologies (UK) Ltd - -Usage: - rpctest --json --name [options] - rpctest --help - -Options: - --jsonrpc-addr HOST Specify the hostname portion of the JSONRPC API - server [default: 127.0.0.1]. - --jsonrpc-port PORT Specify the port portion of the JSONRPC API server - [default: 8545]. -"#; - -#[derive(Debug, RustcDecodable)] -struct Args { - arg_test_file: String, - arg_test_name: String, - flag_jsonrpc_addr: String, - flag_jsonrpc_port: u16, -} - -struct Configuration { - args: Args, -} - -impl Configuration { - fn parse() -> Self { - Configuration { - args: Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit()) - } - } - - fn execute(&self) { - println!("file path: {:?}", self.args.arg_test_file); - println!("test name: {:?}", self.args.arg_test_name); - - let path = Path::new(&self.args.arg_test_file); - let file = File::open(path).unwrap_or_else(|_| { - println!("Cannot open file."); - process::exit(1); - }); - - let tests: ethjson::blockchain::Test = serde_json::from_reader(file).unwrap_or_else(|err| { - println!("Invalid json file."); - println!("{:?}", err); - process::exit(2); - }); - - let blockchain = tests.get(&self.args.arg_test_name).unwrap_or_else(|| { - println!("Invalid test name."); - process::exit(3); - }); - - let genesis = Genesis::from(blockchain.genesis()); - let state = PodState::from(blockchain.pre_state.clone()); - let mut spec = ethereum::new_frontier_test(); - spec.set_genesis_state(state); - spec.overwrite_genesis_params(genesis); - assert!(spec.is_state_root_valid()); - - let temp = RandomTempPath::new(); - { - let client: Arc = Client::new(ClientConfig::default(), spec, temp.as_path(), IoChannel::disconnected()).unwrap(); - for b in &blockchain.blocks_rlp() { - let _ = client.import_block(b.clone()); - client.flush_queue(); - client.import_verified_blocks(); - } - let sync = Arc::new(TestSyncProvider::new(SyncConfig { - protocol_version: 65, - num_peers: 120 - })); - - let miner = Arc::new(TestMinerService::default()); - let mut accs = HashMap::new(); - accs.insert(Address::from(1), TestAccount::new("test")); - let accounts = Arc::new(TestAccountProvider::new(accs)); - let server = rpc::RpcServer::new(); - server.add_delegate(EthClient::new(&client, &sync, &accounts, &miner, true).to_delegate()); - server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); - - let url = format!("{}:{}", self.args.flag_jsonrpc_addr, self.args.flag_jsonrpc_port); - let panic_handler = server.start_http(url.as_ref(), "*", 1); - let exit = Arc::new(Condvar::new()); - - let e = exit.clone(); - CtrlC::set_handler(move || { e.notify_all(); }); - - let e = exit.clone(); - panic_handler.on_panic(move |_reason| { e.notify_all(); }); - - let mutex = Mutex::new(()); - let _ = exit.wait(mutex.lock()).unwrap(); - } - - } -} - -fn main() { - Configuration::parse().execute(); -} diff --git a/signer/src/authcode_store.rs b/rpc/src/authcodes.rs similarity index 99% rename from signer/src/authcode_store.rs rename to rpc/src/authcodes.rs index 3ca640aa79a..57de437abcf 100644 --- a/signer/src/authcode_store.rs +++ b/rpc/src/authcodes.rs @@ -14,11 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use rand::Rng; -use rand::os::OsRng; use std::io::{self, Read, Write}; use std::path::Path; use std::{fs, time, mem}; + +use rand::Rng; +use rand::os::OsRng; use util::{H256, Hashable, Itertools}; /// Providing current time in seconds @@ -347,5 +348,3 @@ mod tests { } } - - diff --git a/rpc/src/metadata.rs b/rpc/src/http_common.rs similarity index 64% rename from rpc/src/metadata.rs rename to rpc/src/http_common.rs index af3a5d18386..edbb16140c1 100644 --- a/rpc/src/metadata.rs +++ b/rpc/src/http_common.rs @@ -14,11 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Transport-specific metadata extractors. + use jsonrpc_core; use http; use hyper; use minihttp; -use HttpMetaExtractor; + +/// HTTP RPC server impl-independent metadata extractor +pub trait HttpMetaExtractor: Send + Sync + 'static { + /// Type of Metadata + type Metadata: jsonrpc_core::Metadata; + /// Extracts metadata from given params. + fn read_metadata(&self, origin: Option, user_agent: Option, dapps_origin: Option) -> Self::Metadata; +} pub struct HyperMetaExtractor { extractor: T, @@ -37,13 +46,14 @@ impl http::MetaExtractor for HyperMetaExtractor where M: jsonrpc_core::Metadata, { fn read_metadata(&self, req: &hyper::server::Request) -> M { - let origin = req.headers().get::() - .map(|origin| format!("{}://{}", origin.scheme, origin.host)) - .unwrap_or_else(|| "unknown".into()); - let dapps_origin = req.headers().get_raw("x-parity-origin") + let as_string = |header: Option<&http::request_response::header::Raw>| header .and_then(|raw| raw.one()) .map(|raw| String::from_utf8_lossy(raw).into_owned()); - self.extractor.read_metadata(origin, dapps_origin) + + let origin = as_string(req.headers().get_raw("origin")); + let user_agent = as_string(req.headers().get_raw("user-agent")); + let dapps_origin = as_string(req.headers().get_raw("x-parity-origin")); + self.extractor.read_metadata(origin, user_agent, dapps_origin) } } @@ -64,11 +74,10 @@ impl minihttp::MetaExtractor for MiniMetaExtractor where M: jsonrpc_core::Metadata, { fn read_metadata(&self, req: &minihttp::Req) -> M { - let origin = req.header("origin") - .unwrap_or_else(|| "unknown") - .to_owned(); + let origin = req.header("origin").map(|h| h.to_owned()); + let user_agent = req.header("user-agent").map(|h| h.to_owned()); let dapps_origin = req.header("x-parity-origin").map(|h| h.to_owned()); - self.extractor.read_metadata(origin, dapps_origin) + self.extractor.read_metadata(origin, user_agent, dapps_origin) } } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 4540d6b33e9..22c9b98cd7b 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -14,13 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Ethcore rpc. +//! Parity RPC. + #![warn(missing_docs)] -#![cfg_attr(feature="nightly", feature(plugin))] -#![cfg_attr(feature="nightly", plugin(clippy))] +#![cfg_attr(feature="dev", feature(plugin))] +#![cfg_attr(feature="dev", plugin(clippy))] +extern crate cid; +extern crate crypto as rust_crypto; extern crate futures; +extern crate multihash; extern crate order_stat; +extern crate rand; extern crate rustc_serialize; extern crate semver; extern crate serde; @@ -28,10 +33,6 @@ extern crate serde_json; extern crate time; extern crate tokio_timer; extern crate transient_hashmap; -extern crate cid; -extern crate multihash; -extern crate crypto as rust_crypto; -extern crate rand; extern crate jsonrpc_core; extern crate jsonrpc_http_server as http; @@ -41,6 +42,7 @@ extern crate jsonrpc_pubsub; extern crate ethash; extern crate ethcore; +extern crate ethcore_devtools as devtools; extern crate ethcore_io as io; extern crate ethcore_ipc; extern crate ethcore_light as light; @@ -66,8 +68,6 @@ extern crate serde_derive; #[cfg(test)] extern crate ethjson; -#[cfg(test)] -extern crate ethcore_devtools as devtools; #[cfg(test)] #[macro_use] @@ -75,9 +75,12 @@ extern crate pretty_assertions; pub extern crate jsonrpc_ws_server as ws; -mod metadata; +mod authcodes; +mod http_common; pub mod v1; +pub mod tests; + pub use jsonrpc_pubsub::Session as PubSubSession; pub use ipc::{Server as IpcServer, MetaExtractor as IpcMetaExtractor, RequestContext as IpcRequestContext}; pub use http::{ @@ -86,8 +89,11 @@ pub use http::{ AccessControlAllowOrigin, Host, DomainsValidation }; -pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant, dispatch}; +pub use v1::{NetworkSettings, Metadata, Origin, informant, dispatch, signer, dapps}; pub use v1::block_import::is_major_importing; +pub use v1::extractors::{RpcExtractor, WsExtractor, WsStats, WsDispatcher}; +pub use authcodes::{AuthCodes, TimeProvider}; +pub use http_common::HttpMetaExtractor; use std::net::SocketAddr; use http::tokio_core; @@ -100,6 +106,16 @@ pub enum HttpServer { Hyper(http::Server), } +impl HttpServer { + /// Returns current listening address. + pub fn address(&self) -> &SocketAddr { + match *self { + HttpServer::Mini(ref s) => s.address(), + HttpServer::Hyper(ref s) => &s.addrs()[0], + } + } +} + /// RPC HTTP Server error #[derive(Debug)] pub enum HttpServerError { @@ -128,14 +144,6 @@ impl From for HttpServerError { } } -/// HTTP RPC server impl-independent metadata extractor -pub trait HttpMetaExtractor: Send + Sync + 'static { - /// Type of Metadata - type Metadata: jsonrpc_core::Metadata; - /// Extracts metadata from given params. - fn read_metadata(&self, origin: String, dapps_origin: Option) -> Self::Metadata; -} - /// HTTP server implementation-specific settings. pub enum HttpSettings { /// Enable fast minihttp server with given number of threads. @@ -164,7 +172,7 @@ pub fn start_http( HttpSettings::Dapps(middleware) => { let mut builder = http::ServerBuilder::new(handler) .event_loop_remote(remote) - .meta_extractor(metadata::HyperMetaExtractor::new(extractor)) + .meta_extractor(http_common::HyperMetaExtractor::new(extractor)) .cors(cors_domains.into()) .allowed_hosts(allowed_hosts.into()); @@ -177,7 +185,7 @@ pub fn start_http( HttpSettings::Threads(threads) => { minihttp::ServerBuilder::new(handler) .threads(threads) - .meta_extractor(metadata::MiniMetaExtractor::new(extractor)) + .meta_extractor(http_common::MiniMetaExtractor::new(extractor)) .cors(cors_domains.into()) .allowed_hosts(allowed_hosts.into()) .start_http(addr) @@ -205,13 +213,14 @@ pub fn start_ipc( } /// Start WS server and return `Server` handle. -pub fn start_ws( +pub fn start_ws( addr: &SocketAddr, handler: H, remote: tokio_core::reactor::Remote, allowed_origins: ws::DomainsValidation, allowed_hosts: ws::DomainsValidation, extractor: T, + middleware: V, stats: U, ) -> Result where M: jsonrpc_core::Metadata, @@ -219,9 +228,11 @@ pub fn start_ws( H: Into>, T: ws::MetaExtractor, U: ws::SessionStats, + V: ws::RequestMiddleware, { ws::ServerBuilder::new(handler) .event_loop_remote(remote) + .request_middleware(middleware) .allowed_origins(allowed_origins) .allowed_hosts(allowed_hosts) .session_meta_extractor(extractor) diff --git a/rpc/src/tests/helpers.rs b/rpc/src/tests/helpers.rs new file mode 100644 index 00000000000..e7a1684c56e --- /dev/null +++ b/rpc/src/tests/helpers.rs @@ -0,0 +1,84 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::ops::{Deref, DerefMut}; + +use devtools::RandomTempPath; +use parity_reactor::{EventLoop, TokioRemote}; + +use authcodes::AuthCodes; + +/// Server with event loop +pub struct Server { + /// Server + pub server: T, + /// RPC Event Loop + pub event_loop: EventLoop, +} + +impl Server { + pub fn new(f: F) -> Server where + F: FnOnce(TokioRemote) -> T, + { + let event_loop = EventLoop::spawn(); + let remote = event_loop.raw_remote(); + + Server { + server: f(remote), + event_loop: event_loop, + } + } +} + +impl Deref for Server { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.server + } +} + +/// Struct representing authcodes +pub struct GuardedAuthCodes { + authcodes: AuthCodes, + /// The path to the mock authcodes + pub path: RandomTempPath, +} + +impl GuardedAuthCodes { + pub fn new() -> Self { + let mut path = RandomTempPath::new(); + path.panic_on_drop_failure = false; + + GuardedAuthCodes { + authcodes: AuthCodes::from_file(&path).unwrap(), + path: path, + } + } +} + +impl Deref for GuardedAuthCodes { + type Target = AuthCodes; + fn deref(&self) -> &Self::Target { + &self.authcodes + } +} + +impl DerefMut for GuardedAuthCodes { + fn deref_mut(&mut self) -> &mut AuthCodes { + &mut self.authcodes + } +} diff --git a/signer/build.rs b/rpc/src/tests/mod.rs similarity index 79% rename from signer/build.rs rename to rpc/src/tests/mod.rs index 6a89bd269d5..d4d9538dcaa 100644 --- a/signer/build.rs +++ b/rpc/src/tests/mod.rs @@ -14,12 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -extern crate rustc_version; +//! RPC integration tests. -use rustc_version::{version_meta, Channel}; - -fn main() { - if let Channel::Nightly = version_meta().channel { - println!("cargo:rustc-cfg=nightly"); - } -} +mod helpers; +#[cfg(test)] mod rpc; +pub mod ws; diff --git a/rpc/src/tests/rpc.rs b/rpc/src/tests/rpc.rs new file mode 100644 index 00000000000..7bd156cf538 --- /dev/null +++ b/rpc/src/tests/rpc.rs @@ -0,0 +1,172 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use devtools::http_client; +use jsonrpc_core::MetaIoHandler; +use http::{self, hyper}; + +use {HttpSettings, HttpServer}; +use tests::helpers::Server; +use v1::{extractors, Metadata}; + +fn serve(handler: Option>) -> Server { + let address = "127.0.0.1:0".parse().unwrap(); + let handler = handler.unwrap_or_default(); + + Server::new(|remote| ::start_http( + &address, + http::DomainsValidation::Disabled, + http::DomainsValidation::Disabled, + handler, + remote, + extractors::RpcExtractor, + HttpSettings::Dapps(Some(|_req: &hyper::server::Request, _control: &hyper::Control| { + http::RequestMiddlewareAction::Proceed { + should_continue_on_invalid_cors: false + } + })), + ).unwrap()) +} + +/// Test a single request to running server +fn request(server: Server, request: &str) -> http_client::Response { + http_client::request(server.server.address(), request) +} + +#[cfg(test)] +mod testsing { + use jsonrpc_core::{MetaIoHandler, Value}; + use jsonrpc_core::futures::{Future, future}; + use v1::Metadata; + use super::{request, Server}; + + fn serve() -> (Server<::HttpServer>, ::std::net::SocketAddr) { + let mut io = MetaIoHandler::default(); + io.add_method_with_meta("hello", |_, meta: Metadata| { + future::ok(Value::String(format!("{}", meta.origin))).boxed() + }); + let server = super::serve(Some(io)); + let address = server.server.address().to_owned(); + + (server, address) + } + + #[test] + fn should_extract_rpc_origin() { + // given + let (server, address) = serve(); + + // when + let req = r#"{"method":"hello","params":[],"jsonrpc":"2.0","id":1}"#; + let expected = "34\n{\"jsonrpc\":\"2.0\",\"result\":\"unknown via RPC\",\"id\":1}\n\n0\n\n"; + let res = request(server, + &format!("\ + POST / HTTP/1.1\r\n\ + Host: {}\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n\ + Connection: close\r\n\ + \r\n\ + {} + ", address, req.len(), req) + ); + + // then + res.assert_status("HTTP/1.1 200 OK"); + assert_eq!(res.body, expected); + } + + #[test] + fn should_extract_rpc_origin_with_service() { + // given + let (server, address) = serve(); + + // when + let req = r#"{"method":"hello","params":[],"jsonrpc":"2.0","id":1}"#; + let expected = "38\n{\"jsonrpc\":\"2.0\",\"result\":\"curl/7.16.3 via RPC\",\"id\":1}\n\n0\n\n"; + let res = request(server, + &format!("\ + POST / HTTP/1.1\r\n\ + Host: {}\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n\ + Connection: close\r\n\ + User-Agent: curl/7.16.3\r\n\ + \r\n\ + {} + ", address, req.len(), req) + ); + + // then + res.assert_status("HTTP/1.1 200 OK"); + assert_eq!(res.body, expected); + } + + #[test] + fn should_extract_dapp_origin() { + // given + let (server, address) = serve(); + + // when + let req = r#"{"method":"hello","params":[],"jsonrpc":"2.0","id":1}"#; + let expected = "3A\n{\"jsonrpc\":\"2.0\",\"result\":\"Dapp http://parity.io\",\"id\":1}\n\n0\n\n"; + let res = request(server, + &format!("\ + POST / HTTP/1.1\r\n\ + Host: {}\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n\ + Origin: http://parity.io\r\n\ + Connection: close\r\n\ + User-Agent: curl/7.16.3\r\n\ + \r\n\ + {} + ", address, req.len(), req) + ); + + // then + res.assert_status("HTTP/1.1 200 OK"); + assert_eq!(res.body, expected); + } + + #[test] + fn should_extract_dapp_origin_from_extension() { + // given + let (server, address) = serve(); + + // when + let req = r#"{"method":"hello","params":[],"jsonrpc":"2.0","id":1}"#; + let expected = "44\n{\"jsonrpc\":\"2.0\",\"result\":\"Dapp http://wallet.ethereum.org\",\"id\":1}\n\n0\n\n"; + let res = request(server, + &format!("\ + POST / HTTP/1.1\r\n\ + Host: {}\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n\ + Origin: null\r\n\ + X-Parity-Origin: http://wallet.ethereum.org\r\n\ + Connection: close\r\n\ + User-Agent: curl/7.16.3\r\n\ + \r\n\ + {} + ", address, req.len(), req) + ); + + // then + res.assert_status("HTTP/1.1 200 OK"); + assert_eq!(res.body, expected); + } +} diff --git a/signer/src/tests/mod.rs b/rpc/src/tests/ws.rs similarity index 53% rename from signer/src/tests/mod.rs rename to rpc/src/tests/ws.rs index bc90a6cd37f..77fb1ea2cf4 100644 --- a/signer/src/tests/mod.rs +++ b/rpc/src/tests/ws.rs @@ -14,79 +14,42 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::ops::{Deref, DerefMut}; +//! WebSockets server tests. + use std::sync::Arc; use devtools::http_client; -use devtools::RandomTempPath; - -use rpc::ConfirmationsQueue; -use jsonrpc_core::IoHandler; -use jsonrpc_server_utils::reactor::RpcEventLoop; +use jsonrpc_core::MetaIoHandler; use rand; +use ws; -use ServerBuilder; -use Server; -use AuthCodes; - -/// Struct representing authcodes -pub struct GuardedAuthCodes { - authcodes: AuthCodes, - /// The path to the mock authcodes - pub path: RandomTempPath, -} -impl Deref for GuardedAuthCodes { - type Target = AuthCodes; - fn deref(&self) -> &Self::Target { - &self.authcodes - } -} -impl DerefMut for GuardedAuthCodes { - fn deref_mut(&mut self) -> &mut AuthCodes { - &mut self.authcodes - } -} - -/// Server with event loop -pub struct ServerLoop { - /// Signer Server - pub server: Server, - /// RPC Event Loop - pub event_loop: RpcEventLoop, -} - -impl Deref for ServerLoop { - type Target = Server; - - fn deref(&self) -> &Self::Target { - &self.server - } -} +use v1::{extractors, informant}; +use tests::helpers::{GuardedAuthCodes, Server}; /// Setup a mock signer for tests -pub fn serve() -> (ServerLoop, usize, GuardedAuthCodes) { - let mut path = RandomTempPath::new(); - path.panic_on_drop_failure = false; - let queue = Arc::new(ConfirmationsQueue::default()); - let builder = ServerBuilder::new(queue, path.to_path_buf()); +pub fn serve() -> (Server, usize, GuardedAuthCodes) { let port = 35000 + rand::random::() % 10000; - let event_loop = RpcEventLoop::spawn().unwrap(); - let io = IoHandler::default(); - let remote = event_loop.remote(); - let server = builder.start(format!("127.0.0.1:{}", port).parse().unwrap(), io, remote).unwrap(); - let res = ServerLoop { - server: server, - event_loop: event_loop, - }; - - (res, port, GuardedAuthCodes { - authcodes: AuthCodes::from_file(&path).unwrap(), - path: path, - }) + let address = format!("127.0.0.1:{}", port).parse().unwrap(); + let io = MetaIoHandler::default(); + let authcodes = GuardedAuthCodes::new(); + let stats = Arc::new(informant::RpcStats::default()); + + let res = Server::new(|remote| ::start_ws( + &address, + io, + remote, + ws::DomainsValidation::Disabled, + ws::DomainsValidation::Disabled, + extractors::WsExtractor::new(Some(&authcodes.path)), + extractors::WsExtractor::new(Some(&authcodes.path)), + extractors::WsStats::new(stats), + ).unwrap()); + + (res, port, authcodes) } /// Test a single request to running server -pub fn request(server: ServerLoop, request: &str) -> http_client::Response { +pub fn request(server: Server, request: &str) -> http_client::Response { http_client::request(server.server.addr(), request) } @@ -97,49 +60,6 @@ mod testing { use devtools::http_client; use super::{serve, request}; - #[test] - fn should_reject_invalid_host() { - // given - let server = serve().0; - - // when - let response = request(server, - "\ - GET / HTTP/1.1\r\n\ - Host: test:8180\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); - assert!(response.body.contains("URL Blocked")); - http_client::assert_security_headers_present(&response.headers, None); - } - - #[test] - fn should_allow_home_parity_host() { - // given - let server = serve().0; - - // when - let response = request(server, - "\ - GET http://parity.web3.site/ HTTP/1.1\r\n\ - Host: parity.web3.site\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - http_client::assert_security_headers_present(&response.headers, None); - } - #[test] fn should_not_redirect_to_parity_host() { // given @@ -157,48 +77,7 @@ mod testing { ); // then - assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - } - - #[test] - fn should_serve_styles_even_on_disallowed_domain() { - // given - let server = serve().0; - - // when - let response = request(server, - "\ - GET /styles.css HTTP/1.1\r\n\ - Host: test:8180\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - http_client::assert_security_headers_present(&response.headers, None); - } - - #[test] - fn should_return_200_ok_for_connect_requests() { - // given - let server = serve().0; - - // when - let response = request(server, - "\ - CONNECT parity.web3.site:8080 HTTP/1.1\r\n\ - Host: parity.web3.site\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + assert_eq!(response.status, "HTTP/1.1 200 Ok".to_owned()); } #[test] @@ -221,7 +100,7 @@ mod testing { ); // then - assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); + assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned()); http_client::assert_security_headers_present(&response.headers, None); } @@ -300,7 +179,7 @@ mod testing { // then assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned()); - assert_eq!(response2.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); + assert_eq!(response2.status, "HTTP/1.1 403 Forbidden".to_owned()); http_client::assert_security_headers_present(&response2.headers, None); } } diff --git a/rpc/src/v1/extractors.rs b/rpc/src/v1/extractors.rs new file mode 100644 index 00000000000..1feaf4d9b81 --- /dev/null +++ b/rpc/src/v1/extractors.rs @@ -0,0 +1,263 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Parity-specific metadata extractors. + +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use authcodes; +use http_common::HttpMetaExtractor; +use ipc; +use jsonrpc_core as core; +use jsonrpc_pubsub::Session; +use ws; +use util::H256; + +use v1::{Metadata, Origin}; +use v1::informant::RpcStats; + +/// Common HTTP & IPC metadata extractor. +pub struct RpcExtractor; + +impl HttpMetaExtractor for RpcExtractor { + type Metadata = Metadata; + + fn read_metadata(&self, origin: Option, user_agent: Option, dapps_origin: Option) -> Metadata { + let mut metadata = Metadata::default(); + + metadata.origin = match (origin.as_ref().map(|s| s.as_str()), user_agent, dapps_origin) { + (Some("null"), _, Some(dapp)) => Origin::Dapps(dapp.into()), + (Some(dapp), _, _) => Origin::Dapps(dapp.to_owned().into()), + (None, Some(service), _) => Origin::Rpc(service.into()), + (None, _, _) => Origin::Rpc("unknown".into()), + }; + + metadata + } +} + +impl ipc::MetaExtractor for RpcExtractor { + fn extract(&self, _req: &ipc::RequestContext) -> Metadata { + let mut metadata = Metadata::default(); + // TODO [ToDr] Extract proper session id when it's available in context. + metadata.origin = Origin::Ipc(1.into()); + metadata + } +} + +/// WebSockets server metadata extractor and request middleware. +pub struct WsExtractor { + authcodes_path: Option, +} + +impl WsExtractor { + /// Creates new `WsExtractor` with given authcodes path. + pub fn new(path: Option<&Path>) -> Self { + WsExtractor { + authcodes_path: path.map(|p| p.to_owned()), + } + } +} + +impl ws::MetaExtractor for WsExtractor { + fn extract(&self, req: &ws::RequestContext) -> Metadata { + let mut metadata = Metadata::default(); + let id = req.session_id as u64; + // TODO [ToDr] Extract dapp from Origin + let dapp = "".into(); + metadata.origin = match self.authcodes_path { + Some(ref path) => { + let authorization = req.protocols.get(0).and_then(|p| auth_token_hash(&path, p)); + match authorization { + Some(id) => Origin::Signer { session: id.into(), dapp: dapp }, + None => Origin::Ws { session: id.into(), dapp: dapp }, + } + }, + None => Origin::Ws { session: id.into(), dapp: dapp }, + }; + metadata.session = Some(Arc::new(Session::new(req.sender()))); + metadata + } +} + +impl ws::RequestMiddleware for WsExtractor { + fn process(&self, req: &ws::ws::Request) -> ws::MiddlewareAction { + use self::ws::ws::Response; + + // Reply with 200 Ok to HEAD requests. + if req.method() == "HEAD" { + let mut response = Response::new(200, "Ok"); + add_security_headers(&mut response); + return Some(response).into(); + } + + // Display WS info. + if req.header("sec-websocket-key").is_none() { + let mut response = Response::new(200, "Ok"); + response.set_body("WebSocket interface is active. Open WS connection to access RPC."); + add_security_headers(&mut response); + return Some(response).into(); + } + + // If protocol is provided it needs to be valid. + let protocols = req.protocols().ok().unwrap_or_else(Vec::new); + if let Some(ref path) = self.authcodes_path { + if protocols.len() == 1 { + let authorization = auth_token_hash(&path, protocols[0]); + if authorization.is_none() { + warn!( + "Blocked connection from {} using invalid token.", + req.header("origin").and_then(|e| ::std::str::from_utf8(e).ok()).unwrap_or("Unknown Origin") + ); + let mut response = Response::new(403, "Forbidden"); + add_security_headers(&mut response); + return Some(response).into(); + } + } + } + + // Otherwise just proceed. + ws::MiddlewareAction::Proceed + } +} + +fn add_security_headers(res: &mut ws::ws::Response) { + let mut headers = res.headers_mut(); + headers.push(("X-Frame-Options".into(), b"SAMEORIGIN".to_vec())); + headers.push(("X-XSS-Protection".into(), b"1; mode=block".to_vec())); + headers.push(("X-Content-Type-Options".into(), b"nosniff".to_vec())); +} + +fn auth_token_hash(codes_path: &Path, protocol: &str) -> Option { + let mut split = protocol.split('_'); + let auth = split.next().and_then(|v| v.parse().ok()); + let time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok()); + + if let (Some(auth), Some(time)) = (auth, time) { + // Check if the code is valid + return authcodes::AuthCodes::from_file(codes_path) + .ok() + .and_then(|mut codes| { + // remove old tokens + codes.clear_garbage(); + + let res = codes.is_valid(&auth, time); + // make sure to save back authcodes - it might have been modified + if codes.to_file(codes_path).is_err() { + warn!(target: "signer", "Couldn't save authorization codes to file."); + } + + if res { + Some(auth) + } else { + None + } + }) + } + + None +} + +/// WebSockets RPC usage statistics. +pub struct WsStats { + stats: Arc, +} + +impl WsStats { + /// Creates new WS usage tracker. + pub fn new(stats: Arc) -> Self { + WsStats { + stats: stats, + } + } +} + +impl ws::SessionStats for WsStats { + fn open_session(&self, _id: ws::SessionId) { + self.stats.open_session() + } + + fn close_session(&self, _id: ws::SessionId) { + self.stats.close_session() + } +} + +/// WebSockets middleware dispatching requests to different handles dependning on metadata. +pub struct WsDispatcher> { + full_handler: core::MetaIoHandler, +} + +impl> WsDispatcher { + /// Create new `WsDispatcher` with given full handler. + pub fn new(full_handler: core::MetaIoHandler) -> Self { + WsDispatcher { + full_handler: full_handler, + } + } +} + +impl> core::Middleware for WsDispatcher { + fn on_request(&self, request: core::Request, meta: Metadata, process: F) -> core::FutureResponse where + F: FnOnce(core::Request, Metadata) -> core::FutureResponse, + { + let use_full = match &meta.origin { + &Origin::Signer { .. } => true, + _ => false, + }; + + if use_full { + self.full_handler.handle_rpc_request(request, meta) + } else { + process(request, meta) + } + } +} + +#[cfg(test)] +mod tests { + use super::RpcExtractor; + use {HttpMetaExtractor, Origin}; + + #[test] + fn should_extract_rpc_origin() { + // given + let extractor = RpcExtractor; + + // when + let meta1 = extractor.read_metadata(None, None, None); + let meta2 = extractor.read_metadata(None, Some("http://parity.io".to_owned()), None); + let meta3 = extractor.read_metadata(None, Some("http://parity.io".to_owned()), Some("ignored".into())); + + // then + assert_eq!(meta1.origin, Origin::Rpc("unknown".into())); + assert_eq!(meta2.origin, Origin::Rpc("http://parity.io".into())); + assert_eq!(meta3.origin, Origin::Rpc("http://parity.io".into())); + } + + #[test] + fn should_dapps_origin() { + // given + let extractor = RpcExtractor; + let dapp = "https://wallet.ethereum.org".to_owned(); + + // when + let meta = extractor.read_metadata(Some("null".into()), None, Some(dapp.clone())); + + // then + assert_eq!(meta.origin, Origin::Dapps(dapp.into())); + } +} diff --git a/rpc/src/v1/helpers/dapps.rs b/rpc/src/v1/helpers/dapps.rs new file mode 100644 index 00000000000..34f4fe1b5dd --- /dev/null +++ b/rpc/src/v1/helpers/dapps.rs @@ -0,0 +1,33 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Dapps Service + +use v1::types::LocalDapp; + +/// Dapps Server service. +pub trait DappsService: Send + Sync + 'static { + /// List available local dapps. + fn list_dapps(&self) -> Vec; +} + +impl DappsService for F where + F: Fn() -> Vec + Send + Sync + 'static +{ + fn list_dapps(&self) -> Vec { + (*self)() + } +} diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 02b5848df89..8ec266c7690 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -209,6 +209,14 @@ pub fn dapps_disabled() -> Error { } } +pub fn ws_disabled() -> Error { + Error { + code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), + message: "WebSockets Server is disabled. This API is not available.".into(), + data: None, + } +} + pub fn network_disabled() -> Error { Error { code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index 1f950f11325..fcb452039e0 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -19,10 +19,10 @@ pub mod errors; pub mod accounts; pub mod block_import; +pub mod dapps; pub mod dispatch; pub mod fake_sign; pub mod light_fetch; -pub mod informant; pub mod oneshot; pub mod ipfs; pub mod secretstore; @@ -50,3 +50,7 @@ pub use self::signing_queue::{ pub use self::signer::SignerService; pub use self::subscribers::Subscribers; pub use self::subscription_manager::GenericPollManager; + +pub fn to_url(address: &Option<(String, u16)>) -> Option { + address.as_ref().map(|&(ref iface, ref port)| format!("{}:{}", iface, port)) +} diff --git a/rpc/src/v1/helpers/signer.rs b/rpc/src/v1/helpers/signer.rs index 52c3e731dfd..f35832a408a 100644 --- a/rpc/src/v1/helpers/signer.rs +++ b/rpc/src/v1/helpers/signer.rs @@ -27,21 +27,21 @@ const TOKEN_LIFETIME_SECS: u32 = 3600; /// Manages communication with Signer crate pub struct SignerService { + is_enabled: bool, queue: Arc, web_proxy_tokens: Mutex>, generate_new_token: Box Result + Send + Sync + 'static>, - address: Option<(String, u16)>, } impl SignerService { /// Creates new Signer Service given function to generate new tokens. - pub fn new(new_token: F, address: Option<(String, u16)>) -> Self + pub fn new(new_token: F, is_enabled: bool) -> Self where F: Fn() -> Result + Send + Sync + 'static { SignerService { queue: Arc::new(ConfirmationsQueue::default()), web_proxy_tokens: Mutex::new(TransientHashMap::new(TOKEN_LIFETIME_SECS)), generate_new_token: Box::new(new_token), - address: address, + is_enabled: is_enabled, } } @@ -69,20 +69,15 @@ impl SignerService { self.queue.clone() } - /// Returns signer address (if signer enabled) or `None` otherwise - pub fn address(&self) -> Option<(String, u16)> { - self.address.clone() - } - /// Returns true if Signer is enabled. pub fn is_enabled(&self) -> bool { - self.address.is_some() + self.is_enabled } #[cfg(test)] /// Creates new Signer Service for tests. - pub fn new_test(address: Option<(String, u16)>) -> Self { - SignerService::new(|| Ok("new_token".into()), address) + pub fn new_test(is_enabled: bool) -> Self { + SignerService::new(|| Ok("new_token".into()), is_enabled) } } diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index 015d4fef75f..f0cdb5236d4 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -33,7 +33,7 @@ use light::client::LightChainClient; use jsonrpc_core::Error; use jsonrpc_macros::Trailing; -use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings}; +use v1::helpers::{self, errors, ipfs, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::dispatch::LightDispatcher; use v1::helpers::light_fetch::LightFetch; use v1::metadata::Metadata; @@ -54,8 +54,8 @@ pub struct ParityClient { logger: Arc, settings: Arc, signer: Option>, - dapps_interface: Option, - dapps_port: Option, + dapps_address: Option<(String, u16)>, + ws_address: Option<(String, u16)>, eip86_transition: u64, } @@ -68,8 +68,8 @@ impl ParityClient { logger: Arc, settings: Arc, signer: Option>, - dapps_interface: Option, - dapps_port: Option, + dapps_address: Option<(String, u16)>, + ws_address: Option<(String, u16)>, ) -> Self { ParityClient { light_dispatch: light_dispatch, @@ -77,8 +77,8 @@ impl ParityClient { logger: logger, settings: settings, signer: signer, - dapps_interface: dapps_interface, - dapps_port: dapps_port, + dapps_address: dapps_address, + ws_address: ws_address, eip86_transition: client.eip86_transition(), } } @@ -294,22 +294,14 @@ impl Parity for ParityClient { Ok(map) } - fn signer_port(&self) -> Result { - self.signer - .clone() - .and_then(|signer| signer.address()) - .map(|address| address.1) - .ok_or_else(|| errors::signer_disabled()) - } - - fn dapps_port(&self) -> Result { - self.dapps_port + fn dapps_url(&self) -> Result { + helpers::to_url(&self.dapps_address) .ok_or_else(|| errors::dapps_disabled()) } - fn dapps_interface(&self) -> Result { - self.dapps_interface.clone() - .ok_or_else(|| errors::dapps_disabled()) + fn ws_url(&self) -> Result { + helpers::to_url(&self.ws_address) + .ok_or_else(|| errors::ws_disabled()) } fn next_nonce(&self, address: H160) -> BoxFuture { diff --git a/rpc/src/v1/impls/light/parity_set.rs b/rpc/src/v1/impls/light/parity_set.rs index 40af2f44cab..cab2fa91c5e 100644 --- a/rpc/src/v1/impls/light/parity_set.rs +++ b/rpc/src/v1/impls/light/parity_set.rs @@ -26,21 +26,24 @@ use futures::{BoxFuture, Future}; use util::sha3; use jsonrpc_core::Error; +use v1::helpers::dapps::DappsService; use v1::helpers::errors; use v1::traits::ParitySet; -use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction}; +use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction, LocalDapp}; /// Parity-specific rpc interface for operations altering the settings. pub struct ParitySetClient { net: Arc, + dapps: Option>, fetch: F, } impl ParitySetClient { /// Creates new `ParitySetClient` with given `Fetch`. - pub fn new(net: Arc, fetch: F) -> Self { + pub fn new(net: Arc, dapps: Option>, fetch: F) -> Self { ParitySetClient { net: net, + dapps: dapps, fetch: fetch, } } @@ -132,6 +135,10 @@ impl ParitySet for ParitySetClient { })) } + fn dapps_list(&self) -> Result, Error> { + self.dapps.as_ref().map(|dapps| dapps.list_dapps()).ok_or_else(errors::dapps_disabled) + } + fn upgrade_ready(&self) -> Result, Error> { Err(errors::light_unimplemented(None)) } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index ae91af54cde..377be5bb0ae 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -38,7 +38,7 @@ use crypto::DEFAULT_MAC; use jsonrpc_core::Error; use jsonrpc_macros::Trailing; -use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings}; +use v1::helpers::{self, errors, ipfs, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::accounts::unwrap_provider; use v1::metadata::Metadata; use v1::traits::Parity; @@ -67,8 +67,8 @@ pub struct ParityClient where logger: Arc, settings: Arc, signer: Option>, - dapps_interface: Option, - dapps_port: Option, + dapps_address: Option<(String, u16)>, + ws_address: Option<(String, u16)>, eip86_transition: u64, } @@ -89,8 +89,8 @@ impl ParityClient where logger: Arc, settings: Arc, signer: Option>, - dapps_interface: Option, - dapps_port: Option, + dapps_address: Option<(String, u16)>, + ws_address: Option<(String, u16)>, ) -> Self { ParityClient { client: Arc::downgrade(client), @@ -102,8 +102,8 @@ impl ParityClient where logger: logger, settings: settings, signer: signer, - dapps_interface: dapps_interface, - dapps_port: dapps_port, + dapps_address: dapps_address, + ws_address: ws_address, eip86_transition: client.eip86_transition(), } } @@ -317,22 +317,14 @@ impl Parity for ParityClient where ) } - fn signer_port(&self) -> Result { - self.signer - .clone() - .and_then(|signer| signer.address()) - .map(|address| address.1) - .ok_or_else(|| errors::signer_disabled()) - } - - fn dapps_port(&self) -> Result { - self.dapps_port + fn dapps_url(&self) -> Result { + helpers::to_url(&self.dapps_address) .ok_or_else(|| errors::dapps_disabled()) } - fn dapps_interface(&self) -> Result { - self.dapps_interface.clone() - .ok_or_else(|| errors::dapps_disabled()) + fn ws_url(&self) -> Result { + helpers::to_url(&self.ws_address) + .ok_or_else(|| errors::ws_disabled()) } fn next_nonce(&self, address: H160) -> BoxFuture { diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index 7bd8bcb9178..a6baa4a9496 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -28,9 +28,10 @@ use util::sha3; use updater::{Service as UpdateService}; use jsonrpc_core::Error; +use v1::helpers::dapps::DappsService; use v1::helpers::errors; use v1::traits::ParitySet; -use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction}; +use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction, LocalDapp}; /// Parity-specific rpc interface for operations altering the settings. pub struct ParitySetClient { @@ -38,6 +39,7 @@ pub struct ParitySetClient { miner: Weak, updater: Weak, net: Weak, + dapps: Option>, fetch: F, eip86_transition: u64, } @@ -46,12 +48,20 @@ impl ParitySetClient where C: MiningBlockChainClient + 'static, { /// Creates new `ParitySetClient` with given `Fetch`. - pub fn new(client: &Arc, miner: &Arc, updater: &Arc, net: &Arc, fetch: F) -> Self { + pub fn new( + client: &Arc, + miner: &Arc, + updater: &Arc, + net: &Arc, + dapps: Option>, + fetch: F, + ) -> Self { ParitySetClient { client: Arc::downgrade(client), miner: Arc::downgrade(miner), updater: Arc::downgrade(updater), net: Arc::downgrade(net), + dapps: dapps, fetch: fetch, eip86_transition: client.eip86_transition(), } @@ -166,6 +176,10 @@ impl ParitySet for ParitySetClient where })) } + fn dapps_list(&self) -> Result, Error> { + self.dapps.as_ref().map(|dapps| dapps.list_dapps()).ok_or_else(errors::dapps_disabled) + } + fn upgrade_ready(&self) -> Result, Error> { let updater = take_weak!(self.updater); Ok(updater.upgrade_ready().map(Into::into)) diff --git a/rpc/src/v1/helpers/informant.rs b/rpc/src/v1/informant.rs similarity index 100% rename from rpc/src/v1/helpers/informant.rs rename to rpc/src/v1/informant.rs diff --git a/rpc/src/v1/metadata.rs b/rpc/src/v1/metadata.rs index 74567d510ae..b65003ad83b 100644 --- a/rpc/src/v1/metadata.rs +++ b/rpc/src/v1/metadata.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Parity RPC requests Metadata. use std::sync::Arc; use jsonrpc_core; @@ -35,7 +36,9 @@ impl Metadata { pub fn dapp_id(&self) -> DappId { // TODO [ToDr] Extract dapp info from Ws connections. match self.origin { - Origin::Dapps(ref dapp_id) => dapp_id.clone(), + Origin::Dapps(ref dapp) => dapp.clone(), + Origin::Ws { ref dapp, .. } => dapp.clone(), + Origin::Signer { ref dapp, .. } => dapp.clone(), _ => DappId::default(), } } diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index bd8675196ce..a50b6ec90c7 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -52,14 +52,30 @@ macro_rules! try_bf { #[macro_use] mod helpers; mod impls; -mod metadata; +mod types; +#[cfg(test)] +mod tests; +pub mod extractors; +pub mod informant; +pub mod metadata; pub mod traits; -pub mod tests; -pub mod types; pub use self::traits::{Web3, Eth, EthFilter, EthPubSub, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, PubSub, Signer, Personal, Traces, Rpc, SecretStore}; pub use self::impls::*; -pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import, informant, dispatch}; +pub use self::helpers::{NetworkSettings, block_import, dispatch}; pub use self::metadata::Metadata; pub use self::types::Origin; +pub use self::extractors::{RpcExtractor, WsExtractor, WsStats, WsDispatcher}; + +/// Signer utilities +pub mod signer { + pub use super::helpers::{SigningQueue, SignerService, ConfirmationsQueue}; + pub use super::types::{ConfirmationRequest, TransactionModification, U256, TransactionCondition}; +} + +/// Dapps integration utilities +pub mod dapps { + pub use super::helpers::dapps::DappsService; + pub use super::types::LocalDapp; +} diff --git a/rpc/src/v1/tests/helpers/dapps.rs b/rpc/src/v1/tests/helpers/dapps.rs new file mode 100644 index 00000000000..44148ab0f1c --- /dev/null +++ b/rpc/src/v1/tests/helpers/dapps.rs @@ -0,0 +1,37 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Test implementation of dapps service. + +use v1::types::LocalDapp; +use v1::helpers::dapps::DappsService; + +/// Test implementation of dapps service. Will always return the same list of dapps. +#[derive(Default, Clone)] +pub struct TestDappsService; + +impl DappsService for TestDappsService { + fn list_dapps(&self) -> Vec { + vec![LocalDapp { + id: "skeleton".into(), + name: "Skeleton".into(), + description: "A skeleton dapp".into(), + version: "0.1".into(), + author: "Parity Technologies Ltd".into(), + icon_url: "title.png".into(), + }] + } +} diff --git a/rpc/src/v1/tests/helpers/mod.rs b/rpc/src/v1/tests/helpers/mod.rs index 35bd60f5610..aae48a2d280 100644 --- a/rpc/src/v1/tests/helpers/mod.rs +++ b/rpc/src/v1/tests/helpers/mod.rs @@ -16,14 +16,16 @@ //! Test rpc services. -mod sync_provider; -mod miner_service; +mod dapps; mod fetch; +mod miner_service; mod snapshot_service; +mod sync_provider; mod update_service; -pub use self::sync_provider::{Config, TestSyncProvider}; -pub use self::miner_service::TestMinerService; +pub use self::dapps::TestDappsService; pub use self::fetch::TestFetch; +pub use self::miner_service::TestMinerService; pub use self::snapshot_service::TestSnapshotService; -pub use self::update_service::TestUpdater; \ No newline at end of file +pub use self::sync_provider::{Config, TestSyncProvider}; +pub use self::update_service::TestUpdater; diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 23b68e853bd..aeeb7902de7 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -41,8 +41,8 @@ pub struct Dependencies { pub settings: Arc, pub network: Arc, pub accounts: Arc, - pub dapps_interface: Option, - pub dapps_port: Option, + pub dapps_address: Option<(String, u16)>, + pub ws_address: Option<(String, u16)>, } impl Dependencies { @@ -66,8 +66,8 @@ impl Dependencies { }), network: Arc::new(TestManageNetwork), accounts: Arc::new(AccountProvider::transient_provider()), - dapps_interface: Some("127.0.0.1".into()), - dapps_port: Some(18080), + dapps_address: Some(("127.0.0.1".into(), 18080)), + ws_address: Some(("127.0.0.1".into(), 18546)), } } @@ -84,8 +84,8 @@ impl Dependencies { self.logger.clone(), self.settings.clone(), signer, - self.dapps_interface.clone(), - self.dapps_port, + self.dapps_address.clone(), + self.ws_address.clone(), ) } @@ -345,7 +345,7 @@ fn rpc_parity_node_name() { #[test] fn rpc_parity_unsigned_transactions_count() { let deps = Dependencies::new(); - let io = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180)))); + let io = deps.with_signer(SignerService::new_test(true)); let request = r#"{"jsonrpc": "2.0", "method": "parity_unsignedTransactionsCount", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":0,"id":1}"#; @@ -386,34 +386,17 @@ fn rpc_parity_encrypt() { } #[test] -fn rpc_parity_signer_port() { - // given - let deps = Dependencies::new(); - let io1 = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180)))); - let io2 = deps.default_client(); - - // when - let request = r#"{"jsonrpc": "2.0", "method": "parity_signerPort", "params": [], "id": 1}"#; - let response1 = r#"{"jsonrpc":"2.0","result":18180,"id":1}"#; - let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"Trusted Signer is disabled. This API is not available."},"id":1}"#; - - // then - assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned())); - assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned())); -} - -#[test] -fn rpc_parity_dapps_port() { +fn rpc_parity_ws_address() { // given let mut deps = Dependencies::new(); let io1 = deps.default_client(); - deps.dapps_port = None; + deps.ws_address = None; let io2 = deps.default_client(); // when - let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsPort", "params": [], "id": 1}"#; - let response1 = r#"{"jsonrpc":"2.0","result":18080,"id":1}"#; - let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"Dapps Server is disabled. This API is not available."},"id":1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_wsUrl", "params": [], "id": 1}"#; + let response1 = r#"{"jsonrpc":"2.0","result":"127.0.0.1:18546","id":1}"#; + let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"WebSockets Server is disabled. This API is not available."},"id":1}"#; // then assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned())); @@ -421,16 +404,16 @@ fn rpc_parity_dapps_port() { } #[test] -fn rpc_parity_dapps_interface() { +fn rpc_parity_dapps_address() { // given let mut deps = Dependencies::new(); let io1 = deps.default_client(); - deps.dapps_interface = None; + deps.dapps_address = None; let io2 = deps.default_client(); // when - let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsInterface", "params": [], "id": 1}"#; - let response1 = r#"{"jsonrpc":"2.0","result":"127.0.0.1","id":1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsUrl", "params": [], "id": 1}"#; + let response1 = r#"{"jsonrpc":"2.0","result":"127.0.0.1:18080","id":1}"#; let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"Dapps Server is disabled. This API is not available."},"id":1}"#; // then diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index 0e2d6ab276e..cff9e710dbe 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -25,7 +25,7 @@ use ethsync::ManageNetwork; use jsonrpc_core::IoHandler; use v1::{ParitySet, ParitySetClient}; -use v1::tests::helpers::{TestMinerService, TestFetch, TestUpdater}; +use v1::tests::helpers::{TestMinerService, TestFetch, TestUpdater, TestDappsService}; use super::manage_network::TestManageNetwork; fn miner_service() -> Arc { @@ -46,8 +46,14 @@ fn updater_service() -> Arc { pub type TestParitySetClient = ParitySetClient; -fn parity_set_client(client: &Arc, miner: &Arc, updater: &Arc, net: &Arc) -> TestParitySetClient { - ParitySetClient::new(client, miner, updater, &(net.clone() as Arc), TestFetch::default()) +fn parity_set_client( + client: &Arc, + miner: &Arc, + updater: &Arc, + net: &Arc, +) -> TestParitySetClient { + let dapps_service = Arc::new(TestDappsService); + ParitySetClient::new(client, miner, updater, &(net.clone() as Arc), Some(dapps_service), TestFetch::default()) } #[test] @@ -232,3 +238,18 @@ fn rpc_parity_remove_transaction() { miner.pending_transactions.lock().insert(hash, signed); assert_eq!(io.handle_request_sync(&request), Some(response.to_owned())); } + +#[test] +fn rpc_parity_set_dapps_list() { + let miner = miner_service(); + let client = client_service(); + let network = network_service(); + let updater = updater_service(); + let mut io = IoHandler::new(); + io.extend_with(parity_set_client(&client, &miner, &updater, &network).to_delegate()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsList", "params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":[{"author":"Parity Technologies Ltd","description":"A skeleton dapp","iconUrl":"title.png","id":"skeleton","name":"Skeleton","version":"0.1"}],"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); +} diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index afa008cf379..e2e54b2e3b2 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -58,7 +58,7 @@ fn miner_service() -> Arc { } fn signer_tester() -> SignerTester { - let signer = Arc::new(SignerService::new_test(None)); + let signer = Arc::new(SignerService::new_test(false)); let accounts = accounts_provider(); let opt_accounts = Some(accounts.clone()); let client = blockchain_client(); diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index 8963286ea2e..3e1f67154b2 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -47,7 +47,7 @@ struct SigningTester { impl Default for SigningTester { fn default() -> Self { - let signer = Arc::new(SignerService::new_test(None)); + let signer = Arc::new(SignerService::new_test(false)); let client = Arc::new(TestBlockChainClient::default()); let miner = Arc::new(TestMinerService::default()); let accounts = Arc::new(AccountProvider::transient_provider()); diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index 498d1b82dea..92904aa4089 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -151,17 +151,13 @@ build_rpc_trait! { #[rpc(name = "parity_localTransactions")] fn local_transactions(&self) -> Result, Error>; - /// Returns current Trusted Signer port or an error if signer is disabled. - #[rpc(name = "parity_signerPort")] - fn signer_port(&self) -> Result; + /// Returns current Dapps Server interface and port or an error if dapps server is disabled. + #[rpc(name = "parity_dappsUrl")] + fn dapps_url(&self) -> Result; - /// Returns current Dapps Server port or an error if dapps server is disabled. - #[rpc(name = "parity_dappsPort")] - fn dapps_port(&self) -> Result; - - /// Returns current Dapps Server interface address or an error if dapps server is disabled. - #[rpc(name = "parity_dappsInterface")] - fn dapps_interface(&self) -> Result; + /// Returns current WS Server interface and port or an error if ws server is disabled. + #[rpc(name = "parity_wsUrl")] + fn ws_url(&self) -> Result; /// Returns next nonce for particular sender. Should include all transactions in the queue. #[rpc(async, name = "parity_nextNonce")] diff --git a/rpc/src/v1/traits/parity_set.rs b/rpc/src/v1/traits/parity_set.rs index b91b335746c..1feb39718d0 100644 --- a/rpc/src/v1/traits/parity_set.rs +++ b/rpc/src/v1/traits/parity_set.rs @@ -19,7 +19,7 @@ use jsonrpc_core::Error; use futures::BoxFuture; -use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction}; +use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction, LocalDapp}; build_rpc_trait! { /// Parity-specific rpc interface for operations altering the settings. @@ -96,6 +96,10 @@ build_rpc_trait! { #[rpc(async, name = "parity_hashContent")] fn hash_content(&self, String) -> BoxFuture; + /// Returns a list of local dapps + #[rpc(name = "parity_dappsList")] + fn dapps_list(&self) -> Result, Error>; + /// Is there a release ready for install? #[rpc(name = "parity_upgradeReady")] fn upgrade_ready(&self) -> Result, Error>; diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index 9a6d6837af1..bbc8b350e04 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -283,12 +283,15 @@ mod tests { nonce: Some(1.into()), condition: None, }), - origin: Origin::Signer(5.into()), + origin: Origin::Signer { + dapp: "http://parity.io".into(), + session: 5.into(), + } }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":{"signer":"0x0000000000000000000000000000000000000000000000000000000000000005"}}"#; + let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":{"signer":{"dapp":"http://parity.io","session":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); diff --git a/rpc/src/v1/types/dapps.rs b/rpc/src/v1/types/dapps.rs new file mode 100644 index 00000000000..fb4a868f088 --- /dev/null +++ b/rpc/src/v1/types/dapps.rs @@ -0,0 +1,57 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +/// Local Dapp +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct LocalDapp { + /// ID of local dapp + pub id: String, + /// Dapp name + pub name: String, + /// Dapp description + pub description: String, + /// Dapp version string + pub version: String, + /// Dapp author + pub author: String, + /// Dapp icon + #[serde(rename="iconUrl")] + pub icon_url: String, +} + +#[cfg(test)] +mod tests { + use serde_json; + use super::LocalDapp; + + #[test] + fn dapp_serialization() { + let s = r#"{"id":"skeleton","name":"Skeleton","description":"A skeleton dapp","version":"0.1","author":"Parity Technologies Ltd","iconUrl":"title.png"}"#; + + let dapp = LocalDapp { + id: "skeleton".into(), + name: "Skeleton".into(), + description: "A skeleton dapp".into(), + version: "0.1".into(), + author: "Parity Technologies Ltd".into(), + icon_url: "title.png".into(), + }; + + let serialized = serde_json::to_string(&dapp).unwrap(); + assert_eq!(serialized, s); + } +} diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index 97f2eb2ae67..ef90d844b9e 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -24,6 +24,7 @@ mod bytes; mod call_request; mod confirmations; mod consensus_status; +mod dapps; mod derivation; mod filter; mod hash; @@ -55,6 +56,7 @@ pub use self::confirmations::{ TransactionModification, SignRequest, DecryptRequest, Either }; pub use self::consensus_status::*; +pub use self::dapps::LocalDapp; pub use self::derivation::{DeriveHash, DeriveHierarchical, Derive}; pub use self::filter::{Filter, FilterChanges}; pub use self::hash::{H64, H160, H256, H512, H520, H2048}; diff --git a/rpc/src/v1/types/provenance.rs b/rpc/src/v1/types/provenance.rs index 81201f8e4e7..b52f0cb7746 100644 --- a/rpc/src/v1/types/provenance.rs +++ b/rpc/src/v1/types/provenance.rs @@ -33,12 +33,22 @@ pub enum Origin { /// IPC server (includes session hash) #[serde(rename="ipc")] Ipc(H256), - /// WS server (includes session hash) + /// WS server #[serde(rename="ws")] - Ws(H256), - /// Signer (includes session hash) + Ws { + /// Dapp id + dapp: DappId, + /// Session id + session: H256, + }, + /// Signer (authorized WS server) #[serde(rename="signer")] - Signer(H256), + Signer { + /// Dapp id + dapp: DappId, + /// Session id + session: H256 + }, /// Unknown #[serde(rename="unknown")] Unknown, @@ -53,11 +63,11 @@ impl Default for Origin { impl fmt::Display for Origin { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Origin::Rpc(ref origin) => write!(f, "RPC (service: {})", origin), + Origin::Rpc(ref origin) => write!(f, "{} via RPC", origin), Origin::Dapps(ref origin) => write!(f, "Dapp {}", origin), Origin::Ipc(ref session) => write!(f, "IPC (session: {})", session), - Origin::Ws(ref session) => write!(f, "WebSocket (session: {})", session), - Origin::Signer(ref session) => write!(f, "UI (session: {})", session), + Origin::Ws { ref session, ref dapp } => write!(f, "{} via WebSocket (session: {})", dapp, session), + Origin::Signer { ref session, ref dapp } => write!(f, "{} via UI (session: {})", dapp, session), Origin::Unknown => write!(f, "unknown origin"), } } @@ -114,9 +124,15 @@ mod tests { let o1 = Origin::Rpc("test service".into()); let o2 = Origin::Dapps("http://parity.io".into()); let o3 = Origin::Ipc(5.into()); - let o4 = Origin::Signer(10.into()); + let o4 = Origin::Signer { + dapp: "http://parity.io".into(), + session: 10.into(), + }; let o5 = Origin::Unknown; - let o6 = Origin::Ws(5.into()); + let o6 = Origin::Ws { + dapp: "http://parity.io".into(), + session: 5.into(), + }; // when let res1 = serde_json::to_string(&o1).unwrap(); @@ -130,9 +146,9 @@ mod tests { assert_eq!(res1, r#"{"rpc":"test service"}"#); assert_eq!(res2, r#"{"dapp":"http://parity.io"}"#); assert_eq!(res3, r#"{"ipc":"0x0000000000000000000000000000000000000000000000000000000000000005"}"#); - assert_eq!(res4, r#"{"signer":"0x000000000000000000000000000000000000000000000000000000000000000a"}"#); + assert_eq!(res4, r#"{"signer":{"dapp":"http://parity.io","session":"0x000000000000000000000000000000000000000000000000000000000000000a"}}"#); assert_eq!(res5, r#""unknown""#); - assert_eq!(res6, r#"{"ws":"0x0000000000000000000000000000000000000000000000000000000000000005"}"#); + assert_eq!(res6, r#"{"ws":{"dapp":"http://parity.io","session":"0x0000000000000000000000000000000000000000000000000000000000000005"}}"#); } #[test] diff --git a/rpc_cli/src/lib.rs b/rpc_cli/src/lib.rs index 8d00405b561..23aa06f151c 100644 --- a/rpc_cli/src/lib.rs +++ b/rpc_cli/src/lib.rs @@ -7,7 +7,7 @@ extern crate ethcore_bigint as bigint; extern crate parity_rpc as rpc; extern crate parity_rpc_client as client; -use rpc::v1::types::{U256, ConfirmationRequest}; +use rpc::signer::{U256, ConfirmationRequest}; use client::signer_client::SignerRpc; use std::io::{Write, BufRead, BufReader, stdout, stdin}; use std::path::PathBuf; diff --git a/rpc_client/Cargo.toml b/rpc_client/Cargo.toml index c4e68acefeb..6af9b4d25b1 100644 --- a/rpc_client/Cargo.toml +++ b/rpc_client/Cargo.toml @@ -14,8 +14,8 @@ serde = "0.9" serde_json = "0.9" tempdir = "0.3.5" url = "1.2.0" +matches = "0.1" jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } -ws = { git = "https://github.com/paritytech/ws-rs.git", branch = "parity-1.7" } +jsonrpc-ws-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } parity-rpc = { path = "../rpc" } -ethcore-signer = { path = "../signer" } ethcore-util = { path = "../util" } diff --git a/rpc_client/src/client.rs b/rpc_client/src/client.rs index 3ef7ad7cea8..f31570c7a87 100644 --- a/rpc_client/src/client.rs +++ b/rpc_client/src/client.rs @@ -13,7 +13,7 @@ use util::{Hashable, Mutex}; use url::Url; use std::fs::File; -use ws::{ +use ws::ws::{ self, Request, Handler, @@ -204,6 +204,7 @@ impl Rpc { let rpc = Self::connect(url, authpath).map(|rpc| rpc).wait()?; rpc } + /// Non-blocking, returns a future pub fn connect( url: &str, authpath: &PathBuf @@ -241,6 +242,7 @@ impl Rpc { } } } + /// Non-blocking, returns a future of the request response pub fn request( &mut self, method: &'static str, params: Vec diff --git a/rpc_client/src/lib.rs b/rpc_client/src/lib.rs index e2f53e60669..d1967ccbd58 100644 --- a/rpc_client/src/lib.rs +++ b/rpc_client/src/lib.rs @@ -1,34 +1,36 @@ pub mod client; pub mod signer_client; -extern crate ethcore_signer; extern crate ethcore_util as util; extern crate futures; extern crate jsonrpc_core; +extern crate jsonrpc_ws_server as ws; extern crate parity_rpc as rpc; extern crate rand; extern crate serde; extern crate serde_json; extern crate tempdir; extern crate url; -extern crate ws; #[macro_use] extern crate log; +#[cfg(test)] +#[macro_use] +extern crate matches; + + #[cfg(test)] mod tests { - #[macro_use] - extern crate matches; use futures::Future; use std::path::PathBuf; use client::{Rpc, RpcError}; - use ethcore_signer; + use rpc; #[test] fn test_connection_refused() { - let (_srv, port, mut authcodes) = ethcore_signer::tests::serve(); + let (_srv, port, mut authcodes) = rpc::tests::ws::serve(); let _ = authcodes.generate_new(); authcodes.to_file(&authcodes.path).unwrap(); @@ -43,7 +45,7 @@ mod tests { #[test] fn test_authcode_fail() { - let (_srv, port, _) = ethcore_signer::tests::serve(); + let (_srv, port, _) = rpc::tests::ws::serve(); let path = PathBuf::from("nonexist"); let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &path); @@ -55,7 +57,7 @@ mod tests { #[test] fn test_authcode_correct() { - let (_srv, port, mut authcodes) = ethcore_signer::tests::serve(); + let (_srv, port, mut authcodes) = rpc::tests::ws::serve(); let _ = authcodes.generate_new(); authcodes.to_file(&authcodes.path).unwrap(); diff --git a/rpc_client/src/signer_client.rs b/rpc_client/src/signer_client.rs index 317dc0fece0..ae051efb61a 100644 --- a/rpc_client/src/signer_client.rs +++ b/rpc_client/src/signer_client.rs @@ -1,5 +1,5 @@ use client::{Rpc, RpcError}; -use rpc::v1::types::{ConfirmationRequest, TransactionModification, U256, TransactionCondition}; +use rpc::signer::{ConfirmationRequest, TransactionModification, U256, TransactionCondition}; use serde; use serde_json::{Value as JsonValue, to_value}; use std::path::PathBuf; @@ -13,11 +13,11 @@ impl SignerRpc { pub fn new(url: &str, authfile: &PathBuf) -> Result { Ok(SignerRpc { rpc: Rpc::new(&url, authfile)? }) } - pub fn requests_to_confirm(&mut self) -> - BoxFuture, RpcError>, Canceled> - { + + pub fn requests_to_confirm(&mut self) -> BoxFuture, RpcError>, Canceled> { self.rpc.request("signer_requestsToConfirm", vec![]) } + pub fn confirm_request( &mut self, id: U256, @@ -25,17 +25,15 @@ impl SignerRpc { new_gas_price: Option, new_condition: Option>, pwd: &str - ) -> BoxFuture, Canceled> - { + ) -> BoxFuture, Canceled> { self.rpc.request("signer_confirmRequest", vec![ Self::to_value(&format!("{:#x}", id)), Self::to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, condition: new_condition }), Self::to_value(&pwd), ]) } - pub fn reject_request(&mut self, id: U256) -> - BoxFuture, Canceled> - { + + pub fn reject_request(&mut self, id: U256) -> BoxFuture, Canceled> { self.rpc.request("signer_rejectRequest", vec![ JsonValue::String(format!("{:#x}", id)) ]) diff --git a/scripts/targets.sh b/scripts/targets.sh index f3ae6a2d5fd..fb10c43f200 100644 --- a/scripts/targets.sh +++ b/scripts/targets.sh @@ -7,7 +7,8 @@ export TARGETS=" -p ethcore-bigint\ -p parity-dapps \ -p parity-rpc \ - -p ethcore-signer \ + -p parity-rpc-client \ + -p rpc-cli \ -p ethcore-util \ -p ethcore-network \ -p ethcore-io \ diff --git a/signer/Cargo.toml b/signer/Cargo.toml deleted file mode 100644 index 4f7c0f179c8..00000000000 --- a/signer/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -description = "Ethcore Trusted Signer" -homepage = "http://parity.io" -license = "GPL-3.0" -name = "ethcore-signer" -version = "1.7.0" -authors = ["Parity Technologies "] -build = "build.rs" - -[build-dependencies] -rustc_version = "0.1" - -[dependencies] -rand = "0.3.14" -jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } -jsonrpc-server-utils = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } -log = "0.3" -env_logger = "0.4" -ws = { git = "https://github.com/paritytech/ws-rs.git", branch = "parity-1.7" } -parity-dapps-glue = { version = "1.7", optional = true } -ethcore-util = { path = "../util" } -ethcore-io = { path = "../util/io" } -parity-rpc = { path = "../rpc" } -ethcore-devtools = { path = "../devtools" } -parity-ui = { path = "../dapps/ui", version = "1.4", optional = true } - -clippy = { version = "0.0.103", optional = true} - -[features] -dev = ["clippy"] -ui = ["parity-dapps-glue", "parity-ui", "parity-ui/no-precompiled-js"] -ui-precompiled = ["parity-dapps-glue", "parity-ui", "parity-ui/use-precompiled-js"] diff --git a/signer/src/lib.rs b/signer/src/lib.rs deleted file mode 100644 index a1c935c5b40..00000000000 --- a/signer/src/lib.rs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -#![warn(missing_docs)] -#![cfg_attr(all(nightly, feature="dev"), feature(plugin))] -#![cfg_attr(all(nightly, feature="dev"), plugin(clippy))] - -//! Signer module -//! -//! This module manages your private keys and accounts/identities -//! that can be used within Dapps. -//! -//! It exposes API (over `WebSockets`) accessed by Signer UIs. -//! Each transaction sent by Dapp is broadcasted to Signer UIs -//! and their responsibility is to confirm (or confirm and sign) -//! the transaction for you. -//! -//! ``` -//! extern crate jsonrpc_core; -//! extern crate jsonrpc_server_utils; -//! extern crate ethcore_signer; -//! extern crate parity_rpc; -//! -//! use std::sync::Arc; -//! use jsonrpc_core::IoHandler; -//! use jsonrpc_server_utils::reactor::RpcEventLoop; -//! use ethcore_signer::ServerBuilder; -//! use parity_rpc::ConfirmationsQueue; -//! -//! fn main() { -//! let queue = Arc::new(ConfirmationsQueue::default()); -//! let io = IoHandler::default(); -//! let event_loop = RpcEventLoop::spawn().unwrap(); -//! let remote = event_loop.remote(); -//! let _server = ServerBuilder::new(queue, "/tmp/authcodes".into()) -//! .start("127.0.0.1:8084".parse().unwrap(), io, remote); -//! } -//! ``` - -#[macro_use] -extern crate log; -extern crate env_logger; -extern crate rand; - -extern crate ethcore_io as io; -extern crate ethcore_util as util; -extern crate jsonrpc_core; -extern crate jsonrpc_server_utils; -extern crate parity_rpc as rpc; -extern crate ws; - -extern crate ethcore_devtools as devtools; - -mod authcode_store; -mod ws_server; - -/// Exported tests for use in signer RPC client testing -pub mod tests; -pub use authcode_store::*; -pub use ws_server::*; diff --git a/signer/src/ws_server/error_tpl.html b/signer/src/ws_server/error_tpl.html deleted file mode 100644 index 04a0f3c30a1..00000000000 --- a/signer/src/ws_server/error_tpl.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - {meta} - {title} - - - -
-
-

{title}

-

{message}

-

{details}

-
-
- {version} -
- - diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs deleted file mode 100644 index 7bff6cf19aa..00000000000 --- a/signer/src/ws_server/mod.rs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -//! `WebSockets` server. - -use ws; -use std::default::Default; -use std::net::SocketAddr; -use std::ops::Drop; -use std::path::PathBuf; -use std::sync::Arc; -use std::thread; -use std; - -use io::{PanicHandler, OnPanicListener, MayPanic}; -use jsonrpc_core::{Metadata, Middleware, MetaIoHandler}; -use jsonrpc_server_utils::tokio_core::reactor::Remote; -use rpc::{ConfirmationsQueue}; -use rpc::informant::RpcStats; - -mod session; - -pub use self::session::MetaExtractor; - -/// Signer startup error -#[derive(Debug)] -pub enum ServerError { - /// Wrapped `std::io::Error` - IoError(std::io::Error), - /// Other `ws-rs` error - WebSocket(ws::Error) -} - -impl From for ServerError { - fn from(err: ws::Error) -> Self { - match err.kind { - ws::ErrorKind::Io(e) => ServerError::IoError(e), - _ => ServerError::WebSocket(err), - } - } -} - -/// Dummy metadata extractor -#[derive(Clone)] -pub struct NoopExtractor; -impl session::MetaExtractor for NoopExtractor {} - -/// Builder for `WebSockets` server -pub struct ServerBuilder { - queue: Arc, - authcodes_path: PathBuf, - skip_origin_validation: bool, - stats: Option>, -} - -impl ServerBuilder { - /// Creates new `ServerBuilder` - pub fn new(queue: Arc, authcodes_path: PathBuf) -> Self { - ServerBuilder { - queue: queue, - authcodes_path: authcodes_path, - skip_origin_validation: false, - stats: None, - } - } - - /// If set to `true` server will not verify Origin of incoming requests. - /// Not recommended. Use only for development. - pub fn skip_origin_validation(mut self, skip: bool) -> Self { - self.skip_origin_validation = skip; - self - } - - /// Configure statistic collection - pub fn stats(mut self, stats: Arc) -> Self { - self.stats = Some(stats); - self - } - - /// Starts a new `WebSocket` server in separate thread. - /// Returns a `Server` handle which closes the server when droped. - pub fn start, H: Into>>( - self, - addr: SocketAddr, - handler: H, - remote: Remote, - ) -> Result { - self.start_with_extractor(addr, handler, remote, NoopExtractor) - } - - /// Starts a new `WebSocket` server in separate thread. - /// Returns a `Server` handle which closes the server when droped. - pub fn start_with_extractor, H: Into>, T: session::MetaExtractor>( - self, - addr: SocketAddr, - handler: H, - remote: Remote, - meta_extractor: T, - ) -> Result { - Server::start( - addr, - handler.into(), - remote, - self.queue, - self.authcodes_path, - self.skip_origin_validation, - self.stats, - meta_extractor, - ) - } - -} - -/// `WebSockets` server implementation. -pub struct Server { - handle: Option>, - broadcaster: ws::Sender, - queue: Arc, - panic_handler: Arc, - addr: SocketAddr, -} - -impl Server { - /// Returns the address this server is listening on - pub fn addr(&self) -> &SocketAddr { - &self.addr - } - - /// Starts a new `WebSocket` server in separate thread. - /// Returns a `Server` handle which closes the server when droped. - fn start, T: session::MetaExtractor>( - addr: SocketAddr, - handler: MetaIoHandler, - remote: Remote, - queue: Arc, - authcodes_path: PathBuf, - skip_origin_validation: bool, - stats: Option>, - meta_extractor: T, - ) -> Result { - let config = { - let mut config = ws::Settings::default(); - // accept only handshakes beginning with GET - config.method_strict = true; - // Was shutting down server when suspending on linux: - config.shutdown_on_interrupt = false; - config - }; - - // Create WebSocket - let origin = format!("{}", addr); - let port = addr.port(); - let ws = ws::Builder::new().with_settings(config).build( - session::Factory::new(handler, remote, origin, port, authcodes_path, skip_origin_validation, stats, meta_extractor) - )?; - - let panic_handler = PanicHandler::new_in_arc(); - let ph = panic_handler.clone(); - let broadcaster = ws.broadcaster(); - - // Spawn a thread with event loop - let handle = thread::spawn(move || { - ph.catch_panic(move || { - match ws.listen(addr).map_err(ServerError::from) { - Err(ServerError::IoError(io)) => die(format!( - "Signer: Could not start listening on specified address. Make sure that no other instance is running on Signer's port. Details: {:?}", - io - )), - Err(any_error) => die(format!( - "Signer: Unknown error occurred when starting Signer. Details: {:?}", - any_error - )), - Ok(server) => server, - } - }).unwrap(); - }); - - // Return a handle - Ok(Server { - handle: Some(handle), - broadcaster: broadcaster, - queue: queue, - panic_handler: panic_handler, - addr: addr, - }) - } -} - -impl MayPanic for Server { - fn on_panic(&self, closure: F) where F: OnPanicListener { - self.panic_handler.on_panic(closure); - } -} - -impl Drop for Server { - fn drop(&mut self) { - self.queue.finish(); - self.broadcaster.shutdown().unwrap(); - self.handle.take().unwrap().join().unwrap(); - } -} - -fn die(msg: String) -> ! { - println!("ERROR: {}", msg); - std::process::exit(1); -} diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs deleted file mode 100644 index 91984ff05ee..00000000000 --- a/signer/src/ws_server/session.rs +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -//! Session handlers factory. - -use std::path::{PathBuf, Path}; -use std::sync::Arc; -use std::str::FromStr; - -use authcode_store::AuthCodes; -use jsonrpc_core::{Metadata, Middleware, MetaIoHandler}; -use jsonrpc_core::futures::Future; -use jsonrpc_server_utils::tokio_core::reactor::Remote; -use rpc::informant::RpcStats; -use util::{H256, version}; -use ws; - -#[cfg(feature = "parity-ui")] -mod ui { - extern crate parity_ui as ui; - extern crate parity_dapps_glue as dapps; - - use self::dapps::WebApp; - - #[derive(Default)] - pub struct Handler { - ui: ui::App, - } - - impl Handler { - pub fn handle(&self, req: &str) -> Option<&dapps::File> { - let file = match req { - "" | "/" => "index.html", - path => &path[1..], - }; - self.ui.file(file) - } - } -} -#[cfg(not(feature = "parity-ui"))] -mod ui { - pub struct File { - pub content: &'static [u8], - pub content_type: &'static str, - } - - #[derive(Default)] - pub struct Handler; - - impl Handler { - pub fn handle(&self, _req: &str) -> Option<&File> { - None - } - } -} - -const HOME_DOMAIN: &'static str = "parity.web3.site"; - -fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool { - match header.map(|h| String::from_utf8_lossy(h).into_owned()) { - Some(ref origin) if origin.starts_with("chrome-extension://") => true, - Some(ref origin) if origin.starts_with(self_origin) => true, - Some(ref origin) if origin.starts_with(&format!("http://{}", self_origin)) => true, - Some(ref origin) if origin.starts_with(HOME_DOMAIN) => true, - Some(ref origin) if origin.starts_with(&format!("http://{}", HOME_DOMAIN)) => true, - _ => false, - } -} - -fn auth_token_hash(codes_path: &Path, protocols: ws::Result>) -> Option { - match protocols { - Ok(ref protocols) if protocols.len() == 1 => { - let protocol = protocols[0]; - let mut split = protocol.split('_'); - let auth = split.next().and_then(|v| H256::from_str(v).ok()); - let time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok()); - - if let (Some(auth), Some(time)) = (auth, time) { - // Check if the code is valid - AuthCodes::from_file(codes_path) - .ok() - .and_then(|mut codes| { - // remove old tokens - codes.clear_garbage(); - - let res = codes.is_valid(&auth, time); - // make sure to save back authcodes - it might have been modified - if codes.to_file(codes_path).is_err() { - warn!(target: "signer", "Couldn't save authorization codes to file."); - } - - if res { - Some(auth) - } else { - None - } - }) - } else { - None - } - }, - _ => None, - } -} - -fn add_headers(mut response: ws::Response, mime: &str) -> ws::Response { - let content_len = format!("{}", response.len()); - { - let mut headers = response.headers_mut(); - headers.push(("X-Frame-Options".into(), b"SAMEORIGIN".to_vec())); - headers.push(("X-XSS-Protection".into(), b"1; mode=block".to_vec())); - headers.push(("X-Content-Type-Options".into(), b"nosniff".to_vec())); - headers.push(("Server".into(), b"Parity/SignerUI".to_vec())); - headers.push(("Content-Length".into(), content_len.as_bytes().to_vec())); - headers.push(("Content-Type".into(), mime.as_bytes().to_vec())); - headers.push(("Connection".into(), b"close".to_vec())); - } - response -} - -/// Metadata extractor from session data. -pub trait MetaExtractor: Send + Clone + 'static { - /// Extract metadata for given session - fn extract_metadata(&self, _session_id: &H256) -> M { - Default::default() - } -} - -pub struct Session, T> { - session_id: H256, - out: ws::Sender, - skip_origin_validation: bool, - self_origin: String, - self_port: u16, - authcodes_path: PathBuf, - handler: Arc>, - remote: Remote, - file_handler: Arc, - stats: Option>, - meta_extractor: T, -} - -impl, T> Drop for Session { - fn drop(&mut self) { - self.stats.as_ref().map(|stats| stats.close_session()); - } -} - -impl, T: MetaExtractor> ws::Handler for Session { - fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> { - trace!(target: "signer", "Handling request: {:?}", req); - - // TODO [ToDr] ws server is not handling proxied requests correctly: - // Trim domain name from resource part: - let resource = req.resource().trim_left_matches(&format!("http://{}:{}", HOME_DOMAIN, self.self_port)); - let resource = resource.trim_left_matches(&format!("http://{}", HOME_DOMAIN)); - - // Styles file is allowed for error pages to display nicely. - let is_styles_file = resource == "/styles.css"; - - // Check request origin and host header. - if !self.skip_origin_validation { - let origin = req.header("origin").or_else(|| req.header("Origin")).map(|x| &x[..]); - let host = req.header("host").or_else(|| req.header("Host")).map(|x| &x[..]); - - let is_valid = origin_is_allowed(&self.self_origin, origin) || (origin.is_none() && origin_is_allowed(&self.self_origin, host)); - let is_valid = is_styles_file || is_valid; - - if !is_valid { - warn!(target: "signer", "Blocked connection to Signer API from untrusted origin."); - return Ok(error( - ErrorType::Forbidden, - "URL Blocked", - "You are not allowed to access Trusted Signer using this URL.", - Some(&format!("Use: http://{}", self.self_origin)), - )); - } - } - - // PROXY requests when running behind home.parity - if req.method() == "CONNECT" { - let mut res = ws::Response::ok("".into()); - res.headers_mut().push(("Content-Length".into(), b"0".to_vec())); - res.headers_mut().push(("Connection".into(), b"keep-alive".to_vec())); - return Ok(res); - } - - // Detect if it's a websocket request - // (styles file skips origin validation, so make sure to prevent WS connections on this resource) - if req.header("sec-websocket-key").is_some() && !is_styles_file { - // Check authorization - let auth_token_hash = auth_token_hash(&self.authcodes_path, req.protocols()); - match auth_token_hash { - None => { - info!(target: "signer", "Unauthorized connection to Signer API blocked."); - return Ok(error(ErrorType::Forbidden, "Not Authorized", "Request to this API was not authorized.", None)); - }, - Some(auth) => { - self.session_id = auth; - }, - } - - let protocols = req.protocols().expect("Existence checked by authorization."); - let protocol = protocols.get(0).expect("Proved by authorization."); - return ws::Response::from_request(req).map(|mut res| { - // To make WebSockets connection successful we need to send back the protocol header. - res.set_protocol(protocol); - res - }); - } - - debug!(target: "signer", "Requesting resource: {:?}", resource); - // Otherwise try to serve a page. - Ok(self.file_handler.handle(resource) - .map_or_else( - // return 404 not found - || error(ErrorType::NotFound, "Not found", "Requested file was not found.", None), - // or serve the file - |f| add_headers(ws::Response::ok_raw(f.content.to_vec()), f.content_type) - )) - } - - fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { - let req = msg.as_text()?; - let out = self.out.clone(); - // TODO [ToDr] Move to on_connect - let metadata = self.meta_extractor.extract_metadata(&self.session_id); - - let future = self.handler.handle_request(req, metadata).map(move |response| { - if let Some(result) = response { - let res = out.send(result); - if let Err(e) = res { - warn!(target: "signer", "Error while sending response: {:?}", e); - } - } - }); - self.remote.spawn(move |_| future); - Ok(()) - } -} - -pub struct Factory, T> { - handler: Arc>, - remote: Remote, - skip_origin_validation: bool, - self_origin: String, - self_port: u16, - authcodes_path: PathBuf, - meta_extractor: T, - file_handler: Arc, - stats: Option>, -} - -impl, T> Factory { - pub fn new( - handler: MetaIoHandler, - remote: Remote, - self_origin: String, - self_port: u16, - authcodes_path: PathBuf, - skip_origin_validation: bool, - stats: Option>, - meta_extractor: T, - ) -> Self { - Factory { - handler: Arc::new(handler), - remote: remote, - skip_origin_validation: skip_origin_validation, - self_origin: self_origin, - self_port: self_port, - authcodes_path: authcodes_path, - meta_extractor: meta_extractor, - file_handler: Arc::new(ui::Handler::default()), - stats: stats, - } - } -} - -impl, T: MetaExtractor> ws::Factory for Factory { - type Handler = Session; - - fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler { - self.stats.as_ref().map(|stats| stats.open_session()); - - Session { - session_id: 0.into(), - out: sender, - handler: self.handler.clone(), - remote: self.remote.clone(), - skip_origin_validation: self.skip_origin_validation, - self_origin: self.self_origin.clone(), - self_port: self.self_port, - authcodes_path: self.authcodes_path.clone(), - meta_extractor: self.meta_extractor.clone(), - file_handler: self.file_handler.clone(), - stats: self.stats.clone(), - } - } -} - -enum ErrorType { - NotFound, - Forbidden, -} - -fn error(error: ErrorType, title: &str, message: &str, details: Option<&str>) -> ws::Response { - let content = format!( - include_str!("./error_tpl.html"), - title=title, - meta="", - message=message, - details=details.unwrap_or(""), - version=version(), - ); - let res = match error { - ErrorType::NotFound => ws::Response::not_found(content), - ErrorType::Forbidden => ws::Response::forbidden(content), - }; - add_headers(res, "text/html") -}