Merge remote-tracking branch 'origin/master' into check-updates
This commit is contained in:
commit
c12702fc51
78
Cargo.lock
generated
78
Cargo.lock
generated
@ -34,9 +34,11 @@ dependencies = [
|
|||||||
"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)",
|
||||||
"parity-hash-fetch 1.5.0",
|
"parity-hash-fetch 1.5.0",
|
||||||
"parity-updater 1.5.0",
|
"parity-updater 1.5.0",
|
||||||
|
"parity-rpc-client 1.4.0",
|
||||||
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rlp 0.1.0",
|
"rlp 0.1.0",
|
||||||
"rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rpc-cli 1.4.0",
|
||||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -733,6 +735,14 @@ dependencies = [
|
|||||||
"miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gcc"
|
name = "gcc"
|
||||||
version = "0.3.35"
|
version = "0.3.35"
|
||||||
@ -874,6 +884,18 @@ name = "itoa"
|
|||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonrpc-core"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-core"
|
name = "jsonrpc-core"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
@ -1307,6 +1329,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
<<<<<<< HEAD
|
||||||
name = "parity-hash-fetch"
|
name = "parity-hash-fetch"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@ -1316,6 +1339,25 @@ dependencies = [
|
|||||||
"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)",
|
||||||
"mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
=======
|
||||||
|
name = "parity-rpc-client"
|
||||||
|
version = "1.4.0"
|
||||||
|
dependencies = [
|
||||||
|
"ethcore-rpc 1.5.0",
|
||||||
|
"ethcore-signer 1.5.0",
|
||||||
|
"ethcore-util 1.5.0",
|
||||||
|
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"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)",
|
||||||
|
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)",
|
||||||
|
>>>>>>> origin/master
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1337,7 +1379,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "git+https://github.com/ethcore/js-precompiled.git#762c6d10f8640a6c4b875d776490282680bfe3e2"
|
source = "git+https://github.com/ethcore/js-precompiled.git#ad6617a73dbb17c53dddc0fc567e70ea5b8e882f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@ -1600,6 +1642,28 @@ dependencies = [
|
|||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rpassword"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rpc-cli"
|
||||||
|
version = "1.4.0"
|
||||||
|
dependencies = [
|
||||||
|
"ethcore-bigint 0.1.2",
|
||||||
|
"ethcore-rpc 1.5.0",
|
||||||
|
"ethcore-util 1.5.0",
|
||||||
|
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"parity-rpc-client 1.4.0",
|
||||||
|
"rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-crypto"
|
name = "rust-crypto"
|
||||||
version = "0.2.36"
|
version = "0.2.36"
|
||||||
@ -1856,6 +1920,14 @@ name = "target_info"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempdir"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "term"
|
name = "term"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@ -2131,6 +2203,7 @@ dependencies = [
|
|||||||
"checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f"
|
"checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f"
|
||||||
"checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa"
|
"checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa"
|
||||||
"checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb"
|
"checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb"
|
||||||
|
"checksum futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bad0a2ac64b227fdc10c254051ae5af542cf19c9328704fd4092f7914196897"
|
||||||
"checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312"
|
"checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312"
|
||||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||||
"checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1"
|
"checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1"
|
||||||
@ -2144,6 +2217,7 @@ dependencies = [
|
|||||||
"checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c"
|
"checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c"
|
||||||
"checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76"
|
"checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76"
|
||||||
"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
|
"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
|
||||||
|
"checksum jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3c5094610b07f28f3edaf3947b732dadb31dbba4941d4d0c1c7a8350208f4414"
|
||||||
"checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
"checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
"checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
@ -2218,6 +2292,7 @@ dependencies = [
|
|||||||
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
||||||
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
|
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
|
||||||
"checksum rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d3a99497c5c544e629cc8b359ae5ede321eba5fa8e5a8078f3ced727a976c3f"
|
"checksum rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d3a99497c5c544e629cc8b359ae5ede321eba5fa8e5a8078f3ced727a976c3f"
|
||||||
|
"checksum rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab6e42be826e215f30ff830904f8f4a0933c6e2ae890e1af8b408f5bae60081e"
|
||||||
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
|
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
|
||||||
"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b"
|
"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b"
|
||||||
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
|
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
|
||||||
@ -2249,6 +2324,7 @@ dependencies = [
|
|||||||
"checksum syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44bded3cabafc65c90b663b1071bd2d198a9ab7515e6ce729e4570aaf53c407e"
|
"checksum syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44bded3cabafc65c90b663b1071bd2d198a9ab7515e6ce729e4570aaf53c407e"
|
||||||
"checksum syntex_syntax 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7628a0506e8f9666fdabb5f265d0059b059edac9a3f810bda077abb5d826bd8d"
|
"checksum syntex_syntax 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7628a0506e8f9666fdabb5f265d0059b059edac9a3f810bda077abb5d826bd8d"
|
||||||
"checksum target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe"
|
"checksum target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe"
|
||||||
|
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||||
"checksum term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281"
|
"checksum term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281"
|
||||||
"checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a"
|
"checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a"
|
||||||
"checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
|
"checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
|
||||||
|
@ -47,6 +47,9 @@ ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
|
|||||||
ethcore-logger = { path = "logger" }
|
ethcore-logger = { path = "logger" }
|
||||||
ethcore-stratum = { path = "stratum" }
|
ethcore-stratum = { path = "stratum" }
|
||||||
ethcore-dapps = { path = "dapps", optional = true }
|
ethcore-dapps = { path = "dapps", optional = true }
|
||||||
|
clippy = { version = "0.0.103", optional = true}
|
||||||
|
rpc-cli = { path = "rpc_cli" }
|
||||||
|
parity-rpc-client = { path = "rpc_client" }
|
||||||
ethcore-light = { path = "ethcore/light" }
|
ethcore-light = { path = "ethcore/light" }
|
||||||
parity-hash-fetch = { path = "hash-fetch" }
|
parity-hash-fetch = { path = "hash-fetch" }
|
||||||
parity-updater = { path = "updater" }
|
parity-updater = { path = "updater" }
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
mod stores;
|
mod stores;
|
||||||
|
|
||||||
use self::stores::{AddressBook, DappsSettingsStore};
|
use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -156,10 +156,49 @@ impl AccountProvider {
|
|||||||
Ok(accounts)
|
Ok(accounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a whitelist of accounts exposed for unknown dapps.
|
||||||
|
/// `None` means that all accounts will be visible.
|
||||||
|
pub fn set_new_dapps_whitelist(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> {
|
||||||
|
self.dapps_settings.write().set_policy(match accounts {
|
||||||
|
None => NewDappsPolicy::AllAccounts,
|
||||||
|
Some(accounts) => NewDappsPolicy::Whitelist(accounts),
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a whitelist of accounts exposed for unknown dapps.
|
||||||
|
/// `None` means that all accounts will be visible.
|
||||||
|
pub fn new_dapps_whitelist(&self) -> Result<Option<Vec<Address>>, Error> {
|
||||||
|
Ok(match self.dapps_settings.read().policy() {
|
||||||
|
NewDappsPolicy::AllAccounts => None,
|
||||||
|
NewDappsPolicy::Whitelist(accounts) => Some(accounts),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a list of dapps recently requesting accounts.
|
||||||
|
pub fn recent_dapps(&self) -> Result<Vec<DappId>, Error> {
|
||||||
|
Ok(self.dapps_settings.read().recent_dapps())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks dapp as recently used.
|
||||||
|
pub fn note_dapp_used(&self, dapp: DappId) -> Result<(), Error> {
|
||||||
|
let mut dapps = self.dapps_settings.write();
|
||||||
|
dapps.mark_dapp_used(dapp.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets addresses visile for dapp.
|
/// Gets addresses visile for dapp.
|
||||||
pub fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
|
pub fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
|
||||||
let accounts = self.dapps_settings.read().get();
|
let dapps = self.dapps_settings.read();
|
||||||
Ok(accounts.get(&dapp).map(|settings| settings.accounts.clone()).unwrap_or_else(Vec::new))
|
|
||||||
|
let accounts = dapps.settings().get(&dapp).map(|settings| settings.accounts.clone());
|
||||||
|
match accounts {
|
||||||
|
Some(accounts) => Ok(accounts),
|
||||||
|
None => match dapps.policy() {
|
||||||
|
NewDappsPolicy::AllAccounts => self.accounts(),
|
||||||
|
NewDappsPolicy::Whitelist(accounts) => Ok(accounts),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets addresses visile for dapp.
|
/// Sets addresses visile for dapp.
|
||||||
@ -423,6 +462,8 @@ mod tests {
|
|||||||
// given
|
// given
|
||||||
let ap = AccountProvider::transient_provider();
|
let ap = AccountProvider::transient_provider();
|
||||||
let app = "app1".to_owned();
|
let app = "app1".to_owned();
|
||||||
|
// set `AllAccounts` policy
|
||||||
|
ap.set_new_dapps_whitelist(None).unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into()]).unwrap();
|
ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into()]).unwrap();
|
||||||
@ -430,4 +471,23 @@ mod tests {
|
|||||||
// then
|
// then
|
||||||
assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_set_dapps_policy() {
|
||||||
|
// given
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
let address = ap.new_account("test").unwrap();
|
||||||
|
|
||||||
|
// When returning nothing
|
||||||
|
ap.set_new_dapps_whitelist(Some(vec![])).unwrap();
|
||||||
|
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]);
|
||||||
|
|
||||||
|
// change to all
|
||||||
|
ap.set_new_dapps_whitelist(None).unwrap();
|
||||||
|
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]);
|
||||||
|
|
||||||
|
// change to a whitelist
|
||||||
|
ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap();
|
||||||
|
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,11 @@
|
|||||||
//! Address Book and Dapps Settings Store
|
//! Address Book and Dapps Settings Store
|
||||||
|
|
||||||
use std::{fs, fmt, hash, ops};
|
use std::{fs, fmt, hash, ops};
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use ethstore::ethkey::Address;
|
use ethstore::ethkey::Address;
|
||||||
use ethjson::misc::{AccountMeta, DappsSettings as JsonSettings};
|
use ethjson::misc::{AccountMeta, DappsSettings as JsonSettings, NewDappsPolicy as JsonNewDappsPolicy};
|
||||||
use account_provider::DappId;
|
use account_provider::DappId;
|
||||||
|
|
||||||
/// Disk-backed map from Address to String. Uses JSON.
|
/// Disk-backed map from Address to String. Uses JSON.
|
||||||
@ -105,43 +105,106 @@ impl From<DappsSettings> for JsonSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dapps user settings
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub enum NewDappsPolicy {
|
||||||
|
AllAccounts,
|
||||||
|
Whitelist(Vec<Address>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JsonNewDappsPolicy> for NewDappsPolicy {
|
||||||
|
fn from(s: JsonNewDappsPolicy) -> Self {
|
||||||
|
match s {
|
||||||
|
JsonNewDappsPolicy::AllAccounts => NewDappsPolicy::AllAccounts,
|
||||||
|
JsonNewDappsPolicy::Whitelist(accounts) => NewDappsPolicy::Whitelist(
|
||||||
|
accounts.into_iter().map(Into::into).collect()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NewDappsPolicy> for JsonNewDappsPolicy {
|
||||||
|
fn from(s: NewDappsPolicy) -> Self {
|
||||||
|
match s {
|
||||||
|
NewDappsPolicy::AllAccounts => JsonNewDappsPolicy::AllAccounts,
|
||||||
|
NewDappsPolicy::Whitelist(accounts) => JsonNewDappsPolicy::Whitelist(
|
||||||
|
accounts.into_iter().map(Into::into).collect()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_RECENT_DAPPS: usize = 10;
|
||||||
|
|
||||||
/// Disk-backed map from DappId to Settings. Uses JSON.
|
/// Disk-backed map from DappId to Settings. Uses JSON.
|
||||||
pub struct DappsSettingsStore {
|
pub struct DappsSettingsStore {
|
||||||
cache: DiskMap<DappId, DappsSettings>,
|
/// Dapps Settings
|
||||||
|
settings: DiskMap<DappId, DappsSettings>,
|
||||||
|
/// New Dapps Policy
|
||||||
|
policy: DiskMap<String, NewDappsPolicy>,
|
||||||
|
/// Recently Accessed Dapps (transient)
|
||||||
|
recent: VecDeque<DappId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DappsSettingsStore {
|
impl DappsSettingsStore {
|
||||||
/// Creates new store at given directory path.
|
/// Creates new store at given directory path.
|
||||||
pub fn new(path: String) -> Self {
|
pub fn new(path: String) -> Self {
|
||||||
let mut r = DappsSettingsStore {
|
let mut r = DappsSettingsStore {
|
||||||
cache: DiskMap::new(path, "dapps_accounts.json".into())
|
settings: DiskMap::new(path.clone(), "dapps_accounts.json".into()),
|
||||||
|
policy: DiskMap::new(path.clone(), "dapps_policy.json".into()),
|
||||||
|
recent: VecDeque::with_capacity(MAX_RECENT_DAPPS),
|
||||||
};
|
};
|
||||||
r.cache.revert(JsonSettings::read_dapps_settings);
|
r.settings.revert(JsonSettings::read_dapps_settings);
|
||||||
|
r.policy.revert(JsonNewDappsPolicy::read_new_dapps_policy);
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates transient store (no changes are saved to disk).
|
/// Creates transient store (no changes are saved to disk).
|
||||||
pub fn transient() -> Self {
|
pub fn transient() -> Self {
|
||||||
DappsSettingsStore {
|
DappsSettingsStore {
|
||||||
cache: DiskMap::transient()
|
settings: DiskMap::transient(),
|
||||||
|
policy: DiskMap::transient(),
|
||||||
|
recent: VecDeque::with_capacity(MAX_RECENT_DAPPS),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get copy of the dapps settings
|
/// Get copy of the dapps settings
|
||||||
pub fn get(&self) -> HashMap<DappId, DappsSettings> {
|
pub fn settings(&self) -> HashMap<DappId, DappsSettings> {
|
||||||
self.cache.clone()
|
self.settings.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self) {
|
/// Returns current new dapps policy
|
||||||
self.cache.save(JsonSettings::write_dapps_settings)
|
pub fn policy(&self) -> NewDappsPolicy {
|
||||||
|
self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns recent dapps (in order of last request)
|
||||||
|
pub fn recent_dapps(&self) -> Vec<DappId> {
|
||||||
|
self.recent.iter().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks recent dapp as used
|
||||||
|
pub fn mark_dapp_used(&mut self, dapp: DappId) {
|
||||||
|
self.recent.retain(|id| id != &dapp);
|
||||||
|
self.recent.push_front(dapp);
|
||||||
|
while self.recent.len() > MAX_RECENT_DAPPS {
|
||||||
|
self.recent.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets current new dapps policy
|
||||||
|
pub fn set_policy(&mut self, policy: NewDappsPolicy) {
|
||||||
|
self.policy.insert("default".into(), policy);
|
||||||
|
self.policy.save(JsonNewDappsPolicy::write_new_dapps_policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets accounts for specific dapp.
|
||||||
pub fn set_accounts(&mut self, id: DappId, accounts: Vec<Address>) {
|
pub fn set_accounts(&mut self, id: DappId, accounts: Vec<Address>) {
|
||||||
{
|
{
|
||||||
let mut settings = self.cache.entry(id).or_insert_with(DappsSettings::default);
|
let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default);
|
||||||
settings.accounts = accounts;
|
settings.accounts = accounts;
|
||||||
}
|
}
|
||||||
self.save();
|
self.settings.save(JsonSettings::write_dapps_settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +279,7 @@ impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{AddressBook, DappsSettingsStore, DappsSettings};
|
use super::{AddressBook, DappsSettingsStore, DappsSettings, NewDappsPolicy};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use ethjson::misc::AccountMeta;
|
use ethjson::misc::AccountMeta;
|
||||||
use devtools::RandomTempPath;
|
use devtools::RandomTempPath;
|
||||||
@ -232,25 +295,6 @@ mod tests {
|
|||||||
assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]);
|
assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_save_and_reload_dapps_settings() {
|
|
||||||
// given
|
|
||||||
let temp = RandomTempPath::create_dir();
|
|
||||||
let path = temp.as_str().to_owned();
|
|
||||||
let mut b = DappsSettingsStore::new(path.clone());
|
|
||||||
|
|
||||||
// when
|
|
||||||
b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]);
|
|
||||||
|
|
||||||
// then
|
|
||||||
let b = DappsSettingsStore::new(path);
|
|
||||||
assert_eq!(b.get(), hash_map![
|
|
||||||
"dappOne".into() => DappsSettings {
|
|
||||||
accounts: vec![1.into(), 2.into()],
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_remove_address() {
|
fn should_remove_address() {
|
||||||
let temp = RandomTempPath::create_dir();
|
let temp = RandomTempPath::create_dir();
|
||||||
@ -268,4 +312,58 @@ mod tests {
|
|||||||
3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None}
|
3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_save_and_reload_dapps_settings() {
|
||||||
|
// given
|
||||||
|
let temp = RandomTempPath::create_dir();
|
||||||
|
let path = temp.as_str().to_owned();
|
||||||
|
let mut b = DappsSettingsStore::new(path.clone());
|
||||||
|
|
||||||
|
// when
|
||||||
|
b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]);
|
||||||
|
|
||||||
|
// then
|
||||||
|
let b = DappsSettingsStore::new(path);
|
||||||
|
assert_eq!(b.settings(), hash_map![
|
||||||
|
"dappOne".into() => DappsSettings {
|
||||||
|
accounts: vec![1.into(), 2.into()],
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_maintain_a_list_of_recent_dapps() {
|
||||||
|
let mut store = DappsSettingsStore::transient();
|
||||||
|
assert!(store.recent_dapps().is_empty(), "Initially recent dapps should be empty.");
|
||||||
|
|
||||||
|
store.mark_dapp_used("dapp1".into());
|
||||||
|
assert_eq!(store.recent_dapps(), vec!["dapp1".to_owned()]);
|
||||||
|
|
||||||
|
store.mark_dapp_used("dapp2".into());
|
||||||
|
assert_eq!(store.recent_dapps(), vec!["dapp2".to_owned(), "dapp1".to_owned()]);
|
||||||
|
|
||||||
|
store.mark_dapp_used("dapp1".into());
|
||||||
|
assert_eq!(store.recent_dapps(), vec!["dapp1".to_owned(), "dapp2".to_owned()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_store_dapps_policy() {
|
||||||
|
// given
|
||||||
|
let temp = RandomTempPath::create_dir();
|
||||||
|
let path = temp.as_str().to_owned();
|
||||||
|
let mut store = DappsSettingsStore::new(path.clone());
|
||||||
|
|
||||||
|
// Test default policy
|
||||||
|
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts);
|
||||||
|
|
||||||
|
// when
|
||||||
|
store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()]));
|
||||||
|
|
||||||
|
// then
|
||||||
|
let store = DappsSettingsStore::new(path);
|
||||||
|
assert_eq!(store.policy.clone(), hash_map![
|
||||||
|
"default".into() => NewDappsPolicy::Whitelist(vec![1.into(), 2.into()])
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "0.2.124",
|
"version": "0.2.125",
|
||||||
"main": "release/index.js",
|
"main": "release/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
|
@ -342,7 +342,8 @@ export default class Contract {
|
|||||||
options: _options,
|
options: _options,
|
||||||
autoRemove,
|
autoRemove,
|
||||||
callback,
|
callback,
|
||||||
filterId
|
filterId,
|
||||||
|
id: subscriptionId
|
||||||
};
|
};
|
||||||
|
|
||||||
if (skipInitFetch) {
|
if (skipInitFetch) {
|
||||||
@ -452,13 +453,13 @@ export default class Contract {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.then((logsArray) => {
|
.then((logsArray) => {
|
||||||
logsArray.forEach((logs, subscriptionId) => {
|
logsArray.forEach((logs, index) => {
|
||||||
if (!logs || !logs.length) {
|
if (!logs || !logs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.sendData(subscriptionId, null, this.parseEventLogs(logs));
|
this._sendData(subscriptions[index].id, null, this.parseEventLogs(logs));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('_sendSubscriptionChanges', error);
|
console.error('_sendSubscriptionChanges', error);
|
||||||
}
|
}
|
||||||
|
@ -49,3 +49,32 @@ impl DappsSettings {
|
|||||||
serde_json::to_writer(writer, &m.iter().map(|(a, m)| (a.clone().into(), m.clone().into())).collect::<HashMap<DappId, DappsSettings>>())
|
serde_json::to_writer(writer, &m.iter().map(|(a, m)| (a.clone().into(), m.clone().into())).collect::<HashMap<DappId, DappsSettings>>())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Accounts policy for new dapps.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum NewDappsPolicy {
|
||||||
|
/// All accounts are exposed by default.
|
||||||
|
AllAccounts,
|
||||||
|
/// Only accounts listed here are exposed by default for new dapps.
|
||||||
|
Whitelist(Vec<hash::Address>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewDappsPolicy {
|
||||||
|
/// Read a hash map of `String -> NewDappsPolicy`
|
||||||
|
pub fn read_new_dapps_policy<R, S>(reader: R) -> Result<HashMap<String, S>, serde_json::Error> where
|
||||||
|
R: io::Read,
|
||||||
|
S: From<NewDappsPolicy> + Clone,
|
||||||
|
{
|
||||||
|
serde_json::from_reader(reader).map(|ok: HashMap<String, NewDappsPolicy>|
|
||||||
|
ok.into_iter().map(|(a, m)| (a.into(), m.into())).collect()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a hash map of `String -> NewDappsPolicy`
|
||||||
|
pub fn write_new_dapps_policy<W, S>(m: &HashMap<String, S>, writer: &mut W) -> Result<(), serde_json::Error> where
|
||||||
|
W: io::Write,
|
||||||
|
S: Into<NewDappsPolicy> + Clone,
|
||||||
|
{
|
||||||
|
serde_json::to_writer(writer, &m.iter().map(|(a, m)| (a.clone().into(), m.clone().into())).collect::<HashMap<String, NewDappsPolicy>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,5 +19,5 @@
|
|||||||
mod account_meta;
|
mod account_meta;
|
||||||
mod dapps_settings;
|
mod dapps_settings;
|
||||||
|
|
||||||
pub use self::dapps_settings::DappsSettings;
|
pub use self::dapps_settings::{DappsSettings, NewDappsPolicy};
|
||||||
pub use self::account_meta::AccountMeta;
|
pub use self::account_meta::AccountMeta;
|
||||||
|
@ -32,6 +32,8 @@ usage! {
|
|||||||
cmd_import: bool,
|
cmd_import: bool,
|
||||||
cmd_signer: bool,
|
cmd_signer: bool,
|
||||||
cmd_new_token: bool,
|
cmd_new_token: bool,
|
||||||
|
cmd_sign: bool,
|
||||||
|
cmd_reject: bool,
|
||||||
cmd_snapshot: bool,
|
cmd_snapshot: bool,
|
||||||
cmd_restore: bool,
|
cmd_restore: bool,
|
||||||
cmd_ui: bool,
|
cmd_ui: bool,
|
||||||
@ -44,6 +46,7 @@ usage! {
|
|||||||
arg_pid_file: String,
|
arg_pid_file: String,
|
||||||
arg_file: Option<String>,
|
arg_file: Option<String>,
|
||||||
arg_path: Vec<String>,
|
arg_path: Vec<String>,
|
||||||
|
arg_id: Option<usize>,
|
||||||
|
|
||||||
// Flags
|
// Flags
|
||||||
// -- Legacy Options
|
// -- Legacy Options
|
||||||
@ -515,6 +518,8 @@ mod tests {
|
|||||||
cmd_blocks: false,
|
cmd_blocks: false,
|
||||||
cmd_import: false,
|
cmd_import: false,
|
||||||
cmd_signer: false,
|
cmd_signer: false,
|
||||||
|
cmd_sign: false,
|
||||||
|
cmd_reject: false,
|
||||||
cmd_new_token: false,
|
cmd_new_token: false,
|
||||||
cmd_snapshot: false,
|
cmd_snapshot: false,
|
||||||
cmd_restore: false,
|
cmd_restore: false,
|
||||||
@ -527,6 +532,7 @@ mod tests {
|
|||||||
// Arguments
|
// Arguments
|
||||||
arg_pid_file: "".into(),
|
arg_pid_file: "".into(),
|
||||||
arg_file: None,
|
arg_file: None,
|
||||||
|
arg_id: None,
|
||||||
arg_path: vec![],
|
arg_path: vec![],
|
||||||
|
|
||||||
// -- Operating Options
|
// -- Operating Options
|
||||||
|
@ -12,6 +12,9 @@ Usage:
|
|||||||
parity import [ <file> ] [options]
|
parity import [ <file> ] [options]
|
||||||
parity export (blocks | state) [ <file> ] [options]
|
parity export (blocks | state) [ <file> ] [options]
|
||||||
parity signer new-token [options]
|
parity signer new-token [options]
|
||||||
|
parity signer list [options]
|
||||||
|
parity signer sign [ <id> ] [ --password FILE ] [options]
|
||||||
|
parity signer reject <id> [options]
|
||||||
parity snapshot <file> [options]
|
parity snapshot <file> [options]
|
||||||
parity restore [ <file> ] [options]
|
parity restore [ <file> ] [options]
|
||||||
parity tools hash <file>
|
parity tools hash <file>
|
||||||
|
@ -44,6 +44,8 @@ use presale::ImportWallet;
|
|||||||
use account::{AccountCmd, NewAccount, ListAccounts, ImportAccounts, ImportFromGethAccounts};
|
use account::{AccountCmd, NewAccount, ListAccounts, ImportAccounts, ImportFromGethAccounts};
|
||||||
use snapshot::{self, SnapshotCommand};
|
use snapshot::{self, SnapshotCommand};
|
||||||
|
|
||||||
|
const AUTHCODE_FILENAME: &'static str = "authcodes";
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Cmd {
|
pub enum Cmd {
|
||||||
Run(RunCmd),
|
Run(RunCmd),
|
||||||
@ -52,6 +54,21 @@ pub enum Cmd {
|
|||||||
ImportPresaleWallet(ImportWallet),
|
ImportPresaleWallet(ImportWallet),
|
||||||
Blockchain(BlockchainCmd),
|
Blockchain(BlockchainCmd),
|
||||||
SignerToken(SignerConfiguration),
|
SignerToken(SignerConfiguration),
|
||||||
|
SignerSign {
|
||||||
|
id: Option<usize>,
|
||||||
|
pwfile: Option<PathBuf>,
|
||||||
|
port: u16,
|
||||||
|
authfile: PathBuf,
|
||||||
|
},
|
||||||
|
SignerList {
|
||||||
|
port: u16,
|
||||||
|
authfile: PathBuf
|
||||||
|
},
|
||||||
|
SignerReject {
|
||||||
|
id: Option<usize>,
|
||||||
|
port: u16,
|
||||||
|
authfile: PathBuf
|
||||||
|
},
|
||||||
Snapshot(SnapshotCommand),
|
Snapshot(SnapshotCommand),
|
||||||
Hash(Option<String>),
|
Hash(Option<String>),
|
||||||
}
|
}
|
||||||
@ -105,8 +122,36 @@ impl Configuration {
|
|||||||
|
|
||||||
let cmd = if self.args.flag_version {
|
let cmd = if self.args.flag_version {
|
||||||
Cmd::Version
|
Cmd::Version
|
||||||
} else if self.args.cmd_signer && self.args.cmd_new_token {
|
} else if self.args.cmd_signer {
|
||||||
Cmd::SignerToken(signer_conf)
|
let mut authfile = PathBuf::from(signer_conf.signer_path.clone());
|
||||||
|
authfile.push(AUTHCODE_FILENAME);
|
||||||
|
|
||||||
|
if self.args.cmd_new_token {
|
||||||
|
Cmd::SignerToken(signer_conf)
|
||||||
|
} else if self.args.cmd_sign {
|
||||||
|
let pwfile = self.args.flag_password.get(0).map(|pwfile| {
|
||||||
|
PathBuf::from(pwfile)
|
||||||
|
});
|
||||||
|
Cmd::SignerSign {
|
||||||
|
id: self.args.arg_id,
|
||||||
|
pwfile: pwfile,
|
||||||
|
port: signer_conf.port,
|
||||||
|
authfile: authfile,
|
||||||
|
}
|
||||||
|
} else if self.args.cmd_reject {
|
||||||
|
Cmd::SignerReject {
|
||||||
|
id: self.args.arg_id,
|
||||||
|
port: signer_conf.port,
|
||||||
|
authfile: authfile,
|
||||||
|
}
|
||||||
|
} else if self.args.cmd_list {
|
||||||
|
Cmd::SignerList {
|
||||||
|
port: signer_conf.port,
|
||||||
|
authfile: authfile,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
} else if self.args.cmd_tools && self.args.cmd_hash {
|
} else if self.args.cmd_tools && self.args.cmd_hash {
|
||||||
Cmd::Hash(self.args.arg_file)
|
Cmd::Hash(self.args.arg_file)
|
||||||
} else if self.args.cmd_db && self.args.cmd_kill {
|
} else if self.args.cmd_db && self.args.cmd_kill {
|
||||||
@ -1176,4 +1221,3 @@ mod tests {
|
|||||||
assert!(conf.init_reserved_nodes().is_ok());
|
assert!(conf.init_reserved_nodes().is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +72,8 @@ extern crate ethcore_stratum;
|
|||||||
#[cfg(feature = "dapps")]
|
#[cfg(feature = "dapps")]
|
||||||
extern crate ethcore_dapps;
|
extern crate ethcore_dapps;
|
||||||
|
|
||||||
|
extern crate rpc_cli;
|
||||||
|
|
||||||
macro_rules! dependency {
|
macro_rules! dependency {
|
||||||
($dep_ty:ident, $url:expr) => {
|
($dep_ty:ident, $url:expr) => {
|
||||||
{
|
{
|
||||||
@ -155,6 +157,9 @@ fn execute(command: Execute, can_restart: bool) -> Result<PostExecutionAction, S
|
|||||||
Cmd::ImportPresaleWallet(presale_cmd) => presale::execute(presale_cmd).map(|s| PostExecutionAction::Print(s)),
|
Cmd::ImportPresaleWallet(presale_cmd) => presale::execute(presale_cmd).map(|s| PostExecutionAction::Print(s)),
|
||||||
Cmd::Blockchain(blockchain_cmd) => blockchain::execute(blockchain_cmd).map(|s| PostExecutionAction::Print(s)),
|
Cmd::Blockchain(blockchain_cmd) => blockchain::execute(blockchain_cmd).map(|s| PostExecutionAction::Print(s)),
|
||||||
Cmd::SignerToken(signer_cmd) => signer::execute(signer_cmd).map(|s| PostExecutionAction::Print(s)),
|
Cmd::SignerToken(signer_cmd) => signer::execute(signer_cmd).map(|s| PostExecutionAction::Print(s)),
|
||||||
|
Cmd::SignerSign { id, pwfile, port, authfile } => rpc_cli::signer_sign(id, pwfile, port, authfile).map(|s| PostExecutionAction::Print(s)),
|
||||||
|
Cmd::SignerList { port, authfile } => rpc_cli::signer_list(port, authfile).map(|s| PostExecutionAction::Print(s)),
|
||||||
|
Cmd::SignerReject { id, port, authfile } => rpc_cli::signer_reject(id, port, authfile).map(|s| PostExecutionAction::Print(s)),
|
||||||
Cmd::Snapshot(snapshot_cmd) => snapshot::execute(snapshot_cmd).map(|s| PostExecutionAction::Print(s)),
|
Cmd::Snapshot(snapshot_cmd) => snapshot::execute(snapshot_cmd).map(|s| PostExecutionAction::Print(s)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,7 +340,11 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
let dapp = id.0;
|
let dapp = id.0;
|
||||||
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
let accounts = try!(store.dapps_addresses(dapp.into()).map_err(|e| errors::internal("Could not fetch accounts.", e)));
|
let accounts = try!(store
|
||||||
|
.note_dapp_used(dapp.clone().into())
|
||||||
|
.and_then(|_| store.dapps_addresses(dapp.into()))
|
||||||
|
.map_err(|e| errors::internal("Could not fetch accounts.", e))
|
||||||
|
);
|
||||||
|
|
||||||
Ok(accounts.into_iter().map(Into::into).collect())
|
Ok(accounts.into_iter().map(Into::into).collect())
|
||||||
}
|
}
|
||||||
|
@ -164,19 +164,51 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
|
|
||||||
fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec<RpcH160>) -> Result<bool, Error> {
|
fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec<RpcH160>) -> Result<bool, Error> {
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
let addresses = addresses.into_iter().map(Into::into).collect();
|
|
||||||
|
|
||||||
store.set_dapps_addresses(dapp.into(), addresses)
|
store.set_dapps_addresses(dapp.into(), into_vec(addresses))
|
||||||
.map_err(|e| errors::account("Couldn't set dapps addresses.", e))
|
.map_err(|e| errors::account("Couldn't set dapps addresses.", e))
|
||||||
.map(|_| true)
|
.map(|_| true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<RpcH160>, Error> {
|
||||||
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
|
store.dapps_addresses(dapp.into())
|
||||||
|
.map_err(|e| errors::account("Couldn't get dapps addresses.", e))
|
||||||
|
.map(into_vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_new_dapps_whitelist(&self, whitelist: Option<Vec<RpcH160>>) -> Result<bool, Error> {
|
||||||
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
|
store
|
||||||
|
.set_new_dapps_whitelist(whitelist.map(into_vec))
|
||||||
|
.map_err(|e| errors::account("Couldn't set dapps whitelist.", e))
|
||||||
|
.map(|_| true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_dapps_whitelist(&self) -> Result<Option<Vec<RpcH160>>, Error> {
|
||||||
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
|
store.new_dapps_whitelist()
|
||||||
|
.map_err(|e| errors::account("Couldn't get dapps whitelist.", e))
|
||||||
|
.map(|accounts| accounts.map(into_vec))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recent_dapps(&self) -> Result<Vec<DappId>, Error> {
|
||||||
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
|
store.recent_dapps()
|
||||||
|
.map_err(|e| errors::account("Couldn't get recent dapps.", e))
|
||||||
|
.map(into_vec)
|
||||||
|
}
|
||||||
|
|
||||||
fn import_geth_accounts(&self, addresses: Vec<RpcH160>) -> Result<Vec<RpcH160>, Error> {
|
fn import_geth_accounts(&self, addresses: Vec<RpcH160>) -> Result<Vec<RpcH160>, Error> {
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
store
|
store
|
||||||
.import_geth_accounts(addresses.into_iter().map(Into::into).collect(), false)
|
.import_geth_accounts(into_vec(addresses), false)
|
||||||
.map(|imported| imported.into_iter().map(Into::into).collect())
|
.map(into_vec)
|
||||||
.map_err(|e| errors::account("Couldn't import Geth accounts", e))
|
.map_err(|e| errors::account("Couldn't import Geth accounts", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,10 +216,12 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
try!(self.active());
|
try!(self.active());
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
Ok(store.list_geth_accounts(false)
|
Ok(into_vec(store.list_geth_accounts(false)))
|
||||||
.into_iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_vec<A, B>(a: Vec<A>) -> Vec<B> where
|
||||||
|
A: Into<B>
|
||||||
|
{
|
||||||
|
a.into_iter().map(Into::into).collect()
|
||||||
|
}
|
||||||
|
@ -354,11 +354,18 @@ fn rpc_eth_gas_price() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn rpc_eth_accounts() {
|
fn rpc_eth_accounts() {
|
||||||
let tester = EthTester::default();
|
let tester = EthTester::default();
|
||||||
let _address = tester.accounts_provider.new_account("").unwrap();
|
let address = tester.accounts_provider.new_account("").unwrap();
|
||||||
|
tester.accounts_provider.set_new_dapps_whitelist(None).unwrap();
|
||||||
|
|
||||||
|
// with current policy it should return the account
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#;
|
||||||
|
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
|
|
||||||
|
tester.accounts_provider.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap();
|
||||||
// even with some account it should return empty list (no dapp detected)
|
// even with some account it should return empty list (no dapp detected)
|
||||||
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":[],"id":1}"#;
|
let response = r#"{"jsonrpc":"2.0","result":["0x0000000000000000000000000000000000000001"],"id":1}"#;
|
||||||
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
|
|
||||||
// when we add visible address it should return that.
|
// when we add visible address it should return that.
|
||||||
|
@ -117,7 +117,7 @@ fn should_be_able_to_set_meta() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rpc_parity_set_dapps_accounts() {
|
fn rpc_parity_set_and_get_dapps_accounts() {
|
||||||
// given
|
// given
|
||||||
let tester = setup();
|
let tester = setup();
|
||||||
assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![]);
|
assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![]);
|
||||||
@ -129,6 +129,52 @@ fn rpc_parity_set_dapps_accounts() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![10.into()]);
|
assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![10.into()]);
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_getDappsAddresses","params":["app1"], "id": 1}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#;
|
||||||
|
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpc_parity_set_and_get_new_dapps_whitelist() {
|
||||||
|
// given
|
||||||
|
let tester = setup();
|
||||||
|
|
||||||
|
// when set to whitelist
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsWhitelist","params":[["0x000000000000000000000000000000000000000a"]], "id": 1}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
|
||||||
|
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(tester.accounts.new_dapps_whitelist().unwrap(), Some(vec![10.into()]));
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsWhitelist","params":[], "id": 1}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":["0x000000000000000000000000000000000000000a"],"id":1}"#;
|
||||||
|
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
|
|
||||||
|
// when set to empty
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_setNewDappsWhitelist","params":[null], "id": 1}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
|
||||||
|
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(tester.accounts.new_dapps_whitelist().unwrap(), None);
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_getNewDappsWhitelist","params":[], "id": 1}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":null,"id":1}"#;
|
||||||
|
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpc_parity_recent_dapps() {
|
||||||
|
// given
|
||||||
|
let tester = setup();
|
||||||
|
|
||||||
|
// when
|
||||||
|
// trigger dapp usage
|
||||||
|
tester.accounts.note_dapp_used("dapp1".into()).unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_listRecentDapps","params":[], "id": 1}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":["dapp1"],"id":1}"#;
|
||||||
|
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -78,6 +78,24 @@ build_rpc_trait! {
|
|||||||
#[rpc(name = "parity_setDappsAddresses")]
|
#[rpc(name = "parity_setDappsAddresses")]
|
||||||
fn set_dapps_addresses(&self, DappId, Vec<H160>) -> Result<bool, Error>;
|
fn set_dapps_addresses(&self, DappId, Vec<H160>) -> Result<bool, Error>;
|
||||||
|
|
||||||
|
/// Gets accounts exposed for particular dapp.
|
||||||
|
#[rpc(name = "parity_getDappsAddresses")]
|
||||||
|
fn dapps_addresses(&self, DappId) -> Result<Vec<H160>, Error>;
|
||||||
|
|
||||||
|
/// Sets accounts exposed for new dapps.
|
||||||
|
/// `None` means that all accounts will be exposed.
|
||||||
|
#[rpc(name = "parity_setNewDappsWhitelist")]
|
||||||
|
fn set_new_dapps_whitelist(&self, Option<Vec<H160>>) -> Result<bool, Error>;
|
||||||
|
|
||||||
|
/// Gets accounts exposed for new dapps.
|
||||||
|
/// `None` means that all accounts will be exposed.
|
||||||
|
#[rpc(name = "parity_getNewDappsWhitelist")]
|
||||||
|
fn new_dapps_whitelist(&self) -> Result<Option<Vec<H160>>, Error>;
|
||||||
|
|
||||||
|
/// Sets accounts exposed for particular dapp.
|
||||||
|
#[rpc(name = "parity_listRecentDapps")]
|
||||||
|
fn recent_dapps(&self) -> Result<Vec<DappId>, Error>;
|
||||||
|
|
||||||
/// Imports a number of Geth accounts, with the list provided as the argument.
|
/// Imports a number of Geth accounts, with the list provided as the argument.
|
||||||
#[rpc(name = "parity_importGethAccounts")]
|
#[rpc(name = "parity_importGethAccounts")]
|
||||||
fn import_geth_accounts(&self, Vec<H160>) -> Result<Vec<H160>, Error>;
|
fn import_geth_accounts(&self, Vec<H160>) -> Result<Vec<H160>, Error>;
|
||||||
|
@ -18,11 +18,13 @@
|
|||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
|
use util::log::Colour;
|
||||||
|
|
||||||
use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes};
|
use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes};
|
||||||
use v1::helpers;
|
use v1::helpers;
|
||||||
|
|
||||||
/// Confirmation waiting in a queue
|
/// Confirmation waiting in a queue
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub struct ConfirmationRequest {
|
pub struct ConfirmationRequest {
|
||||||
/// Id of this confirmation
|
/// Id of this confirmation
|
||||||
pub id: U256,
|
pub id: U256,
|
||||||
@ -39,8 +41,25 @@ impl From<helpers::ConfirmationRequest> for ConfirmationRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ConfirmationRequest {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "#{}: {}", self.id, self.payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ConfirmationPayload {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
ConfirmationPayload::SendTransaction(ref transaction) => write!(f, "{}", transaction),
|
||||||
|
ConfirmationPayload::SignTransaction(ref transaction) => write!(f, "(Sign only) {}", transaction),
|
||||||
|
ConfirmationPayload::Signature(ref sign) => write!(f, "{}", sign),
|
||||||
|
ConfirmationPayload::Decrypt(ref decrypt) => write!(f, "{}", decrypt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sign request
|
/// Sign request
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub struct SignRequest {
|
pub struct SignRequest {
|
||||||
/// Address
|
/// Address
|
||||||
pub address: H160,
|
pub address: H160,
|
||||||
@ -57,8 +76,19 @@ impl From<(H160, H256)> for SignRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SignRequest {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"sign 0x{:?} with {}",
|
||||||
|
self.hash,
|
||||||
|
Colour::White.bold().paint(format!("0x{:?}", self.address)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Decrypt request
|
/// Decrypt request
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub struct DecryptRequest {
|
pub struct DecryptRequest {
|
||||||
/// Address
|
/// Address
|
||||||
pub address: H160,
|
pub address: H160,
|
||||||
@ -75,6 +105,16 @@ impl From<(H160, Bytes)> for DecryptRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DecryptRequest {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"decrypt data with {}",
|
||||||
|
Colour::White.bold().paint(format!("0x{:?}", self.address)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Confirmation response for particular payload
|
/// Confirmation response for particular payload
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum ConfirmationResponse {
|
pub enum ConfirmationResponse {
|
||||||
@ -111,7 +151,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)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub enum ConfirmationPayload {
|
pub enum ConfirmationPayload {
|
||||||
/// Send Transaction
|
/// Send Transaction
|
||||||
#[serde(rename="sendTransaction")]
|
#[serde(rename="sendTransaction")]
|
||||||
@ -145,7 +185,7 @@ impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Possible modifications to the confirmed transaction sent by `Trusted Signer`
|
/// Possible modifications to the confirmed transaction sent by `Trusted Signer`
|
||||||
#[derive(Debug, PartialEq, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct TransactionModification {
|
pub struct TransactionModification {
|
||||||
/// Modified gas price
|
/// Modified gas price
|
||||||
@ -325,4 +365,3 @@ mod tests {
|
|||||||
assert_eq!(res.unwrap(), expected.to_owned());
|
assert_eq!(res.unwrap(), expected.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,12 @@ impl Into<String> for DappId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<String> for DappId {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
DappId(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
use v1::types::{Bytes, H160, U256};
|
use v1::types::{Bytes, H160, U256};
|
||||||
use v1::helpers;
|
use v1::helpers;
|
||||||
|
use util::log::Colour;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// Transaction request coming from RPC
|
/// Transaction request coming from RPC
|
||||||
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
@ -40,6 +43,43 @@ pub struct TransactionRequest {
|
|||||||
pub nonce: Option<U256>,
|
pub nonce: Option<U256>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn format_ether(i: U256) -> String {
|
||||||
|
let mut string = format!("{}", i);
|
||||||
|
let idx = string.len() as isize - 18;
|
||||||
|
if idx <= 0 {
|
||||||
|
let mut prefix = String::from("0.");
|
||||||
|
for _ in 0..idx.abs() {
|
||||||
|
prefix.push('0');
|
||||||
|
}
|
||||||
|
string = prefix + &string;
|
||||||
|
} else {
|
||||||
|
string.insert(idx as usize, '.');
|
||||||
|
}
|
||||||
|
String::from(string.trim_right_matches('0')
|
||||||
|
.trim_right_matches('.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TransactionRequest {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let eth = self.value.unwrap_or(U256::from(0));
|
||||||
|
match self.to {
|
||||||
|
Some(ref to) => write!(
|
||||||
|
f,
|
||||||
|
"{} ETH from {} to 0x{:?}",
|
||||||
|
Colour::White.bold().paint(format_ether(eth)),
|
||||||
|
Colour::White.bold().paint(format!("0x{:?}", self.from)),
|
||||||
|
to
|
||||||
|
),
|
||||||
|
None => write!(
|
||||||
|
f,
|
||||||
|
"{} ETH from {} for contract creation",
|
||||||
|
Colour::White.bold().paint(format_ether(eth)),
|
||||||
|
Colour::White.bold().paint(format!("0x{:?}", self.from)),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<helpers::TransactionRequest> for TransactionRequest {
|
impl From<helpers::TransactionRequest> for TransactionRequest {
|
||||||
fn from(r: helpers::TransactionRequest) -> Self {
|
fn from(r: helpers::TransactionRequest) -> Self {
|
||||||
TransactionRequest {
|
TransactionRequest {
|
||||||
@ -191,5 +231,15 @@ mod tests {
|
|||||||
|
|
||||||
assert!(deserialized.is_err(), "Should be error because to is empty");
|
assert!(deserialized.is_err(), "Should be error because to is empty");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_ether() {
|
||||||
|
assert_eq!(&format_ether(U256::from(1000000000000000000u64)), "1");
|
||||||
|
assert_eq!(&format_ether(U256::from(500000000000000000u64)), "0.5");
|
||||||
|
assert_eq!(&format_ether(U256::from(50000000000000000u64)), "0.05");
|
||||||
|
assert_eq!(&format_ether(U256::from(5000000000000000u64)), "0.005");
|
||||||
|
assert_eq!(&format_ether(U256::from(2000000000000000000u64)), "2");
|
||||||
|
assert_eq!(&format_ether(U256::from(2500000000000000000u64)), "2.5");
|
||||||
|
assert_eq!(&format_ether(U256::from(10000000000000000000u64)), "10");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,6 +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 std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::fmt;
|
||||||
use serde;
|
use serde;
|
||||||
use util::{U256 as EthU256, U128 as EthU128, Uint};
|
use util::{U256 as EthU256, U128 as EthU128, Uint};
|
||||||
|
|
||||||
@ -46,6 +47,18 @@ macro_rules! impl_uint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for $name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::LowerHex for $name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:#x}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl serde::Serialize for $name {
|
impl serde::Serialize for $name {
|
||||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: serde::Serializer {
|
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: serde::Serializer {
|
||||||
serializer.serialize_str(&format!("0x{}", self.0.to_hex()))
|
serializer.serialize_str(&format!("0x{}", self.0.to_hex()))
|
||||||
|
15
rpc_cli/Cargo.toml
Normal file
15
rpc_cli/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["Ethcore <admin@ethcore.io>"]
|
||||||
|
description = "Parity Cli Tool"
|
||||||
|
homepage = "http://ethcore.io"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
name = "rpc-cli"
|
||||||
|
version = "1.4.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "0.1"
|
||||||
|
rpassword = "0.3.0"
|
||||||
|
ethcore-bigint = { path = "../util/bigint" }
|
||||||
|
ethcore-rpc = { path = "../rpc" }
|
||||||
|
parity-rpc-client = { path = "../rpc_client" }
|
||||||
|
ethcore-util = { path = "../util" }
|
182
rpc_cli/src/lib.rs
Normal file
182
rpc_cli/src/lib.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
extern crate futures;
|
||||||
|
|
||||||
|
extern crate ethcore_util as util;
|
||||||
|
extern crate ethcore_rpc as rpc;
|
||||||
|
extern crate ethcore_bigint as bigint;
|
||||||
|
extern crate rpassword;
|
||||||
|
|
||||||
|
extern crate parity_rpc_client as client;
|
||||||
|
|
||||||
|
use rpc::v1::types::{U256, ConfirmationRequest};
|
||||||
|
use client::signer_client::SignerRpc;
|
||||||
|
use std::io::{Write, BufRead, BufReader, stdout, stdin};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
use futures::Future;
|
||||||
|
|
||||||
|
fn sign_interactive(
|
||||||
|
signer: &mut SignerRpc,
|
||||||
|
password: &str,
|
||||||
|
request: ConfirmationRequest
|
||||||
|
) {
|
||||||
|
print!("\n{}\nSign this transaction? (y)es/(N)o/(r)eject: ", request);
|
||||||
|
let _ = stdout().flush();
|
||||||
|
match BufReader::new(stdin()).lines().next() {
|
||||||
|
Some(Ok(line)) => {
|
||||||
|
match line.to_lowercase().chars().nth(0) {
|
||||||
|
Some('y') => {
|
||||||
|
match sign_transaction(signer, request.id, password) {
|
||||||
|
Ok(s) | Err(s) => println!("{}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some('r') => {
|
||||||
|
match reject_transaction(signer, request.id) {
|
||||||
|
Ok(s) | Err(s) => println!("{}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => println!("Could not read from stdin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_transactions(
|
||||||
|
signer: &mut SignerRpc,
|
||||||
|
password: String
|
||||||
|
) -> Result<String, String> {
|
||||||
|
try!(signer.requests_to_confirm().map(|reqs| {
|
||||||
|
match reqs {
|
||||||
|
Ok(ref reqs) if reqs.is_empty() => {
|
||||||
|
Ok("No transactions in signing queue".to_owned())
|
||||||
|
}
|
||||||
|
Ok(reqs) => {
|
||||||
|
for r in reqs {
|
||||||
|
sign_interactive(signer, &password, r)
|
||||||
|
}
|
||||||
|
Ok("".to_owned())
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
Err(format!("error: {:?}", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).map_err(|err| {
|
||||||
|
format!("{:?}", err)
|
||||||
|
}).wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_transactions(signer: &mut SignerRpc) -> Result<String, String> {
|
||||||
|
try!(signer.requests_to_confirm().map(|reqs| {
|
||||||
|
match reqs {
|
||||||
|
Ok(ref reqs) if reqs.is_empty() => {
|
||||||
|
Ok("No transactions in signing queue".to_owned())
|
||||||
|
}
|
||||||
|
Ok(ref reqs) => {
|
||||||
|
Ok(format!("Transaction queue:\n{}", reqs
|
||||||
|
.iter()
|
||||||
|
.map(|r| format!("{}", r))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")))
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
Err(format!("error: {:?}", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).map_err(|err| {
|
||||||
|
format!("{:?}", err)
|
||||||
|
}).wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_transaction(
|
||||||
|
signer: &mut SignerRpc, id: U256, password: &str
|
||||||
|
) -> Result<String, String> {
|
||||||
|
try!(signer.confirm_request(id, None, None, password).map(|res| {
|
||||||
|
match res {
|
||||||
|
Ok(u) => Ok(format!("Signed transaction id: {:#x}", u)),
|
||||||
|
Err(e) => Err(format!("{:?}", e)),
|
||||||
|
}
|
||||||
|
}).map_err(|err| {
|
||||||
|
format!("{:?}", err)
|
||||||
|
}).wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reject_transaction(
|
||||||
|
signer: &mut SignerRpc, id: U256) -> Result<String, String>
|
||||||
|
{
|
||||||
|
try!(signer.reject_request(id).map(|res| {
|
||||||
|
match res {
|
||||||
|
Ok(true) => Ok(format!("Rejected transaction id {:#x}", id)),
|
||||||
|
Ok(false) => Err(format!("No such request")),
|
||||||
|
Err(e) => Err(format!("{:?}", e)),
|
||||||
|
}
|
||||||
|
}).map_err(|err| {
|
||||||
|
format!("{:?}", err)
|
||||||
|
}).wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmds
|
||||||
|
|
||||||
|
pub fn signer_list(
|
||||||
|
signerport: u16, authfile: PathBuf
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
||||||
|
let mut signer = try!(SignerRpc::new(addr, &authfile).map_err(|err| {
|
||||||
|
format!("{:?}", err)
|
||||||
|
}));
|
||||||
|
list_transactions(&mut signer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signer_reject(
|
||||||
|
id: Option<usize>, signerport: u16, authfile: PathBuf
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let id = try!(id.ok_or(format!("id required for signer reject")));
|
||||||
|
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
||||||
|
let mut signer = try!(SignerRpc::new(addr, &authfile).map_err(|err| {
|
||||||
|
format!("{:?}", err)
|
||||||
|
}));
|
||||||
|
reject_transaction(&mut signer, U256::from(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signer_sign(
|
||||||
|
id: Option<usize>,
|
||||||
|
pwfile: Option<PathBuf>,
|
||||||
|
signerport: u16,
|
||||||
|
authfile: PathBuf
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let password;
|
||||||
|
match pwfile {
|
||||||
|
Some(pwfile) => {
|
||||||
|
match File::open(pwfile) {
|
||||||
|
Ok(fd) => {
|
||||||
|
match BufReader::new(fd).lines().next() {
|
||||||
|
Some(Ok(line)) => password = line,
|
||||||
|
_ => return Err(format!("No password in file"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) =>
|
||||||
|
return Err(format!("Could not open password file: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
password = match rpassword::prompt_password_stdout("Password: ") {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return Err(format!("{}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
||||||
|
let mut signer = try!(SignerRpc::new(addr, &authfile).map_err(|err| {
|
||||||
|
format!("{:?}", err)
|
||||||
|
}));
|
||||||
|
|
||||||
|
match id {
|
||||||
|
Some(id) => {
|
||||||
|
sign_transaction(&mut signer, U256::from(id), &password)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
sign_transactions(&mut signer, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
rpc_client/Cargo.toml
Normal file
23
rpc_client/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["Ethcore <admin@ethcore.io>"]
|
||||||
|
description = "Parity Rpc Client"
|
||||||
|
homepage = "http://ethcore.io"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
name = "parity-rpc-client"
|
||||||
|
version = "1.4.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "0.1"
|
||||||
|
jsonrpc-core = "3.0.2"
|
||||||
|
lazy_static = "0.2.1"
|
||||||
|
log = "0.3.6"
|
||||||
|
matches = "0.1.2"
|
||||||
|
rand = "0.3.14"
|
||||||
|
serde = "0.8"
|
||||||
|
serde_json = "0.8"
|
||||||
|
tempdir = "0.3.5"
|
||||||
|
url = "1.2.0"
|
||||||
|
ws = { git = "https://github.com/ethcore/ws-rs.git", branch = "mio-upstream-stable" }
|
||||||
|
ethcore-rpc = { path = "../rpc" }
|
||||||
|
ethcore-signer = { path = "../signer" }
|
||||||
|
ethcore-util = { path = "../util" }
|
322
rpc_client/src/client.rs
Normal file
322
rpc_client/src/client.rs
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
extern crate jsonrpc_core;
|
||||||
|
|
||||||
|
use std::fmt::{Debug, Formatter, Error as FmtError};
|
||||||
|
use std::io::{BufReader, BufRead};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use util::{Hashable, Mutex};
|
||||||
|
use url::Url;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
use ws::{
|
||||||
|
self,
|
||||||
|
Request,
|
||||||
|
Handler,
|
||||||
|
Sender,
|
||||||
|
Handshake,
|
||||||
|
Error as WsError,
|
||||||
|
ErrorKind as WsErrorKind,
|
||||||
|
Message,
|
||||||
|
Result as WsResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::{
|
||||||
|
self as json,
|
||||||
|
Value as JsonValue,
|
||||||
|
Error as JsonError,
|
||||||
|
};
|
||||||
|
|
||||||
|
use futures::{BoxFuture, Canceled, Complete, Future, oneshot, done};
|
||||||
|
|
||||||
|
use jsonrpc_core::{Id, Version, Params, Error as JsonRpcError};
|
||||||
|
use jsonrpc_core::request::MethodCall;
|
||||||
|
use jsonrpc_core::response::{SyncOutput, Success, Failure};
|
||||||
|
|
||||||
|
/// The actual websocket connection handler, passed into the
|
||||||
|
/// event loop of ws-rs
|
||||||
|
struct RpcHandler {
|
||||||
|
pending: Pending,
|
||||||
|
// Option is used here as temporary storage until connection
|
||||||
|
// is setup and the values are moved into the new `Rpc`
|
||||||
|
complete: Option<Complete<Result<Rpc, RpcError>>>,
|
||||||
|
auth_code: String,
|
||||||
|
out: Option<Sender>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcHandler {
|
||||||
|
fn new(
|
||||||
|
out: Sender,
|
||||||
|
auth_code: String,
|
||||||
|
complete: Complete<Result<Rpc, RpcError>>
|
||||||
|
) -> Self {
|
||||||
|
RpcHandler {
|
||||||
|
out: Some(out),
|
||||||
|
auth_code: auth_code,
|
||||||
|
pending: Pending::new(),
|
||||||
|
complete: Some(complete),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler for RpcHandler {
|
||||||
|
fn build_request(&mut self, url: &Url) -> WsResult<Request> {
|
||||||
|
match Request::from_url(url) {
|
||||||
|
Ok(mut r) => {
|
||||||
|
let timestamp = try!(time::UNIX_EPOCH.elapsed().map_err(|err| {
|
||||||
|
WsError::new(WsErrorKind::Internal, format!("{}", err))
|
||||||
|
}));
|
||||||
|
let secs = timestamp.as_secs();
|
||||||
|
let hashed = format!("{}:{}", self.auth_code, secs).sha3();
|
||||||
|
let proto = format!("{:?}_{}", hashed, secs);
|
||||||
|
r.add_protocol(&proto);
|
||||||
|
Ok(r)
|
||||||
|
},
|
||||||
|
Err(e) =>
|
||||||
|
Err(WsError::new(WsErrorKind::Internal, format!("{}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_error(&mut self, err: WsError) {
|
||||||
|
match self.complete.take() {
|
||||||
|
Some(c) => c.complete(Err(RpcError::WsError(err))),
|
||||||
|
None => println!("unexpected error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_open(&mut self, _: Handshake) -> WsResult<()> {
|
||||||
|
match (self.complete.take(), self.out.take()) {
|
||||||
|
(Some(c), Some(out)) => {
|
||||||
|
c.complete(Ok(Rpc {
|
||||||
|
out: out,
|
||||||
|
counter: AtomicUsize::new(0),
|
||||||
|
pending: self.pending.clone(),
|
||||||
|
}));
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let msg = format!("on_open called twice");
|
||||||
|
Err(WsError::new(WsErrorKind::Internal, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_message(&mut self, msg: Message) -> WsResult<()> {
|
||||||
|
let ret: Result<JsonValue, JsonRpcError>;
|
||||||
|
let response_id;
|
||||||
|
let string = &msg.to_string();
|
||||||
|
match json::from_str::<SyncOutput>(&string) {
|
||||||
|
Ok(SyncOutput::Success(Success { result, id: Id::Num(id), .. })) =>
|
||||||
|
{
|
||||||
|
ret = Ok(result);
|
||||||
|
response_id = id as usize;
|
||||||
|
}
|
||||||
|
Ok(SyncOutput::Failure(Failure { error, id: Id::Num(id), .. })) => {
|
||||||
|
ret = Err(error);
|
||||||
|
response_id = id as usize;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
|
target: "rpc-client",
|
||||||
|
"recieved invalid message: {}\n {:?}",
|
||||||
|
string,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Ok(())
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
warn!(
|
||||||
|
target: "rpc-client",
|
||||||
|
"recieved invalid message: {}",
|
||||||
|
string
|
||||||
|
);
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.pending.remove(response_id) {
|
||||||
|
Some(c) => c.complete(ret.map_err(|err| {
|
||||||
|
RpcError::JsonRpc(err)
|
||||||
|
})),
|
||||||
|
None => warn!(
|
||||||
|
target: "rpc-client",
|
||||||
|
"warning: unexpected id: {}",
|
||||||
|
response_id
|
||||||
|
),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keeping track of issued requests to be matched up with responses
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Pending(
|
||||||
|
Arc<Mutex<BTreeMap<usize, Complete<Result<JsonValue, RpcError>>>>>
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Pending {
|
||||||
|
fn new() -> Self {
|
||||||
|
Pending(Arc::new(Mutex::new(BTreeMap::new())))
|
||||||
|
}
|
||||||
|
fn insert(&mut self, k: usize, v: Complete<Result<JsonValue, RpcError>>) {
|
||||||
|
self.0.lock().insert(k, v);
|
||||||
|
}
|
||||||
|
fn remove(
|
||||||
|
&mut self,
|
||||||
|
k: usize
|
||||||
|
) -> Option<Complete<Result<JsonValue, RpcError>>> {
|
||||||
|
self.0.lock().remove(&k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_authcode(path: &PathBuf) -> Result<String, RpcError> {
|
||||||
|
if let Ok(fd) = File::open(path) {
|
||||||
|
if let Some(Ok(line)) = BufReader::new(fd).lines().next() {
|
||||||
|
let mut parts = line.split(';');
|
||||||
|
let token = parts.next();
|
||||||
|
|
||||||
|
if let Some(code) = token {
|
||||||
|
return Ok(code.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(RpcError::NoAuthCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The handle to the connection
|
||||||
|
pub struct Rpc {
|
||||||
|
out: Sender,
|
||||||
|
counter: AtomicUsize,
|
||||||
|
pending: Pending,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rpc {
|
||||||
|
/// Blocking, returns a new initialized connection or RpcError
|
||||||
|
pub fn new(url: &str, authpath: &PathBuf) -> Result<Self, RpcError> {
|
||||||
|
let rpc = try!(Self::connect(url, authpath).map(|rpc| rpc).wait());
|
||||||
|
rpc
|
||||||
|
}
|
||||||
|
/// Non-blocking, returns a future
|
||||||
|
pub fn connect(
|
||||||
|
url: &str, authpath: &PathBuf
|
||||||
|
) -> BoxFuture<Result<Self, RpcError>, Canceled> {
|
||||||
|
let (c, p) = oneshot::<Result<Self, RpcError>>();
|
||||||
|
match get_authcode(authpath) {
|
||||||
|
Err(e) => return done(Ok(Err(e))).boxed(),
|
||||||
|
Ok(code) => {
|
||||||
|
let url = String::from(url);
|
||||||
|
// The ws::connect takes a FnMut closure, which means c cannot
|
||||||
|
// be moved into it, since it's consumed on complete.
|
||||||
|
// Therefore we wrap it in an option and pick it out once.
|
||||||
|
let mut once = Some(c);
|
||||||
|
thread::spawn(move || {
|
||||||
|
let conn = ws::connect(url, |out| {
|
||||||
|
// this will panic if the closure is called twice,
|
||||||
|
// which it should never be.
|
||||||
|
let c = once.take()
|
||||||
|
.expect("connection closure called only once");
|
||||||
|
RpcHandler::new(out, code.clone(), c)
|
||||||
|
});
|
||||||
|
match conn {
|
||||||
|
Err(err) => {
|
||||||
|
// since ws::connect is only called once, it cannot
|
||||||
|
// both fail and succeed.
|
||||||
|
let c = once.take()
|
||||||
|
.expect("connection closure called only once");
|
||||||
|
c.complete(Err(RpcError::WsError(err)));
|
||||||
|
},
|
||||||
|
// c will complete on the `on_open` event in the Handler
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
p.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Non-blocking, returns a future of the request response
|
||||||
|
pub fn request<T>(
|
||||||
|
&mut self, method: &'static str, params: Vec<JsonValue>
|
||||||
|
) -> BoxFuture<Result<T, RpcError>, Canceled>
|
||||||
|
where T: Deserialize + Send + Sized {
|
||||||
|
|
||||||
|
let (c, p) = oneshot::<Result<JsonValue, RpcError>>();
|
||||||
|
|
||||||
|
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
self.pending.insert(id, c);
|
||||||
|
|
||||||
|
let request = MethodCall {
|
||||||
|
jsonrpc: Version::V2,
|
||||||
|
method: method.to_owned(),
|
||||||
|
params: Some(Params::Array(params)),
|
||||||
|
id: Id::Num(id as u64),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = json::to_string(&request)
|
||||||
|
.expect("request is serializable");
|
||||||
|
let _ = self.out.send(serialized);
|
||||||
|
|
||||||
|
p.map(|result| {
|
||||||
|
match result {
|
||||||
|
Ok(json) => {
|
||||||
|
let t: T = try!(json::from_value(json));
|
||||||
|
Ok(t)
|
||||||
|
},
|
||||||
|
Err(err) => Err(err)
|
||||||
|
}
|
||||||
|
}).boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RpcError {
|
||||||
|
WrongVersion(String),
|
||||||
|
ParseError(JsonError),
|
||||||
|
MalformedResponse(String),
|
||||||
|
JsonRpc(JsonRpcError),
|
||||||
|
WsError(WsError),
|
||||||
|
Canceled(Canceled),
|
||||||
|
UnexpectedId,
|
||||||
|
NoAuthCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for RpcError {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||||
|
match *self {
|
||||||
|
RpcError::WrongVersion(ref s)
|
||||||
|
=> write!(f, "Expected version 2.0, got {}", s),
|
||||||
|
RpcError::ParseError(ref err)
|
||||||
|
=> write!(f, "ParseError: {}", err),
|
||||||
|
RpcError::MalformedResponse(ref s)
|
||||||
|
=> write!(f, "Malformed response: {}", s),
|
||||||
|
RpcError::JsonRpc(ref json)
|
||||||
|
=> write!(f, "JsonRpc error: {:?}", json),
|
||||||
|
RpcError::WsError(ref s)
|
||||||
|
=> write!(f, "Websocket error: {}", s),
|
||||||
|
RpcError::Canceled(ref s)
|
||||||
|
=> write!(f, "Futures error: {:?}", s),
|
||||||
|
RpcError::UnexpectedId
|
||||||
|
=> write!(f, "Unexpected response id"),
|
||||||
|
RpcError::NoAuthCode
|
||||||
|
=> write!(f, "No authcodes available"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JsonError> for RpcError {
|
||||||
|
fn from(err: JsonError) -> RpcError {
|
||||||
|
RpcError::ParseError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<WsError> for RpcError {
|
||||||
|
fn from(err: WsError) -> RpcError {
|
||||||
|
RpcError::WsError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Canceled> for RpcError {
|
||||||
|
fn from(err: Canceled) -> RpcError {
|
||||||
|
RpcError::Canceled(err)
|
||||||
|
}
|
||||||
|
}
|
73
rpc_client/src/lib.rs
Normal file
73
rpc_client/src/lib.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
pub mod client;
|
||||||
|
pub mod signer_client;
|
||||||
|
|
||||||
|
extern crate ws;
|
||||||
|
extern crate ethcore_signer;
|
||||||
|
extern crate url;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate ethcore_util as util;
|
||||||
|
extern crate ethcore_rpc as rpc;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate rand;
|
||||||
|
extern crate tempdir;
|
||||||
|
extern crate jsonrpc_core;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate matches;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use futures::Future;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use client::{Rpc, RpcError};
|
||||||
|
use ethcore_signer;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_connection_refused() {
|
||||||
|
let (_srv, port, mut authcodes) = ethcore_signer::tests::serve();
|
||||||
|
|
||||||
|
let _ = authcodes.generate_new();
|
||||||
|
authcodes.to_file(&authcodes.path).unwrap();
|
||||||
|
|
||||||
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port - 1),
|
||||||
|
authcodes.path.as_path());
|
||||||
|
|
||||||
|
let _ = connect.map(|conn| {
|
||||||
|
assert!(matches!(&conn, &Err(RpcError::WsError(_))));
|
||||||
|
}).wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_authcode_fail() {
|
||||||
|
let (_srv, port, _) = ethcore_signer::tests::serve();
|
||||||
|
let path = PathBuf::from("nonexist");
|
||||||
|
|
||||||
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &path);
|
||||||
|
|
||||||
|
let _ = connect.map(|conn| {
|
||||||
|
assert!(matches!(&conn, &Err(RpcError::NoAuthCode)));
|
||||||
|
}).wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_authcode_correct() {
|
||||||
|
let (_srv, port, mut authcodes) = ethcore_signer::tests::serve();
|
||||||
|
|
||||||
|
let _ = authcodes.generate_new();
|
||||||
|
authcodes.to_file(&authcodes.path).unwrap();
|
||||||
|
|
||||||
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port),
|
||||||
|
authcodes.path.as_path());
|
||||||
|
|
||||||
|
let _ = connect.map(|conn| {
|
||||||
|
assert!(conn.is_ok())
|
||||||
|
}).wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
43
rpc_client/src/signer_client.rs
Normal file
43
rpc_client/src/signer_client.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use client::{Rpc, RpcError};
|
||||||
|
use rpc::v1::types::{ConfirmationRequest,
|
||||||
|
TransactionModification,
|
||||||
|
U256};
|
||||||
|
use serde_json::{Value as JsonValue, to_value};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use futures::{BoxFuture, Canceled};
|
||||||
|
|
||||||
|
pub struct SignerRpc {
|
||||||
|
rpc: Rpc,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignerRpc {
|
||||||
|
pub fn new(url: &str, authfile: &PathBuf) -> Result<Self, RpcError> {
|
||||||
|
Ok(SignerRpc { rpc: try!(Rpc::new(&url, authfile)) })
|
||||||
|
}
|
||||||
|
pub fn requests_to_confirm(&mut self) ->
|
||||||
|
BoxFuture<Result<Vec<ConfirmationRequest>, RpcError>, Canceled>
|
||||||
|
{
|
||||||
|
self.rpc.request("signer_requestsToConfirm", vec![])
|
||||||
|
}
|
||||||
|
pub fn confirm_request(
|
||||||
|
&mut self,
|
||||||
|
id: U256,
|
||||||
|
new_gas: Option<U256>,
|
||||||
|
new_gas_price: Option<U256>,
|
||||||
|
pwd: &str
|
||||||
|
) -> BoxFuture<Result<U256, RpcError>, Canceled>
|
||||||
|
{
|
||||||
|
self.rpc.request("signer_confirmRequest", vec![
|
||||||
|
to_value(&format!("{:#x}", id)),
|
||||||
|
to_value(&TransactionModification { gas_price: new_gas_price, gas: new_gas }),
|
||||||
|
to_value(&pwd),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
pub fn reject_request(&mut self, id: U256) ->
|
||||||
|
BoxFuture<Result<bool, RpcError>, Canceled>
|
||||||
|
{
|
||||||
|
self.rpc.request("signer_rejectRequest", vec![
|
||||||
|
JsonValue::String(format!("{:#x}", id))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
@ -52,13 +52,13 @@ extern crate ethcore_io as io;
|
|||||||
extern crate ethcore_rpc as rpc;
|
extern crate ethcore_rpc as rpc;
|
||||||
extern crate jsonrpc_core;
|
extern crate jsonrpc_core;
|
||||||
extern crate ws;
|
extern crate ws;
|
||||||
#[cfg(test)]
|
|
||||||
extern crate ethcore_devtools as devtools;
|
extern crate ethcore_devtools as devtools;
|
||||||
|
|
||||||
mod authcode_store;
|
mod authcode_store;
|
||||||
mod ws_server;
|
mod ws_server;
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
|
/// Exported tests for use in signer RPC client testing
|
||||||
|
pub mod tests;
|
||||||
pub use authcode_store::*;
|
pub use authcode_store::*;
|
||||||
pub use ws_server::*;
|
pub use ws_server::*;
|
||||||
|
@ -15,20 +15,23 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::time;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use devtools::{http_client, RandomTempPath};
|
|
||||||
|
use devtools::http_client;
|
||||||
|
use devtools::RandomTempPath;
|
||||||
|
|
||||||
use rpc::ConfirmationsQueue;
|
use rpc::ConfirmationsQueue;
|
||||||
use util::Hashable;
|
|
||||||
use rand;
|
use rand;
|
||||||
|
|
||||||
use ServerBuilder;
|
use ServerBuilder;
|
||||||
use Server;
|
use Server;
|
||||||
use AuthCodes;
|
use AuthCodes;
|
||||||
|
|
||||||
|
/// Struct representing authcodes
|
||||||
pub struct GuardedAuthCodes {
|
pub struct GuardedAuthCodes {
|
||||||
authcodes: AuthCodes,
|
authcodes: AuthCodes,
|
||||||
path: RandomTempPath,
|
/// The path to the mock authcodes
|
||||||
|
pub path: RandomTempPath,
|
||||||
}
|
}
|
||||||
impl Deref for GuardedAuthCodes {
|
impl Deref for GuardedAuthCodes {
|
||||||
type Target = AuthCodes;
|
type Target = AuthCodes;
|
||||||
@ -42,6 +45,7 @@ impl DerefMut for GuardedAuthCodes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Setup a mock signer for testsp
|
||||||
pub fn serve() -> (Server, usize, GuardedAuthCodes) {
|
pub fn serve() -> (Server, usize, GuardedAuthCodes) {
|
||||||
let mut path = RandomTempPath::new();
|
let mut path = RandomTempPath::new();
|
||||||
path.panic_on_drop_failure = false;
|
path.panic_on_drop_failure = false;
|
||||||
@ -56,194 +60,202 @@ pub fn serve() -> (Server, usize, GuardedAuthCodes) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test a single request to running server
|
||||||
pub fn request(server: Server, request: &str) -> http_client::Response {
|
pub fn request(server: Server, request: &str) -> http_client::Response {
|
||||||
http_client::request(server.addr(), request)
|
http_client::request(server.addr(), request)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[cfg(test)]
|
||||||
fn should_reject_invalid_host() {
|
mod testing {
|
||||||
// given
|
use std::time;
|
||||||
let server = serve().0;
|
use util::Hashable;
|
||||||
|
use devtools::http_client;
|
||||||
|
use super::{serve, request};
|
||||||
|
|
||||||
// when
|
#[test]
|
||||||
let response = request(server,
|
fn should_reject_invalid_host() {
|
||||||
"\
|
// given
|
||||||
GET / HTTP/1.1\r\n\
|
let server = serve().0;
|
||||||
Host: test:8180\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
{}
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
// when
|
||||||
assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
|
let response = request(server,
|
||||||
assert!(response.body.contains("URL Blocked"));
|
"\
|
||||||
http_client::assert_security_headers_present(&response.headers, None);
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: test:8180\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
{}
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
|
||||||
|
assert!(response.body.contains("URL Blocked"));
|
||||||
|
http_client::assert_security_headers_present(&response.headers, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_allow_home_parity_host() {
|
||||||
|
// given
|
||||||
|
let server = serve().0;
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
"\
|
||||||
|
GET http://home.parity/ HTTP/1.1\r\n\
|
||||||
|
Host: home.parity\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
{}
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||||
|
http_client::assert_security_headers_present(&response.headers, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_serve_styles_even_on_disallowed_domain() {
|
||||||
|
// given
|
||||||
|
let server = serve().0;
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
"\
|
||||||
|
GET /styles.css HTTP/1.1\r\n\
|
||||||
|
Host: test:8180\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
{}
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||||
|
http_client::assert_security_headers_present(&response.headers, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_200_ok_for_connect_requests() {
|
||||||
|
// given
|
||||||
|
let server = serve().0;
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
"\
|
||||||
|
CONNECT home.parity:8080 HTTP/1.1\r\n\
|
||||||
|
Host: home.parity\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
{}
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_block_if_authorization_is_incorrect() {
|
||||||
|
// given
|
||||||
|
let (server, port, _) = serve();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
&format!("\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: 127.0.0.1:{}\r\n\
|
||||||
|
Connection: Upgrade\r\n\
|
||||||
|
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
|
||||||
|
Sec-WebSocket-Protocol: wrong\r\n\
|
||||||
|
Sec-WebSocket-Version: 13\r\n\
|
||||||
|
\r\n\
|
||||||
|
{{}}
|
||||||
|
", port)
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
|
||||||
|
http_client::assert_security_headers_present(&response.headers, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_allow_if_authorization_is_correct() {
|
||||||
|
// given
|
||||||
|
let (server, port, mut authcodes) = serve();
|
||||||
|
let code = authcodes.generate_new().unwrap().replace("-", "");
|
||||||
|
authcodes.to_file(&authcodes.path).unwrap();
|
||||||
|
let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
&format!("\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: 127.0.0.1:{}\r\n\
|
||||||
|
Connection: Close\r\n\
|
||||||
|
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
|
||||||
|
Sec-WebSocket-Protocol: {:?}_{}\r\n\
|
||||||
|
Sec-WebSocket-Version: 13\r\n\
|
||||||
|
\r\n\
|
||||||
|
{{}}
|
||||||
|
",
|
||||||
|
port,
|
||||||
|
format!("{}:{}", code, timestamp).sha3(),
|
||||||
|
timestamp,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(response.status, "HTTP/1.1 101 Switching Protocols".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_allow_initial_connection_but_only_once() {
|
||||||
|
// given
|
||||||
|
let (server, port, authcodes) = serve();
|
||||||
|
let code = "initial";
|
||||||
|
let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||||
|
assert!(authcodes.is_empty());
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response1 = http_client::request(server.addr(),
|
||||||
|
&format!("\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: 127.0.0.1:{}\r\n\
|
||||||
|
Connection: Close\r\n\
|
||||||
|
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
|
||||||
|
Sec-WebSocket-Protocol:{:?}_{}\r\n\
|
||||||
|
Sec-WebSocket-Version: 13\r\n\
|
||||||
|
\r\n\
|
||||||
|
{{}}
|
||||||
|
",
|
||||||
|
port,
|
||||||
|
format!("{}:{}", code, timestamp).sha3(),
|
||||||
|
timestamp,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let response2 = http_client::request(server.addr(),
|
||||||
|
&format!("\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: 127.0.0.1:{}\r\n\
|
||||||
|
Connection: Close\r\n\
|
||||||
|
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
|
||||||
|
Sec-WebSocket-Protocol:{:?}_{}\r\n\
|
||||||
|
Sec-WebSocket-Version: 13\r\n\
|
||||||
|
\r\n\
|
||||||
|
{{}}
|
||||||
|
",
|
||||||
|
port,
|
||||||
|
format!("{}:{}", code, timestamp).sha3(),
|
||||||
|
timestamp,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned());
|
||||||
|
assert_eq!(response2.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
|
||||||
|
http_client::assert_security_headers_present(&response2.headers, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_allow_home_parity_host() {
|
|
||||||
// given
|
|
||||||
let server = serve().0;
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
"\
|
|
||||||
GET http://home.parity/ HTTP/1.1\r\n\
|
|
||||||
Host: home.parity\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
{}
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
|
||||||
http_client::assert_security_headers_present(&response.headers, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_serve_styles_even_on_disallowed_domain() {
|
|
||||||
// given
|
|
||||||
let server = serve().0;
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
"\
|
|
||||||
GET /styles.css HTTP/1.1\r\n\
|
|
||||||
Host: test:8180\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
{}
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
|
||||||
http_client::assert_security_headers_present(&response.headers, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_return_200_ok_for_connect_requests() {
|
|
||||||
// given
|
|
||||||
let server = serve().0;
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
"\
|
|
||||||
CONNECT home.parity:8080 HTTP/1.1\r\n\
|
|
||||||
Host: home.parity\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
{}
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_block_if_authorization_is_incorrect() {
|
|
||||||
// given
|
|
||||||
let (server, port, _) = serve();
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
&format!("\
|
|
||||||
GET / HTTP/1.1\r\n\
|
|
||||||
Host: 127.0.0.1:{}\r\n\
|
|
||||||
Connection: Upgrade\r\n\
|
|
||||||
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
|
|
||||||
Sec-WebSocket-Protocol: wrong\r\n\
|
|
||||||
Sec-WebSocket-Version: 13\r\n\
|
|
||||||
\r\n\
|
|
||||||
{{}}
|
|
||||||
", port)
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
|
|
||||||
http_client::assert_security_headers_present(&response.headers, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_allow_if_authorization_is_correct() {
|
|
||||||
// given
|
|
||||||
let (server, port, mut authcodes) = serve();
|
|
||||||
let code = authcodes.generate_new().unwrap().replace("-", "");
|
|
||||||
authcodes.to_file(&authcodes.path).unwrap();
|
|
||||||
let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
&format!("\
|
|
||||||
GET / HTTP/1.1\r\n\
|
|
||||||
Host: 127.0.0.1:{}\r\n\
|
|
||||||
Connection: Close\r\n\
|
|
||||||
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
|
|
||||||
Sec-WebSocket-Protocol: {:?}_{}\r\n\
|
|
||||||
Sec-WebSocket-Version: 13\r\n\
|
|
||||||
\r\n\
|
|
||||||
{{}}
|
|
||||||
",
|
|
||||||
port,
|
|
||||||
format!("{}:{}", code, timestamp).sha3(),
|
|
||||||
timestamp,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response.status, "HTTP/1.1 101 Switching Protocols".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_allow_initial_connection_but_only_once() {
|
|
||||||
// given
|
|
||||||
let (server, port, authcodes) = serve();
|
|
||||||
let code = "initial";
|
|
||||||
let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
|
||||||
assert!(authcodes.is_empty());
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response1 = http_client::request(server.addr(),
|
|
||||||
&format!("\
|
|
||||||
GET / HTTP/1.1\r\n\
|
|
||||||
Host: 127.0.0.1:{}\r\n\
|
|
||||||
Connection: Close\r\n\
|
|
||||||
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
|
|
||||||
Sec-WebSocket-Protocol:{:?}_{}\r\n\
|
|
||||||
Sec-WebSocket-Version: 13\r\n\
|
|
||||||
\r\n\
|
|
||||||
{{}}
|
|
||||||
",
|
|
||||||
port,
|
|
||||||
format!("{}:{}", code, timestamp).sha3(),
|
|
||||||
timestamp,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let response2 = http_client::request(server.addr(),
|
|
||||||
&format!("\
|
|
||||||
GET / HTTP/1.1\r\n\
|
|
||||||
Host: 127.0.0.1:{}\r\n\
|
|
||||||
Connection: Close\r\n\
|
|
||||||
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
|
|
||||||
Sec-WebSocket-Protocol:{:?}_{}\r\n\
|
|
||||||
Sec-WebSocket-Version: 13\r\n\
|
|
||||||
\r\n\
|
|
||||||
{{}}
|
|
||||||
",
|
|
||||||
port,
|
|
||||||
format!("{}:{}", code, timestamp).sha3(),
|
|
||||||
timestamp,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned());
|
|
||||||
assert_eq!(response2.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
|
|
||||||
http_client::assert_security_headers_present(&response2.headers, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user