diff --git a/Cargo.lock b/Cargo.lock index a01351fbd..faed42dcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,10 +25,9 @@ dependencies = [ "ethcore-util 1.6.0", "ethsync 1.6.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index f304c917b..4dac6a221 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ number_prefix = "0.2" rpassword = "0.2.1" semver = "0.5" ansi_term = "0.7" -lazy_static = "0.2" regex = "0.1" isatty = "0.1" toml = "0.2" @@ -24,7 +23,7 @@ serde = "0.9" serde_json = "0.9" app_dirs = "1.1.1" fdlimit = "0.1" -hyper = { version = "0.9", default-features = false } +hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" } jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } ethsync = { path = "sync" } diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index c30fd1d49..cc6f4d81a 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -77,8 +77,7 @@ impl HttpMetaExtractor for MetadataExtractor { }) }); Metadata { - dapp_id: dapp_id, - origin: Origin::Dapps, + origin: Origin::Dapps(dapp_id.map(Into::into).unwrap_or_default()), } } } diff --git a/dapps/src/tests/rpc.rs b/dapps/src/tests/rpc.rs index 7c2486099..0dbba384c 100644 --- a/dapps/src/tests/rpc.rs +++ b/dapps/src/tests/rpc.rs @@ -55,8 +55,8 @@ fn should_extract_metadata() { // given let mut io = MetaIoHandler::default(); io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { - assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned())); - assert_eq!(meta.origin, Origin::Dapps); + assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into())); + assert_eq!(meta.dapp_id(), "https://parity.io/".into()); future::ok(Value::String("Hello World!".into())).boxed() }); let server = serve_with_rpc(io); @@ -89,8 +89,8 @@ fn should_extract_metadata_from_custom_header() { // given let mut io = MetaIoHandler::default(); io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { - assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned())); - assert_eq!(meta.origin, Origin::Dapps); + assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into())); + assert_eq!(meta.dapp_id(), "https://parity.io/".into()); future::ok(Value::String("Hello World!".into())).boxed() }); let server = serve_with_rpc(io); diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index a8f953d6f..e4bb2ea2f 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -649,8 +649,8 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use engines::{Engine, EngineError, Seal}; - use super::*; - use super::message::*; + use super::{Step, View, Height, message_info_rlp, message_full_rlp}; + use super::message::VoteStep; /// Accounts inserted with "0" and "1" are validators. First proposer is "0". fn setup() -> (Spec, Arc) { diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index 206f1cb7e..5842aaafa 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -305,7 +305,7 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { } fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec) -> Option { - let trace_position_deq = trace_position.into_iter().collect::>(); + let trace_position_deq = VecDeque::from(trace_position); self.extras.block_hash(block_number) .and_then(|block_hash| self.transactions_traces(&block_hash) .and_then(|traces| traces.into_iter().nth(tx_position)) diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index 92a363566..094cda25a 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -203,6 +203,13 @@ export function outSignerRequest (request) { request[key].signTransaction = outTransaction(request[key].signTransaction); request[key].sendTransaction = outTransaction(request[key].sendTransaction); break; + + case 'origin': + const type = Object.keys(request[key])[0]; + const details = request[key][type]; + + request[key] = { type, details }; + break; } }); } diff --git a/js/src/api/transport/ws/ws.js b/js/src/api/transport/ws/ws.js index 478c3e8c5..4b41935cd 100644 --- a/js/src/api/transport/ws/ws.js +++ b/js/src/api/transport/ws/ws.js @@ -28,6 +28,7 @@ export default class Ws extends JsonRpcBase { this._url = url; this._token = token; this._messages = {}; + this._sessionHash = null; this._connecting = false; this._connected = false; @@ -78,12 +79,14 @@ export default class Ws extends JsonRpcBase { this._ws.onmessage = null; this._ws.close(); this._ws = null; + this._sessionHash = null; } this._connecting = true; this._connected = false; this._lastError = null; + this._sessionHash = sha3; this._ws = new WebSocket(this._url, hash); this._ws.onerror = this._onError; this._ws.onopen = this._onOpen; @@ -255,6 +258,10 @@ export default class Ws extends JsonRpcBase { return this._token; } + get sessionHash () { + return this._sessionHash; + } + get isAutoConnect () { return this._autoConnect; } diff --git a/js/src/views/Signer/components/RequestOrigin/index.js b/js/src/views/Signer/components/RequestOrigin/index.js new file mode 100644 index 000000000..2f703c924 --- /dev/null +++ b/js/src/views/Signer/components/RequestOrigin/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './requestOrigin'; diff --git a/js/src/views/Signer/components/RequestOrigin/requestOrigin.css b/js/src/views/Signer/components/RequestOrigin/requestOrigin.css new file mode 100644 index 000000000..f4aac4c48 --- /dev/null +++ b/js/src/views/Signer/components/RequestOrigin/requestOrigin.css @@ -0,0 +1,43 @@ +/* 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 . +*/ + +.container { + text-align: left; + margin: 3em .5em; + opacity: 0.6; + font-size: 0.8em; + + .unknown { + color: #e44; + } + + .url { + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .hash { + margin-left: .2em; + } + + .hash, .url { + margin-bottom: -.2em; + display: inline-block; + } +} diff --git a/js/src/views/Signer/components/RequestOrigin/requestOrigin.js b/js/src/views/Signer/components/RequestOrigin/requestOrigin.js new file mode 100644 index 000000000..fb9ef4cd6 --- /dev/null +++ b/js/src/views/Signer/components/RequestOrigin/requestOrigin.js @@ -0,0 +1,116 @@ +// 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 . + +import React, { Component, PropTypes } from 'react'; + +import IdentityIcon from '~/ui/IdentityIcon'; + +import styles from './requestOrigin.css'; + +export default class RequestOrigin extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + static propTypes = { + origin: PropTypes.shape({ + type: PropTypes.oneOf(['unknown', 'dapp', 'rpc', 'ipc', 'signer']), + details: PropTypes.string.isRequired + }).isRequired + }; + + render () { + const { origin } = this.props; + + return ( +
+ Requested { this.renderOrigin(origin) } +
+ ); + } + + renderOrigin (origin) { + if (origin.type === 'unknown') { + return ( + via unknown interface + ); + } + + if (origin.type === 'dapp') { + return ( + + by a dapp at + { origin.details || 'unknown URL' } + + + ); + } + + if (origin.type === 'rpc') { + return ( + + via RPC + ({ origin.details || 'unidentified' }) + + + ); + } + + if (origin.type === 'ipc') { + return ( + + via IPC session + + + + + ); + } + + if (origin.type === 'signer') { + return this.renderSigner(origin.details); + } + } + + renderSigner (session) { + if (session.substr(2) === this.context.api.transport.sessionHash) { + return ( + via current tab + ); + } + + return ( + + via UI session + + + + + ); + } +} diff --git a/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js b/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js new file mode 100644 index 000000000..f1de13db3 --- /dev/null +++ b/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js @@ -0,0 +1,72 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import RequestOrigin from './'; + +const context = { + context: { + api: { + transport: { + sessionHash: '1234' + } + } + } +}; + +describe('views/Signer/components/RequestOrigin', () => { + it('renders unknown', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested via unknown interface'); + }); + + it('renders dapps', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested by a dapp at http://parity.io'); + }); + + it('renders rpc', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested via RPC (unidentified)'); + }); + + it('renders ipc', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested via IPC session'); + }); + + it('renders signer', () => { + expect(shallow( + , + context + ).text()).to.equal('Requested via UI session'); + + expect(shallow( + , + context + ).text()).to.equal('Requested via current tab'); + }); +}); diff --git a/js/src/views/Signer/components/RequestPending/requestPending.js b/js/src/views/Signer/components/RequestPending/requestPending.js index f860fcd00..2d745d26f 100644 --- a/js/src/views/Signer/components/RequestPending/requestPending.js +++ b/js/src/views/Signer/components/RequestPending/requestPending.js @@ -30,6 +30,7 @@ export default class RequestPending extends Component { isTest: PropTypes.bool.isRequired, onConfirm: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired, + origin: PropTypes.object.isRequired, payload: PropTypes.oneOfType([ PropTypes.shape({ sendTransaction: PropTypes.object.isRequired }), PropTypes.shape({ sign: PropTypes.object.isRequired }), @@ -51,7 +52,7 @@ export default class RequestPending extends Component { }; render () { - const { className, date, focus, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props; + const { className, date, focus, gasLimit, id, isSending, isTest, onReject, payload, store, origin } = this.props; if (payload.sign) { const { sign } = payload; @@ -68,6 +69,7 @@ export default class RequestPending extends Component { isTest={ isTest } onConfirm={ this.onConfirm } onReject={ onReject } + origin={ origin } store={ store } /> ); @@ -87,6 +89,7 @@ export default class RequestPending extends Component { isTest={ isTest } onConfirm={ this.onConfirm } onReject={ onReject } + origin={ origin } store={ store } transaction={ transaction } /> diff --git a/js/src/views/Signer/components/SignRequest/signRequest.js b/js/src/views/Signer/components/SignRequest/signRequest.js index 21f5211e6..c5b9cc976 100644 --- a/js/src/views/Signer/components/SignRequest/signRequest.js +++ b/js/src/views/Signer/components/SignRequest/signRequest.js @@ -19,6 +19,7 @@ import { observer } from 'mobx-react'; import Account from '../Account'; import TransactionPendingForm from '../TransactionPendingForm'; +import RequestOrigin from '../RequestOrigin'; import styles from './signRequest.css'; @@ -40,9 +41,9 @@ export default class SignRequest extends Component { }; static propTypes = { - id: PropTypes.object.isRequired, address: PropTypes.string.isRequired, data: PropTypes.string.isRequired, + id: PropTypes.object.isRequired, isFinished: PropTypes.bool.isRequired, isTest: PropTypes.bool.isRequired, store: PropTypes.object.isRequired, @@ -52,11 +53,16 @@ export default class SignRequest extends Component { isSending: PropTypes.bool, onConfirm: PropTypes.func, onReject: PropTypes.func, + origin: PropTypes.any, status: PropTypes.string }; static defaultProps = { - focus: false + focus: false, + origin: { + type: 'unknown', + details: '' + } }; componentWillMount () { @@ -92,7 +98,7 @@ export default class SignRequest extends Component { renderDetails () { const { api } = this.context; - const { address, isTest, store, data } = this.props; + const { address, isTest, store, data, origin } = this.props; const { balances, externalLink } = store; const balance = balances[address]; @@ -110,6 +116,7 @@ export default class SignRequest extends Component { externalLink={ externalLink } isTest={ isTest } /> +

A request to sign data using your account:

diff --git a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css index fb11b8506..048a59c67 100644 --- a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css +++ b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css @@ -39,15 +39,17 @@ width: 40%; vertical-align: top; - img { - display: inline-block; - width: 50px; - height: 50px; - margin: 5px; - } + .account { + img { + display: inline-block; + width: 50px; + height: 50px; + margin: 5px; + } - span { - display: block; + span { + display: block; + } } } diff --git a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js index e73fee922..119af2382 100644 --- a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js +++ b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js @@ -22,6 +22,8 @@ import { Button, MethodDecoding } from '~/ui'; import * as tUtil from '../util/transaction'; import Account from '../Account'; +import RequestOrigin from '../RequestOrigin'; + import styles from './transactionMainDetails.css'; export default class TransactionMainDetails extends Component { @@ -33,11 +35,19 @@ export default class TransactionMainDetails extends Component { gasStore: PropTypes.object, id: PropTypes.object.isRequired, isTest: PropTypes.bool.isRequired, + origin: PropTypes.any, totalValue: PropTypes.object.isRequired, transaction: PropTypes.object.isRequired, value: PropTypes.object.isRequired }; + static defaultProps = { + origin: { + type: 'unknown', + details: '' + } + }; + componentWillMount () { const { totalValue, value } = this.props; @@ -51,7 +61,7 @@ export default class TransactionMainDetails extends Component { } render () { - const { children, externalLink, from, fromBalance, gasStore, isTest, transaction } = this.props; + const { children, externalLink, from, fromBalance, gasStore, isTest, transaction, origin } = this.props; return (
@@ -64,6 +74,7 @@ export default class TransactionMainDetails extends Component { isTest={ isTest } />
+
{ const { actions, gasLimit, isTest } = this.props; - const { date, id, isSending, payload } = data; + const { date, id, isSending, payload, origin } = data; return ( diff --git a/js/src/views/Signer/containers/RequestsPage/requestsPage.js b/js/src/views/Signer/containers/RequestsPage/requestsPage.js index ac460b8d4..7ec4abf2a 100644 --- a/js/src/views/Signer/containers/RequestsPage/requestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/requestsPage.js @@ -107,7 +107,7 @@ class RequestsPage extends Component { renderPending = (data, index) => { const { actions, gasLimit, isTest } = this.props; - const { date, id, isSending, payload } = data; + const { date, id, isSending, payload, origin } = data; return ( diff --git a/parity/main.rs b/parity/main.rs index 63d59d5fa..b0c8f9070 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -28,7 +28,7 @@ extern crate ctrlc; extern crate docopt; extern crate env_logger; extern crate fdlimit; -extern crate hyper; // for price_info.rs +extern crate hyper; extern crate isatty; extern crate jsonrpc_core; extern crate num_cpus; @@ -60,15 +60,14 @@ extern crate parity_reactor; extern crate parity_updater as updater; extern crate rpc_cli; +#[macro_use] +extern crate log as rlog; + #[cfg(feature="stratum")] extern crate ethcore_stratum; #[cfg(feature = "dapps")] extern crate ethcore_dapps; - -#[macro_use] -extern crate log as rlog; - macro_rules! dependency { ($dep_ty:ident, $url:expr) => { { diff --git a/parity/rpc.rs b/parity/rpc.rs index fd077f1ff..49bd94699 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -21,9 +21,10 @@ use std::io; use io::PanicHandler; use dir::default_data_path; -use ethcore_rpc::{self as rpc, RpcServerError, IpcServerError, Metadata}; +use ethcore_rpc::{self as rpc, RpcServerError, IpcServerError, Metadata, Origin}; use ethcore_rpc::informant::{RpcStats, Middleware}; use helpers::parity_ipc_path; +use hyper; use jsonrpc_core::MetaIoHandler; use jsonrpc_core::reactor::{RpcHandler, Remote}; use rpc_apis; @@ -89,6 +90,18 @@ pub struct Dependencies { pub stats: Arc, } +pub struct RpcExtractor; +impl rpc::HttpMetaExtractor for RpcExtractor { + fn read_metadata(&self, req: &hyper::server::Request) -> Metadata { + let origin = req.headers().get::() + .map(|origin| format!("{}://{}", origin.scheme, origin.host)) + .unwrap_or_else(|| "unknown".into()); + let mut metadata = Metadata::default(); + metadata.origin = Origin::Rpc(origin); + metadata + } +} + pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Result, String> { if !conf.enabled { return Ok(None); @@ -113,7 +126,7 @@ pub fn setup_http_rpc_server( let apis = setup_apis(apis, dependencies); let handler = RpcHandler::new(Arc::new(apis), dependencies.remote.clone()); let ph = dependencies.panic_handler.clone(); - let start_result = rpc::start_http(url, cors_domains, allowed_hosts, ph, handler); + let start_result = rpc::start_http(url, cors_domains, allowed_hosts, ph, handler, RpcExtractor); match start_result { Err(RpcServerError::IoError(err)) => match err.kind() { io::ErrorKind::AddrInUse => Err(format!("RPC 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)), diff --git a/parity/signer.rs b/parity/signer.rs index 9b22968dc..346276496 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -23,12 +23,14 @@ pub use ethcore_signer::Server as SignerServer; use ansi_term::Colour; use dir::default_data_path; use ethcore_rpc::informant::RpcStats; +use ethcore_rpc; use ethcore_signer as signer; use helpers::replace_home; use io::{ForwardPanic, PanicHandler}; use jsonrpc_core::reactor::{RpcHandler, Remote}; use rpc_apis; use util::path::restrict_permissions_owner; +use util::H256; const CODES_FILENAME: &'static str = "authcodes"; @@ -67,6 +69,16 @@ pub struct NewToken { pub message: String, } +#[derive(Debug, Default, Clone)] +pub struct StandardExtractor; +impl signer::MetaExtractor for StandardExtractor { + fn extract_metadata(&self, session: &H256) -> ethcore_rpc::Metadata { + let mut metadata = ethcore_rpc::Metadata::default(); + metadata.origin = ethcore_rpc::Origin::Signer((*session).into()); + metadata + } +} + pub fn start(conf: Configuration, deps: Dependencies) -> Result, String> { if !conf.enabled { Ok(None) @@ -133,7 +145,7 @@ fn do_start(conf: Configuration, deps: Dependencies) -> Result>( +pub fn start_http( addr: &SocketAddr, cors_domains: Option>, allowed_hosts: Option>, panic_handler: Arc, handler: RpcHandler, -) -> Result { + extractor: T, +) -> Result where + M: jsonrpc_core::Metadata, + S: jsonrpc_core::Middleware, + T: HttpMetaExtractor, +{ let cors_domains = cors_domains.map(|domains| { domains.into_iter() @@ -90,6 +95,7 @@ pub fn start_http>( }); ServerBuilder::with_rpc_handler(handler) + .meta_extractor(Arc::new(extractor)) .cors(cors_domains.into()) .allowed_hosts(allowed_hosts.into()) .panic_handler(move || { diff --git a/rpc/src/v1/helpers/requests.rs b/rpc/src/v1/helpers/requests.rs index 993a8c5cd..4a3a3704d 100644 --- a/rpc/src/v1/helpers/requests.rs +++ b/rpc/src/v1/helpers/requests.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use util::{Address, U256, Bytes}; -use v1::types::TransactionCondition; +use v1::types::{Origin, TransactionCondition}; /// Transaction request coming from RPC #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] @@ -102,6 +102,8 @@ pub struct ConfirmationRequest { pub id: U256, /// Payload to confirm pub payload: ConfirmationPayload, + /// Request origin + pub origin: Origin, } /// Payload to confirm in Trusted Signer diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index f224dcf0c..36563d061 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -22,8 +22,7 @@ use jsonrpc_core; use util::{Mutex, RwLock, U256, Address}; use ethcore::account_provider::DappId; use v1::helpers::{ConfirmationRequest, ConfirmationPayload}; -use v1::metadata::Metadata; -use v1::types::{ConfirmationResponse, H160 as RpcH160}; +use v1::types::{ConfirmationResponse, H160 as RpcH160, Origin, DappId as RpcDappId}; /// Result that can be returned from JSON RPC. pub type RpcResult = Result; @@ -37,9 +36,9 @@ pub enum DefaultAccount { ForDapp(DappId), } -impl From for DefaultAccount { - fn from(meta: Metadata) -> Self { - DefaultAccount::ForDapp(meta.dapp_id.unwrap_or_default().into()) +impl From for DefaultAccount { + fn from(dapp_id: RpcDappId) -> Self { + DefaultAccount::ForDapp(dapp_id.into()) } } @@ -84,7 +83,7 @@ const QUEUE_LIMIT: usize = 50; pub trait SigningQueue: Send + Sync { /// Add new request to the queue. /// Returns a `ConfirmationPromise` that can be used to await for resolution of given request. - fn add_request(&self, request: ConfirmationPayload) -> Result; + fn add_request(&self, request: ConfirmationPayload, origin: Origin) -> Result; /// Removes a request from the queue. /// Notifies possible token holders that request was rejected. @@ -267,7 +266,7 @@ impl Drop for ConfirmationsQueue { } impl SigningQueue for ConfirmationsQueue { - fn add_request(&self, request: ConfirmationPayload) -> Result { + fn add_request(&self, request: ConfirmationPayload, origin: Origin) -> Result { if self.len() > QUEUE_LIMIT { return Err(QueueAddError::LimitReached); } @@ -290,6 +289,7 @@ impl SigningQueue for ConfirmationsQueue { request: ConfirmationRequest { id: id, payload: request, + origin: origin, }, }); queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.") @@ -362,7 +362,7 @@ mod test { // when let q = queue.clone(); let handle = thread::spawn(move || { - let v = q.add_request(request).unwrap(); + let v = q.add_request(request, Default::default()).unwrap(); let (tx, rx) = mpsc::channel(); v.wait_for_result(move |res| { tx.send(res).unwrap(); @@ -397,7 +397,7 @@ mod test { *v = Some(notification); }).expect("Should be closed nicely.") }); - queue.add_request(request).unwrap(); + queue.add_request(request, Default::default()).unwrap(); queue.finish(); // then @@ -413,7 +413,7 @@ mod test { let request = request(); // when - queue.add_request(request.clone()).unwrap(); + queue.add_request(request.clone(), Default::default()).unwrap(); let all = queue.requests(); // then diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 7a954ccaf..d763db836 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -311,7 +311,7 @@ impl Eth for EthClient where } fn author(&self, meta: Metadata) -> BoxFuture { - let dapp = meta.dapp_id.unwrap_or_default(); + let dapp = meta.dapp_id(); let author = move || { let mut miner = take_weak!(self.miner).author(); @@ -342,7 +342,7 @@ impl Eth for EthClient where } fn accounts(&self, meta: Metadata) -> BoxFuture, Error> { - let dapp = meta.dapp_id.unwrap_or_default(); + let dapp = meta.dapp_id(); let accounts = move || { let accounts = self.dapp_accounts(dapp.into())?; diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 47765bd41..c321657d2 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -178,7 +178,7 @@ impl Eth for EthClient { } fn accounts(&self, meta: Metadata) -> BoxFuture, Error> { - let dapp: DappId = meta.dapp_id.unwrap_or_default().into(); + let dapp: DappId = meta.dapp_id().into(); let accounts = self.accounts .note_dapp_used(dapp.clone()) diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 3b2267395..431c34e84 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -145,7 +145,7 @@ impl Parity for ParityClient where } fn default_account(&self, meta: Self::Metadata) -> BoxFuture { - let dapp_id = meta.dapp_id.unwrap_or_default(); + let dapp_id = meta.dapp_id(); let default_account = move || { Ok(take_weak!(self.accounts) .dapps_addresses(dapp_id.into()) diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 03ce5ffeb..fba058aee 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -101,7 +101,7 @@ impl Personal for PersonalClient { let default = match request.from.as_ref() { Some(account) => Ok(account.clone().into()), None => accounts - .default_address(meta.dapp_id.unwrap_or_default().into()) + .default_address(meta.dapp_id().into()) .map_err(|e| errors::account("Cannot find default account.", e)), }; diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index 6bf2155ed..9dae40730 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -38,7 +38,8 @@ use v1::types::{ RichRawTransaction as RpcRichRawTransaction, TransactionRequest as RpcTransactionRequest, ConfirmationPayload as RpcConfirmationPayload, - ConfirmationResponse as RpcConfirmationResponse + ConfirmationResponse as RpcConfirmationResponse, + Origin, }; const MAX_PENDING_DURATION: u64 = 60 * 60; @@ -81,7 +82,7 @@ impl SigningQueueClient { } } - fn dispatch(&self, payload: RpcConfirmationPayload, default_account: DefaultAccount) -> BoxFuture { + fn dispatch(&self, payload: RpcConfirmationPayload, default_account: DefaultAccount, origin: Origin) -> BoxFuture { let accounts = take_weakf!(self.accounts); let default_account = match default_account { DefaultAccount::Provided(acc) => acc, @@ -100,7 +101,7 @@ impl SigningQueueClient { .boxed() } else { future::done( - signer.add_request(payload) + signer.add_request(payload, origin) .map(DispatchResult::Promise) .map_err(|_| errors::request_rejected_limit()) ).boxed() @@ -113,23 +114,26 @@ impl SigningQueueClient { impl ParitySigning for SigningQueueClient { type Metadata = Metadata; - fn post_sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture, Error> { + fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture, Error> { let pending = self.pending.clone(); - self.dispatch(RpcConfirmationPayload::Signature((address.clone(), data).into()), DefaultAccount::Provided(address.into())) - .map(move |result| match result { - DispatchResult::Value(v) => RpcEither::Or(v), - DispatchResult::Promise(promise) => { - let id = promise.id(); - pending.lock().insert(id, promise); - RpcEither::Either(id.into()) - }, - }) - .boxed() + self.dispatch( + RpcConfirmationPayload::Signature((address.clone(), data).into()), + DefaultAccount::Provided(address.into()), + meta.origin + ).map(move |result| match result { + DispatchResult::Value(v) => RpcEither::Or(v), + DispatchResult::Promise(promise) => { + let id = promise.id(); + pending.lock().insert(id, promise); + RpcEither::Either(id.into()) + }, + }) + .boxed() } fn post_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture, Error> { let pending = self.pending.clone(); - self.dispatch(RpcConfirmationPayload::SendTransaction(request), meta.into()) + self.dispatch(RpcConfirmationPayload::SendTransaction(request), meta.dapp_id().into(), meta.origin) .map(move |result| match result { DispatchResult::Value(v) => RpcEither::Or(v), DispatchResult::Promise(promise) => { @@ -156,8 +160,12 @@ impl ParitySigning for SigningQueueClient { res } - fn decrypt_message(&self, address: RpcH160, data: RpcBytes) -> BoxFuture { - let res = self.dispatch(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into()); + fn decrypt_message(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { + let res = self.dispatch( + RpcConfirmationPayload::Decrypt((address.clone(), data).into()), + address.into(), + meta.origin, + ); let (ready, p) = futures::oneshot(); @@ -181,8 +189,12 @@ impl ParitySigning for SigningQueueClient { impl EthSigning for SigningQueueClient { type Metadata = Metadata; - fn sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture { - let res = self.dispatch(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into()); + fn sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { + let res = self.dispatch( + RpcConfirmationPayload::Signature((address.clone(), data).into()), + address.into(), + meta.origin, + ); let (ready, p) = futures::oneshot(); @@ -200,7 +212,11 @@ impl EthSigning for SigningQueueClient { } fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture { - let res = self.dispatch(RpcConfirmationPayload::SendTransaction(request), meta.into()); + let res = self.dispatch( + RpcConfirmationPayload::SendTransaction(request), + meta.dapp_id().into(), + meta.origin, + ); let (ready, p) = futures::oneshot(); @@ -218,7 +234,11 @@ impl EthSigning for SigningQueueClient { } fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture { - let res = self.dispatch(RpcConfirmationPayload::SignTransaction(request), meta.into()); + let res = self.dispatch( + RpcConfirmationPayload::SignTransaction(request), + meta.dapp_id().into(), + meta.origin, + ); let (ready, p) = futures::oneshot(); diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 333b823f9..b4900f7ec 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -72,7 +72,7 @@ impl EthSigning for SigningUnsafeClient { type Metadata = Metadata; - fn sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture { + fn sign(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { self.handle(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into()) .then(|res| match res { Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature), @@ -83,7 +83,7 @@ impl EthSigning for SigningUnsafeClient } fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture { - self.handle(RpcConfirmationPayload::SendTransaction(request), meta.into()) + self.handle(RpcConfirmationPayload::SendTransaction(request), meta.dapp_id().into()) .then(|res| match res { Ok(RpcConfirmationResponse::SendTransaction(hash)) => Ok(hash), Err(e) => Err(e), @@ -93,7 +93,7 @@ impl EthSigning for SigningUnsafeClient } fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture { - self.handle(RpcConfirmationPayload::SignTransaction(request), meta.into()) + self.handle(RpcConfirmationPayload::SignTransaction(request), meta.dapp_id().into()) .then(|res| match res { Ok(RpcConfirmationResponse::SignTransaction(tx)) => Ok(tx), Err(e) => Err(e), @@ -106,7 +106,7 @@ impl EthSigning for SigningUnsafeClient impl ParitySigning for SigningUnsafeClient { type Metadata = Metadata; - fn decrypt_message(&self, address: RpcH160, data: RpcBytes) -> BoxFuture { + fn decrypt_message(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into()) .then(|res| match res { Ok(RpcConfirmationResponse::Decrypt(data)) => Ok(data), @@ -116,7 +116,7 @@ impl ParitySigning for SigningUnsafeClient { .boxed() } - fn post_sign(&self, _: RpcH160, _: RpcBytes) -> BoxFuture, Error> { + fn post_sign(&self, _: Metadata, _: RpcH160, _: RpcBytes) -> BoxFuture, Error> { // We don't support this in non-signer mode. future::err(errors::signer_disabled()).boxed() } diff --git a/rpc/src/v1/metadata.rs b/rpc/src/v1/metadata.rs index e3c31252c..26c79d976 100644 --- a/rpc/src/v1/metadata.rs +++ b/rpc/src/v1/metadata.rs @@ -16,33 +16,22 @@ use jsonrpc_core; +use v1::types::{DappId, Origin}; + /// RPC methods metadata. #[derive(Clone, Default, Debug, PartialEq)] pub struct Metadata { - /// Current dapplication identifier - pub dapp_id: Option, /// Request origin pub origin: Origin, } -/// RPC request origin -#[derive(Clone, Debug, PartialEq)] -pub enum Origin { - /// RPC server - Rpc, - /// Dapps server - Dapps, - /// IPC server - Ipc, - /// Signer - Signer, - /// Unknown - Unknown, -} - -impl Default for Origin { - fn default() -> Self { - Origin::Unknown +impl Metadata { + /// Get + pub fn dapp_id(&self) -> DappId { + match self.origin { + Origin::Dapps(ref dapp_id) => dapp_id.clone(), + _ => DappId::default(), + } } } diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index c69acbea3..bd0656219 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -61,4 +61,5 @@ pub mod types; pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc}; pub use self::impls::*; pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import, informant, dispatch}; -pub use self::metadata::{Metadata, Origin}; +pub use self::metadata::Metadata; +pub use self::types::Origin; diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index dd5f63347..3b668b030 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -37,6 +37,7 @@ use v1::{Eth, EthClient, EthClientOptions, EthFilter, EthFilterClient, EthSignin use v1::helpers::dispatch::FullDispatcher; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService, TestSnapshotService}; use v1::metadata::Metadata; +use v1::types::Origin; fn blockchain_client() -> Arc { let client = TestBlockChainClient::new(); @@ -387,7 +388,7 @@ fn rpc_eth_accounts() { let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#; let mut meta = Metadata::default(); - meta.dapp_id = Some("app1".into()); + meta.origin = Origin::Dapps("app1".into()); assert_eq!((*tester.io).handle_request_sync(request, meta), Some(response.to_owned())); } diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index 4bec863de..06b75d911 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -25,7 +25,7 @@ use rlp::encode; use serde_json; use jsonrpc_core::IoHandler; -use v1::{SignerClient, Signer}; +use v1::{SignerClient, Signer, Origin}; use v1::metadata::Metadata; use v1::tests::helpers::TestMinerService; use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload}; @@ -88,15 +88,15 @@ fn should_return_list_of_items_to_confirm() { data: vec![], nonce: None, condition: None, - })).unwrap(); - tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into())).unwrap(); + }), Origin::Dapps("http://parity.io".into())).unwrap(); + tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into()), Origin::Unknown).unwrap(); // when let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let response = concat!( r#"{"jsonrpc":"2.0","result":["#, - r#"{"id":"0x1","payload":{"sendTransaction":{"condition":null,"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, - r#"{"id":"0x2","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#, + r#"{"id":"0x1","origin":{"dapp":"http://parity.io"},"payload":{"sendTransaction":{"condition":null,"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, + r#"{"id":"0x2","origin":"unknown","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#, r#"],"id":1}"# ); @@ -119,7 +119,7 @@ fn should_reject_transaction_from_queue_without_dispatching() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when @@ -146,7 +146,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when @@ -162,7 +162,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { fn should_not_remove_sign_if_password_is_invalid() { // given let tester = signer_tester(); - tester.signer.add_request(ConfirmationPayload::Signature(0.into(), vec![5].into())).unwrap(); + tester.signer.add_request(ConfirmationPayload::Signature(0.into(), vec![5].into()), Origin::Unknown).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when @@ -190,7 +190,7 @@ fn should_confirm_transaction_and_dispatch() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), @@ -236,7 +236,7 @@ fn should_alter_the_sender_and_nonce() { data: vec![], nonce: Some(10.into()), condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), @@ -286,7 +286,7 @@ fn should_confirm_transaction_with_token() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), @@ -335,7 +335,7 @@ fn should_confirm_transaction_with_rlp() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), @@ -383,7 +383,7 @@ fn should_return_error_when_sender_does_not_match() { data: vec![], nonce: None, condition: None, - })).unwrap(); + }), Origin::Unknown).unwrap(); let t = Transaction { nonce: U256::zero(), diff --git a/rpc/src/v1/traits/eth_signing.rs b/rpc/src/v1/traits/eth_signing.rs index 51b7c4efc..e3b9c9b20 100644 --- a/rpc/src/v1/traits/eth_signing.rs +++ b/rpc/src/v1/traits/eth_signing.rs @@ -27,8 +27,8 @@ build_rpc_trait! { type Metadata; /// Signs the hash of data with given address signature. - #[rpc(async, name = "eth_sign")] - fn sign(&self, H160, Bytes) -> BoxFuture; + #[rpc(meta, name = "eth_sign")] + fn sign(&self, Self::Metadata, H160, Bytes) -> BoxFuture; /// Sends transaction; will block waiting for signer to return the /// transaction hash. diff --git a/rpc/src/v1/traits/parity_signing.rs b/rpc/src/v1/traits/parity_signing.rs index 3370bc259..372c31fb2 100644 --- a/rpc/src/v1/traits/parity_signing.rs +++ b/rpc/src/v1/traits/parity_signing.rs @@ -27,8 +27,8 @@ build_rpc_trait! { /// Posts sign request asynchronously. /// Will return a confirmation ID for later use with check_transaction. - #[rpc(async, name = "parity_postSign")] - fn post_sign(&self, H160, Bytes) -> BoxFuture, Error>; + #[rpc(meta, name = "parity_postSign")] + fn post_sign(&self, Self::Metadata, H160, Bytes) -> BoxFuture, Error>; /// Posts transaction asynchronously. /// Will return a transaction ID for later use with check_transaction. @@ -42,7 +42,7 @@ build_rpc_trait! { /// Decrypt some ECIES-encrypted message. /// First parameter is the address with which it is encrypted, second is the ciphertext. - #[rpc(async, name = "parity_decryptMessage")] - fn decrypt_message(&self, H160, Bytes) -> BoxFuture; + #[rpc(meta, name = "parity_decryptMessage")] + fn decrypt_message(&self, Self::Metadata, H160, Bytes) -> BoxFuture; } } diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index 7a05ee914..dd44e5750 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -21,16 +21,19 @@ use serde::{Serialize, Serializer}; use util::log::Colour; use util::bytes::ToPretty; -use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, TransactionCondition}; +use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, TransactionCondition, Origin}; use v1::helpers; /// Confirmation waiting in a queue #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ConfirmationRequest { /// Id of this confirmation pub id: U256, /// Payload pub payload: ConfirmationPayload, + /// Request origin + pub origin: Origin, } impl From for ConfirmationRequest { @@ -38,13 +41,14 @@ impl From for ConfirmationRequest { ConfirmationRequest { id: c.id.into(), payload: c.payload.into(), + origin: c.origin, } } } impl fmt::Display for ConfirmationRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "#{}: {}", self.id, self.payload) + write!(f, "#{}: {} coming from {}", self.id, self.payload, self.origin) } } @@ -61,6 +65,7 @@ impl fmt::Display for ConfirmationPayload { /// Sign request #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct SignRequest { /// Address pub address: H160, @@ -90,6 +95,7 @@ impl fmt::Display for SignRequest { /// Decrypt request #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DecryptRequest { /// Address pub address: H160, @@ -153,6 +159,7 @@ pub struct ConfirmationResponseWithToken { /// Confirmation payload, i.e. the thing to be confirmed #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub enum ConfirmationPayload { /// Send Transaction #[serde(rename="sendTransaction")] @@ -249,11 +256,12 @@ mod tests { let request = helpers::ConfirmationRequest { id: 15.into(), payload: helpers::ConfirmationPayload::Signature(1.into(), vec![5].into()), + origin: Origin::Rpc("test service".into()), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#; + let expected = r#"{"id":"0xf","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}},"origin":{"rpc":"test service"}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -275,11 +283,12 @@ mod tests { nonce: Some(1.into()), condition: None, }), + origin: Origin::Signer(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}}}"#; + 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"}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -301,11 +310,12 @@ mod tests { nonce: Some(1.into()), condition: None, }), + origin: Origin::Dapps("http://parity.io".into()), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}}}"#; + let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":{"dapp":"http://parity.io"}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -319,11 +329,12 @@ mod tests { payload: helpers::ConfirmationPayload::Decrypt( 10.into(), vec![1, 2, 3].into(), ), + origin: Default::default(), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"decrypt":{"address":"0x000000000000000000000000000000000000000a","msg":"0x010203"}}}"#; + let expected = r#"{"id":"0xf","payload":{"decrypt":{"address":"0x000000000000000000000000000000000000000a","msg":"0x010203"}},"origin":"unknown"}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); diff --git a/rpc/src/v1/types/dapp_id.rs b/rpc/src/v1/types/dapp_id.rs deleted file mode 100644 index e594bab87..000000000 --- a/rpc/src/v1/types/dapp_id.rs +++ /dev/null @@ -1,80 +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 . - -//! Dapp Id type - -use ethcore::account_provider::DappId as EthDappId; - -/// Dapplication Internal Id -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] -pub struct DappId(pub String); - -impl Into for DappId { - fn into(self) -> String { - self.0 - } -} - -impl From for DappId { - fn from(s: String) -> Self { - DappId(s) - } -} - -impl From for DappId { - fn from(id: EthDappId) -> Self { - DappId(id.into()) - } -} - -impl Into for DappId { - fn into(self) -> EthDappId { - Into::::into(self).into() - } -} - -#[cfg(test)] -mod tests { - - use serde_json; - use super::DappId; - - #[test] - fn should_serialize_dapp_id() { - // given - let id = DappId("testapp".into()); - - // when - let res = serde_json::to_string(&id).unwrap(); - - // then - assert_eq!(res, r#""testapp""#); - } - - #[test] - fn should_deserialize_dapp_id() { - // given - let id = r#""testapp""#; - - // when - let res: DappId = serde_json::from_str(id).unwrap(); - - // then - assert_eq!(res, DappId("testapp".into())); - } - - -} diff --git a/rpc/src/v1/types/hash.rs b/rpc/src/v1/types/hash.rs index 0c8672601..c96a3433b 100644 --- a/rpc/src/v1/types/hash.rs +++ b/rpc/src/v1/types/hash.rs @@ -36,11 +36,18 @@ macro_rules! impl_hash { } impl fmt::Debug for $name { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0.to_hex()) } } + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let hex = self.0.to_hex(); + write!(f, "{}..{}", &hex[0..2], &hex[$size-2..$size]) + } + } + impl From for $name where $other: From { fn from(o: T) -> Self { $name($other::from(o).0) diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index e32acc8ca..83223d947 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -18,29 +18,30 @@ //! RPC types mod account_info; -mod bytes; mod block; mod block_number; +mod bytes; mod call_request; mod confirmations; -mod dapp_id; +mod consensus_status; mod filter; mod hash; +mod histogram; mod index; mod log; +mod provenance; +mod receipt; +mod rpc_settings; mod sync; +mod trace; +mod trace_filter; mod transaction; mod transaction_request; mod transaction_condition; -mod receipt; -mod rpc_settings; -mod trace; -mod trace_filter; mod uint; mod work; -mod histogram; -mod consensus_status; +pub use self::account_info::{AccountInfo, HwAccountInfo}; pub use self::bytes::Bytes; pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block_number::BlockNumber; @@ -49,24 +50,24 @@ pub use self::confirmations::{ ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, TransactionModification, SignRequest, DecryptRequest, Either }; -pub use self::dapp_id::DappId; +pub use self::consensus_status::*; pub use self::filter::{Filter, FilterChanges}; pub use self::hash::{H64, H160, H256, H512, H520, H2048}; +pub use self::histogram::Histogram; pub use self::index::Index; pub use self::log::Log; +pub use self::provenance::{Origin, DappId}; +pub use self::receipt::Receipt; +pub use self::rpc_settings::RpcSettings; pub use self::sync::{ SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, TransactionStats, ChainStatus, EthProtocolInfo, LesProtocolInfo, }; +pub use self::trace::{LocalizedTrace, TraceResults}; +pub use self::trace_filter::TraceFilter; pub use self::transaction::{Transaction, RichRawTransaction, LocalTransactionStatus}; pub use self::transaction_request::TransactionRequest; pub use self::transaction_condition::TransactionCondition; -pub use self::receipt::Receipt; -pub use self::rpc_settings::RpcSettings; -pub use self::trace::{LocalizedTrace, TraceResults}; -pub use self::trace_filter::TraceFilter; pub use self::uint::{U128, U256}; pub use self::work::Work; -pub use self::histogram::Histogram; -pub use self::consensus_status::*; -pub use self::account_info::{AccountInfo, HwAccountInfo}; + diff --git a/rpc/src/v1/types/provenance.rs b/rpc/src/v1/types/provenance.rs new file mode 100644 index 000000000..1e014dd5f --- /dev/null +++ b/rpc/src/v1/types/provenance.rs @@ -0,0 +1,154 @@ +// 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 . + +//! Request Provenance + +use std::fmt; +use ethcore::account_provider::DappId as EthDappId; +use v1::types::H256; + +/// RPC request origin +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub enum Origin { + /// RPC server (includes request origin) + #[serde(rename="rpc")] + Rpc(String), + /// Dapps server (includes DappId) + #[serde(rename="dapp")] + Dapps(DappId), + /// IPC server (includes session hash) + #[serde(rename="ipc")] + Ipc(H256), + /// Signer (includes session hash) + #[serde(rename="signer")] + Signer(H256), + /// Unknown + #[serde(rename="unknown")] + Unknown, +} + +impl Default for Origin { + fn default() -> Self { + Origin::Unknown + } +} + +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::Dapps(ref origin) => write!(f, "Dapp {}", origin), + Origin::Ipc(ref session) => write!(f, "IPC (session: {})", session), + Origin::Signer(ref session) => write!(f, "UI (session: {})", session), + Origin::Unknown => write!(f, "unknown origin"), + } + } +} + +/// Dapplication Internal Id +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] +pub struct DappId(pub String); + +impl fmt::Display for DappId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Into for DappId { + fn into(self) -> String { + self.0 + } +} + +impl From for DappId { + fn from(s: String) -> Self { + DappId(s) + } +} + +impl<'a> From<&'a str> for DappId { + fn from(s: &'a str) -> Self { + DappId(s.to_owned()) + } +} + +impl From for DappId { + fn from(id: EthDappId) -> Self { + DappId(id.into()) + } +} + +impl Into for DappId { + fn into(self) -> EthDappId { + Into::::into(self).into() + } +} + +#[cfg(test)] +mod tests { + use serde_json; + use super::{DappId, Origin}; + + #[test] + fn should_serialize_origin() { + // given + 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 o5 = Origin::Unknown; + + // when + let res1 = serde_json::to_string(&o1).unwrap(); + let res2 = serde_json::to_string(&o2).unwrap(); + let res3 = serde_json::to_string(&o3).unwrap(); + let res4 = serde_json::to_string(&o4).unwrap(); + let res5 = serde_json::to_string(&o5).unwrap(); + + // then + 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!(res5, r#""unknown""#); + } + + #[test] + fn should_serialize_dapp_id() { + // given + let id = DappId("testapp".into()); + + // when + let res = serde_json::to_string(&id).unwrap(); + + // then + assert_eq!(res, r#""testapp""#); + } + + #[test] + fn should_deserialize_dapp_id() { + // given + let id = r#""testapp""#; + + // when + let res: DappId = serde_json::from_str(id).unwrap(); + + // then + assert_eq!(res, DappId("testapp".into())); + } +} diff --git a/rpc/src/v1/types/transaction_condition.rs b/rpc/src/v1/types/transaction_condition.rs index 2f530f686..1682f2dd9 100644 --- a/rpc/src/v1/types/transaction_condition.rs +++ b/rpc/src/v1/types/transaction_condition.rs @@ -18,6 +18,7 @@ use ethcore; /// Represents condition on minimum block number or block timestamp. #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub enum TransactionCondition { /// Valid at this minimum block number. #[serde(rename="block")] diff --git a/rpc_client/src/lib.rs b/rpc_client/src/lib.rs index 87dd3a6ee..7bc265250 100644 --- a/rpc_client/src/lib.rs +++ b/rpc_client/src/lib.rs @@ -18,6 +18,9 @@ extern crate log; #[cfg(test)] mod tests { + #[macro_use] + extern crate matches; + use futures::Future; use std::path::PathBuf; use client::{Rpc, RpcError}; diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index 7fc046f49..b799b0f66 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -33,6 +33,8 @@ use rpc::informant::RpcStats; mod session; +pub use self::session::MetaExtractor; + /// Signer startup error #[derive(Debug)] pub enum ServerError { @@ -51,6 +53,11 @@ impl From for ServerError { } } +/// Dummy metadata extractor +#[derive(Clone)] +pub struct NoopExtractor; +impl session::MetaExtractor for NoopExtractor {} + /// Builder for `WebSockets` server pub struct ServerBuilder { queue: Arc, @@ -86,6 +93,17 @@ impl ServerBuilder { /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. pub fn start>(self, addr: SocketAddr, handler: RpcHandler) -> Result { + self.start_with_extractor(addr, handler, NoopExtractor) + } + + /// Starts a new `WebSocket` server in separate thread. + /// Returns a `Server` handle which closes the server when droped. + pub fn start_with_extractor, T: session::MetaExtractor>( + self, + addr: SocketAddr, + handler: RpcHandler, + meta_extractor: T, + ) -> Result { Server::start( addr, handler, @@ -93,8 +111,10 @@ impl ServerBuilder { self.authcodes_path, self.skip_origin_validation, self.stats, + meta_extractor, ) } + } /// `WebSockets` server implementation. @@ -114,13 +134,14 @@ impl Server { /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. - fn start>( + fn start, T: session::MetaExtractor>( addr: SocketAddr, handler: RpcHandler, queue: Arc, authcodes_path: PathBuf, skip_origin_validation: bool, stats: Option>, + meta_extractor: T, ) -> Result { let config = { let mut config = ws::Settings::default(); @@ -135,7 +156,7 @@ impl Server { let origin = format!("{}", addr); let port = addr.port(); let ws = ws::Builder::new().with_settings(config).build( - session::Factory::new(handler, origin, port, authcodes_path, skip_origin_validation, stats) + session::Factory::new(handler, origin, port, authcodes_path, skip_origin_validation, stats, meta_extractor) )?; let panic_handler = PanicHandler::new_in_arc(); diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs index f19e86215..5194855ab 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -16,15 +16,16 @@ //! Session handlers factory. -use ws; -use authcode_store::AuthCodes; use std::path::{PathBuf, Path}; use std::sync::Arc; use std::str::FromStr; + +use authcode_store::AuthCodes; use jsonrpc_core::{Metadata, Middleware}; use jsonrpc_core::reactor::RpcHandler; use rpc::informant::RpcStats; use util::{H256, version}; +use ws; #[cfg(feature = "parity-ui")] mod ui { @@ -78,35 +79,39 @@ fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool { } } -fn auth_is_valid(codes_path: &Path, protocols: ws::Result>) -> bool { +fn auth_token_hash(codes_path: &Path, protocols: ws::Result>) -> Option { match protocols { Ok(ref protocols) if protocols.len() == 1 => { - protocols.iter().any(|protocol| { - 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()); + 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) - .map(|mut codes| { - // remove old tokens - codes.clear_garbage(); + 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."); - } - res - }) - .unwrap_or(false) - } else { - false - } - }) + 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 + } }, - _ => false + _ => None, } } @@ -125,7 +130,16 @@ fn add_headers(mut response: ws::Response, mime: &str) -> ws::Response { response } -pub struct Session> { +/// Metadata extractor from session data. +pub trait MetaExtractor: Send + Clone + 'static { + /// Extract metadata for given session + fn extract_metadata(&self, _session_id: &H256) -> M { + Default::default() + } +} + +pub struct Session, T> { + session_id: H256, out: ws::Sender, skip_origin_validation: bool, self_origin: String, @@ -134,16 +148,16 @@ pub struct Session> { handler: RpcHandler, file_handler: Arc, stats: Option>, + meta_extractor: T, } -impl> Drop for Session { +impl, T> Drop for Session { fn drop(&mut self) { self.stats.as_ref().map(|stats| stats.close_session()); } } -impl> ws::Handler for Session { - #[cfg_attr(feature="dev", allow(collapsible_if))] +impl, T: MetaExtractor> ws::Handler for Session { fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> { trace!(target: "signer", "Handling request: {:?}", req); @@ -186,9 +200,15 @@ impl> ws::Handler for Session { // (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 - if !auth_is_valid(&self.authcodes_path, req.protocols()) { - info!(target: "signer", "Unauthorized connection to Signer API blocked."); - return Ok(error(ErrorType::Forbidden, "Not Authorized", "Request to this API was not authorized.", None)); + 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."); @@ -214,8 +234,8 @@ impl> ws::Handler for Session { fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { let req = msg.as_text()?; let out = self.out.clone(); - // TODO [ToDr] Extract metadata for PubSub/Session - let metadata = Default::default(); + // TODO [ToDr] Move to on_connect + let metadata = self.meta_extractor.extract_metadata(&self.session_id); self.handler.handle_request(req, metadata, move |response| { if let Some(result) = response { @@ -229,24 +249,26 @@ impl> ws::Handler for Session { } } -pub struct Factory> { +pub struct Factory, T> { handler: RpcHandler, skip_origin_validation: bool, self_origin: String, self_port: u16, authcodes_path: PathBuf, + meta_extractor: T, file_handler: Arc, stats: Option>, } -impl> Factory { +impl, T> Factory { pub fn new( handler: RpcHandler, self_origin: String, - self_port: u16, + self_port: u16, authcodes_path: PathBuf, skip_origin_validation: bool, stats: Option>, + meta_extractor: T, ) -> Self { Factory { handler: handler, @@ -254,25 +276,28 @@ impl> Factory { 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> ws::Factory for Factory { - type Handler = Session; +impl, T: MetaExtractor> ws::Factory for Factory { + type Handler = Session; fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler { self.stats.as_ref().map(|stats| stats.open_session()); Session { + session_id: 0.into(), out: sender, handler: self.handler.clone(), 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(), }