Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Persistent tracking of dapps (#4302)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
tomusdrw authored and gavofyork committed Jan 30, 2017
1 parent 47e1c5e commit cf348da
Show file tree
Hide file tree
Showing 15 changed files with 337 additions and 128 deletions.
19 changes: 13 additions & 6 deletions dapps/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,19 @@ impl Endpoint for RpcEndpoint {
struct MetadataExtractor;
impl HttpMetaExtractor<Metadata> for MetadataExtractor {
fn read_metadata(&self, request: &hyper::server::Request<hyper::net::HttpStream>) -> Metadata {
let dapp_id = request.headers().get::<hyper::header::Referer>()
.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::<hyper::header::Origin>()
.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,
Expand Down
21 changes: 13 additions & 8 deletions dapps/src/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -58,7 +59,7 @@ impl Deref for ServerLoop {
}
}

pub fn init_server<F, B>(process: F, remote: Remote) -> (ServerLoop, Arc<FakeRegistrar>) where
pub fn init_server<F, B>(process: F, io: MetaIoHandler<Metadata>, remote: Remote) -> (ServerLoop, Arc<FakeRegistrar>) where
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
B: Fetch,
{
Expand All @@ -70,7 +71,7 @@ pub fn init_server<F, B>(process: F, remote: Remote) -> (ServerLoop, Arc<FakeReg
// TODO [ToDr] When https://github.com/ethcore/jsonrpc/issues/26 is resolved
// this additional EventLoop wouldn't be needed, we should be able to re-use remote.
let event_loop = RpcEventLoop::spawn();
let handler = event_loop.handler(Arc::new(MetaIoHandler::default()));
let handler = event_loop.handler(Arc::new(io));
let server = process(ServerBuilder::new(
&dapps_path, registrar.clone(), remote,
))
Expand Down Expand Up @@ -100,20 +101,24 @@ pub fn serve_with_auth(user: &str, pass: &str) -> ServerLoop {
}
}

pub fn serve_with_rpc(io: MetaIoHandler<Metadata>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(None), io, Remote::new_sync()).0
}

pub fn serve_hosts(hosts: Option<Vec<String>>) -> 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<FakeRegistrar>) {
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<FakeRegistrar>) {
init_server(|builder| {
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<FakeRegistrar>) {
Expand All @@ -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)
}
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions dapps/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ mod api;
mod authorization;
mod fetch;
mod redirection;
mod rpc;
mod validation;

119 changes: 119 additions & 0 deletions dapps/src/tests/rpc.rs
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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());
}
19 changes: 15 additions & 4 deletions ethcore/src/account_provider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,18 @@ impl From<SSError> for Error {
}

/// Dapp identifier
pub type DappId = String;
#[derive(Default, Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct DappId(String);

impl From<DappId> for String {
fn from(id: DappId) -> String { id.0 }
}
impl From<String> 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")
Expand Down Expand Up @@ -181,7 +192,7 @@ impl AccountProvider {
}

/// Gets a list of dapps recently requesting accounts.
pub fn recent_dapps(&self) -> Result<Vec<DappId>, Error> {
pub fn recent_dapps(&self) -> Result<HashMap<DappId, u64>, Error> {
Ok(self.dapps_settings.read().recent_dapps())
}

Expand Down Expand Up @@ -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};

Expand Down Expand Up @@ -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();

Expand Down
Loading

0 comments on commit cf348da

Please sign in to comment.