From cf348dae60483b33768897d4a7f34dae19925c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 30 Jan 2017 10:59:46 +0100 Subject: [PATCH] Persistent tracking of dapps (#4302) * Tests for RPC * Extracting dapp_id from Origin and x-parity-origin * Separate type for DappId * Persistent tracking of recent dapps * Fixing tests * Exposing dapp timestamps * Fixing import to work on stable * Fixing test again --- dapps/src/rpc.rs | 19 ++- dapps/src/tests/helpers/mod.rs | 21 ++-- dapps/src/tests/mod.rs | 1 + dapps/src/tests/rpc.rs | 119 +++++++++++++++++++ ethcore/src/account_provider/mod.rs | 19 ++- ethcore/src/account_provider/stores.rs | 130 ++++++++++++++++----- ethstore/src/dir/disk.rs | 8 +- json/src/hash.rs | 6 +- json/src/misc/account_meta.rs | 30 +---- json/src/misc/dapps_settings.rs | 50 ++------ json/src/misc/mod.rs | 33 +++++- rpc/src/v1/impls/parity_accounts.rs | 4 +- rpc/src/v1/tests/mocked/parity_accounts.rs | 2 +- rpc/src/v1/traits/parity_accounts.rs | 5 +- rpc/src/v1/types/dapp_id.rs | 16 ++- 15 files changed, 336 insertions(+), 127 deletions(-) create mode 100644 dapps/src/tests/rpc.rs diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index 43f123c19..1fffec6ee 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -57,12 +57,19 @@ impl Endpoint for RpcEndpoint { struct MetadataExtractor; impl HttpMetaExtractor for MetadataExtractor { fn read_metadata(&self, request: &hyper::server::Request) -> Metadata { - let dapp_id = request.headers().get::() - .and_then(|referer| hyper::Url::parse(referer).ok()) - .and_then(|url| { - url.path_segments() - .and_then(|mut split| split.next()) - .map(|app_id| app_id.to_owned()) + let dapp_id = request.headers().get::() + .map(|origin| format!("{}://{}", origin.scheme, origin.host)) + .or_else(|| { + // fallback to custom header, but only if origin is null + request.headers().get_raw("origin") + .and_then(|raw| raw.one()) + .and_then(|raw| if raw == "null".as_bytes() { + request.headers().get_raw("x-parity-origin") + .and_then(|raw| raw.one()) + .map(|raw| String::from_utf8_lossy(raw).into_owned()) + } else { + None + }) }); Metadata { dapp_id: dapp_id, diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index 89c8d1a18..9df98c343 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -19,6 +19,7 @@ use std::str; use std::ops::Deref; use std::sync::Arc; use env_logger::LogBuilder; +use ethcore_rpc::Metadata; use jsonrpc_core::MetaIoHandler; use jsonrpc_core::reactor::RpcEventLoop; @@ -58,7 +59,7 @@ impl Deref for ServerLoop { } } -pub fn init_server(process: F, remote: Remote) -> (ServerLoop, Arc) where +pub fn init_server(process: F, io: MetaIoHandler, remote: Remote) -> (ServerLoop, Arc) where F: FnOnce(ServerBuilder) -> ServerBuilder, B: Fetch, { @@ -70,7 +71,7 @@ pub fn init_server(process: F, remote: Remote) -> (ServerLoop, Arc ServerLoop { } } +pub fn serve_with_rpc(io: MetaIoHandler) -> ServerLoop { + init_server(|builder| builder.allowed_hosts(None), io, Remote::new_sync()).0 +} + pub fn serve_hosts(hosts: Option>) -> ServerLoop { - init_server(|builder| builder.allowed_hosts(hosts), Remote::new_sync()).0 + init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0 } pub fn serve_with_registrar() -> (ServerLoop, Arc) { - init_server(|builder| builder.allowed_hosts(None), Remote::new_sync()) + init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync()) } pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc) { @@ -113,7 +118,7 @@ pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc) { builder .sync_status(Arc::new(|| true)) .allowed_hosts(None) - }, Remote::new_sync()) + }, Default::default(), Remote::new_sync()) } pub fn serve_with_registrar_and_fetch() -> (ServerLoop, FakeFetch, Arc) { @@ -125,7 +130,7 @@ pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Serv let f = fetch.clone(); let (server, reg) = init_server(move |builder| { builder.allowed_hosts(None).fetch(f.clone()) - }, if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() }); + }, Default::default(), if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() }); (server, fetch, reg) } @@ -138,13 +143,13 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) { .allowed_hosts(None) .fetch(f.clone()) .web_proxy_tokens(Arc::new(move |token| &token == web_token)) - }, Remote::new_sync()); + }, Default::default(), Remote::new_sync()); (server, fetch) } pub fn serve() -> ServerLoop { - init_server(|builder| builder.allowed_hosts(None), Remote::new_sync()).0 + init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync()).0 } pub fn request(server: ServerLoop, request: &str) -> http_client::Response { diff --git a/dapps/src/tests/mod.rs b/dapps/src/tests/mod.rs index d6caccdc2..ced211d53 100644 --- a/dapps/src/tests/mod.rs +++ b/dapps/src/tests/mod.rs @@ -22,5 +22,6 @@ mod api; mod authorization; mod fetch; mod redirection; +mod rpc; mod validation; diff --git a/dapps/src/tests/rpc.rs b/dapps/src/tests/rpc.rs new file mode 100644 index 000000000..54834e264 --- /dev/null +++ b/dapps/src/tests/rpc.rs @@ -0,0 +1,119 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use futures::{future, Future}; +use ethcore_rpc::{Metadata, Origin}; +use jsonrpc_core::{MetaIoHandler, Value}; + +use tests::helpers::{serve_with_rpc, request}; + +#[test] +fn should_serve_rpc() { + // given + let mut io = MetaIoHandler::new(); + io.add_method("rpc_test", |_| { + Ok(Value::String("Hello World!".into())) + }); + let server = serve_with_rpc(io); + + // when + let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#; + let response = request(server, &format!( + "\ + POST /rpc/ HTTP/1.1\r\n\ + Host: 127.0.0.1:8080\r\n\ + Connection: close\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n\ + \r\n\ + {}\r\n\ + ", + req.as_bytes().len(), + req, + )); + + // then + response.assert_status("HTTP/1.1 200 OK"); + assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned()); +} + +#[test] +fn should_extract_metadata() { + // given + let mut io = MetaIoHandler::new(); + io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { + assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned())); + assert_eq!(meta.origin, Origin::Dapps); + future::ok(Value::String("Hello World!".into())).boxed() + }); + let server = serve_with_rpc(io); + + // when + let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#; + let response = request(server, &format!( + "\ + POST /rpc/ HTTP/1.1\r\n\ + Host: 127.0.0.1:8080\r\n\ + Connection: close\r\n\ + Origin: https://parity.io/\r\n\ + X-Parity-Origin: https://this.should.be.ignored\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n\ + \r\n\ + {}\r\n\ + ", + req.as_bytes().len(), + req, + )); + + // then + response.assert_status("HTTP/1.1 200 OK"); + assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned()); +} + +#[test] +fn should_extract_metadata_from_custom_header() { + // given + let mut io = MetaIoHandler::new(); + io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { + assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned())); + assert_eq!(meta.origin, Origin::Dapps); + future::ok(Value::String("Hello World!".into())).boxed() + }); + let server = serve_with_rpc(io); + + // when + let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#; + let response = request(server, &format!( + "\ + POST /rpc/ HTTP/1.1\r\n\ + Host: 127.0.0.1:8080\r\n\ + Connection: close\r\n\ + Origin: null\r\n\ + X-Parity-Origin: https://parity.io/\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n\ + \r\n\ + {}\r\n\ + ", + req.as_bytes().len(), + req, + )); + + // then + response.assert_status("HTTP/1.1 200 OK"); + assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned()); +} diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index b7d0d1b88..246097195 100644 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -74,7 +74,18 @@ impl From for Error { } /// Dapp identifier -pub type DappId = String; +#[derive(Default, Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct DappId(String); + +impl From for String { + fn from(id: DappId) -> String { id.0 } +} +impl From for DappId { + fn from(id: String) -> DappId { DappId(id) } +} +impl<'a> From<&'a str> for DappId { + fn from(id: &'a str) -> DappId { DappId(id.to_owned()) } +} fn transient_sstore() -> EthMultiStore { EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed") @@ -181,7 +192,7 @@ impl AccountProvider { } /// Gets a list of dapps recently requesting accounts. - pub fn recent_dapps(&self) -> Result, Error> { + pub fn recent_dapps(&self) -> Result, Error> { Ok(self.dapps_settings.read().recent_dapps()) } @@ -405,7 +416,7 @@ impl AccountProvider { #[cfg(test)] mod tests { - use super::{AccountProvider, Unlock}; + use super::{AccountProvider, Unlock, DappId}; use std::time::Instant; use ethstore::ethkey::{Generator, Random}; @@ -466,7 +477,7 @@ mod tests { fn should_set_dapps_addresses() { // given let ap = AccountProvider::transient_provider(); - let app = "app1".to_owned(); + let app = DappId("app1".into()); // set `AllAccounts` policy ap.set_new_dapps_whitelist(None).unwrap(); diff --git a/ethcore/src/account_provider/stores.rs b/ethcore/src/account_provider/stores.rs index b468f62cb..d5d55e57b 100644 --- a/ethcore/src/account_provider/stores.rs +++ b/ethcore/src/account_provider/stores.rs @@ -17,11 +17,17 @@ //! Address Book and Dapps Settings Store use std::{fs, fmt, hash, ops}; -use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::{self, AtomicUsize}; +use std::collections::HashMap; use std::path::PathBuf; use ethstore::ethkey::Address; -use ethjson::misc::{AccountMeta, DappsSettings as JsonSettings, NewDappsPolicy as JsonNewDappsPolicy}; +use ethjson::misc::{ + AccountMeta, + DappsSettings as JsonSettings, + DappsHistory as JsonDappsHistory, + NewDappsPolicy as JsonNewDappsPolicy, +}; use account_provider::DappId; /// Disk-backed map from Address to String. Uses JSON. @@ -35,7 +41,7 @@ impl AddressBook { let mut r = AddressBook { cache: DiskMap::new(path, "address_book.json".into()) }; - r.cache.revert(AccountMeta::read_address_map); + r.cache.revert(AccountMeta::read); r } @@ -52,7 +58,7 @@ impl AddressBook { } fn save(&self) { - self.cache.save(AccountMeta::write_address_map) + self.cache.save(AccountMeta::write) } /// Sets new name for given address. @@ -134,7 +140,51 @@ impl From for JsonNewDappsPolicy { } } -const MAX_RECENT_DAPPS: usize = 10; +/// Transient dapps data +#[derive(Default, Debug, Clone, Eq, PartialEq)] +pub struct TransientDappsData { + /// Timestamp of last access + pub last_accessed: u64, +} + +impl From for TransientDappsData { + fn from(s: JsonDappsHistory) -> Self { + TransientDappsData { + last_accessed: s.last_accessed, + } + } +} + +impl From for JsonDappsHistory { + fn from(s: TransientDappsData) -> Self { + JsonDappsHistory { + last_accessed: s.last_accessed, + } + } +} + +enum TimeProvider { + Clock, + Incremenetal(AtomicUsize) +} + +impl TimeProvider { + fn get(&self) -> u64 { + match *self { + TimeProvider::Clock => { + ::std::time::UNIX_EPOCH.elapsed() + .expect("Correct time is required to be set") + .as_secs() + + }, + TimeProvider::Incremenetal(ref time) => { + time.fetch_add(1, atomic::Ordering::SeqCst) as u64 + }, + } + } +} + +const MAX_RECENT_DAPPS: usize = 50; /// Disk-backed map from DappId to Settings. Uses JSON. pub struct DappsSettingsStore { @@ -142,8 +192,10 @@ pub struct DappsSettingsStore { settings: DiskMap, /// New Dapps Policy policy: DiskMap, - /// Recently Accessed Dapps (transient) - recent: VecDeque, + /// Transient Data of recently Accessed Dapps + history: DiskMap, + /// Time + time: TimeProvider, } impl DappsSettingsStore { @@ -152,10 +204,12 @@ impl DappsSettingsStore { let mut r = DappsSettingsStore { 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), + history: DiskMap::new(path.clone(), "dapps_history.json".into()), + time: TimeProvider::Clock, }; - r.settings.revert(JsonSettings::read_dapps_settings); - r.policy.revert(JsonNewDappsPolicy::read_new_dapps_policy); + r.settings.revert(JsonSettings::read); + r.policy.revert(JsonNewDappsPolicy::read); + r.history.revert(JsonDappsHistory::read); r } @@ -164,7 +218,8 @@ impl DappsSettingsStore { DappsSettingsStore { settings: DiskMap::transient(), policy: DiskMap::transient(), - recent: VecDeque::with_capacity(MAX_RECENT_DAPPS), + history: DiskMap::transient(), + time: TimeProvider::Incremenetal(AtomicUsize::new(1)), } } @@ -178,24 +233,36 @@ impl DappsSettingsStore { self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts) } - /// Returns recent dapps (in order of last request) - pub fn recent_dapps(&self) -> Vec { - self.recent.iter().cloned().collect() + /// Returns recent dapps with last accessed timestamp + pub fn recent_dapps(&self) -> HashMap { + self.history.iter().map(|(k, v)| (k.clone(), v.last_accessed)).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(); + { + let mut entry = self.history.entry(dapp).or_insert_with(|| Default::default()); + entry.last_accessed = self.time.get(); } + // Clear extraneous entries + while self.history.len() > MAX_RECENT_DAPPS { + let min = self.history.iter() + .min_by_key(|&(_, ref v)| v.last_accessed) + .map(|(ref k, _)| k.clone()) + .cloned(); + + match min { + Some(k) => self.history.remove(&k), + None => break, + }; + } + self.history.save(JsonDappsHistory::write); } /// 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); + self.policy.save(JsonNewDappsPolicy::write); } /// Sets accounts for specific dapp. @@ -204,7 +271,7 @@ impl DappsSettingsStore { let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default); settings.accounts = accounts; } - self.settings.save(JsonSettings::write_dapps_settings); + self.settings.save(JsonSettings::write); } } @@ -280,6 +347,7 @@ impl DiskMap { #[cfg(test)] mod tests { use super::{AddressBook, DappsSettingsStore, DappsSettings, NewDappsPolicy}; + use account_provider::DappId; use std::collections::HashMap; use ethjson::misc::AccountMeta; use devtools::RandomTempPath; @@ -333,18 +401,22 @@ mod tests { } #[test] - fn should_maintain_a_list_of_recent_dapps() { + fn should_maintain_a_map_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()]); + let dapp1: DappId = "dapp1".into(); + let dapp2: DappId = "dapp2".into(); + store.mark_dapp_used(dapp1.clone()); + let recent = store.recent_dapps(); + assert_eq!(recent.len(), 1); + assert_eq!(recent.get(&dapp1), Some(&1)); - 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()]); + store.mark_dapp_used(dapp2.clone()); + let recent = store.recent_dapps(); + assert_eq!(recent.len(), 2); + assert_eq!(recent.get(&dapp1), Some(&1)); + assert_eq!(recent.get(&dapp2), Some(&2)); } #[test] @@ -353,7 +425,7 @@ mod tests { 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); diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 523402db0..6ca6b6378 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -22,7 +22,13 @@ use {json, SafeAccount, Error}; use json::Uuid; use super::KeyDirectory; -const IGNORED_FILES: &'static [&'static str] = &["thumbs.db", "address_book.json", "dapps_policy.json"]; +const IGNORED_FILES: &'static [&'static str] = &[ + "thumbs.db", + "address_book.json", + "dapps_policy.json", + "dapps_accounts.json", + "dapps_history.json", +]; #[cfg(not(windows))] fn restrict_permissions_to_owner(file_path: &Path) -> Result<(), i32> { diff --git a/json/src/hash.rs b/json/src/hash.rs index c5c032c2f..26ac888cf 100644 --- a/json/src/hash.rs +++ b/json/src/hash.rs @@ -29,9 +29,9 @@ macro_rules! impl_hash { #[derive(Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)] pub struct $name(pub $inner); - impl Into<$inner> for $name { - fn into(self) -> $inner { - self.0 + impl From<$name> for $inner { + fn from(other: $name) -> $inner { + other.0 } } diff --git a/json/src/misc/account_meta.rs b/json/src/misc/account_meta.rs index 2d7d57004..9c4d67286 100644 --- a/json/src/misc/account_meta.rs +++ b/json/src/misc/account_meta.rs @@ -16,14 +16,10 @@ //! Misc deserialization. -use std::io::{Read, Write}; -use std::collections::HashMap; -use serde_json; -use util; use hash; /// Collected account metadata -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AccountMeta { /// The name of the account. pub name: String, @@ -33,26 +29,4 @@ pub struct AccountMeta { pub uuid: Option, } -impl Default for AccountMeta { - fn default() -> Self { - AccountMeta { - name: String::new(), - meta: "{}".to_owned(), - uuid: None, - } - } -} - -impl AccountMeta { - /// Read a hash map of Address -> AccountMeta. - pub fn read_address_map(reader: R) -> Result, serde_json::Error> where R: Read { - serde_json::from_reader(reader).map(|ok: HashMap| - ok.into_iter().map(|(a, m)| (a.into(), m)).collect() - ) - } - - /// Write a hash map of Address -> AccountMeta. - pub fn write_address_map(m: &HashMap, writer: &mut W) -> Result<(), serde_json::Error> where W: Write { - serde_json::to_writer(writer, &m.iter().map(|(a, m)| (a.clone().into(), m)).collect::>()) - } -} +impl_serialization!(hash::Address => AccountMeta); diff --git a/json/src/misc/dapps_settings.rs b/json/src/misc/dapps_settings.rs index 76afc594b..1e5565baf 100644 --- a/json/src/misc/dapps_settings.rs +++ b/json/src/misc/dapps_settings.rs @@ -16,13 +16,8 @@ //! Dapps settings de/serialization. -use std::io; -use std::collections::HashMap; -use serde_json; use hash; -type DappId = String; - /// Settings for specific dapp. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DappsSettings { @@ -30,26 +25,17 @@ pub struct DappsSettings { pub accounts: Vec, } -impl DappsSettings { - /// Read a hash map of DappId -> DappsSettings - pub fn read_dapps_settings(reader: R) -> Result, serde_json::Error> where - R: io::Read, - S: From + Clone, - { - serde_json::from_reader(reader).map(|ok: HashMap| - ok.into_iter().map(|(a, m)| (a.into(), m.into())).collect() - ) - } +impl_serialization!(String => DappsSettings); - /// Write a hash map of DappId -> DappsSettings - pub fn write_dapps_settings(m: &HashMap, writer: &mut W) -> Result<(), serde_json::Error> where - W: io::Write, - S: Into + Clone, - { - serde_json::to_writer(writer, &m.iter().map(|(a, m)| (a.clone().into(), m.clone().into())).collect::>()) - } +/// History for specific dapp. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DappsHistory { + /// Last accessed timestamp + pub last_accessed: u64, } +impl_serialization!(String => DappsHistory); + /// Accounts policy for new dapps. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum NewDappsPolicy { @@ -59,22 +45,4 @@ pub enum NewDappsPolicy { Whitelist(Vec), } -impl NewDappsPolicy { - /// Read a hash map of `String -> NewDappsPolicy` - pub fn read_new_dapps_policy(reader: R) -> Result, serde_json::Error> where - R: io::Read, - S: From + Clone, - { - serde_json::from_reader(reader).map(|ok: HashMap| - ok.into_iter().map(|(a, m)| (a.into(), m.into())).collect() - ) - } - - /// Write a hash map of `String -> NewDappsPolicy` - pub fn write_new_dapps_policy(m: &HashMap, writer: &mut W) -> Result<(), serde_json::Error> where - W: io::Write, - S: Into + Clone, - { - serde_json::to_writer(writer, &m.iter().map(|(a, m)| (a.clone().into(), m.clone().into())).collect::>()) - } -} +impl_serialization!(String => NewDappsPolicy); diff --git a/json/src/misc/mod.rs b/json/src/misc/mod.rs index 9ec609b24..d587f2f15 100644 --- a/json/src/misc/mod.rs +++ b/json/src/misc/mod.rs @@ -16,8 +16,39 @@ //! Misc deserialization. +macro_rules! impl_serialization { + ($key: ty => $name: ty) => { + impl $name { + /// Read a hash map of DappId -> $name + pub fn read(reader: R) -> Result<::std::collections::HashMap, ::serde_json::Error> where + R: ::std::io::Read, + D: From<$key> + ::std::hash::Hash + Eq, + S: From<$name> + Clone, + { + ::serde_json::from_reader(reader).map(|ok: ::std::collections::HashMap<$key, $name>| + ok.into_iter().map(|(a, m)| (a.into(), m.into())).collect() + ) + } + + /// Write a hash map of DappId -> $name + pub fn write(m: &::std::collections::HashMap, writer: &mut W) -> Result<(), ::serde_json::Error> where + W: ::std::io::Write, + D: Into<$key> + ::std::hash::Hash + Eq + Clone, + S: Into<$name> + Clone, + { + ::serde_json::to_writer( + writer, + &m.iter() + .map(|(a, m)| (a.clone().into(), m.clone().into())) + .collect::<::std::collections::HashMap<$key, $name>>() + ) + } + } + } +} + mod account_meta; mod dapps_settings; -pub use self::dapps_settings::{DappsSettings, NewDappsPolicy}; +pub use self::dapps_settings::{DappsSettings, DappsHistory, NewDappsPolicy}; pub use self::account_meta::AccountMeta; diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index 387250013..7cf67de98 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -196,12 +196,12 @@ impl ParityAccounts for ParityAccountsClient where C: MiningBlock .map(|accounts| accounts.map(into_vec)) } - fn recent_dapps(&self) -> Result, Error> { + fn recent_dapps(&self) -> Result, Error> { let store = take_weak!(self.accounts); store.recent_dapps() .map_err(|e| errors::account("Couldn't get recent dapps.", e)) - .map(into_vec) + .map(|map| map.into_iter().map(|(k, v)| (k.into(), v)).collect()) } fn import_geth_accounts(&self, addresses: Vec) -> Result, Error> { diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index e3614e1cf..476e97229 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -173,7 +173,7 @@ fn rpc_parity_recent_dapps() { // then let request = r#"{"jsonrpc": "2.0", "method": "parity_listRecentDapps","params":[], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":["dapp1"],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"dapp1":1},"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index 499b526be..52a803cc8 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -92,9 +92,10 @@ build_rpc_trait! { #[rpc(name = "parity_getNewDappsWhitelist")] fn new_dapps_whitelist(&self) -> Result>, Error>; - /// Sets accounts exposed for particular dapp. + /// Returns identified dapps that recently used RPC + /// Includes last usage timestamp. #[rpc(name = "parity_listRecentDapps")] - fn recent_dapps(&self) -> Result, Error>; + fn recent_dapps(&self) -> Result, Error>; /// Imports a number of Geth accounts, with the list provided as the argument. #[rpc(name = "parity_importGethAccounts")] diff --git a/rpc/src/v1/types/dapp_id.rs b/rpc/src/v1/types/dapp_id.rs index 14e23f9c1..e594bab87 100644 --- a/rpc/src/v1/types/dapp_id.rs +++ b/rpc/src/v1/types/dapp_id.rs @@ -16,8 +16,10 @@ //! Dapp Id type +use ethcore::account_provider::DappId as EthDappId; + /// Dapplication Internal Id -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] pub struct DappId(pub String); impl Into for DappId { @@ -32,6 +34,18 @@ impl From for DappId { } } +impl From for DappId { + fn from(id: EthDappId) -> Self { + DappId(id.into()) + } +} + +impl Into for DappId { + fn into(self) -> EthDappId { + Into::::into(self).into() + } +} + #[cfg(test)] mod tests {