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:
Tomasz Drwięga
2017-05-24 12:24:07 +02:00
committed by Arkadiy Paronyan
parent 7499efecf6
commit cbcc369a2d
91 changed files with 2171 additions and 2591 deletions

View File

@@ -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" }

View File

@@ -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"

View File

@@ -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();
}

350
rpc/src/authcodes.rs Normal file
View File

@@ -0,0 +1,350 @@
// 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::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
pub trait TimeProvider {
/// Returns timestamp (in seconds since epoch)
fn now(&self) -> u64;
}
impl<F : Fn() -> u64> TimeProvider for F {
fn now(&self) -> u64 {
self()
}
}
/// Default implementation of `TimeProvider` using system time.
#[derive(Default)]
pub struct DefaultTimeProvider;
impl TimeProvider for DefaultTimeProvider {
fn now(&self) -> u64 {
time::UNIX_EPOCH.elapsed().expect("Valid time has to be set in your system.").as_secs()
}
}
/// No of seconds the hash is valid
const TIME_THRESHOLD: u64 = 7;
/// minimal length of hash
const TOKEN_LENGTH: usize = 16;
/// special "initial" token used for authorization when there are no tokens yet.
const INITIAL_TOKEN: &'static str = "initial";
/// Separator between fields in serialized tokens file.
const SEPARATOR: &'static str = ";";
/// Number of seconds to keep unused tokens.
const UNUSED_TOKEN_TIMEOUT: u64 = 3600 * 24; // a day
struct Code {
code: String,
/// Duration since unix_epoch
created_at: time::Duration,
/// Duration since unix_epoch
last_used_at: Option<time::Duration>,
}
fn decode_time(val: &str) -> Option<time::Duration> {
let time = val.parse::<u64>().ok();
time.map(time::Duration::from_secs)
}
fn encode_time(time: time::Duration) -> String {
format!("{}", time.as_secs())
}
/// Manages authorization codes for `SignerUIs`
pub struct AuthCodes<T: TimeProvider = DefaultTimeProvider> {
codes: Vec<Code>,
now: T,
}
impl AuthCodes<DefaultTimeProvider> {
/// Reads `AuthCodes` from file and creates new instance using `DefaultTimeProvider`.
#[cfg_attr(feature="dev", allow(single_char_pattern))]
pub fn from_file(file: &Path) -> io::Result<AuthCodes> {
let content = {
if let Ok(mut file) = fs::File::open(file) {
let mut s = String::new();
let _ = file.read_to_string(&mut s)?;
s
} else {
"".into()
}
};
let time_provider = DefaultTimeProvider::default();
let codes = content.lines()
.filter_map(|line| {
let mut parts = line.split(SEPARATOR);
let token = parts.next();
let created = parts.next();
let used = parts.next();
match token {
None => None,
Some(token) if token.len() < TOKEN_LENGTH => None,
Some(token) => {
Some(Code {
code: token.into(),
last_used_at: used.and_then(decode_time),
created_at: created.and_then(decode_time)
.unwrap_or_else(|| time::Duration::from_secs(time_provider.now())),
})
}
}
})
.collect();
Ok(AuthCodes {
codes: codes,
now: time_provider,
})
}
}
impl<T: TimeProvider> AuthCodes<T> {
/// Writes all `AuthCodes` to a disk.
pub fn to_file(&self, file: &Path) -> io::Result<()> {
let mut file = fs::File::create(file)?;
let content = self.codes.iter().map(|code| {
let mut data = vec![code.code.clone(), encode_time(code.created_at.clone())];
if let Some(used_at) = code.last_used_at {
data.push(encode_time(used_at));
}
data.join(SEPARATOR)
}).join("\n");
file.write_all(content.as_bytes())
}
/// Creates a new `AuthCodes` store with given `TimeProvider`.
pub fn new(codes: Vec<String>, now: T) -> Self {
AuthCodes {
codes: codes.into_iter().map(|code| Code {
code: code,
created_at: time::Duration::from_secs(now.now()),
last_used_at: None,
}).collect(),
now: now,
}
}
/// Checks if given hash is correct authcode of `SignerUI`
/// Updates this hash last used field in case it's valid.
#[cfg_attr(feature="dev", allow(wrong_self_convention))]
pub fn is_valid(&mut self, hash: &H256, time: u64) -> bool {
let now = self.now.now();
// check time
if time >= now + TIME_THRESHOLD || time <= now - TIME_THRESHOLD {
warn!(target: "signer", "Received old authentication request. ({} vs {})", now, time);
return false;
}
let as_token = |code| format!("{}:{}", code, time).sha3();
// Check if it's the initial token.
if self.is_empty() {
let initial = &as_token(INITIAL_TOKEN) == hash;
// Initial token can be used only once.
if initial {
let _ = self.generate_new();
}
return initial;
}
// look for code
for mut code in &mut self.codes {
if &as_token(&code.code) == hash {
code.last_used_at = Some(time::Duration::from_secs(now));
return true;
}
}
false
}
/// Generates and returns a new code that can be used by `SignerUIs`
pub fn generate_new(&mut self) -> io::Result<String> {
let mut rng = OsRng::new()?;
let code = rng.gen_ascii_chars().take(TOKEN_LENGTH).collect::<String>();
let readable_code = code.as_bytes()
.chunks(4)
.filter_map(|f| String::from_utf8(f.to_vec()).ok())
.collect::<Vec<String>>()
.join("-");
trace!(target: "signer", "New authentication token generated.");
self.codes.push(Code {
code: code,
created_at: time::Duration::from_secs(self.now.now()),
last_used_at: None,
});
Ok(readable_code)
}
/// Returns true if there are no tokens in this store
pub fn is_empty(&self) -> bool {
self.codes.is_empty()
}
/// Removes old tokens that have not been used since creation.
pub fn clear_garbage(&mut self) {
let now = self.now.now();
let threshold = time::Duration::from_secs(now.saturating_sub(UNUSED_TOKEN_TIMEOUT));
let codes = mem::replace(&mut self.codes, Vec::new());
for code in codes {
// Skip codes that are old and were never used.
if code.last_used_at.is_none() && code.created_at <= threshold {
continue;
}
self.codes.push(code);
}
}
}
#[cfg(test)]
mod tests {
use devtools;
use std::io::{Read, Write};
use std::{time, fs};
use std::cell::Cell;
use util::{H256, Hashable};
use super::*;
fn generate_hash(val: &str, time: u64) -> H256 {
format!("{}:{}", val, time).sha3()
}
#[test]
fn should_return_true_if_code_is_initial_and_store_is_empty() {
// given
let code = "initial";
let time = 99;
let mut codes = AuthCodes::new(vec![], || 100);
// when
let res1 = codes.is_valid(&generate_hash(code, time), time);
let res2 = codes.is_valid(&generate_hash(code, time), time);
// then
assert_eq!(res1, true);
assert_eq!(res2, false);
}
#[test]
fn should_return_true_if_hash_is_valid() {
// given
let code = "23521352asdfasdfadf";
let time = 99;
let mut codes = AuthCodes::new(vec![code.into()], || 100);
// when
let res = codes.is_valid(&generate_hash(code, time), time);
// then
assert_eq!(res, true);
}
#[test]
fn should_return_false_if_code_is_unknown() {
// given
let code = "23521352asdfasdfadf";
let time = 99;
let mut codes = AuthCodes::new(vec!["1".into()], || 100);
// when
let res = codes.is_valid(&generate_hash(code, time), time);
// then
assert_eq!(res, false);
}
#[test]
fn should_return_false_if_hash_is_valid_but_time_is_invalid() {
// given
let code = "23521352asdfasdfadf";
let time = 107;
let time2 = 93;
let mut codes = AuthCodes::new(vec![code.into()], || 100);
// when
let res1 = codes.is_valid(&generate_hash(code, time), time);
let res2 = codes.is_valid(&generate_hash(code, time2), time2);
// then
assert_eq!(res1, false);
assert_eq!(res2, false);
}
#[test]
fn should_read_old_format_from_file() {
// given
let path = devtools::RandomTempPath::new();
let code = "23521352asdfasdfadf";
{
let mut file = fs::File::create(&path).unwrap();
file.write_all(b"a\n23521352asdfasdfadf\nb\n").unwrap();
}
// when
let mut authcodes = AuthCodes::from_file(&path).unwrap();
let time = time::UNIX_EPOCH.elapsed().unwrap().as_secs();
// then
assert!(authcodes.is_valid(&generate_hash(code, time), time), "Code should be read from file");
}
#[test]
fn should_remove_old_unused_tokens() {
// given
let path = devtools::RandomTempPath::new();
let code1 = "11111111asdfasdf111";
let code2 = "22222222asdfasdf222";
let code3 = "33333333asdfasdf333";
let time = Cell::new(100);
let mut codes = AuthCodes::new(vec![code1.into(), code2.into(), code3.into()], || time.get());
// `code2` should not be removed (we never remove tokens that were used)
codes.is_valid(&generate_hash(code2, time.get()), time.get());
// when
time.set(100 + 10_000_000);
// mark `code1` as used now
codes.is_valid(&generate_hash(code1, time.get()), time.get());
let new_code = codes.generate_new().unwrap().replace('-', "");
codes.clear_garbage();
codes.to_file(&path).unwrap();
// then
let mut content = String::new();
let mut file = fs::File::open(&path).unwrap();
file.read_to_string(&mut content).unwrap();
assert_eq!(content, format!("{};100;10000100\n{};100;100\n{};10000100", code1, code2, new_code));
}
}

View File

@@ -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)
}
}

View File

@@ -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
View 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
}
}

21
rpc/src/tests/mod.rs Normal file
View File

@@ -0,0 +1,21 @@
// 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/>.
//! RPC integration tests.
mod helpers;
#[cfg(test)] mod rpc;
pub mod ws;

172
rpc/src/tests/rpc.rs Normal file
View 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);
}
}

185
rpc/src/tests/ws.rs Normal file
View File

@@ -0,0 +1,185 @@
// 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 tests.
use std::sync::Arc;
use devtools::http_client;
use jsonrpc_core::MetaIoHandler;
use rand;
use ws;
use v1::{extractors, informant};
use tests::helpers::{GuardedAuthCodes, Server};
/// Setup a mock signer for tests
pub fn serve() -> (Server<ws::Server>, usize, GuardedAuthCodes) {
let port = 35000 + rand::random::<usize>() % 10000;
let address = format!("127.0.0.1:{}", port).parse().unwrap();
let io = MetaIoHandler::default();
let authcodes = GuardedAuthCodes::new();
let stats = Arc::new(informant::RpcStats::default());
let res = Server::new(|remote| ::start_ws(
&address,
io,
remote,
ws::DomainsValidation::Disabled,
ws::DomainsValidation::Disabled,
extractors::WsExtractor::new(Some(&authcodes.path)),
extractors::WsExtractor::new(Some(&authcodes.path)),
extractors::WsStats::new(stats),
).unwrap());
(res, port, authcodes)
}
/// Test a single request to running server
pub fn request(server: Server<ws::Server>, request: &str) -> http_client::Response {
http_client::request(server.server.addr(), request)
}
#[cfg(test)]
mod testing {
use std::time;
use util::Hashable;
use devtools::http_client;
use super::{serve, request};
#[test]
fn should_not_redirect_to_parity_host() {
// given
let (server, port, _) = serve();
// when
let response = request(server,
&format!("\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:{}\r\n\
Connection: close\r\n\
\r\n\
{{}}
", port)
);
// then
assert_eq!(response.status, "HTTP/1.1 200 Ok".to_owned());
}
#[test]
fn should_block_if_authorization_is_incorrect() {
// given
let (server, port, _) = serve();
// when
let response = request(server,
&format!("\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:{}\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
Sec-WebSocket-Protocol: wrong\r\n\
Sec-WebSocket-Version: 13\r\n\
\r\n\
{{}}
", port)
);
// then
assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned());
http_client::assert_security_headers_present(&response.headers, None);
}
#[test]
fn should_allow_if_authorization_is_correct() {
// given
let (server, port, mut authcodes) = serve();
let code = authcodes.generate_new().unwrap().replace("-", "");
authcodes.to_file(&authcodes.path).unwrap();
let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs();
// when
let response = request(server,
&format!("\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:{}\r\n\
Connection: Close\r\n\
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
Sec-WebSocket-Protocol: {:?}_{}\r\n\
Sec-WebSocket-Version: 13\r\n\
\r\n\
{{}}
",
port,
format!("{}:{}", code, timestamp).sha3(),
timestamp,
)
);
// then
assert_eq!(response.status, "HTTP/1.1 101 Switching Protocols".to_owned());
}
#[test]
fn should_allow_initial_connection_but_only_once() {
// given
let (server, port, authcodes) = serve();
let code = "initial";
let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs();
assert!(authcodes.is_empty());
// when
let response1 = http_client::request(server.addr(),
&format!("\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:{}\r\n\
Connection: Close\r\n\
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
Sec-WebSocket-Protocol:{:?}_{}\r\n\
Sec-WebSocket-Version: 13\r\n\
\r\n\
{{}}
",
port,
format!("{}:{}", code, timestamp).sha3(),
timestamp,
)
);
let response2 = http_client::request(server.addr(),
&format!("\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:{}\r\n\
Connection: Close\r\n\
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
Sec-WebSocket-Protocol:{:?}_{}\r\n\
Sec-WebSocket-Version: 13\r\n\
\r\n\
{{}}
",
port,
format!("{}:{}", code, timestamp).sha3(),
timestamp,
)
);
// then
assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".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
View 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()));
}
}

View 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)()
}
}

View File

@@ -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),

View File

@@ -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))
}

View File

@@ -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)
}
}

View File

@@ -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> {

View File

@@ -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))
}

View File

@@ -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> {

View File

@@ -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))

View File

@@ -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(),
}
}

View File

@@ -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;
}

View 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(),
}]
}
}

View File

@@ -16,14 +16,16 @@
//! Test rpc services.
mod sync_provider;
mod miner_service;
mod dapps;
mod fetch;
mod miner_service;
mod snapshot_service;
mod sync_provider;
mod update_service;
pub use self::sync_provider::{Config, TestSyncProvider};
pub use self::miner_service::TestMinerService;
pub use self::dapps::TestDappsService;
pub use self::fetch::TestFetch;
pub use self::miner_service::TestMinerService;
pub use self::snapshot_service::TestSnapshotService;
pub use self::update_service::TestUpdater;
pub use self::sync_provider::{Config, TestSyncProvider};
pub use self::update_service::TestUpdater;

View File

@@ -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

View File

@@ -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()));
}

View File

@@ -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();

View File

@@ -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());

View File

@@ -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")]

View File

@@ -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>;

View File

@@ -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
View 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);
}
}

View File

@@ -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};

View File

@@ -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]