Signer provenance (#4477)

* Signer - Tracking Request Provenance

* Basic UI

* Changing messages

* VecDeque::from

* Fix dapps tests

* Addressing UI grumbles
This commit is contained in:
Tomasz Drwięga 2017-02-14 22:45:43 +01:00 committed by Gav Wood
parent d925cc05da
commit 5369a129ae
46 changed files with 745 additions and 269 deletions

3
Cargo.lock generated
View File

@ -25,10 +25,9 @@ dependencies = [
"ethcore-util 1.6.0", "ethcore-util 1.6.0",
"ethsync 1.6.0", "ethsync 1.6.0",
"fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "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)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -16,7 +16,6 @@ number_prefix = "0.2"
rpassword = "0.2.1" rpassword = "0.2.1"
semver = "0.5" semver = "0.5"
ansi_term = "0.7" ansi_term = "0.7"
lazy_static = "0.2"
regex = "0.1" regex = "0.1"
isatty = "0.1" isatty = "0.1"
toml = "0.2" toml = "0.2"
@ -24,7 +23,7 @@ serde = "0.9"
serde_json = "0.9" serde_json = "0.9"
app_dirs = "1.1.1" app_dirs = "1.1.1"
fdlimit = "0.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" } ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
ethsync = { path = "sync" } ethsync = { path = "sync" }

View File

@ -77,8 +77,7 @@ impl HttpMetaExtractor<Metadata> for MetadataExtractor {
}) })
}); });
Metadata { Metadata {
dapp_id: dapp_id, origin: Origin::Dapps(dapp_id.map(Into::into).unwrap_or_default()),
origin: Origin::Dapps,
} }
} }
} }

View File

@ -55,8 +55,8 @@ fn should_extract_metadata() {
// given // given
let mut io = MetaIoHandler::default(); let mut io = MetaIoHandler::default();
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { 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("https://parity.io/".into()));
assert_eq!(meta.origin, Origin::Dapps); assert_eq!(meta.dapp_id(), "https://parity.io/".into());
future::ok(Value::String("Hello World!".into())).boxed() future::ok(Value::String("Hello World!".into())).boxed()
}); });
let server = serve_with_rpc(io); let server = serve_with_rpc(io);
@ -89,8 +89,8 @@ fn should_extract_metadata_from_custom_header() {
// given // given
let mut io = MetaIoHandler::default(); let mut io = MetaIoHandler::default();
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { 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("https://parity.io/".into()));
assert_eq!(meta.origin, Origin::Dapps); assert_eq!(meta.dapp_id(), "https://parity.io/".into());
future::ok(Value::String("Hello World!".into())).boxed() future::ok(Value::String("Hello World!".into())).boxed()
}); });
let server = serve_with_rpc(io); let server = serve_with_rpc(io);

View File

@ -649,8 +649,8 @@ mod tests {
use account_provider::AccountProvider; use account_provider::AccountProvider;
use spec::Spec; use spec::Spec;
use engines::{Engine, EngineError, Seal}; use engines::{Engine, EngineError, Seal};
use super::*; use super::{Step, View, Height, message_info_rlp, message_full_rlp};
use super::message::*; use super::message::VoteStep;
/// Accounts inserted with "0" and "1" are validators. First proposer is "0". /// Accounts inserted with "0" and "1" are validators. First proposer is "0".
fn setup() -> (Spec, Arc<AccountProvider>) { fn setup() -> (Spec, Arc<AccountProvider>) {

View File

@ -305,7 +305,7 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras {
} }
fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec<usize>) -> Option<LocalizedTrace> { fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec<usize>) -> Option<LocalizedTrace> {
let trace_position_deq = trace_position.into_iter().collect::<VecDeque<usize>>(); let trace_position_deq = VecDeque::from(trace_position);
self.extras.block_hash(block_number) self.extras.block_hash(block_number)
.and_then(|block_hash| self.transactions_traces(&block_hash) .and_then(|block_hash| self.transactions_traces(&block_hash)
.and_then(|traces| traces.into_iter().nth(tx_position)) .and_then(|traces| traces.into_iter().nth(tx_position))

View File

@ -203,6 +203,13 @@ export function outSignerRequest (request) {
request[key].signTransaction = outTransaction(request[key].signTransaction); request[key].signTransaction = outTransaction(request[key].signTransaction);
request[key].sendTransaction = outTransaction(request[key].sendTransaction); request[key].sendTransaction = outTransaction(request[key].sendTransaction);
break; break;
case 'origin':
const type = Object.keys(request[key])[0];
const details = request[key][type];
request[key] = { type, details };
break;
} }
}); });
} }

View File

@ -28,6 +28,7 @@ export default class Ws extends JsonRpcBase {
this._url = url; this._url = url;
this._token = token; this._token = token;
this._messages = {}; this._messages = {};
this._sessionHash = null;
this._connecting = false; this._connecting = false;
this._connected = false; this._connected = false;
@ -78,12 +79,14 @@ export default class Ws extends JsonRpcBase {
this._ws.onmessage = null; this._ws.onmessage = null;
this._ws.close(); this._ws.close();
this._ws = null; this._ws = null;
this._sessionHash = null;
} }
this._connecting = true; this._connecting = true;
this._connected = false; this._connected = false;
this._lastError = null; this._lastError = null;
this._sessionHash = sha3;
this._ws = new WebSocket(this._url, hash); this._ws = new WebSocket(this._url, hash);
this._ws.onerror = this._onError; this._ws.onerror = this._onError;
this._ws.onopen = this._onOpen; this._ws.onopen = this._onOpen;
@ -255,6 +258,10 @@ export default class Ws extends JsonRpcBase {
return this._token; return this._token;
} }
get sessionHash () {
return this._sessionHash;
}
get isAutoConnect () { get isAutoConnect () {
return this._autoConnect; return this._autoConnect;
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './requestOrigin';

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
.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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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 (
<div className={ styles.container }>
Requested { this.renderOrigin(origin) }
</div>
);
}
renderOrigin (origin) {
if (origin.type === 'unknown') {
return (
<span className={ styles.unknown }>via unknown interface</span>
);
}
if (origin.type === 'dapp') {
return (
<span>
by a dapp at <span className={ styles.url }>
{ origin.details || 'unknown URL' }
</span>
</span>
);
}
if (origin.type === 'rpc') {
return (
<span>
via RPC <span className={ styles.url }>
({ origin.details || 'unidentified' })
</span>
</span>
);
}
if (origin.type === 'ipc') {
return (
<span>
via IPC session
<span
className={ styles.hash }
title={ origin.details }
>
<IdentityIcon
address={ origin.details }
tiny
/>
</span>
</span>
);
}
if (origin.type === 'signer') {
return this.renderSigner(origin.details);
}
}
renderSigner (session) {
if (session.substr(2) === this.context.api.transport.sessionHash) {
return (
<span title={ session }>via current tab</span>
);
}
return (
<span>
via UI session
<span
className={ styles.hash }
title={ `UI Session id: ${session}` }
>
<IdentityIcon
address={ session }
tiny
/>
</span>
</span>
);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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(
<RequestOrigin origin={ { type: 'unknown', details: '' } } />,
context
).text()).to.equal('Requested via unknown interface');
});
it('renders dapps', () => {
expect(shallow(
<RequestOrigin origin={ { type: 'dapp', details: 'http://parity.io' } } />,
context
).text()).to.equal('Requested by a dapp at http://parity.io');
});
it('renders rpc', () => {
expect(shallow(
<RequestOrigin origin={ { type: 'rpc', details: '' } } />,
context
).text()).to.equal('Requested via RPC (unidentified)');
});
it('renders ipc', () => {
expect(shallow(
<RequestOrigin origin={ { type: 'ipc', details: '0x1234' } } />,
context
).text()).to.equal('Requested via IPC session<Connect(IdentityIcon) />');
});
it('renders signer', () => {
expect(shallow(
<RequestOrigin origin={ { type: 'signer', details: '0x12345' } } />,
context
).text()).to.equal('Requested via UI session<Connect(IdentityIcon) />');
expect(shallow(
<RequestOrigin origin={ { type: 'signer', details: '0x1234' } } />,
context
).text()).to.equal('Requested via current tab');
});
});

View File

@ -30,6 +30,7 @@ export default class RequestPending extends Component {
isTest: PropTypes.bool.isRequired, isTest: PropTypes.bool.isRequired,
onConfirm: PropTypes.func.isRequired, onConfirm: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired,
origin: PropTypes.object.isRequired,
payload: PropTypes.oneOfType([ payload: PropTypes.oneOfType([
PropTypes.shape({ sendTransaction: PropTypes.object.isRequired }), PropTypes.shape({ sendTransaction: PropTypes.object.isRequired }),
PropTypes.shape({ sign: PropTypes.object.isRequired }), PropTypes.shape({ sign: PropTypes.object.isRequired }),
@ -51,7 +52,7 @@ export default class RequestPending extends Component {
}; };
render () { 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) { if (payload.sign) {
const { sign } = payload; const { sign } = payload;
@ -68,6 +69,7 @@ export default class RequestPending extends Component {
isTest={ isTest } isTest={ isTest }
onConfirm={ this.onConfirm } onConfirm={ this.onConfirm }
onReject={ onReject } onReject={ onReject }
origin={ origin }
store={ store } store={ store }
/> />
); );
@ -87,6 +89,7 @@ export default class RequestPending extends Component {
isTest={ isTest } isTest={ isTest }
onConfirm={ this.onConfirm } onConfirm={ this.onConfirm }
onReject={ onReject } onReject={ onReject }
origin={ origin }
store={ store } store={ store }
transaction={ transaction } transaction={ transaction }
/> />

View File

@ -19,6 +19,7 @@ import { observer } from 'mobx-react';
import Account from '../Account'; import Account from '../Account';
import TransactionPendingForm from '../TransactionPendingForm'; import TransactionPendingForm from '../TransactionPendingForm';
import RequestOrigin from '../RequestOrigin';
import styles from './signRequest.css'; import styles from './signRequest.css';
@ -40,9 +41,9 @@ export default class SignRequest extends Component {
}; };
static propTypes = { static propTypes = {
id: PropTypes.object.isRequired,
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,
data: PropTypes.string.isRequired, data: PropTypes.string.isRequired,
id: PropTypes.object.isRequired,
isFinished: PropTypes.bool.isRequired, isFinished: PropTypes.bool.isRequired,
isTest: PropTypes.bool.isRequired, isTest: PropTypes.bool.isRequired,
store: PropTypes.object.isRequired, store: PropTypes.object.isRequired,
@ -52,11 +53,16 @@ export default class SignRequest extends Component {
isSending: PropTypes.bool, isSending: PropTypes.bool,
onConfirm: PropTypes.func, onConfirm: PropTypes.func,
onReject: PropTypes.func, onReject: PropTypes.func,
origin: PropTypes.any,
status: PropTypes.string status: PropTypes.string
}; };
static defaultProps = { static defaultProps = {
focus: false focus: false,
origin: {
type: 'unknown',
details: ''
}
}; };
componentWillMount () { componentWillMount () {
@ -92,7 +98,7 @@ export default class SignRequest extends Component {
renderDetails () { renderDetails () {
const { api } = this.context; const { api } = this.context;
const { address, isTest, store, data } = this.props; const { address, isTest, store, data, origin } = this.props;
const { balances, externalLink } = store; const { balances, externalLink } = store;
const balance = balances[address]; const balance = balances[address];
@ -110,6 +116,7 @@ export default class SignRequest extends Component {
externalLink={ externalLink } externalLink={ externalLink }
isTest={ isTest } isTest={ isTest }
/> />
<RequestOrigin origin={ origin } />
</div> </div>
<div className={ styles.info } title={ api.util.sha3(data) }> <div className={ styles.info } title={ api.util.sha3(data) }>
<p>A request to sign data using your account:</p> <p>A request to sign data using your account:</p>

View File

@ -39,6 +39,7 @@
width: 40%; width: 40%;
vertical-align: top; vertical-align: top;
.account {
img { img {
display: inline-block; display: inline-block;
width: 50px; width: 50px;
@ -49,6 +50,7 @@
span { span {
display: block; display: block;
} }
}
} }
.method { .method {

View File

@ -22,6 +22,8 @@ import { Button, MethodDecoding } from '~/ui';
import * as tUtil from '../util/transaction'; import * as tUtil from '../util/transaction';
import Account from '../Account'; import Account from '../Account';
import RequestOrigin from '../RequestOrigin';
import styles from './transactionMainDetails.css'; import styles from './transactionMainDetails.css';
export default class TransactionMainDetails extends Component { export default class TransactionMainDetails extends Component {
@ -33,11 +35,19 @@ export default class TransactionMainDetails extends Component {
gasStore: PropTypes.object, gasStore: PropTypes.object,
id: PropTypes.object.isRequired, id: PropTypes.object.isRequired,
isTest: PropTypes.bool.isRequired, isTest: PropTypes.bool.isRequired,
origin: PropTypes.any,
totalValue: PropTypes.object.isRequired, totalValue: PropTypes.object.isRequired,
transaction: PropTypes.object.isRequired, transaction: PropTypes.object.isRequired,
value: PropTypes.object.isRequired value: PropTypes.object.isRequired
}; };
static defaultProps = {
origin: {
type: 'unknown',
details: ''
}
};
componentWillMount () { componentWillMount () {
const { totalValue, value } = this.props; const { totalValue, value } = this.props;
@ -51,7 +61,7 @@ export default class TransactionMainDetails extends Component {
} }
render () { render () {
const { children, externalLink, from, fromBalance, gasStore, isTest, transaction } = this.props; const { children, externalLink, from, fromBalance, gasStore, isTest, transaction, origin } = this.props;
return ( return (
<div className={ styles.transaction }> <div className={ styles.transaction }>
@ -64,6 +74,7 @@ export default class TransactionMainDetails extends Component {
isTest={ isTest } isTest={ isTest }
/> />
</div> </div>
<RequestOrigin origin={ origin } />
</div> </div>
<div className={ styles.method }> <div className={ styles.method }>
<MethodDecoding <MethodDecoding

View File

@ -43,6 +43,7 @@ export default class TransactionPending extends Component {
nonce: PropTypes.number, nonce: PropTypes.number,
onConfirm: PropTypes.func.isRequired, onConfirm: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired,
origin: PropTypes.any,
store: PropTypes.object.isRequired, store: PropTypes.object.isRequired,
transaction: PropTypes.shape({ transaction: PropTypes.shape({
condition: PropTypes.object, condition: PropTypes.object,
@ -56,7 +57,11 @@ export default class TransactionPending extends Component {
}; };
static defaultProps = { static defaultProps = {
focus: false focus: false,
origin: {
type: 'unknown',
details: ''
}
}; };
gasStore = new GasPriceEditor.Store(this.context.api, { gasStore = new GasPriceEditor.Store(this.context.api, {
@ -87,7 +92,7 @@ export default class TransactionPending extends Component {
} }
renderTransaction () { renderTransaction () {
const { className, focus, id, isSending, isTest, store, transaction } = this.props; const { className, focus, id, isSending, isTest, store, transaction, origin } = this.props;
const { totalValue } = this.state; const { totalValue } = this.state;
const { balances, externalLink } = store; const { balances, externalLink } = store;
const { from, value } = transaction; const { from, value } = transaction;
@ -104,6 +109,7 @@ export default class TransactionPending extends Component {
gasStore={ this.gasStore } gasStore={ this.gasStore }
id={ id } id={ id }
isTest={ isTest } isTest={ isTest }
origin={ origin }
totalValue={ totalValue } totalValue={ totalValue }
transaction={ transaction } transaction={ transaction }
value={ value } value={ value }

View File

@ -81,7 +81,7 @@ class Embedded extends Component {
renderPending = (data, index) => { renderPending = (data, index) => {
const { actions, gasLimit, isTest } = this.props; const { actions, gasLimit, isTest } = this.props;
const { date, id, isSending, payload } = data; const { date, id, isSending, payload, origin } = data;
return ( return (
<RequestPending <RequestPending
@ -95,6 +95,7 @@ class Embedded extends Component {
key={ id } key={ id }
onConfirm={ actions.startConfirmRequest } onConfirm={ actions.startConfirmRequest }
onReject={ actions.startRejectRequest } onReject={ actions.startRejectRequest }
origin={ origin }
payload={ payload } payload={ payload }
store={ this.store } store={ this.store }
/> />

View File

@ -107,7 +107,7 @@ class RequestsPage extends Component {
renderPending = (data, index) => { renderPending = (data, index) => {
const { actions, gasLimit, isTest } = this.props; const { actions, gasLimit, isTest } = this.props;
const { date, id, isSending, payload } = data; const { date, id, isSending, payload, origin } = data;
return ( return (
<RequestPending <RequestPending
@ -121,6 +121,7 @@ class RequestsPage extends Component {
key={ id } key={ id }
onConfirm={ actions.startConfirmRequest } onConfirm={ actions.startConfirmRequest }
onReject={ actions.startRejectRequest } onReject={ actions.startRejectRequest }
origin={ origin }
payload={ payload } payload={ payload }
store={ this.store } store={ this.store }
/> />

View File

@ -28,7 +28,7 @@ extern crate ctrlc;
extern crate docopt; extern crate docopt;
extern crate env_logger; extern crate env_logger;
extern crate fdlimit; extern crate fdlimit;
extern crate hyper; // for price_info.rs extern crate hyper;
extern crate isatty; extern crate isatty;
extern crate jsonrpc_core; extern crate jsonrpc_core;
extern crate num_cpus; extern crate num_cpus;
@ -60,15 +60,14 @@ extern crate parity_reactor;
extern crate parity_updater as updater; extern crate parity_updater as updater;
extern crate rpc_cli; extern crate rpc_cli;
#[macro_use]
extern crate log as rlog;
#[cfg(feature="stratum")] #[cfg(feature="stratum")]
extern crate ethcore_stratum; extern crate ethcore_stratum;
#[cfg(feature = "dapps")] #[cfg(feature = "dapps")]
extern crate ethcore_dapps; extern crate ethcore_dapps;
#[macro_use]
extern crate log as rlog;
macro_rules! dependency { macro_rules! dependency {
($dep_ty:ident, $url:expr) => { ($dep_ty:ident, $url:expr) => {
{ {

View File

@ -21,9 +21,10 @@ use std::io;
use io::PanicHandler; use io::PanicHandler;
use dir::default_data_path; 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 ethcore_rpc::informant::{RpcStats, Middleware};
use helpers::parity_ipc_path; use helpers::parity_ipc_path;
use hyper;
use jsonrpc_core::MetaIoHandler; use jsonrpc_core::MetaIoHandler;
use jsonrpc_core::reactor::{RpcHandler, Remote}; use jsonrpc_core::reactor::{RpcHandler, Remote};
use rpc_apis; use rpc_apis;
@ -89,6 +90,18 @@ pub struct Dependencies {
pub stats: Arc<RpcStats>, pub stats: Arc<RpcStats>,
} }
pub struct RpcExtractor;
impl rpc::HttpMetaExtractor<Metadata> for RpcExtractor {
fn read_metadata(&self, req: &hyper::server::Request<hyper::net::HttpStream>) -> Metadata {
let origin = req.headers().get::<hyper::header::Origin>()
.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<Option<HttpServer>, String> { pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Result<Option<HttpServer>, String> {
if !conf.enabled { if !conf.enabled {
return Ok(None); return Ok(None);
@ -113,7 +126,7 @@ pub fn setup_http_rpc_server(
let apis = setup_apis(apis, dependencies); let apis = setup_apis(apis, dependencies);
let handler = RpcHandler::new(Arc::new(apis), dependencies.remote.clone()); let handler = RpcHandler::new(Arc::new(apis), dependencies.remote.clone());
let ph = dependencies.panic_handler.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 { match start_result {
Err(RpcServerError::IoError(err)) => match err.kind() { 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)), 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)),

View File

@ -23,12 +23,14 @@ pub use ethcore_signer::Server as SignerServer;
use ansi_term::Colour; use ansi_term::Colour;
use dir::default_data_path; use dir::default_data_path;
use ethcore_rpc::informant::RpcStats; use ethcore_rpc::informant::RpcStats;
use ethcore_rpc;
use ethcore_signer as signer; use ethcore_signer as signer;
use helpers::replace_home; use helpers::replace_home;
use io::{ForwardPanic, PanicHandler}; use io::{ForwardPanic, PanicHandler};
use jsonrpc_core::reactor::{RpcHandler, Remote}; use jsonrpc_core::reactor::{RpcHandler, Remote};
use rpc_apis; use rpc_apis;
use util::path::restrict_permissions_owner; use util::path::restrict_permissions_owner;
use util::H256;
const CODES_FILENAME: &'static str = "authcodes"; const CODES_FILENAME: &'static str = "authcodes";
@ -67,6 +69,16 @@ pub struct NewToken {
pub message: String, pub message: String,
} }
#[derive(Debug, Default, Clone)]
pub struct StandardExtractor;
impl signer::MetaExtractor<ethcore_rpc::Metadata> 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<Option<SignerServer>, String> { pub fn start(conf: Configuration, deps: Dependencies) -> Result<Option<SignerServer>, String> {
if !conf.enabled { if !conf.enabled {
Ok(None) Ok(None)
@ -133,7 +145,7 @@ fn do_start(conf: Configuration, deps: Dependencies) -> Result<SignerServer, Str
let server = server.stats(deps.rpc_stats.clone()); let server = server.stats(deps.rpc_stats.clone());
let apis = rpc_apis::setup_rpc(deps.rpc_stats, deps.apis, rpc_apis::ApiSet::SafeContext); let apis = rpc_apis::setup_rpc(deps.rpc_stats, deps.apis, rpc_apis::ApiSet::SafeContext);
let handler = RpcHandler::new(Arc::new(apis), deps.remote); let handler = RpcHandler::new(Arc::new(apis), deps.remote);
server.start(addr, handler) server.start_with_extractor(addr, handler, StandardExtractor)
}; };
match start_result { match start_result {

View File

@ -65,19 +65,24 @@ use io::PanicHandler;
use jsonrpc_core::reactor::RpcHandler; use jsonrpc_core::reactor::RpcHandler;
pub use ipc::{Server as IpcServer, Error as IpcServerError}; pub use ipc::{Server as IpcServer, Error as IpcServerError};
pub use jsonrpc_http_server::{ServerBuilder, Server, RpcServerError}; pub use jsonrpc_http_server::{ServerBuilder, Server, RpcServerError, HttpMetaExtractor};
pub mod v1; pub mod v1;
pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant, dispatch}; pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant, dispatch};
pub use v1::block_import::is_major_importing; pub use v1::block_import::is_major_importing;
/// Start http server asynchronously and returns result with `Server` handle on success or an error. /// Start http server asynchronously and returns result with `Server` handle on success or an error.
pub fn start_http<M: jsonrpc_core::Metadata, S: jsonrpc_core::Middleware<M>>( pub fn start_http<M, T, S>(
addr: &SocketAddr, addr: &SocketAddr,
cors_domains: Option<Vec<String>>, cors_domains: Option<Vec<String>>,
allowed_hosts: Option<Vec<String>>, allowed_hosts: Option<Vec<String>>,
panic_handler: Arc<PanicHandler>, panic_handler: Arc<PanicHandler>,
handler: RpcHandler<M, S>, handler: RpcHandler<M, S>,
) -> Result<Server, RpcServerError> { extractor: T,
) -> Result<Server, RpcServerError> where
M: jsonrpc_core::Metadata,
S: jsonrpc_core::Middleware<M>,
T: HttpMetaExtractor<M>,
{
let cors_domains = cors_domains.map(|domains| { let cors_domains = cors_domains.map(|domains| {
domains.into_iter() domains.into_iter()
@ -90,6 +95,7 @@ pub fn start_http<M: jsonrpc_core::Metadata, S: jsonrpc_core::Middleware<M>>(
}); });
ServerBuilder::with_rpc_handler(handler) ServerBuilder::with_rpc_handler(handler)
.meta_extractor(Arc::new(extractor))
.cors(cors_domains.into()) .cors(cors_domains.into())
.allowed_hosts(allowed_hosts.into()) .allowed_hosts(allowed_hosts.into())
.panic_handler(move || { .panic_handler(move || {

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use util::{Address, U256, Bytes}; use util::{Address, U256, Bytes};
use v1::types::TransactionCondition; use v1::types::{Origin, TransactionCondition};
/// Transaction request coming from RPC /// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
@ -102,6 +102,8 @@ pub struct ConfirmationRequest {
pub id: U256, pub id: U256,
/// Payload to confirm /// Payload to confirm
pub payload: ConfirmationPayload, pub payload: ConfirmationPayload,
/// Request origin
pub origin: Origin,
} }
/// Payload to confirm in Trusted Signer /// Payload to confirm in Trusted Signer

View File

@ -22,8 +22,7 @@ use jsonrpc_core;
use util::{Mutex, RwLock, U256, Address}; use util::{Mutex, RwLock, U256, Address};
use ethcore::account_provider::DappId; use ethcore::account_provider::DappId;
use v1::helpers::{ConfirmationRequest, ConfirmationPayload}; use v1::helpers::{ConfirmationRequest, ConfirmationPayload};
use v1::metadata::Metadata; use v1::types::{ConfirmationResponse, H160 as RpcH160, Origin, DappId as RpcDappId};
use v1::types::{ConfirmationResponse, H160 as RpcH160};
/// Result that can be returned from JSON RPC. /// Result that can be returned from JSON RPC.
pub type RpcResult = Result<ConfirmationResponse, jsonrpc_core::Error>; pub type RpcResult = Result<ConfirmationResponse, jsonrpc_core::Error>;
@ -37,9 +36,9 @@ pub enum DefaultAccount {
ForDapp(DappId), ForDapp(DappId),
} }
impl From<Metadata> for DefaultAccount { impl From<RpcDappId> for DefaultAccount {
fn from(meta: Metadata) -> Self { fn from(dapp_id: RpcDappId) -> Self {
DefaultAccount::ForDapp(meta.dapp_id.unwrap_or_default().into()) DefaultAccount::ForDapp(dapp_id.into())
} }
} }
@ -84,7 +83,7 @@ const QUEUE_LIMIT: usize = 50;
pub trait SigningQueue: Send + Sync { pub trait SigningQueue: Send + Sync {
/// Add new request to the queue. /// Add new request to the queue.
/// Returns a `ConfirmationPromise` that can be used to await for resolution of given request. /// Returns a `ConfirmationPromise` that can be used to await for resolution of given request.
fn add_request(&self, request: ConfirmationPayload) -> Result<ConfirmationPromise, QueueAddError>; fn add_request(&self, request: ConfirmationPayload, origin: Origin) -> Result<ConfirmationPromise, QueueAddError>;
/// Removes a request from the queue. /// Removes a request from the queue.
/// Notifies possible token holders that request was rejected. /// Notifies possible token holders that request was rejected.
@ -267,7 +266,7 @@ impl Drop for ConfirmationsQueue {
} }
impl SigningQueue for ConfirmationsQueue { impl SigningQueue for ConfirmationsQueue {
fn add_request(&self, request: ConfirmationPayload) -> Result<ConfirmationPromise, QueueAddError> { fn add_request(&self, request: ConfirmationPayload, origin: Origin) -> Result<ConfirmationPromise, QueueAddError> {
if self.len() > QUEUE_LIMIT { if self.len() > QUEUE_LIMIT {
return Err(QueueAddError::LimitReached); return Err(QueueAddError::LimitReached);
} }
@ -290,6 +289,7 @@ impl SigningQueue for ConfirmationsQueue {
request: ConfirmationRequest { request: ConfirmationRequest {
id: id, id: id,
payload: request, payload: request,
origin: origin,
}, },
}); });
queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.") queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.")
@ -362,7 +362,7 @@ mod test {
// when // when
let q = queue.clone(); let q = queue.clone();
let handle = thread::spawn(move || { 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(); let (tx, rx) = mpsc::channel();
v.wait_for_result(move |res| { v.wait_for_result(move |res| {
tx.send(res).unwrap(); tx.send(res).unwrap();
@ -397,7 +397,7 @@ mod test {
*v = Some(notification); *v = Some(notification);
}).expect("Should be closed nicely.") }).expect("Should be closed nicely.")
}); });
queue.add_request(request).unwrap(); queue.add_request(request, Default::default()).unwrap();
queue.finish(); queue.finish();
// then // then
@ -413,7 +413,7 @@ mod test {
let request = request(); let request = request();
// when // when
queue.add_request(request.clone()).unwrap(); queue.add_request(request.clone(), Default::default()).unwrap();
let all = queue.requests(); let all = queue.requests();
// then // then

View File

@ -311,7 +311,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
} }
fn author(&self, meta: Metadata) -> BoxFuture<RpcH160, Error> { fn author(&self, meta: Metadata) -> BoxFuture<RpcH160, Error> {
let dapp = meta.dapp_id.unwrap_or_default(); let dapp = meta.dapp_id();
let author = move || { let author = move || {
let mut miner = take_weak!(self.miner).author(); let mut miner = take_weak!(self.miner).author();
@ -342,7 +342,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
} }
fn accounts(&self, meta: Metadata) -> BoxFuture<Vec<RpcH160>, Error> { fn accounts(&self, meta: Metadata) -> BoxFuture<Vec<RpcH160>, Error> {
let dapp = meta.dapp_id.unwrap_or_default(); let dapp = meta.dapp_id();
let accounts = move || { let accounts = move || {
let accounts = self.dapp_accounts(dapp.into())?; let accounts = self.dapp_accounts(dapp.into())?;

View File

@ -178,7 +178,7 @@ impl Eth for EthClient {
} }
fn accounts(&self, meta: Metadata) -> BoxFuture<Vec<RpcH160>, Error> { fn accounts(&self, meta: Metadata) -> BoxFuture<Vec<RpcH160>, Error> {
let dapp: DappId = meta.dapp_id.unwrap_or_default().into(); let dapp: DappId = meta.dapp_id().into();
let accounts = self.accounts let accounts = self.accounts
.note_dapp_used(dapp.clone()) .note_dapp_used(dapp.clone())

View File

@ -145,7 +145,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
} }
fn default_account(&self, meta: Self::Metadata) -> BoxFuture<H160, Error> { fn default_account(&self, meta: Self::Metadata) -> BoxFuture<H160, Error> {
let dapp_id = meta.dapp_id.unwrap_or_default(); let dapp_id = meta.dapp_id();
let default_account = move || { let default_account = move || {
Ok(take_weak!(self.accounts) Ok(take_weak!(self.accounts)
.dapps_addresses(dapp_id.into()) .dapps_addresses(dapp_id.into())

View File

@ -101,7 +101,7 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
let default = match request.from.as_ref() { let default = match request.from.as_ref() {
Some(account) => Ok(account.clone().into()), Some(account) => Ok(account.clone().into()),
None => accounts 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)), .map_err(|e| errors::account("Cannot find default account.", e)),
}; };

View File

@ -38,7 +38,8 @@ use v1::types::{
RichRawTransaction as RpcRichRawTransaction, RichRawTransaction as RpcRichRawTransaction,
TransactionRequest as RpcTransactionRequest, TransactionRequest as RpcTransactionRequest,
ConfirmationPayload as RpcConfirmationPayload, ConfirmationPayload as RpcConfirmationPayload,
ConfirmationResponse as RpcConfirmationResponse ConfirmationResponse as RpcConfirmationResponse,
Origin,
}; };
const MAX_PENDING_DURATION: u64 = 60 * 60; const MAX_PENDING_DURATION: u64 = 60 * 60;
@ -81,7 +82,7 @@ impl<D: Dispatcher + 'static> SigningQueueClient<D> {
} }
} }
fn dispatch(&self, payload: RpcConfirmationPayload, default_account: DefaultAccount) -> BoxFuture<DispatchResult, Error> { fn dispatch(&self, payload: RpcConfirmationPayload, default_account: DefaultAccount, origin: Origin) -> BoxFuture<DispatchResult, Error> {
let accounts = take_weakf!(self.accounts); let accounts = take_weakf!(self.accounts);
let default_account = match default_account { let default_account = match default_account {
DefaultAccount::Provided(acc) => acc, DefaultAccount::Provided(acc) => acc,
@ -100,7 +101,7 @@ impl<D: Dispatcher + 'static> SigningQueueClient<D> {
.boxed() .boxed()
} else { } else {
future::done( future::done(
signer.add_request(payload) signer.add_request(payload, origin)
.map(DispatchResult::Promise) .map(DispatchResult::Promise)
.map_err(|_| errors::request_rejected_limit()) .map_err(|_| errors::request_rejected_limit())
).boxed() ).boxed()
@ -113,10 +114,13 @@ impl<D: Dispatcher + 'static> SigningQueueClient<D> {
impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> { impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
type Metadata = Metadata; type Metadata = Metadata;
fn post_sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> { fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
let pending = self.pending.clone(); let pending = self.pending.clone();
self.dispatch(RpcConfirmationPayload::Signature((address.clone(), data).into()), DefaultAccount::Provided(address.into())) self.dispatch(
.map(move |result| match result { RpcConfirmationPayload::Signature((address.clone(), data).into()),
DefaultAccount::Provided(address.into()),
meta.origin
).map(move |result| match result {
DispatchResult::Value(v) => RpcEither::Or(v), DispatchResult::Value(v) => RpcEither::Or(v),
DispatchResult::Promise(promise) => { DispatchResult::Promise(promise) => {
let id = promise.id(); let id = promise.id();
@ -129,7 +133,7 @@ impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
fn post_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> { fn post_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
let pending = self.pending.clone(); 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 { .map(move |result| match result {
DispatchResult::Value(v) => RpcEither::Or(v), DispatchResult::Value(v) => RpcEither::Or(v),
DispatchResult::Promise(promise) => { DispatchResult::Promise(promise) => {
@ -156,8 +160,12 @@ impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
res res
} }
fn decrypt_message(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcBytes, Error> { fn decrypt_message(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcBytes, Error> {
let res = self.dispatch(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into()); let res = self.dispatch(
RpcConfirmationPayload::Decrypt((address.clone(), data).into()),
address.into(),
meta.origin,
);
let (ready, p) = futures::oneshot(); let (ready, p) = futures::oneshot();
@ -181,8 +189,12 @@ impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
impl<D: Dispatcher + 'static> EthSigning for SigningQueueClient<D> { impl<D: Dispatcher + 'static> EthSigning for SigningQueueClient<D> {
type Metadata = Metadata; type Metadata = Metadata;
fn sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> { fn sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
let res = self.dispatch(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into()); let res = self.dispatch(
RpcConfirmationPayload::Signature((address.clone(), data).into()),
address.into(),
meta.origin,
);
let (ready, p) = futures::oneshot(); let (ready, p) = futures::oneshot();
@ -200,7 +212,11 @@ impl<D: Dispatcher + 'static> EthSigning for SigningQueueClient<D> {
} }
fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcH256, Error> { fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcH256, Error> {
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(); let (ready, p) = futures::oneshot();
@ -218,7 +234,11 @@ impl<D: Dispatcher + 'static> EthSigning for SigningQueueClient<D> {
} }
fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcRichRawTransaction, Error> { fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcRichRawTransaction, Error> {
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(); let (ready, p) = futures::oneshot();

View File

@ -72,7 +72,7 @@ impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
{ {
type Metadata = Metadata; type Metadata = Metadata;
fn sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> { fn sign(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
self.handle(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into()) self.handle(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into())
.then(|res| match res { .then(|res| match res {
Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature), Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature),
@ -83,7 +83,7 @@ impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
} }
fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcH256, Error> { fn send_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcH256, Error> {
self.handle(RpcConfirmationPayload::SendTransaction(request), meta.into()) self.handle(RpcConfirmationPayload::SendTransaction(request), meta.dapp_id().into())
.then(|res| match res { .then(|res| match res {
Ok(RpcConfirmationResponse::SendTransaction(hash)) => Ok(hash), Ok(RpcConfirmationResponse::SendTransaction(hash)) => Ok(hash),
Err(e) => Err(e), Err(e) => Err(e),
@ -93,7 +93,7 @@ impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
} }
fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcRichRawTransaction, Error> { fn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcRichRawTransaction, Error> {
self.handle(RpcConfirmationPayload::SignTransaction(request), meta.into()) self.handle(RpcConfirmationPayload::SignTransaction(request), meta.dapp_id().into())
.then(|res| match res { .then(|res| match res {
Ok(RpcConfirmationResponse::SignTransaction(tx)) => Ok(tx), Ok(RpcConfirmationResponse::SignTransaction(tx)) => Ok(tx),
Err(e) => Err(e), Err(e) => Err(e),
@ -106,7 +106,7 @@ impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
impl<D: Dispatcher + 'static> ParitySigning for SigningUnsafeClient<D> { impl<D: Dispatcher + 'static> ParitySigning for SigningUnsafeClient<D> {
type Metadata = Metadata; type Metadata = Metadata;
fn decrypt_message(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcBytes, Error> { fn decrypt_message(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcBytes, Error> {
self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into()) self.handle(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into())
.then(|res| match res { .then(|res| match res {
Ok(RpcConfirmationResponse::Decrypt(data)) => Ok(data), Ok(RpcConfirmationResponse::Decrypt(data)) => Ok(data),
@ -116,7 +116,7 @@ impl<D: Dispatcher + 'static> ParitySigning for SigningUnsafeClient<D> {
.boxed() .boxed()
} }
fn post_sign(&self, _: RpcH160, _: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> { fn post_sign(&self, _: Metadata, _: RpcH160, _: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
// We don't support this in non-signer mode. // We don't support this in non-signer mode.
future::err(errors::signer_disabled()).boxed() future::err(errors::signer_disabled()).boxed()
} }

View File

@ -16,33 +16,22 @@
use jsonrpc_core; use jsonrpc_core;
use v1::types::{DappId, Origin};
/// RPC methods metadata. /// RPC methods metadata.
#[derive(Clone, Default, Debug, PartialEq)] #[derive(Clone, Default, Debug, PartialEq)]
pub struct Metadata { pub struct Metadata {
/// Current dapplication identifier
pub dapp_id: Option<String>,
/// Request origin /// Request origin
pub origin: Origin, pub origin: Origin,
} }
/// RPC request origin impl Metadata {
#[derive(Clone, Debug, PartialEq)] /// Get
pub enum Origin { pub fn dapp_id(&self) -> DappId {
/// RPC server match self.origin {
Rpc, Origin::Dapps(ref dapp_id) => dapp_id.clone(),
/// Dapps server _ => DappId::default(),
Dapps, }
/// IPC server
Ipc,
/// Signer
Signer,
/// Unknown
Unknown,
}
impl Default for Origin {
fn default() -> Self {
Origin::Unknown
} }
} }

View File

@ -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::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc};
pub use self::impls::*; pub use self::impls::*;
pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import, informant, dispatch}; 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;

View File

@ -37,6 +37,7 @@ use v1::{Eth, EthClient, EthClientOptions, EthFilter, EthFilterClient, EthSignin
use v1::helpers::dispatch::FullDispatcher; use v1::helpers::dispatch::FullDispatcher;
use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService, TestSnapshotService}; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService, TestSnapshotService};
use v1::metadata::Metadata; use v1::metadata::Metadata;
use v1::types::Origin;
fn blockchain_client() -> Arc<TestBlockChainClient> { fn blockchain_client() -> Arc<TestBlockChainClient> {
let client = TestBlockChainClient::new(); 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 request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#;
let mut meta = Metadata::default(); 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())); assert_eq!((*tester.io).handle_request_sync(request, meta), Some(response.to_owned()));
} }

View File

@ -25,7 +25,7 @@ use rlp::encode;
use serde_json; use serde_json;
use jsonrpc_core::IoHandler; use jsonrpc_core::IoHandler;
use v1::{SignerClient, Signer}; use v1::{SignerClient, Signer, Origin};
use v1::metadata::Metadata; use v1::metadata::Metadata;
use v1::tests::helpers::TestMinerService; use v1::tests::helpers::TestMinerService;
use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload}; use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload};
@ -88,15 +88,15 @@ fn should_return_list_of_items_to_confirm() {
data: vec![], data: vec![],
nonce: None, nonce: None,
condition: None, condition: None,
})).unwrap(); }), Origin::Dapps("http://parity.io".into())).unwrap();
tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into())).unwrap(); tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into()), Origin::Unknown).unwrap();
// when // when
let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#;
let response = concat!( let response = concat!(
r#"{"jsonrpc":"2.0","result":["#, 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":"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","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#, r#"{"id":"0x2","origin":"unknown","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#,
r#"],"id":1}"# r#"],"id":1}"#
); );
@ -119,7 +119,7 @@ fn should_reject_transaction_from_queue_without_dispatching() {
data: vec![], data: vec![],
nonce: None, nonce: None,
condition: None, condition: None,
})).unwrap(); }), Origin::Unknown).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
// when // when
@ -146,7 +146,7 @@ fn should_not_remove_transaction_if_password_is_invalid() {
data: vec![], data: vec![],
nonce: None, nonce: None,
condition: None, condition: None,
})).unwrap(); }), Origin::Unknown).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
// when // when
@ -162,7 +162,7 @@ fn should_not_remove_transaction_if_password_is_invalid() {
fn should_not_remove_sign_if_password_is_invalid() { fn should_not_remove_sign_if_password_is_invalid() {
// given // given
let tester = signer_tester(); 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); assert_eq!(tester.signer.requests().len(), 1);
// when // when
@ -190,7 +190,7 @@ fn should_confirm_transaction_and_dispatch() {
data: vec![], data: vec![],
nonce: None, nonce: None,
condition: None, condition: None,
})).unwrap(); }), Origin::Unknown).unwrap();
let t = Transaction { let t = Transaction {
nonce: U256::zero(), nonce: U256::zero(),
@ -236,7 +236,7 @@ fn should_alter_the_sender_and_nonce() {
data: vec![], data: vec![],
nonce: Some(10.into()), nonce: Some(10.into()),
condition: None, condition: None,
})).unwrap(); }), Origin::Unknown).unwrap();
let t = Transaction { let t = Transaction {
nonce: U256::zero(), nonce: U256::zero(),
@ -286,7 +286,7 @@ fn should_confirm_transaction_with_token() {
data: vec![], data: vec![],
nonce: None, nonce: None,
condition: None, condition: None,
})).unwrap(); }), Origin::Unknown).unwrap();
let t = Transaction { let t = Transaction {
nonce: U256::zero(), nonce: U256::zero(),
@ -335,7 +335,7 @@ fn should_confirm_transaction_with_rlp() {
data: vec![], data: vec![],
nonce: None, nonce: None,
condition: None, condition: None,
})).unwrap(); }), Origin::Unknown).unwrap();
let t = Transaction { let t = Transaction {
nonce: U256::zero(), nonce: U256::zero(),
@ -383,7 +383,7 @@ fn should_return_error_when_sender_does_not_match() {
data: vec![], data: vec![],
nonce: None, nonce: None,
condition: None, condition: None,
})).unwrap(); }), Origin::Unknown).unwrap();
let t = Transaction { let t = Transaction {
nonce: U256::zero(), nonce: U256::zero(),

View File

@ -27,8 +27,8 @@ build_rpc_trait! {
type Metadata; type Metadata;
/// Signs the hash of data with given address signature. /// Signs the hash of data with given address signature.
#[rpc(async, name = "eth_sign")] #[rpc(meta, name = "eth_sign")]
fn sign(&self, H160, Bytes) -> BoxFuture<H520, Error>; fn sign(&self, Self::Metadata, H160, Bytes) -> BoxFuture<H520, Error>;
/// Sends transaction; will block waiting for signer to return the /// Sends transaction; will block waiting for signer to return the
/// transaction hash. /// transaction hash.

View File

@ -27,8 +27,8 @@ build_rpc_trait! {
/// Posts sign request asynchronously. /// Posts sign request asynchronously.
/// Will return a confirmation ID for later use with check_transaction. /// Will return a confirmation ID for later use with check_transaction.
#[rpc(async, name = "parity_postSign")] #[rpc(meta, name = "parity_postSign")]
fn post_sign(&self, H160, Bytes) -> BoxFuture<Either<U256, ConfirmationResponse>, Error>; fn post_sign(&self, Self::Metadata, H160, Bytes) -> BoxFuture<Either<U256, ConfirmationResponse>, Error>;
/// Posts transaction asynchronously. /// Posts transaction asynchronously.
/// Will return a transaction ID for later use with check_transaction. /// Will return a transaction ID for later use with check_transaction.
@ -42,7 +42,7 @@ build_rpc_trait! {
/// Decrypt some ECIES-encrypted message. /// Decrypt some ECIES-encrypted message.
/// First parameter is the address with which it is encrypted, second is the ciphertext. /// First parameter is the address with which it is encrypted, second is the ciphertext.
#[rpc(async, name = "parity_decryptMessage")] #[rpc(meta, name = "parity_decryptMessage")]
fn decrypt_message(&self, H160, Bytes) -> BoxFuture<Bytes, Error>; fn decrypt_message(&self, Self::Metadata, H160, Bytes) -> BoxFuture<Bytes, Error>;
} }
} }

View File

@ -21,16 +21,19 @@ use serde::{Serialize, Serializer};
use util::log::Colour; use util::log::Colour;
use util::bytes::ToPretty; 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; use v1::helpers;
/// Confirmation waiting in a queue /// Confirmation waiting in a queue
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfirmationRequest { pub struct ConfirmationRequest {
/// Id of this confirmation /// Id of this confirmation
pub id: U256, pub id: U256,
/// Payload /// Payload
pub payload: ConfirmationPayload, pub payload: ConfirmationPayload,
/// Request origin
pub origin: Origin,
} }
impl From<helpers::ConfirmationRequest> for ConfirmationRequest { impl From<helpers::ConfirmationRequest> for ConfirmationRequest {
@ -38,13 +41,14 @@ impl From<helpers::ConfirmationRequest> for ConfirmationRequest {
ConfirmationRequest { ConfirmationRequest {
id: c.id.into(), id: c.id.into(),
payload: c.payload.into(), payload: c.payload.into(),
origin: c.origin,
} }
} }
} }
impl fmt::Display for ConfirmationRequest { impl fmt::Display for ConfirmationRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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 /// Sign request
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SignRequest { pub struct SignRequest {
/// Address /// Address
pub address: H160, pub address: H160,
@ -90,6 +95,7 @@ impl fmt::Display for SignRequest {
/// Decrypt request /// Decrypt request
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DecryptRequest { pub struct DecryptRequest {
/// Address /// Address
pub address: H160, pub address: H160,
@ -153,6 +159,7 @@ pub struct ConfirmationResponseWithToken {
/// Confirmation payload, i.e. the thing to be confirmed /// Confirmation payload, i.e. the thing to be confirmed
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum ConfirmationPayload { pub enum ConfirmationPayload {
/// Send Transaction /// Send Transaction
#[serde(rename="sendTransaction")] #[serde(rename="sendTransaction")]
@ -249,11 +256,12 @@ mod tests {
let request = helpers::ConfirmationRequest { let request = helpers::ConfirmationRequest {
id: 15.into(), id: 15.into(),
payload: helpers::ConfirmationPayload::Signature(1.into(), vec![5].into()), payload: helpers::ConfirmationPayload::Signature(1.into(), vec![5].into()),
origin: Origin::Rpc("test service".into()),
}; };
// when // when
let res = serde_json::to_string(&ConfirmationRequest::from(request)); 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 // then
assert_eq!(res.unwrap(), expected.to_owned()); assert_eq!(res.unwrap(), expected.to_owned());
@ -275,11 +283,12 @@ mod tests {
nonce: Some(1.into()), nonce: Some(1.into()),
condition: None, condition: None,
}), }),
origin: Origin::Signer(5.into()),
}; };
// when // when
let res = serde_json::to_string(&ConfirmationRequest::from(request)); 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 // then
assert_eq!(res.unwrap(), expected.to_owned()); assert_eq!(res.unwrap(), expected.to_owned());
@ -301,11 +310,12 @@ mod tests {
nonce: Some(1.into()), nonce: Some(1.into()),
condition: None, condition: None,
}), }),
origin: Origin::Dapps("http://parity.io".into()),
}; };
// when // when
let res = serde_json::to_string(&ConfirmationRequest::from(request)); 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 // then
assert_eq!(res.unwrap(), expected.to_owned()); assert_eq!(res.unwrap(), expected.to_owned());
@ -319,11 +329,12 @@ mod tests {
payload: helpers::ConfirmationPayload::Decrypt( payload: helpers::ConfirmationPayload::Decrypt(
10.into(), vec![1, 2, 3].into(), 10.into(), vec![1, 2, 3].into(),
), ),
origin: Default::default(),
}; };
// when // when
let res = serde_json::to_string(&ConfirmationRequest::from(request)); 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 // then
assert_eq!(res.unwrap(), expected.to_owned()); assert_eq!(res.unwrap(), expected.to_owned());

View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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<String> for DappId {
fn into(self) -> String {
self.0
}
}
impl From<String> for DappId {
fn from(s: String) -> Self {
DappId(s)
}
}
impl From<EthDappId> for DappId {
fn from(id: EthDappId) -> Self {
DappId(id.into())
}
}
impl Into<EthDappId> for DappId {
fn into(self) -> EthDappId {
Into::<String>::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()));
}
}

View File

@ -36,11 +36,18 @@ macro_rules! impl_hash {
} }
impl fmt::Debug for $name { 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()) 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<T> From<T> for $name where $other: From<T> { impl<T> From<T> for $name where $other: From<T> {
fn from(o: T) -> Self { fn from(o: T) -> Self {
$name($other::from(o).0) $name($other::from(o).0)

View File

@ -18,29 +18,30 @@
//! RPC types //! RPC types
mod account_info; mod account_info;
mod bytes;
mod block; mod block;
mod block_number; mod block_number;
mod bytes;
mod call_request; mod call_request;
mod confirmations; mod confirmations;
mod dapp_id; mod consensus_status;
mod filter; mod filter;
mod hash; mod hash;
mod histogram;
mod index; mod index;
mod log; mod log;
mod provenance;
mod receipt;
mod rpc_settings;
mod sync; mod sync;
mod trace;
mod trace_filter;
mod transaction; mod transaction;
mod transaction_request; mod transaction_request;
mod transaction_condition; mod transaction_condition;
mod receipt;
mod rpc_settings;
mod trace;
mod trace_filter;
mod uint; mod uint;
mod work; mod work;
mod histogram;
mod consensus_status;
pub use self::account_info::{AccountInfo, HwAccountInfo};
pub use self::bytes::Bytes; pub use self::bytes::Bytes;
pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block::{RichBlock, Block, BlockTransactions};
pub use self::block_number::BlockNumber; pub use self::block_number::BlockNumber;
@ -49,24 +50,24 @@ pub use self::confirmations::{
ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken,
TransactionModification, SignRequest, DecryptRequest, Either TransactionModification, SignRequest, DecryptRequest, Either
}; };
pub use self::dapp_id::DappId; pub use self::consensus_status::*;
pub use self::filter::{Filter, FilterChanges}; pub use self::filter::{Filter, FilterChanges};
pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::hash::{H64, H160, H256, H512, H520, H2048};
pub use self::histogram::Histogram;
pub use self::index::Index; pub use self::index::Index;
pub use self::log::Log; 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::{ pub use self::sync::{
SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo,
TransactionStats, ChainStatus, EthProtocolInfo, LesProtocolInfo, 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::{Transaction, RichRawTransaction, LocalTransactionStatus};
pub use self::transaction_request::TransactionRequest; pub use self::transaction_request::TransactionRequest;
pub use self::transaction_condition::TransactionCondition; 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::uint::{U128, U256};
pub use self::work::Work; pub use self::work::Work;
pub use self::histogram::Histogram;
pub use self::consensus_status::*;
pub use self::account_info::{AccountInfo, HwAccountInfo};

View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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<String> for DappId {
fn into(self) -> String {
self.0
}
}
impl From<String> 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<EthDappId> for DappId {
fn from(id: EthDappId) -> Self {
DappId(id.into())
}
}
impl Into<EthDappId> for DappId {
fn into(self) -> EthDappId {
Into::<String>::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()));
}
}

View File

@ -18,6 +18,7 @@ use ethcore;
/// Represents condition on minimum block number or block timestamp. /// Represents condition on minimum block number or block timestamp.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum TransactionCondition { pub enum TransactionCondition {
/// Valid at this minimum block number. /// Valid at this minimum block number.
#[serde(rename="block")] #[serde(rename="block")]

View File

@ -18,6 +18,9 @@ extern crate log;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[macro_use]
extern crate matches;
use futures::Future; use futures::Future;
use std::path::PathBuf; use std::path::PathBuf;
use client::{Rpc, RpcError}; use client::{Rpc, RpcError};

View File

@ -33,6 +33,8 @@ use rpc::informant::RpcStats;
mod session; mod session;
pub use self::session::MetaExtractor;
/// Signer startup error /// Signer startup error
#[derive(Debug)] #[derive(Debug)]
pub enum ServerError { pub enum ServerError {
@ -51,6 +53,11 @@ impl From<ws::Error> for ServerError {
} }
} }
/// Dummy metadata extractor
#[derive(Clone)]
pub struct NoopExtractor;
impl<M: Metadata> session::MetaExtractor<M> for NoopExtractor {}
/// Builder for `WebSockets` server /// Builder for `WebSockets` server
pub struct ServerBuilder { pub struct ServerBuilder {
queue: Arc<ConfirmationsQueue>, queue: Arc<ConfirmationsQueue>,
@ -86,6 +93,17 @@ impl ServerBuilder {
/// Starts a new `WebSocket` server in separate thread. /// Starts a new `WebSocket` server in separate thread.
/// Returns a `Server` handle which closes the server when droped. /// Returns a `Server` handle which closes the server when droped.
pub fn start<M: Metadata, S: Middleware<M>>(self, addr: SocketAddr, handler: RpcHandler<M, S>) -> Result<Server, ServerError> { pub fn start<M: Metadata, S: Middleware<M>>(self, addr: SocketAddr, handler: RpcHandler<M, S>) -> Result<Server, ServerError> {
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<M: Metadata, S: Middleware<M>, T: session::MetaExtractor<M>>(
self,
addr: SocketAddr,
handler: RpcHandler<M, S>,
meta_extractor: T,
) -> Result<Server, ServerError> {
Server::start( Server::start(
addr, addr,
handler, handler,
@ -93,8 +111,10 @@ impl ServerBuilder {
self.authcodes_path, self.authcodes_path,
self.skip_origin_validation, self.skip_origin_validation,
self.stats, self.stats,
meta_extractor,
) )
} }
} }
/// `WebSockets` server implementation. /// `WebSockets` server implementation.
@ -114,13 +134,14 @@ impl Server {
/// Starts a new `WebSocket` server in separate thread. /// Starts a new `WebSocket` server in separate thread.
/// Returns a `Server` handle which closes the server when droped. /// Returns a `Server` handle which closes the server when droped.
fn start<M: Metadata, S: Middleware<M>>( fn start<M: Metadata, S: Middleware<M>, T: session::MetaExtractor<M>>(
addr: SocketAddr, addr: SocketAddr,
handler: RpcHandler<M, S>, handler: RpcHandler<M, S>,
queue: Arc<ConfirmationsQueue>, queue: Arc<ConfirmationsQueue>,
authcodes_path: PathBuf, authcodes_path: PathBuf,
skip_origin_validation: bool, skip_origin_validation: bool,
stats: Option<Arc<RpcStats>>, stats: Option<Arc<RpcStats>>,
meta_extractor: T,
) -> Result<Server, ServerError> { ) -> Result<Server, ServerError> {
let config = { let config = {
let mut config = ws::Settings::default(); let mut config = ws::Settings::default();
@ -135,7 +156,7 @@ impl Server {
let origin = format!("{}", addr); let origin = format!("{}", addr);
let port = addr.port(); let port = addr.port();
let ws = ws::Builder::new().with_settings(config).build( 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(); let panic_handler = PanicHandler::new_in_arc();

View File

@ -16,15 +16,16 @@
//! Session handlers factory. //! Session handlers factory.
use ws;
use authcode_store::AuthCodes;
use std::path::{PathBuf, Path}; use std::path::{PathBuf, Path};
use std::sync::Arc; use std::sync::Arc;
use std::str::FromStr; use std::str::FromStr;
use authcode_store::AuthCodes;
use jsonrpc_core::{Metadata, Middleware}; use jsonrpc_core::{Metadata, Middleware};
use jsonrpc_core::reactor::RpcHandler; use jsonrpc_core::reactor::RpcHandler;
use rpc::informant::RpcStats; use rpc::informant::RpcStats;
use util::{H256, version}; use util::{H256, version};
use ws;
#[cfg(feature = "parity-ui")] #[cfg(feature = "parity-ui")]
mod ui { mod ui {
@ -78,10 +79,10 @@ fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool {
} }
} }
fn auth_is_valid(codes_path: &Path, protocols: ws::Result<Vec<&str>>) -> bool { fn auth_token_hash(codes_path: &Path, protocols: ws::Result<Vec<&str>>) -> Option<H256> {
match protocols { match protocols {
Ok(ref protocols) if protocols.len() == 1 => { Ok(ref protocols) if protocols.len() == 1 => {
protocols.iter().any(|protocol| { let protocol = protocols[0];
let mut split = protocol.split('_'); let mut split = protocol.split('_');
let auth = split.next().and_then(|v| H256::from_str(v).ok()); 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 time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok());
@ -89,7 +90,8 @@ fn auth_is_valid(codes_path: &Path, protocols: ws::Result<Vec<&str>>) -> bool {
if let (Some(auth), Some(time)) = (auth, time) { if let (Some(auth), Some(time)) = (auth, time) {
// Check if the code is valid // Check if the code is valid
AuthCodes::from_file(codes_path) AuthCodes::from_file(codes_path)
.map(|mut codes| { .ok()
.and_then(|mut codes| {
// remove old tokens // remove old tokens
codes.clear_garbage(); codes.clear_garbage();
@ -98,15 +100,18 @@ fn auth_is_valid(codes_path: &Path, protocols: ws::Result<Vec<&str>>) -> bool {
if codes.to_file(codes_path).is_err() { if codes.to_file(codes_path).is_err() {
warn!(target: "signer", "Couldn't save authorization codes to file."); warn!(target: "signer", "Couldn't save authorization codes to file.");
} }
res
}) if res {
.unwrap_or(false) Some(auth)
} else { } else {
false None
} }
}) })
} else {
None
}
}, },
_ => false _ => None,
} }
} }
@ -125,7 +130,16 @@ fn add_headers(mut response: ws::Response, mime: &str) -> ws::Response {
response response
} }
pub struct Session<M: Metadata, S: Middleware<M>> { /// Metadata extractor from session data.
pub trait MetaExtractor<M: Metadata>: Send + Clone + 'static {
/// Extract metadata for given session
fn extract_metadata(&self, _session_id: &H256) -> M {
Default::default()
}
}
pub struct Session<M: Metadata, S: Middleware<M>, T> {
session_id: H256,
out: ws::Sender, out: ws::Sender,
skip_origin_validation: bool, skip_origin_validation: bool,
self_origin: String, self_origin: String,
@ -134,16 +148,16 @@ pub struct Session<M: Metadata, S: Middleware<M>> {
handler: RpcHandler<M, S>, handler: RpcHandler<M, S>,
file_handler: Arc<ui::Handler>, file_handler: Arc<ui::Handler>,
stats: Option<Arc<RpcStats>>, stats: Option<Arc<RpcStats>>,
meta_extractor: T,
} }
impl<M: Metadata, S: Middleware<M>> Drop for Session<M, S> { impl<M: Metadata, S: Middleware<M>, T> Drop for Session<M, S, T> {
fn drop(&mut self) { fn drop(&mut self) {
self.stats.as_ref().map(|stats| stats.close_session()); self.stats.as_ref().map(|stats| stats.close_session());
} }
} }
impl<M: Metadata, S: Middleware<M>> ws::Handler for Session<M, S> { impl<M: Metadata, S: Middleware<M>, T: MetaExtractor<M>> ws::Handler for Session<M, S, T> {
#[cfg_attr(feature="dev", allow(collapsible_if))]
fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> { fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> {
trace!(target: "signer", "Handling request: {:?}", req); trace!(target: "signer", "Handling request: {:?}", req);
@ -186,9 +200,15 @@ impl<M: Metadata, S: Middleware<M>> ws::Handler for Session<M, S> {
// (styles file skips origin validation, so make sure to prevent WS connections on this resource) // (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 { if req.header("sec-websocket-key").is_some() && !is_styles_file {
// Check authorization // Check authorization
if !auth_is_valid(&self.authcodes_path, req.protocols()) { 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."); info!(target: "signer", "Unauthorized connection to Signer API blocked.");
return Ok(error(ErrorType::Forbidden, "Not Authorized", "Request to this API was not authorized.", None)); return Ok(error(ErrorType::Forbidden, "Not Authorized", "Request to this API was not authorized.", None));
},
Some(auth) => {
self.session_id = auth;
},
} }
let protocols = req.protocols().expect("Existence checked by authorization."); let protocols = req.protocols().expect("Existence checked by authorization.");
@ -214,8 +234,8 @@ impl<M: Metadata, S: Middleware<M>> ws::Handler for Session<M, S> {
fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
let req = msg.as_text()?; let req = msg.as_text()?;
let out = self.out.clone(); let out = self.out.clone();
// TODO [ToDr] Extract metadata for PubSub/Session // TODO [ToDr] Move to on_connect
let metadata = Default::default(); let metadata = self.meta_extractor.extract_metadata(&self.session_id);
self.handler.handle_request(req, metadata, move |response| { self.handler.handle_request(req, metadata, move |response| {
if let Some(result) = response { if let Some(result) = response {
@ -229,17 +249,18 @@ impl<M: Metadata, S: Middleware<M>> ws::Handler for Session<M, S> {
} }
} }
pub struct Factory<M: Metadata, S: Middleware<M>> { pub struct Factory<M: Metadata, S: Middleware<M>, T> {
handler: RpcHandler<M, S>, handler: RpcHandler<M, S>,
skip_origin_validation: bool, skip_origin_validation: bool,
self_origin: String, self_origin: String,
self_port: u16, self_port: u16,
authcodes_path: PathBuf, authcodes_path: PathBuf,
meta_extractor: T,
file_handler: Arc<ui::Handler>, file_handler: Arc<ui::Handler>,
stats: Option<Arc<RpcStats>>, stats: Option<Arc<RpcStats>>,
} }
impl<M: Metadata, S: Middleware<M>> Factory<M, S> { impl<M: Metadata, S: Middleware<M>, T> Factory<M, S, T> {
pub fn new( pub fn new(
handler: RpcHandler<M, S>, handler: RpcHandler<M, S>,
self_origin: String, self_origin: String,
@ -247,6 +268,7 @@ impl<M: Metadata, S: Middleware<M>> Factory<M, S> {
authcodes_path: PathBuf, authcodes_path: PathBuf,
skip_origin_validation: bool, skip_origin_validation: bool,
stats: Option<Arc<RpcStats>>, stats: Option<Arc<RpcStats>>,
meta_extractor: T,
) -> Self { ) -> Self {
Factory { Factory {
handler: handler, handler: handler,
@ -254,25 +276,28 @@ impl<M: Metadata, S: Middleware<M>> Factory<M, S> {
self_origin: self_origin, self_origin: self_origin,
self_port: self_port, self_port: self_port,
authcodes_path: authcodes_path, authcodes_path: authcodes_path,
meta_extractor: meta_extractor,
file_handler: Arc::new(ui::Handler::default()), file_handler: Arc::new(ui::Handler::default()),
stats: stats, stats: stats,
} }
} }
} }
impl<M: Metadata, S: Middleware<M>> ws::Factory for Factory<M, S> { impl<M: Metadata, S: Middleware<M>, T: MetaExtractor<M>> ws::Factory for Factory<M, S, T> {
type Handler = Session<M, S>; type Handler = Session<M, S, T>;
fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler { fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler {
self.stats.as_ref().map(|stats| stats.open_session()); self.stats.as_ref().map(|stats| stats.open_session());
Session { Session {
session_id: 0.into(),
out: sender, out: sender,
handler: self.handler.clone(), handler: self.handler.clone(),
skip_origin_validation: self.skip_origin_validation, skip_origin_validation: self.skip_origin_validation,
self_origin: self.self_origin.clone(), self_origin: self.self_origin.clone(),
self_port: self.self_port, self_port: self.self_port,
authcodes_path: self.authcodes_path.clone(), authcodes_path: self.authcodes_path.clone(),
meta_extractor: self.meta_extractor.clone(),
file_handler: self.file_handler.clone(), file_handler: self.file_handler.clone(),
stats: self.stats.clone(), stats: self.stats.clone(),
} }