UI server refactoring (#5580)
* Full API in Authenticated WS server. * Replacing UI server with Hyper. * Solving CLI, RPCs and tests. * Porting signer tests. * Fixing origin recognition for dapps/rpc. * Fixing tests. Adding parity-rpc-client to test. * Dapps exposed as RPC method. * JS code to support new connection scheme. * Fixing dapps tests. * Updating allowed origins/hosts to support web3.site. * Fixing tests, fixing UI. * Fixing tests. * Removing invalid tests. * Fixing merge. * 404 fallback for UI * Improve ContentFetcher constructor readability. * Naming. * Update .gitlab-ci.yml fix CI lint error * Fixing tests and linting issues. * Fixing new tests. * UI hosts. * Submodules fix.
This commit is contained in:
parent
7499efecf6
commit
cbcc369a2d
@ -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
|
||||
|
49
Cargo.lock
generated
49
Cargo.lock
generated
@ -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)" = "<none>"
|
||||
"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)" = "<none>"
|
||||
"checksum ws 0.6.0 (git+https://github.com/tomusdrw/ws-rs)" = "<none>"
|
||||
"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"
|
||||
|
@ -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"]
|
||||
|
@ -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<Vec<AccessControlAllowOrigin>>,
|
||||
apps: Vec<App>,
|
||||
fetcher: Arc<Fetcher>,
|
||||
}
|
||||
|
||||
impl RestApi {
|
||||
pub fn new(cors_domains: Vec<AccessControlAllowOrigin>, endpoints: &Endpoints, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||
pub fn new(fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||
Box::new(RestApi {
|
||||
cors_domains: Some(cors_domains),
|
||||
apps: Self::list_apps(endpoints),
|
||||
fetcher: fetcher,
|
||||
})
|
||||
}
|
||||
|
||||
fn list_apps(endpoints: &Endpoints) -> Vec<App> {
|
||||
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<header::AccessControlAllowOrigin>,
|
||||
path: Option<EndpointPath>,
|
||||
control: Option<Control>,
|
||||
handler: Box<Handler>,
|
||||
@ -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::AccessControlAllowOrigin>) -> 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<net::HttpStream> for RestApiRouter {
|
||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> 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<net::HttpStream> 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<net::HttpStream> 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)
|
||||
}
|
||||
|
||||
|
@ -21,4 +21,3 @@ mod response;
|
||||
mod types;
|
||||
|
||||
pub use self::api::RestApi;
|
||||
pub use self::types::App;
|
||||
|
@ -23,12 +23,6 @@ pub fn empty() -> Box<Handler> {
|
||||
Box::new(ContentHandler::ok("".into(), mime!(Text/Plain)))
|
||||
}
|
||||
|
||||
pub fn as_json<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
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<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
|
@ -14,46 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<EndpointInfo> 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 {
|
||||
|
55
dapps/src/apps/app.rs
Normal file
55
dapps/src/apps/app.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<EndpointInfo> for App {
|
||||
fn into(self) -> EndpointInfo {
|
||||
EndpointInfo {
|
||||
name: self.name,
|
||||
description: self.description,
|
||||
version: self.version,
|
||||
author: self.author,
|
||||
icon_url: self.icon_url,
|
||||
}
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@ pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + 'static = URLHint
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
only_content: bool,
|
||||
}
|
||||
|
||||
impl<R: URLHint + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
|
||||
@ -66,7 +67,12 @@ impl<R: URLHint + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
|
||||
|
||||
impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
|
||||
pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self {
|
||||
pub fn new(
|
||||
resolver: R,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push(random_filename());
|
||||
|
||||
@ -75,12 +81,23 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
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<Handler> {
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::ServiceUnavailable,
|
||||
@ -91,6 +108,16 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
))
|
||||
}
|
||||
|
||||
fn dapps_disabled(address: Option<(String, u16)>) -> Box<Handler> {
|
||||
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<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
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(),
|
||||
|
@ -14,12 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> Endpoints {
|
||||
let mut pages = Endpoints::new();
|
||||
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> BTreeMap<String, Box<Endpoint>> {
|
||||
let mut pages = BTreeMap::<String, Box<Endpoint>>::new();
|
||||
for dapp in local_dapps(dapps_path.as_ref()) {
|
||||
pages.insert(
|
||||
dapp.id,
|
||||
|
@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use serde_json;
|
||||
pub use api::App as Manifest;
|
||||
pub use apps::App as Manifest;
|
||||
|
||||
pub const MANIFEST_FILENAME: &'static str = "manifest.json";
|
||||
|
||||
|
@ -14,8 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<Endpoint> {
|
||||
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
|
||||
}
|
||||
|
||||
pub fn ui() -> Box<Endpoint> {
|
||||
Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default()))
|
||||
}
|
||||
|
||||
pub fn ui_redirection(ui_address: Option<(String, u16)>) -> Box<Endpoint> {
|
||||
Box::new(ui::Redirection::new(ui_address))
|
||||
}
|
||||
|
||||
pub fn all_endpoints<F: Fetch>(
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
dapps_domain: String,
|
||||
ui_address: Option<(String, u16)>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
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<F: Fetch>(
|
||||
}
|
||||
|
||||
// NOTE [ToDr] Dapps will be currently embeded on 8180
|
||||
insert::<parity_ui::App>(&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::<parity_ui::App>(&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<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str, embed_at: Embeddable) {
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpoint>>, 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()),
|
||||
|
55
dapps/src/apps/ui.rs
Normal file
55
dapps/src/apps/ui.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<Handler> {
|
||||
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(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String, Box<Endpoint>>;
|
||||
pub type Endpoints = Arc<BTreeMap<String, Box<Endpoint>>>;
|
||||
pub type Handler = server::Handler<net::HttpStream> + Send;
|
||||
|
||||
pub trait Endpoint : Send + Sync {
|
||||
|
139
dapps/src/lib.rs
139
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<F> 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<apps::App> {
|
||||
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 {
|
||||
/// Creates new Dapps server middleware.
|
||||
pub fn new<F: Fetch + Clone>(
|
||||
/// Get local endpoints handle.
|
||||
pub fn endpoints(&self) -> Endpoints {
|
||||
Endpoints {
|
||||
endpoints: self.endpoints.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new middleware for UI server.
|
||||
pub fn ui<F: Fetch + Clone>(
|
||||
remote: Remote,
|
||||
signer_address: Option<(String, u16)>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
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 dapps<F: Fetch + Clone>(
|
||||
remote: Remote,
|
||||
ui_address: Option<(String, u16)>,
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
dapps_domain: String,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
@ -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<AccessControlAllowOrigin> {
|
||||
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<apps::fetcher::Fetcher>) -> HashMap<router::SpecialEndpoint, Option<Box<endpoint::Endpoint>>> {
|
||||
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::<AccessControlAllowOrigin>::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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
|
||||
/// 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<T: WebApp + 'static> PageEndpoint<T> {
|
||||
@ -38,6 +39,20 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
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<T: WebApp + 'static> PageEndpoint<T> {
|
||||
prefix: Some(prefix),
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +80,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
prefix: None,
|
||||
safe_to_embed_on: address,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,7 +93,7 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
|
||||
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
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<Info> for EndpointInfo {
|
||||
|
||||
struct BuiltinDapp<T: WebApp + 'static> {
|
||||
app: Arc<T>,
|
||||
fallback_to_index_html: bool,
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> BuiltinDapp<T> {
|
||||
fn new(app: Arc<T>) -> Self {
|
||||
fn new(app: Arc<T>, fallback_to_index_html: bool) -> Self {
|
||||
BuiltinDapp {
|
||||
app: app,
|
||||
fallback_to_index_html: fallback_to_index_html,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,13 +133,19 @@ impl<T: WebApp + 'static> handler::Dapp for BuiltinDapp<T> {
|
||||
type DappFile = BuiltinDappFile<T>;
|
||||
|
||||
fn file(&self, path: &str) -> Option<Self::DappFile> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Endpoint> {
|
||||
pub fn boxed(signer_address: Option<(String, u16)>, dapps_domain: String) -> Box<Endpoint> {
|
||||
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)))
|
||||
}
|
||||
|
@ -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<Endpoints>,
|
||||
fetch: Arc<Fetcher>,
|
||||
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
dapps_domain: String,
|
||||
}
|
||||
|
||||
impl http::RequestMiddleware for Router {
|
||||
fn on_request(&self, req: &server::Request<HttpStream>, 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::<http::hyper::header::Origin>().is_some();
|
||||
let is_origin_set = req.headers().get::<header::Origin>().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/<encoded>/
|
||||
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<Fetcher>,
|
||||
endpoints: Endpoints,
|
||||
endpoints: Option<Endpoints>,
|
||||
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
||||
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<EndpointPath>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_referer_endpoint(req: &server::Request<HttpStream>) -> Option<(EndpointPath, Url)> {
|
||||
fn extract_referer_endpoint(req: &server::Request<HttpStream>, dapps_domain: &str) -> Option<(EndpointPath, Url)> {
|
||||
let referer = req.headers().get::<header::Referer>();
|
||||
|
||||
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<Url>) -> Option<(EndpointPath, Url)> {
|
||||
fn extract_url_referer_endpoint(url: &Option<Url>, 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<Url>) -> 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<Url>) -> Option<(EndpointPath, Url)
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint) {
|
||||
fn extract_endpoint(url: &Option<Url>, dapps_domain: &str) -> (Option<EndpointPath>, SpecialEndpoint) {
|
||||
fn special_endpoint(url: &Url) -> SpecialEndpoint {
|
||||
if url.path.len() <= 1 {
|
||||
return SpecialEndpoint::None;
|
||||
@ -208,14 +206,15 @@ fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, 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<Url>) -> (Option<EndpointPath>, 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()],
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<T: Middleware<Metadata>>(
|
||||
handler: MetaIoHandler<Metadata, T>,
|
||||
remote: Remote,
|
||||
cors_domains: Vec<AccessControlAllowOrigin>,
|
||||
) -> Box<Endpoint> {
|
||||
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<T: Middleware<Metadata>> {
|
||||
handler: Arc<MetaIoHandler<Metadata, T>>,
|
||||
remote: Remote,
|
||||
meta_extractor: Arc<HttpMetaExtractor<Metadata>>,
|
||||
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
|
||||
allowed_hosts: Option<Vec<http::Host>>,
|
||||
}
|
||||
|
||||
|
||||
impl<T: Middleware<Metadata>> Endpoint for RpcEndpoint<T> {
|
||||
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
|
||||
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<http::hyper::net::HttpStream>, _control: &http::hyper::Control) -> http::RequestMiddlewareAction {
|
||||
http::RequestMiddlewareAction::Proceed {
|
||||
should_continue_on_invalid_cors: request.headers().get::<http::hyper::header::Origin>().is_none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MetadataExtractor;
|
||||
impl HttpMetaExtractor<Metadata> for MetadataExtractor {
|
||||
fn read_metadata(&self, request: &http::hyper::server::Request<http::hyper::net::HttpStream>) -> Metadata {
|
||||
let dapp_id = request.headers().get::<http::hyper::header::Origin>()
|
||||
.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()),
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
@ -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<F, B>(process: F, io: IoHandler, remote: Remote) -> (ServerLoop, Arc<FakeRegistrar>) where
|
||||
pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (Server, Arc<FakeRegistrar>) where
|
||||
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
|
||||
B: Fetch,
|
||||
{
|
||||
@ -69,44 +56,41 @@ pub fn init_server<F, B>(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<Vec<String>>) -> ServerLoop {
|
||||
pub fn serve_hosts(hosts: Option<Vec<String>>) -> 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<FakeRegistrar>) {
|
||||
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| builder, Default::default(), Remote::new_sync())
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc<FakeRegistrar>) {
|
||||
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| {
|
||||
builder.sync_status(Arc::new(|| true))
|
||||
}, Default::default(), Remote::new_sync())
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_fetch() -> (ServerLoop, FakeFetch, Arc<FakeRegistrar>) {
|
||||
pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc<FakeRegistrar>) {
|
||||
serve_with_registrar_and_fetch_and_threads(false)
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (ServerLoop, FakeFetch, Arc<FakeRegistrar>) {
|
||||
pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Server, FakeFetch, Arc<FakeRegistrar>) {
|
||||
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<T: Fetch> ServerBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
const DAPPS_DOMAIN: &'static str = "web3.site";
|
||||
|
||||
/// Webapps HTTP server.
|
||||
pub struct Server {
|
||||
@ -260,19 +245,27 @@ impl Server {
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Result<Server, http::Error> {
|
||||
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<Vec<Host>> = 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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -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 (
|
||||
<img
|
||||
src={ `${parityNode}/api/content/${address.replace(/^0x/, '')}` }
|
||||
src={ `/api/content/${address.replace(/^0x/, '')}` }
|
||||
alt={ address }
|
||||
style={ styles }
|
||||
/>
|
||||
|
@ -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 {
|
||||
</span> meta-data:
|
||||
</p>
|
||||
<div className={ styles['meta-image'] }>
|
||||
<img src={ `${parityNode}/api/content/${imageHash}/` } />
|
||||
<img src={ `/api/content/${imageHash}/` } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -55,6 +55,9 @@ class FakeTransport {
|
||||
return Promise.reject('not connected');
|
||||
}
|
||||
|
||||
addMiddleware () {
|
||||
}
|
||||
|
||||
on () {
|
||||
}
|
||||
}
|
||||
|
@ -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 {};
|
||||
|
@ -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);
|
||||
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
constructor (url, nextToken, getTransport = SecureApi.getTransport) {
|
||||
// Returns a protocol with `:` at the end.
|
||||
static protocol () {
|
||||
return window.location.protocol;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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;
|
||||
|
@ -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]*$/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -15,26 +15,21 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
// 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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}));
|
||||
|
@ -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<bool>,
|
||||
port: Option<u16>,
|
||||
interface: Option<String>,
|
||||
hosts: Option<Vec<String>>,
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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<usize>,
|
||||
pwfile: Option<PathBuf>,
|
||||
@ -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<Vec<String>> {
|
||||
if self.args.flag_ui_no_validation {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.hosts(&self.args.flag_ui_hosts, &self.ui_interface())
|
||||
}
|
||||
|
||||
fn rpc_hosts(&self) -> Option<Vec<String>> {
|
||||
self.hosts(&self.args.flag_jsonrpc_hosts, &self.rpc_interface())
|
||||
}
|
||||
@ -825,13 +826,17 @@ impl Configuration {
|
||||
}
|
||||
|
||||
fn ws_config(&self) -> Result<WsConfiguration, String> {
|
||||
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<u16> {
|
||||
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,24 +1048,27 @@ impl Configuration {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cli::Args;
|
||||
use parity_rpc::NetworkSettings;
|
||||
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 blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, DataFormat, ExportState};
|
||||
use presale::ImportWallet;
|
||||
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 devtools::{RandomTempPath};
|
||||
use ethcore::client::{VMType, BlockId};
|
||||
use ethcore::miner::{MinerOptions, PrioritizationStrategy};
|
||||
use parity_rpc::NetworkSettings;
|
||||
use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack};
|
||||
|
||||
use account::{AccountCmd, NewAccount, ImportAccounts, ListAccounts};
|
||||
use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, DataFormat, ExportState};
|
||||
use cli::Args;
|
||||
use dir::{Directories, default_hypervisor_path};
|
||||
use helpers::{default_network_config};
|
||||
use params::SpecType;
|
||||
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,
|
||||
});
|
||||
assert_eq!(conf1.signer_config(), SignerConfiguration {
|
||||
enabled: true,
|
||||
port: 8180,
|
||||
interface: "127.0.0.1".into(),
|
||||
signer_path: "signer".into(),
|
||||
skip_origin_validation: true,
|
||||
hosts: Some(vec![]),
|
||||
});
|
||||
assert_eq!(conf2.signer_config(), SignerConfiguration {
|
||||
assert_eq!(conf1.directories().signer, "signer".to_owned());
|
||||
assert_eq!(conf1.ui_config(), UiConfiguration {
|
||||
enabled: true,
|
||||
interface: "127.0.0.1".into(),
|
||||
port: 8180,
|
||||
hosts: None,
|
||||
});
|
||||
assert_eq!(conf2.directories().signer, "signer".to_owned());
|
||||
assert_eq!(conf2.ui_config(), UiConfiguration {
|
||||
enabled: true,
|
||||
interface: "127.0.0.1".into(),
|
||||
port: 3123,
|
||||
interface: "127.0.0.1".into(),
|
||||
signer_path: "signer".into(),
|
||||
skip_origin_validation: false,
|
||||
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");
|
||||
|
@ -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<SyncStatus>,
|
||||
pub contract_client: Arc<ContractClient>,
|
||||
pub remote: parity_reactor::TokioRemote,
|
||||
pub fetch: FetchClient,
|
||||
pub signer: Arc<SignerService>,
|
||||
pub ui_address: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
pub fn new(configuration: Configuration, deps: Dependencies)
|
||||
-> Result<Option<Middleware>, String>
|
||||
{
|
||||
pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<Middleware>, 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 use self::server::{SyncStatus, Middleware, dapps_middleware};
|
||||
pub fn new_ui(enabled: bool, deps: Dependencies) -> Result<Option<Middleware>, String> {
|
||||
if !enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
server::ui_middleware(
|
||||
deps,
|
||||
rpc::DAPPS_DOMAIN.into(),
|
||||
).map(Some)
|
||||
}
|
||||
|
||||
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<PathBuf>,
|
||||
_dapps_domain: String,
|
||||
) -> Result<Middleware, String> {
|
||||
Err("Your Parity version has been compiled without WebApps support.".into())
|
||||
}
|
||||
|
||||
pub fn ui_middleware(
|
||||
_deps: Dependencies,
|
||||
_dapps_domain: String,
|
||||
) -> Result<Middleware, String> {
|
||||
Err("Your Parity version has been compiled without UI support.".into())
|
||||
}
|
||||
|
||||
pub fn service(_: &Option<Middleware>) -> Option<Arc<rpc_apis::DappsService>> {
|
||||
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<PathBuf>,
|
||||
dapps_domain: String,
|
||||
) -> Result<Middleware, String> {
|
||||
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<Middleware, String> {
|
||||
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<Middleware>) -> Option<Arc<rpc_apis::DappsService>> {
|
||||
middleware.as_ref().map(|m| Arc::new(DappsServiceWrapper {
|
||||
endpoints: m.endpoints()
|
||||
}) as Arc<rpc_apis::DappsService>)
|
||||
}
|
||||
|
||||
pub struct DappsServiceWrapper {
|
||||
endpoints: parity_dapps::Endpoints,
|
||||
}
|
||||
|
||||
impl rpc_apis::DappsService for DappsServiceWrapper {
|
||||
fn list_dapps(&self) -> Vec<rpc_apis::LocalDapp> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<PostExecutionAction, S
|
||||
Cmd::Account(account_cmd) => 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)),
|
||||
|
267
parity/rpc.rs
267
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<usize>,
|
||||
}
|
||||
|
||||
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<Vec<String>>,
|
||||
}
|
||||
|
||||
impl UiConfiguration {
|
||||
pub fn address(&self) -> Option<(String, u16)> {
|
||||
match self.enabled {
|
||||
true => Some((self.interface.clone(), self.port)),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UiConfiguration> 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,17 +140,32 @@ pub struct WsConfiguration {
|
||||
pub apis: ApiSet,
|
||||
pub origins: Option<Vec<String>>,
|
||||
pub hosts: Option<Vec<String>>,
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl WsConfiguration {
|
||||
pub fn address(&self) -> Option<(String, u16)> {
|
||||
match self.enabled {
|
||||
true => Some((self.interface.clone(), self.port)),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,62 +176,6 @@ pub struct Dependencies<D: rpc_apis::Dependencies> {
|
||||
pub stats: Arc<RpcStats>,
|
||||
}
|
||||
|
||||
pub struct RpcExtractor;
|
||||
impl rpc::HttpMetaExtractor for RpcExtractor {
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn read_metadata(&self, origin: String, dapps_origin: Option<String>) -> 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<Metadata> 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<Metadata> 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<RpcStats>,
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_apis<D>(apis: ApiSet, deps: &Dependencies<D>) -> MetaIoHandler<Metadata, Middleware<D::Notifier>>
|
||||
where D: rpc_apis::Dependencies
|
||||
{
|
||||
rpc_apis::setup_rpc(deps.stats.clone(), &*deps.apis, apis)
|
||||
}
|
||||
|
||||
pub fn new_ws<D: rpc_apis::Dependencies>(
|
||||
conf: WsConfiguration,
|
||||
deps: &Dependencies<D>,
|
||||
@ -168,23 +184,41 @@ pub fn new_ws<D: rpc_apis::Dependencies>(
|
||||
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 remote = deps.remote.clone();
|
||||
let allowed_origins = into_domains(conf.origins);
|
||||
let allowed_hosts = into_domains(conf.hosts);
|
||||
|
||||
|
||||
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::<Vec<_>>();
|
||||
deps.apis.extend_with_set(&mut handler, &apis);
|
||||
|
||||
handler
|
||||
};
|
||||
|
||||
let remote = deps.remote.clone();
|
||||
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<D: rpc_apis::Dependencies>(
|
||||
}
|
||||
|
||||
pub fn new_http<D: rpc_apis::Dependencies>(
|
||||
id: &str,
|
||||
options: &str,
|
||||
conf: HttpConfiguration,
|
||||
deps: &Dependencies<D>,
|
||||
middleware: Option<dapps::Middleware>
|
||||
middleware: Option<dapps::Middleware>,
|
||||
) -> Result<Option<HttpServer>, 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<D: rpc_apis::Dependencies>(
|
||||
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<D: rpc_apis::Dependencies>(
|
||||
|
||||
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<T: From<String>>(items: Option<Vec<String>>) -> DomainsValidation<T> {
|
||||
items.map(|vals| vals.into_iter().map(T::from).collect()).into()
|
||||
}
|
||||
|
||||
pub fn new_ipc<D: rpc_apis::Dependencies>(
|
||||
conf: IpcConfiguration,
|
||||
dependencies: &Dependencies<D>
|
||||
@ -252,48 +286,39 @@ pub fn new_ipc<D: rpc_apis::Dependencies>(
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_dapps_origin() {
|
||||
// given
|
||||
let extractor = RpcExtractor;
|
||||
let dapp = "https://wallet.ethereum.org".to_owned();
|
||||
|
||||
// when
|
||||
let meta = extractor.read_metadata("null".into(), Some(dapp.clone()));
|
||||
|
||||
// then
|
||||
assert_eq!(meta.origin, Origin::Dapps(dapp.into()));
|
||||
}
|
||||
fn into_domains<T: From<String>>(items: Option<Vec<String>>) -> DomainsValidation<T> {
|
||||
items.map(|vals| vals.into_iter().map(T::from).collect()).into()
|
||||
}
|
||||
|
||||
fn with_domain(items: Option<Vec<String>>, domain: &str, addresses: &[Option<(String, u16)>]) -> Option<Vec<String>> {
|
||||
items.map(move |items| {
|
||||
let mut items = items.into_iter().collect::<HashSet<_>>();
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_apis<D>(apis: ApiSet, deps: &Dependencies<D>) -> MetaIoHandler<Metadata, Middleware<D::Notifier>>
|
||||
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::<Vec<_>>();
|
||||
deps.apis.extend_with_set(&mut handler, &apis);
|
||||
|
||||
handler
|
||||
}
|
||||
|
@ -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<Metadata, Middleware<Self::Notifier>>, apis: &[Api]);
|
||||
fn extend_with_set<S>(
|
||||
&self,
|
||||
handler: &mut MetaIoHandler<Metadata, S>,
|
||||
apis: &[Api],
|
||||
) where S: core::Middleware<Metadata>;
|
||||
}
|
||||
|
||||
/// RPC dependencies for a full node.
|
||||
@ -201,19 +206,20 @@ pub struct FullDependencies {
|
||||
pub net_service: Arc<ManageNetwork>,
|
||||
pub updater: Arc<Updater>,
|
||||
pub geth_compatibility: bool,
|
||||
pub dapps_interface: Option<String>,
|
||||
pub dapps_port: Option<u16>,
|
||||
pub dapps_service: Option<Arc<DappsService>>,
|
||||
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<T: core::Middleware<Metadata>>(
|
||||
fn extend_api<S>(
|
||||
&self,
|
||||
handler: &mut MetaIoHandler<Metadata, T>,
|
||||
handler: &mut MetaIoHandler<Metadata, S>,
|
||||
apis: &[Api],
|
||||
for_generic_pubsub: bool,
|
||||
) {
|
||||
) where S: core::Middleware<Metadata> {
|
||||
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<Metadata, Middleware<Self::Notifier>>, apis: &[Api]) {
|
||||
fn extend_with_set<S>(
|
||||
&self,
|
||||
handler: &mut MetaIoHandler<Metadata, S>,
|
||||
apis: &[Api],
|
||||
) where S: core::Middleware<Metadata> {
|
||||
self.extend_api(handler, apis, false)
|
||||
}
|
||||
}
|
||||
@ -363,8 +374,9 @@ pub struct LightDependencies {
|
||||
pub on_demand: Arc<::light::on_demand::OnDemand>,
|
||||
pub cache: Arc<Mutex<LightDataCache>>,
|
||||
pub transaction_queue: Arc<RwLock<LightTransactionQueue>>,
|
||||
pub dapps_interface: Option<String>,
|
||||
pub dapps_port: Option<u16>,
|
||||
pub dapps_service: Option<Arc<DappsService>>,
|
||||
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<Metadata, Middleware<Self::Notifier>>, apis: &[Api]) {
|
||||
|
||||
fn extend_with_set<S>(
|
||||
&self,
|
||||
handler: &mut MetaIoHandler<Metadata, S>,
|
||||
apis: &[Api],
|
||||
) where S: core::Middleware<Metadata> {
|
||||
self.extend_api(handler, apis, false)
|
||||
}
|
||||
}
|
||||
@ -552,15 +570,6 @@ impl ApiSet {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_rpc<D: Dependencies>(stats: Arc<RpcStats>, deps: &D, apis: ApiSet) -> MetaIoHandler<Metadata, Middleware<D::Notifier>> {
|
||||
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::<Vec<_>>();
|
||||
deps.extend_with_set(&mut handler, &apis[..]);
|
||||
|
||||
handler
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Api, ApiSet};
|
||||
|
173
parity/run.rs
173
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<String>,
|
||||
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<RotatingLogger>) ->
|
||||
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<RotatingLogger>) ->
|
||||
// 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<RotatingLogger>) ->
|
||||
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<RotatingLogger>) ->
|
||||
pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<(bool, Option<String>), 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<RotatingLogger>) -> 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<RotatingLogger>) -> 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<RotatingLogger>) -> 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<RotatingLogger>) -> 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<RotatingLogger>) -> 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<RotatingLogger>) -> 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
|
||||
|
125
parity/signer.rs
125
parity/signer.rs
@ -15,51 +15,16 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<D: rpc_apis::Dependencies> {
|
||||
pub apis: Arc<D>,
|
||||
pub remote: TokioRemote,
|
||||
pub rpc_stats: Arc<RpcStats>,
|
||||
}
|
||||
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<parity_rpc::Metadata> 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;
|
||||
|
||||
rpc_apis::SignerService::new(move || {
|
||||
generate_new_token(&signer_path).map_err(|e| format!("{:?}", e))
|
||||
}, signer_enabled)
|
||||
}
|
||||
|
||||
pub fn start<D: rpc_apis::Dependencies>(
|
||||
conf: Configuration,
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
deps: Dependencies<D>,
|
||||
) -> Result<Option<SignerServer>, String> {
|
||||
if !conf.enabled {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(do_start(conf, queue, deps)?))
|
||||
}
|
||||
}
|
||||
|
||||
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<String, String> {
|
||||
Ok(generate_token_and_url(&cmd)?.message)
|
||||
pub fn execute(ws_conf: rpc::WsConfiguration, ui_conf: rpc::UiConfiguration) -> Result<String, String> {
|
||||
Ok(generate_token_and_url(&ws_conf, &ui_conf)?.message)
|
||||
}
|
||||
|
||||
pub fn generate_token_and_url(conf: &Configuration) -> Result<NewToken, String> {
|
||||
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<NewToken, String> {
|
||||
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<String> {
|
||||
fn generate_new_token(path: &Path) -> io::Result<String> {
|
||||
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<D: rpc_apis::Dependencies>(
|
||||
conf: Configuration,
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
deps: Dependencies<D>
|
||||
) -> Result<SignerServer, String> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,9 +8,13 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
||||
[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" }
|
||||
|
@ -1,17 +0,0 @@
|
||||
[package]
|
||||
description = "Rpc test client."
|
||||
name = "rpctest"
|
||||
version = "1.7.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[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"
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 <test-file> --name <test-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> = 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();
|
||||
}
|
@ -14,11 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,20 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<String>, user_agent: Option<String>, dapps_origin: Option<String>) -> Self::Metadata;
|
||||
}
|
||||
|
||||
pub struct HyperMetaExtractor<T> {
|
||||
extractor: T,
|
||||
@ -37,13 +46,14 @@ impl<M, T> http::MetaExtractor<M> for HyperMetaExtractor<T> where
|
||||
M: jsonrpc_core::Metadata,
|
||||
{
|
||||
fn read_metadata(&self, req: &hyper::server::Request<hyper::net::HttpStream>) -> M {
|
||||
let origin = req.headers().get::<hyper::header::Origin>()
|
||||
.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<M, T> minihttp::MetaExtractor<M> for MiniMetaExtractor<T> 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)
|
||||
}
|
||||
}
|
@ -14,13 +14,18 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore rpc.
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(feature="nightly", feature(plugin))]
|
||||
#![cfg_attr(feature="nightly", plugin(clippy))]
|
||||
//! Parity RPC.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![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<minihttp::Error> 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<String>) -> Self::Metadata;
|
||||
}
|
||||
|
||||
/// HTTP server implementation-specific settings.
|
||||
pub enum HttpSettings<R: RequestMiddleware> {
|
||||
/// Enable fast minihttp server with given number of threads.
|
||||
@ -164,7 +172,7 @@ pub fn start_http<M, S, H, T, R>(
|
||||
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<M, S, H, T, R>(
|
||||
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<M, S, H, T>(
|
||||
}
|
||||
|
||||
/// Start WS server and return `Server` handle.
|
||||
pub fn start_ws<M, S, H, T, U>(
|
||||
pub fn start_ws<M, S, H, T, U, V>(
|
||||
addr: &SocketAddr,
|
||||
handler: H,
|
||||
remote: tokio_core::reactor::Remote,
|
||||
allowed_origins: ws::DomainsValidation<ws::Origin>,
|
||||
allowed_hosts: ws::DomainsValidation<ws::Host>,
|
||||
extractor: T,
|
||||
middleware: V,
|
||||
stats: U,
|
||||
) -> Result<ws::Server, ws::Error> where
|
||||
M: jsonrpc_core::Metadata,
|
||||
@ -219,9 +228,11 @@ pub fn start_ws<M, S, H, T, U>(
|
||||
H: Into<jsonrpc_core::MetaIoHandler<M, S>>,
|
||||
T: ws::MetaExtractor<M>,
|
||||
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)
|
||||
|
84
rpc/src/tests/helpers.rs
Normal file
84
rpc/src/tests/helpers.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use devtools::RandomTempPath;
|
||||
use parity_reactor::{EventLoop, TokioRemote};
|
||||
|
||||
use authcodes::AuthCodes;
|
||||
|
||||
/// Server with event loop
|
||||
pub struct Server<T> {
|
||||
/// Server
|
||||
pub server: T,
|
||||
/// RPC Event Loop
|
||||
pub event_loop: EventLoop,
|
||||
}
|
||||
|
||||
impl<T> Server<T> {
|
||||
pub fn new<F>(f: F) -> Server<T> 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<T> Deref for Server<T> {
|
||||
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
|
||||
}
|
||||
}
|
@ -14,12 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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;
|
172
rpc/src/tests/rpc.rs
Normal file
172
rpc/src/tests/rpc.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<MetaIoHandler<Metadata>>) -> Server<HttpServer> {
|
||||
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<hyper::net::HttpStream>, _control: &hyper::Control| {
|
||||
http::RequestMiddlewareAction::Proceed {
|
||||
should_continue_on_invalid_cors: false
|
||||
}
|
||||
})),
|
||||
).unwrap())
|
||||
}
|
||||
|
||||
/// Test a single request to running server
|
||||
fn request(server: Server<HttpServer>, 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);
|
||||
}
|
||||
}
|
@ -14,79 +14,42 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<ws::Server>, usize, GuardedAuthCodes) {
|
||||
let port = 35000 + rand::random::<usize>() % 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,
|
||||
};
|
||||
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());
|
||||
|
||||
(res, port, GuardedAuthCodes {
|
||||
authcodes: AuthCodes::from_file(&path).unwrap(),
|
||||
path: path,
|
||||
})
|
||||
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<ws::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);
|
||||
}
|
||||
}
|
263
rpc/src/v1/extractors.rs
Normal file
263
rpc/src/v1/extractors.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<String>, user_agent: Option<String>, dapps_origin: Option<String>) -> 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<Metadata> 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<PathBuf>,
|
||||
}
|
||||
|
||||
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<Metadata> 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<H256> {
|
||||
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<RpcStats>,
|
||||
}
|
||||
|
||||
impl WsStats {
|
||||
/// Creates new WS usage tracker.
|
||||
pub fn new(stats: Arc<RpcStats>) -> 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<M: core::Middleware<Metadata>> {
|
||||
full_handler: core::MetaIoHandler<Metadata, M>,
|
||||
}
|
||||
|
||||
impl<M: core::Middleware<Metadata>> WsDispatcher<M> {
|
||||
/// Create new `WsDispatcher` with given full handler.
|
||||
pub fn new(full_handler: core::MetaIoHandler<Metadata, M>) -> Self {
|
||||
WsDispatcher {
|
||||
full_handler: full_handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: core::Middleware<Metadata>> core::Middleware<Metadata> for WsDispatcher<M> {
|
||||
fn on_request<F>(&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()));
|
||||
}
|
||||
}
|
33
rpc/src/v1/helpers/dapps.rs
Normal file
33
rpc/src/v1/helpers/dapps.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Dapps Service
|
||||
|
||||
use v1::types::LocalDapp;
|
||||
|
||||
/// Dapps Server service.
|
||||
pub trait DappsService: Send + Sync + 'static {
|
||||
/// List available local dapps.
|
||||
fn list_dapps(&self) -> Vec<LocalDapp>;
|
||||
}
|
||||
|
||||
impl<F> DappsService for F where
|
||||
F: Fn() -> Vec<LocalDapp> + Send + Sync + 'static
|
||||
{
|
||||
fn list_dapps(&self) -> Vec<LocalDapp> {
|
||||
(*self)()
|
||||
}
|
||||
}
|
@ -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),
|
||||
|
@ -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<String> {
|
||||
address.as_ref().map(|&(ref iface, ref port)| format!("{}:{}", iface, port))
|
||||
}
|
||||
|
@ -27,21 +27,21 @@ const TOKEN_LIFETIME_SECS: u32 = 3600;
|
||||
|
||||
/// Manages communication with Signer crate
|
||||
pub struct SignerService {
|
||||
is_enabled: bool,
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
web_proxy_tokens: Mutex<TransientHashMap<String, ()>>,
|
||||
generate_new_token: Box<Fn() -> Result<String, String> + Send + Sync + 'static>,
|
||||
address: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl SignerService {
|
||||
/// Creates new Signer Service given function to generate new tokens.
|
||||
pub fn new<F>(new_token: F, address: Option<(String, u16)>) -> Self
|
||||
pub fn new<F>(new_token: F, is_enabled: bool) -> Self
|
||||
where F: Fn() -> Result<String, String> + 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<RotatingLogger>,
|
||||
settings: Arc<NetworkSettings>,
|
||||
signer: Option<Arc<SignerService>>,
|
||||
dapps_interface: Option<String>,
|
||||
dapps_port: Option<u16>,
|
||||
dapps_address: Option<(String, u16)>,
|
||||
ws_address: Option<(String, u16)>,
|
||||
eip86_transition: u64,
|
||||
}
|
||||
|
||||
@ -68,8 +68,8 @@ impl ParityClient {
|
||||
logger: Arc<RotatingLogger>,
|
||||
settings: Arc<NetworkSettings>,
|
||||
signer: Option<Arc<SignerService>>,
|
||||
dapps_interface: Option<String>,
|
||||
dapps_port: Option<u16>,
|
||||
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<u16, Error> {
|
||||
self.signer
|
||||
.clone()
|
||||
.and_then(|signer| signer.address())
|
||||
.map(|address| address.1)
|
||||
.ok_or_else(|| errors::signer_disabled())
|
||||
}
|
||||
|
||||
fn dapps_port(&self) -> Result<u16, Error> {
|
||||
self.dapps_port
|
||||
fn dapps_url(&self) -> Result<String, Error> {
|
||||
helpers::to_url(&self.dapps_address)
|
||||
.ok_or_else(|| errors::dapps_disabled())
|
||||
}
|
||||
|
||||
fn dapps_interface(&self) -> Result<String, Error> {
|
||||
self.dapps_interface.clone()
|
||||
.ok_or_else(|| errors::dapps_disabled())
|
||||
fn ws_url(&self) -> Result<String, Error> {
|
||||
helpers::to_url(&self.ws_address)
|
||||
.ok_or_else(|| errors::ws_disabled())
|
||||
}
|
||||
|
||||
fn next_nonce(&self, address: H160) -> BoxFuture<U256, Error> {
|
||||
|
@ -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<F> {
|
||||
net: Arc<ManageNetwork>,
|
||||
dapps: Option<Arc<DappsService>>,
|
||||
fetch: F,
|
||||
}
|
||||
|
||||
impl<F: Fetch> ParitySetClient<F> {
|
||||
/// Creates new `ParitySetClient` with given `Fetch`.
|
||||
pub fn new(net: Arc<ManageNetwork>, fetch: F) -> Self {
|
||||
pub fn new(net: Arc<ManageNetwork>, dapps: Option<Arc<DappsService>>, fetch: F) -> Self {
|
||||
ParitySetClient {
|
||||
net: net,
|
||||
dapps: dapps,
|
||||
fetch: fetch,
|
||||
}
|
||||
}
|
||||
@ -132,6 +135,10 @@ impl<F: Fetch> ParitySet for ParitySetClient<F> {
|
||||
}))
|
||||
}
|
||||
|
||||
fn dapps_list(&self) -> Result<Vec<LocalDapp>, Error> {
|
||||
self.dapps.as_ref().map(|dapps| dapps.list_dapps()).ok_or_else(errors::dapps_disabled)
|
||||
}
|
||||
|
||||
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error> {
|
||||
Err(errors::light_unimplemented(None))
|
||||
}
|
||||
|
@ -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<C, M, S: ?Sized, U> where
|
||||
logger: Arc<RotatingLogger>,
|
||||
settings: Arc<NetworkSettings>,
|
||||
signer: Option<Arc<SignerService>>,
|
||||
dapps_interface: Option<String>,
|
||||
dapps_port: Option<u16>,
|
||||
dapps_address: Option<(String, u16)>,
|
||||
ws_address: Option<(String, u16)>,
|
||||
eip86_transition: u64,
|
||||
}
|
||||
|
||||
@ -89,8 +89,8 @@ impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> where
|
||||
logger: Arc<RotatingLogger>,
|
||||
settings: Arc<NetworkSettings>,
|
||||
signer: Option<Arc<SignerService>>,
|
||||
dapps_interface: Option<String>,
|
||||
dapps_port: Option<u16>,
|
||||
dapps_address: Option<(String, u16)>,
|
||||
ws_address: Option<(String, u16)>,
|
||||
) -> Self {
|
||||
ParityClient {
|
||||
client: Arc::downgrade(client),
|
||||
@ -102,8 +102,8 @@ impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> 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<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
||||
)
|
||||
}
|
||||
|
||||
fn signer_port(&self) -> Result<u16, Error> {
|
||||
self.signer
|
||||
.clone()
|
||||
.and_then(|signer| signer.address())
|
||||
.map(|address| address.1)
|
||||
.ok_or_else(|| errors::signer_disabled())
|
||||
}
|
||||
|
||||
fn dapps_port(&self) -> Result<u16, Error> {
|
||||
self.dapps_port
|
||||
fn dapps_url(&self) -> Result<String, Error> {
|
||||
helpers::to_url(&self.dapps_address)
|
||||
.ok_or_else(|| errors::dapps_disabled())
|
||||
}
|
||||
|
||||
fn dapps_interface(&self) -> Result<String, Error> {
|
||||
self.dapps_interface.clone()
|
||||
.ok_or_else(|| errors::dapps_disabled())
|
||||
fn ws_url(&self) -> Result<String, Error> {
|
||||
helpers::to_url(&self.ws_address)
|
||||
.ok_or_else(|| errors::ws_disabled())
|
||||
}
|
||||
|
||||
fn next_nonce(&self, address: H160) -> BoxFuture<U256, Error> {
|
||||
|
@ -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<C, M, U, F = fetch::Client> {
|
||||
@ -38,6 +39,7 @@ pub struct ParitySetClient<C, M, U, F = fetch::Client> {
|
||||
miner: Weak<M>,
|
||||
updater: Weak<U>,
|
||||
net: Weak<ManageNetwork>,
|
||||
dapps: Option<Arc<DappsService>>,
|
||||
fetch: F,
|
||||
eip86_transition: u64,
|
||||
}
|
||||
@ -46,12 +48,20 @@ impl<C, M, U, F> ParitySetClient<C, M, U, F>
|
||||
where C: MiningBlockChainClient + 'static,
|
||||
{
|
||||
/// Creates new `ParitySetClient` with given `Fetch`.
|
||||
pub fn new(client: &Arc<C>, miner: &Arc<M>, updater: &Arc<U>, net: &Arc<ManageNetwork>, fetch: F) -> Self {
|
||||
pub fn new(
|
||||
client: &Arc<C>,
|
||||
miner: &Arc<M>,
|
||||
updater: &Arc<U>,
|
||||
net: &Arc<ManageNetwork>,
|
||||
dapps: Option<Arc<DappsService>>,
|
||||
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<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
||||
}))
|
||||
}
|
||||
|
||||
fn dapps_list(&self) -> Result<Vec<LocalDapp>, Error> {
|
||||
self.dapps.as_ref().map(|dapps| dapps.list_dapps()).ok_or_else(errors::dapps_disabled)
|
||||
}
|
||||
|
||||
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error> {
|
||||
let updater = take_weak!(self.updater);
|
||||
Ok(updater.upgrade_ready().map(Into::into))
|
||||
|
@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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(),
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
37
rpc/src/v1/tests/helpers/dapps.rs
Normal file
37
rpc/src/v1/tests/helpers/dapps.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<LocalDapp> {
|
||||
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(),
|
||||
}]
|
||||
}
|
||||
}
|
@ -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::sync_provider::{Config, TestSyncProvider};
|
||||
pub use self::update_service::TestUpdater;
|
@ -41,8 +41,8 @@ pub struct Dependencies {
|
||||
pub settings: Arc<NetworkSettings>,
|
||||
pub network: Arc<ManageNetwork>,
|
||||
pub accounts: Arc<AccountProvider>,
|
||||
pub dapps_interface: Option<String>,
|
||||
pub dapps_port: Option<u16>,
|
||||
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,16 +386,17 @@ fn rpc_parity_encrypt() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_parity_signer_port() {
|
||||
fn rpc_parity_ws_address() {
|
||||
// given
|
||||
let deps = Dependencies::new();
|
||||
let io1 = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180))));
|
||||
let mut deps = Dependencies::new();
|
||||
let io1 = deps.default_client();
|
||||
deps.ws_address = None;
|
||||
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}"#;
|
||||
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()));
|
||||
@ -403,34 +404,16 @@ fn rpc_parity_signer_port() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_parity_dapps_port() {
|
||||
fn rpc_parity_dapps_address() {
|
||||
// given
|
||||
let mut deps = Dependencies::new();
|
||||
let io1 = deps.default_client();
|
||||
deps.dapps_port = None;
|
||||
deps.dapps_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}"#;
|
||||
|
||||
// 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_interface() {
|
||||
// given
|
||||
let mut deps = Dependencies::new();
|
||||
let io1 = deps.default_client();
|
||||
deps.dapps_interface = 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
|
||||
|
@ -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<TestMinerService> {
|
||||
@ -46,8 +46,14 @@ fn updater_service() -> Arc<TestUpdater> {
|
||||
|
||||
pub type TestParitySetClient = ParitySetClient<TestBlockChainClient, TestMinerService, TestUpdater, TestFetch>;
|
||||
|
||||
fn parity_set_client(client: &Arc<TestBlockChainClient>, miner: &Arc<TestMinerService>, updater: &Arc<TestUpdater>, net: &Arc<TestManageNetwork>) -> TestParitySetClient {
|
||||
ParitySetClient::new(client, miner, updater, &(net.clone() as Arc<ManageNetwork>), TestFetch::default())
|
||||
fn parity_set_client(
|
||||
client: &Arc<TestBlockChainClient>,
|
||||
miner: &Arc<TestMinerService>,
|
||||
updater: &Arc<TestUpdater>,
|
||||
net: &Arc<TestManageNetwork>,
|
||||
) -> TestParitySetClient {
|
||||
let dapps_service = Arc::new(TestDappsService);
|
||||
ParitySetClient::new(client, miner, updater, &(net.clone() as Arc<ManageNetwork>), 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()));
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ fn miner_service() -> Arc<TestMinerService> {
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -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());
|
||||
|
@ -151,17 +151,13 @@ build_rpc_trait! {
|
||||
#[rpc(name = "parity_localTransactions")]
|
||||
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error>;
|
||||
|
||||
/// Returns current Trusted Signer port or an error if signer is disabled.
|
||||
#[rpc(name = "parity_signerPort")]
|
||||
fn signer_port(&self) -> Result<u16, Error>;
|
||||
/// Returns current Dapps Server interface and port or an error if dapps server is disabled.
|
||||
#[rpc(name = "parity_dappsUrl")]
|
||||
fn dapps_url(&self) -> Result<String, Error>;
|
||||
|
||||
/// Returns current Dapps Server port or an error if dapps server is disabled.
|
||||
#[rpc(name = "parity_dappsPort")]
|
||||
fn dapps_port(&self) -> Result<u16, Error>;
|
||||
|
||||
/// Returns current Dapps Server interface address or an error if dapps server is disabled.
|
||||
#[rpc(name = "parity_dappsInterface")]
|
||||
fn dapps_interface(&self) -> Result<String, Error>;
|
||||
/// Returns current WS Server interface and port or an error if ws server is disabled.
|
||||
#[rpc(name = "parity_wsUrl")]
|
||||
fn ws_url(&self) -> Result<String, Error>;
|
||||
|
||||
/// Returns next nonce for particular sender. Should include all transactions in the queue.
|
||||
#[rpc(async, name = "parity_nextNonce")]
|
||||
|
@ -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<H256, Error>;
|
||||
|
||||
/// Returns a list of local dapps
|
||||
#[rpc(name = "parity_dappsList")]
|
||||
fn dapps_list(&self) -> Result<Vec<LocalDapp>, Error>;
|
||||
|
||||
/// Is there a release ready for install?
|
||||
#[rpc(name = "parity_upgradeReady")]
|
||||
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error>;
|
||||
|
@ -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());
|
||||
|
57
rpc/src/v1/types/dapps.rs
Normal file
57
rpc/src/v1/types/dapps.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
@ -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};
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
|
@ -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" }
|
||||
|
@ -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<T>(
|
||||
&mut self, method: &'static str, params: Vec<JsonValue>
|
||||
|
@ -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();
|
||||
|
@ -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<Self, RpcError> {
|
||||
Ok(SignerRpc { rpc: Rpc::new(&url, authfile)? })
|
||||
}
|
||||
pub fn requests_to_confirm(&mut self) ->
|
||||
BoxFuture<Result<Vec<ConfirmationRequest>, RpcError>, Canceled>
|
||||
{
|
||||
|
||||
pub fn requests_to_confirm(&mut self) -> BoxFuture<Result<Vec<ConfirmationRequest>, 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<U256>,
|
||||
new_condition: Option<Option<TransactionCondition>>,
|
||||
pwd: &str
|
||||
) -> BoxFuture<Result<U256, RpcError>, Canceled>
|
||||
{
|
||||
) -> BoxFuture<Result<U256, RpcError>, 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<Result<bool, RpcError>, Canceled>
|
||||
{
|
||||
|
||||
pub fn reject_request(&mut self, id: U256) -> BoxFuture<Result<bool, RpcError>, Canceled> {
|
||||
self.rpc.request("signer_rejectRequest", vec![
|
||||
JsonValue::String(format!("{:#x}", id))
|
||||
])
|
||||
|
@ -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 \
|
||||
|
@ -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 <admin@parity.io>"]
|
||||
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"]
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![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::*;
|
@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
{meta}
|
||||
<title>{title}</title>
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="parity-navbar"></div>
|
||||
<div class="parity-box">
|
||||
<h1>{title}</h1>
|
||||
<h3>{message}</h3>
|
||||
<p><code>{details}</code></p>
|
||||
</div>
|
||||
<div class="parity-status">
|
||||
<small>{version}</small>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! `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<ws::Error> 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<M: Metadata> session::MetaExtractor<M> for NoopExtractor {}
|
||||
|
||||
/// Builder for `WebSockets` server
|
||||
pub struct ServerBuilder {
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
authcodes_path: PathBuf,
|
||||
skip_origin_validation: bool,
|
||||
stats: Option<Arc<RpcStats>>,
|
||||
}
|
||||
|
||||
impl ServerBuilder {
|
||||
/// Creates new `ServerBuilder`
|
||||
pub fn new(queue: Arc<ConfirmationsQueue>, 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<RpcStats>) -> 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<M: Metadata, S: Middleware<M>, H: Into<MetaIoHandler<M, S>>>(
|
||||
self,
|
||||
addr: SocketAddr,
|
||||
handler: H,
|
||||
remote: Remote,
|
||||
) -> Result<Server, ServerError> {
|
||||
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<M: Metadata, S: Middleware<M>, H: Into<MetaIoHandler<M, S>>, T: session::MetaExtractor<M>>(
|
||||
self,
|
||||
addr: SocketAddr,
|
||||
handler: H,
|
||||
remote: Remote,
|
||||
meta_extractor: T,
|
||||
) -> Result<Server, ServerError> {
|
||||
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<thread::JoinHandle<()>>,
|
||||
broadcaster: ws::Sender,
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
panic_handler: Arc<PanicHandler>,
|
||||
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<M: Metadata, S: Middleware<M>, T: session::MetaExtractor<M>>(
|
||||
addr: SocketAddr,
|
||||
handler: MetaIoHandler<M, S>,
|
||||
remote: Remote,
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
authcodes_path: PathBuf,
|
||||
skip_origin_validation: bool,
|
||||
stats: Option<Arc<RpcStats>>,
|
||||
meta_extractor: T,
|
||||
) -> Result<Server, ServerError> {
|
||||
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<F>(&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);
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<Vec<&str>>) -> Option<H256> {
|
||||
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<M: Metadata>: Send + Clone + 'static {
|
||||
/// Extract metadata for given session
|
||||
fn extract_metadata(&self, _session_id: &H256) -> M {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Session<M: Metadata, S: Middleware<M>, T> {
|
||||
session_id: H256,
|
||||
out: ws::Sender,
|
||||
skip_origin_validation: bool,
|
||||
self_origin: String,
|
||||
self_port: u16,
|
||||
authcodes_path: PathBuf,
|
||||
handler: Arc<MetaIoHandler<M, S>>,
|
||||
remote: Remote,
|
||||
file_handler: Arc<ui::Handler>,
|
||||
stats: Option<Arc<RpcStats>>,
|
||||
meta_extractor: T,
|
||||
}
|
||||
|
||||
impl<M: Metadata, S: Middleware<M>, T> Drop for Session<M, S, T> {
|
||||
fn drop(&mut self) {
|
||||
self.stats.as_ref().map(|stats| stats.close_session());
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Metadata, S: Middleware<M>, T: MetaExtractor<M>> ws::Handler for Session<M, S, T> {
|
||||
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<M: Metadata, S: Middleware<M>, T> {
|
||||
handler: Arc<MetaIoHandler<M, S>>,
|
||||
remote: Remote,
|
||||
skip_origin_validation: bool,
|
||||
self_origin: String,
|
||||
self_port: u16,
|
||||
authcodes_path: PathBuf,
|
||||
meta_extractor: T,
|
||||
file_handler: Arc<ui::Handler>,
|
||||
stats: Option<Arc<RpcStats>>,
|
||||
}
|
||||
|
||||
impl<M: Metadata, S: Middleware<M>, T> Factory<M, S, T> {
|
||||
pub fn new(
|
||||
handler: MetaIoHandler<M, S>,
|
||||
remote: Remote,
|
||||
self_origin: String,
|
||||
self_port: u16,
|
||||
authcodes_path: PathBuf,
|
||||
skip_origin_validation: bool,
|
||||
stats: Option<Arc<RpcStats>>,
|
||||
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<M: Metadata, S: Middleware<M>, T: MetaExtractor<M>> ws::Factory for Factory<M, S, T> {
|
||||
type Handler = Session<M, S, T>;
|
||||
|
||||
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")
|
||||
}
|
Loading…
Reference in New Issue
Block a user