Signer provenance (#4477)
* Signer - Tracking Request Provenance * Basic UI * Changing messages * VecDeque::from * Fix dapps tests * Addressing UI grumbles
This commit is contained in:
parent
d925cc05da
commit
5369a129ae
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -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)",
|
||||
|
@ -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" }
|
||||
|
@ -77,8 +77,7 @@ impl HttpMetaExtractor<Metadata> for MetadataExtractor {
|
||||
})
|
||||
});
|
||||
Metadata {
|
||||
dapp_id: dapp_id,
|
||||
origin: Origin::Dapps,
|
||||
origin: Origin::Dapps(dapp_id.map(Into::into).unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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<AccountProvider>) {
|
||||
|
@ -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> {
|
||||
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)
|
||||
.and_then(|block_hash| self.transactions_traces(&block_hash)
|
||||
.and_then(|traces| traces.into_iter().nth(tx_position))
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
17
js/src/views/Signer/components/RequestOrigin/index.js
Normal file
17
js/src/views/Signer/components/RequestOrigin/index.js
Normal 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';
|
@ -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;
|
||||
}
|
||||
}
|
116
js/src/views/Signer/components/RequestOrigin/requestOrigin.js
Normal file
116
js/src/views/Signer/components/RequestOrigin/requestOrigin.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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');
|
||||
});
|
||||
});
|
@ -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 }
|
||||
/>
|
||||
|
@ -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 }
|
||||
/>
|
||||
<RequestOrigin origin={ origin } />
|
||||
</div>
|
||||
<div className={ styles.info } title={ api.util.sha3(data) }>
|
||||
<p>A request to sign data using your account:</p>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
<div className={ styles.transaction }>
|
||||
@ -64,6 +74,7 @@ export default class TransactionMainDetails extends Component {
|
||||
isTest={ isTest }
|
||||
/>
|
||||
</div>
|
||||
<RequestOrigin origin={ origin } />
|
||||
</div>
|
||||
<div className={ styles.method }>
|
||||
<MethodDecoding
|
||||
|
@ -43,6 +43,7 @@ export default class TransactionPending extends Component {
|
||||
nonce: PropTypes.number,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onReject: PropTypes.func.isRequired,
|
||||
origin: PropTypes.any,
|
||||
store: PropTypes.object.isRequired,
|
||||
transaction: PropTypes.shape({
|
||||
condition: PropTypes.object,
|
||||
@ -56,7 +57,11 @@ export default class TransactionPending extends Component {
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false
|
||||
focus: false,
|
||||
origin: {
|
||||
type: 'unknown',
|
||||
details: ''
|
||||
}
|
||||
};
|
||||
|
||||
gasStore = new GasPriceEditor.Store(this.context.api, {
|
||||
@ -87,7 +92,7 @@ export default class TransactionPending extends Component {
|
||||
}
|
||||
|
||||
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 { balances, externalLink } = store;
|
||||
const { from, value } = transaction;
|
||||
@ -104,6 +109,7 @@ export default class TransactionPending extends Component {
|
||||
gasStore={ this.gasStore }
|
||||
id={ id }
|
||||
isTest={ isTest }
|
||||
origin={ origin }
|
||||
totalValue={ totalValue }
|
||||
transaction={ transaction }
|
||||
value={ value }
|
||||
|
@ -81,7 +81,7 @@ class Embedded 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 (
|
||||
<RequestPending
|
||||
@ -95,6 +95,7 @@ class Embedded extends Component {
|
||||
key={ id }
|
||||
onConfirm={ actions.startConfirmRequest }
|
||||
onReject={ actions.startRejectRequest }
|
||||
origin={ origin }
|
||||
payload={ payload }
|
||||
store={ this.store }
|
||||
/>
|
||||
|
@ -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 (
|
||||
<RequestPending
|
||||
@ -121,6 +121,7 @@ class RequestsPage extends Component {
|
||||
key={ id }
|
||||
onConfirm={ actions.startConfirmRequest }
|
||||
onReject={ actions.startRejectRequest }
|
||||
origin={ origin }
|
||||
payload={ payload }
|
||||
store={ this.store }
|
||||
/>
|
||||
|
@ -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) => {
|
||||
{
|
||||
|
@ -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<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> {
|
||||
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)),
|
||||
|
@ -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<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> {
|
||||
if !conf.enabled {
|
||||
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 apis = rpc_apis::setup_rpc(deps.rpc_stats, deps.apis, rpc_apis::ApiSet::SafeContext);
|
||||
let handler = RpcHandler::new(Arc::new(apis), deps.remote);
|
||||
server.start(addr, handler)
|
||||
server.start_with_extractor(addr, handler, StandardExtractor)
|
||||
};
|
||||
|
||||
match start_result {
|
||||
|
@ -65,19 +65,24 @@ use io::PanicHandler;
|
||||
use jsonrpc_core::reactor::RpcHandler;
|
||||
|
||||
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 use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant, dispatch};
|
||||
pub use v1::block_import::is_major_importing;
|
||||
|
||||
/// 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,
|
||||
cors_domains: Option<Vec<String>>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
panic_handler: Arc<PanicHandler>,
|
||||
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| {
|
||||
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)
|
||||
.meta_extractor(Arc::new(extractor))
|
||||
.cors(cors_domains.into())
|
||||
.allowed_hosts(allowed_hosts.into())
|
||||
.panic_handler(move || {
|
||||
|
@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
@ -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<ConfirmationResponse, jsonrpc_core::Error>;
|
||||
@ -37,9 +36,9 @@ pub enum DefaultAccount {
|
||||
ForDapp(DappId),
|
||||
}
|
||||
|
||||
impl From<Metadata> for DefaultAccount {
|
||||
fn from(meta: Metadata) -> Self {
|
||||
DefaultAccount::ForDapp(meta.dapp_id.unwrap_or_default().into())
|
||||
impl From<RpcDappId> 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<ConfirmationPromise, QueueAddError>;
|
||||
fn add_request(&self, request: ConfirmationPayload, origin: Origin) -> Result<ConfirmationPromise, QueueAddError>;
|
||||
|
||||
/// 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<ConfirmationPromise, QueueAddError> {
|
||||
fn add_request(&self, request: ConfirmationPayload, origin: Origin) -> Result<ConfirmationPromise, QueueAddError> {
|
||||
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
|
||||
|
@ -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> {
|
||||
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<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> {
|
||||
let dapp = meta.dapp_id.unwrap_or_default();
|
||||
let dapp = meta.dapp_id();
|
||||
|
||||
let accounts = move || {
|
||||
let accounts = self.dapp_accounts(dapp.into())?;
|
||||
|
@ -178,7 +178,7 @@ impl Eth for EthClient {
|
||||
}
|
||||
|
||||
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
|
||||
.note_dapp_used(dapp.clone())
|
||||
|
@ -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> {
|
||||
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())
|
||||
|
@ -101,7 +101,7 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
|
||||
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)),
|
||||
};
|
||||
|
||||
|
@ -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<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 default_account = match default_account {
|
||||
DefaultAccount::Provided(acc) => acc,
|
||||
@ -100,7 +101,7 @@ impl<D: Dispatcher + 'static> SigningQueueClient<D> {
|
||||
.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<D: Dispatcher + 'static> SigningQueueClient<D> {
|
||||
impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
|
||||
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();
|
||||
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<RpcEither<RpcU256, RpcConfirmationResponse>, 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<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
|
||||
res
|
||||
}
|
||||
|
||||
fn decrypt_message(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcBytes, Error> {
|
||||
let res = self.dispatch(RpcConfirmationPayload::Decrypt((address.clone(), data).into()), address.into());
|
||||
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(),
|
||||
meta.origin,
|
||||
);
|
||||
|
||||
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> {
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn sign(&self, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
|
||||
let res = self.dispatch(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into());
|
||||
fn sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
|
||||
let res = self.dispatch(
|
||||
RpcConfirmationPayload::Signature((address.clone(), data).into()),
|
||||
address.into(),
|
||||
meta.origin,
|
||||
);
|
||||
|
||||
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> {
|
||||
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<D: Dispatcher + 'static> EthSigning for SigningQueueClient<D> {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
|
@ -72,7 +72,7 @@ impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
|
||||
{
|
||||
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())
|
||||
.then(|res| match res {
|
||||
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> {
|
||||
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<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
|
||||
}
|
||||
|
||||
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 {
|
||||
Ok(RpcConfirmationResponse::SignTransaction(tx)) => Ok(tx),
|
||||
Err(e) => Err(e),
|
||||
@ -106,7 +106,7 @@ impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
|
||||
impl<D: Dispatcher + 'static> ParitySigning for SigningUnsafeClient<D> {
|
||||
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())
|
||||
.then(|res| match res {
|
||||
Ok(RpcConfirmationResponse::Decrypt(data)) => Ok(data),
|
||||
@ -116,7 +116,7 @@ impl<D: Dispatcher + 'static> ParitySigning for SigningUnsafeClient<D> {
|
||||
.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.
|
||||
future::err(errors::signer_disabled()).boxed()
|
||||
}
|
||||
|
@ -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<String>,
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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<TestBlockChainClient> {
|
||||
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()));
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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<H520, Error>;
|
||||
#[rpc(meta, name = "eth_sign")]
|
||||
fn sign(&self, Self::Metadata, H160, Bytes) -> BoxFuture<H520, Error>;
|
||||
|
||||
/// Sends transaction; will block waiting for signer to return the
|
||||
/// transaction hash.
|
||||
|
@ -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<Either<U256, ConfirmationResponse>, Error>;
|
||||
#[rpc(meta, name = "parity_postSign")]
|
||||
fn post_sign(&self, Self::Metadata, H160, Bytes) -> BoxFuture<Either<U256, ConfirmationResponse>, 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<Bytes, Error>;
|
||||
#[rpc(meta, name = "parity_decryptMessage")]
|
||||
fn decrypt_message(&self, Self::Metadata, H160, Bytes) -> BoxFuture<Bytes, Error>;
|
||||
}
|
||||
}
|
||||
|
@ -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<helpers::ConfirmationRequest> for ConfirmationRequest {
|
||||
@ -38,13 +41,14 @@ impl From<helpers::ConfirmationRequest> 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());
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<T> From<T> for $name where $other: From<T> {
|
||||
fn from(o: T) -> Self {
|
||||
$name($other::from(o).0)
|
||||
|
@ -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};
|
||||
|
||||
|
154
rpc/src/v1/types/provenance.rs
Normal file
154
rpc/src/v1/types/provenance.rs
Normal 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()));
|
||||
}
|
||||
}
|
@ -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")]
|
||||
|
@ -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};
|
||||
|
@ -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<ws::Error> for ServerError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy metadata extractor
|
||||
#[derive(Clone)]
|
||||
pub struct NoopExtractor;
|
||||
impl<M: Metadata> session::MetaExtractor<M> for NoopExtractor {}
|
||||
|
||||
/// Builder for `WebSockets` server
|
||||
pub struct ServerBuilder {
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
@ -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<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(
|
||||
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<M: Metadata, S: Middleware<M>>(
|
||||
fn start<M: Metadata, S: Middleware<M>, T: session::MetaExtractor<M>>(
|
||||
addr: SocketAddr,
|
||||
handler: RpcHandler<M, S>,
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
authcodes_path: PathBuf,
|
||||
skip_origin_validation: bool,
|
||||
stats: Option<Arc<RpcStats>>,
|
||||
meta_extractor: T,
|
||||
) -> Result<Server, ServerError> {
|
||||
let config = {
|
||||
let mut config = ws::Settings::default();
|
||||
@ -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();
|
||||
|
@ -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<Vec<&str>>) -> bool {
|
||||
fn auth_token_hash(codes_path: &Path, protocols: ws::Result<Vec<&str>>) -> Option<H256> {
|
||||
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<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,
|
||||
skip_origin_validation: bool,
|
||||
self_origin: String,
|
||||
@ -134,16 +148,16 @@ pub struct Session<M: Metadata, S: Middleware<M>> {
|
||||
handler: RpcHandler<M, S>,
|
||||
file_handler: Arc<ui::Handler>,
|
||||
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) {
|
||||
self.stats.as_ref().map(|stats| stats.close_session());
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Metadata, S: Middleware<M>> ws::Handler for Session<M, S> {
|
||||
#[cfg_attr(feature="dev", allow(collapsible_if))]
|
||||
impl<M: Metadata, S: Middleware<M>, T: MetaExtractor<M>> ws::Handler for Session<M, S, T> {
|
||||
fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> {
|
||||
trace!(target: "signer", "Handling request: {:?}", req);
|
||||
|
||||
@ -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)
|
||||
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<M: Metadata, S: Middleware<M>> ws::Handler for Session<M, S> {
|
||||
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<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>,
|
||||
skip_origin_validation: bool,
|
||||
self_origin: String,
|
||||
self_port: u16,
|
||||
authcodes_path: PathBuf,
|
||||
meta_extractor: T,
|
||||
file_handler: Arc<ui::Handler>,
|
||||
stats: Option<Arc<RpcStats>>,
|
||||
}
|
||||
|
||||
impl<M: Metadata, S: Middleware<M>> Factory<M, S> {
|
||||
impl<M: Metadata, S: Middleware<M>, T> Factory<M, S, T> {
|
||||
pub fn new(
|
||||
handler: RpcHandler<M, S>,
|
||||
self_origin: String,
|
||||
self_port: u16,
|
||||
self_port: u16,
|
||||
authcodes_path: PathBuf,
|
||||
skip_origin_validation: bool,
|
||||
stats: Option<Arc<RpcStats>>,
|
||||
meta_extractor: T,
|
||||
) -> Self {
|
||||
Factory {
|
||||
handler: handler,
|
||||
@ -254,25 +276,28 @@ impl<M: Metadata, S: Middleware<M>> Factory<M, S> {
|
||||
self_origin: self_origin,
|
||||
self_port: self_port,
|
||||
authcodes_path: authcodes_path,
|
||||
meta_extractor: meta_extractor,
|
||||
file_handler: Arc::new(ui::Handler::default()),
|
||||
stats: stats,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Metadata, S: Middleware<M>> ws::Factory for Factory<M, S> {
|
||||
type Handler = Session<M, S>;
|
||||
impl<M: Metadata, S: Middleware<M>, T: MetaExtractor<M>> ws::Factory for Factory<M, S, T> {
|
||||
type Handler = Session<M, S, T>;
|
||||
|
||||
fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler {
|
||||
self.stats.as_ref().map(|stats| stats.open_session());
|
||||
|
||||
Session {
|
||||
session_id: 0.into(),
|
||||
out: sender,
|
||||
handler: self.handler.clone(),
|
||||
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(),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user