2019-01-07 11:33:07 +01:00
|
|
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
|
|
// This file is part of Parity Ethereum.
|
2017-05-24 12:24:07 +02:00
|
|
|
|
2019-01-07 11:33:07 +01:00
|
|
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
2017-05-24 12:24:07 +02:00
|
|
|
// 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.
|
|
|
|
|
2019-01-07 11:33:07 +01:00
|
|
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
2017-05-24 12:24:07 +02:00
|
|
|
// 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
|
2019-01-07 11:33:07 +01:00
|
|
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
2017-05-24 12:24:07 +02:00
|
|
|
|
|
|
|
//! Parity-specific metadata extractors.
|
|
|
|
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use authcodes;
|
|
|
|
use http_common::HttpMetaExtractor;
|
|
|
|
use ipc;
|
|
|
|
use jsonrpc_core as core;
|
2018-10-22 09:40:50 +02:00
|
|
|
use jsonrpc_core::futures::future::Either;
|
2017-05-24 12:24:07 +02:00
|
|
|
use jsonrpc_pubsub::Session;
|
|
|
|
use ws;
|
2018-01-10 13:35:18 +01:00
|
|
|
use ethereum_types::H256;
|
2017-05-24 12:24:07 +02:00
|
|
|
|
|
|
|
use v1::{Metadata, Origin};
|
|
|
|
use v1::informant::RpcStats;
|
|
|
|
|
|
|
|
/// Common HTTP & IPC metadata extractor.
|
|
|
|
pub struct RpcExtractor;
|
|
|
|
|
|
|
|
impl HttpMetaExtractor for RpcExtractor {
|
|
|
|
type Metadata = Metadata;
|
|
|
|
|
2018-08-07 14:52:23 +02:00
|
|
|
fn read_metadata(&self, origin: Option<String>, user_agent: Option<String>) -> Metadata {
|
2018-02-07 16:13:54 +01:00
|
|
|
Metadata {
|
2018-08-07 14:52:23 +02:00
|
|
|
origin: Origin::Rpc(
|
|
|
|
format!("{} / {}",
|
2019-03-22 12:01:11 +01:00
|
|
|
origin.unwrap_or_else(|| "unknown origin".to_string()),
|
|
|
|
user_agent.unwrap_or_else(|| "unknown agent".to_string()))
|
2018-08-07 14:52:23 +02:00
|
|
|
),
|
2018-02-07 16:13:54 +01:00
|
|
|
session: None,
|
|
|
|
}
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ipc::MetaExtractor<Metadata> for RpcExtractor {
|
2017-06-09 12:20:37 +02:00
|
|
|
fn extract(&self, req: &ipc::RequestContext) -> Metadata {
|
2018-02-07 16:13:54 +01:00
|
|
|
Metadata {
|
|
|
|
origin: Origin::Ipc(req.session_id.into()),
|
|
|
|
session: Some(Arc::new(Session::new(req.sender.clone()))),
|
|
|
|
}
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// WebSockets server metadata extractor and request middleware.
|
|
|
|
pub struct WsExtractor {
|
|
|
|
authcodes_path: Option<PathBuf>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WsExtractor {
|
|
|
|
/// Creates new `WsExtractor` with given authcodes path.
|
|
|
|
pub fn new(path: Option<&Path>) -> Self {
|
|
|
|
WsExtractor {
|
2019-03-22 12:01:11 +01:00
|
|
|
authcodes_path: path.map(ToOwned::to_owned),
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ws::MetaExtractor<Metadata> for WsExtractor {
|
|
|
|
fn extract(&self, req: &ws::RequestContext) -> Metadata {
|
|
|
|
let id = req.session_id as u64;
|
2017-06-09 12:20:37 +02:00
|
|
|
|
2018-02-07 16:13:54 +01:00
|
|
|
let origin = match self.authcodes_path {
|
2017-05-24 12:24:07 +02:00
|
|
|
Some(ref path) => {
|
2017-06-19 18:35:56 +02:00
|
|
|
let authorization = req.protocols.get(0).and_then(|p| auth_token_hash(&path, p, true));
|
2017-05-24 12:24:07 +02:00
|
|
|
match authorization {
|
2019-03-22 12:01:11 +01:00
|
|
|
Some(id) => Origin::Signer { session: id },
|
2018-08-07 14:52:23 +02:00
|
|
|
None => Origin::Ws { session: id.into() },
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
},
|
2018-08-07 14:52:23 +02:00
|
|
|
None => Origin::Ws { session: id.into() },
|
2017-05-24 12:24:07 +02:00
|
|
|
};
|
2018-02-07 16:13:54 +01:00
|
|
|
let session = Some(Arc::new(Session::new(req.sender())));
|
|
|
|
Metadata {
|
|
|
|
origin,
|
|
|
|
session,
|
|
|
|
}
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ws::RequestMiddleware for WsExtractor {
|
|
|
|
fn process(&self, req: &ws::ws::Request) -> ws::MiddlewareAction {
|
|
|
|
use self::ws::ws::Response;
|
|
|
|
|
2019-01-11 16:55:03 +01:00
|
|
|
// Reply with 200 OK to HEAD requests.
|
2017-05-24 12:24:07 +02:00
|
|
|
if req.method() == "HEAD" {
|
2019-01-11 16:55:03 +01:00
|
|
|
let mut response = Response::new(200, "OK", vec![]);
|
2017-05-24 12:24:07 +02:00
|
|
|
add_security_headers(&mut response);
|
|
|
|
return Some(response).into();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Display WS info.
|
|
|
|
if req.header("sec-websocket-key").is_none() {
|
2019-01-11 16:55:03 +01:00
|
|
|
let mut response = Response::new(200, "OK", b"WebSocket interface is active. Open WS connection to access RPC.".to_vec());
|
2017-05-24 12:24:07 +02:00
|
|
|
add_security_headers(&mut response);
|
|
|
|
return Some(response).into();
|
|
|
|
}
|
|
|
|
|
|
|
|
// If protocol is provided it needs to be valid.
|
|
|
|
let protocols = req.protocols().ok().unwrap_or_else(Vec::new);
|
|
|
|
if let Some(ref path) = self.authcodes_path {
|
|
|
|
if protocols.len() == 1 {
|
2017-06-19 18:35:56 +02:00
|
|
|
let authorization = auth_token_hash(&path, protocols[0], false);
|
2017-05-24 12:24:07 +02:00
|
|
|
if authorization.is_none() {
|
|
|
|
warn!(
|
|
|
|
"Blocked connection from {} using invalid token.",
|
|
|
|
req.header("origin").and_then(|e| ::std::str::from_utf8(e).ok()).unwrap_or("Unknown Origin")
|
|
|
|
);
|
2019-01-11 16:55:03 +01:00
|
|
|
let mut response = Response::new(403, "Forbidden", vec![]);
|
2017-05-24 12:24:07 +02:00
|
|
|
add_security_headers(&mut response);
|
|
|
|
return Some(response).into();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise just proceed.
|
|
|
|
ws::MiddlewareAction::Proceed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_security_headers(res: &mut ws::ws::Response) {
|
2017-10-15 15:11:07 +02:00
|
|
|
let headers = res.headers_mut();
|
2017-05-24 12:24:07 +02:00
|
|
|
headers.push(("X-Frame-Options".into(), b"SAMEORIGIN".to_vec()));
|
|
|
|
headers.push(("X-XSS-Protection".into(), b"1; mode=block".to_vec()));
|
|
|
|
headers.push(("X-Content-Type-Options".into(), b"nosniff".to_vec()));
|
2017-06-28 09:12:02 +02:00
|
|
|
headers.push(("Content-Security-Policy".into(),
|
|
|
|
b"default-src 'self';form-action 'none';block-all-mixed-content;sandbox allow-scripts;".to_vec()
|
|
|
|
));
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
|
2017-06-19 18:35:56 +02:00
|
|
|
fn auth_token_hash(codes_path: &Path, protocol: &str, save_file: bool) -> Option<H256> {
|
2017-05-24 12:24:07 +02:00
|
|
|
let mut split = protocol.split('_');
|
|
|
|
let auth = split.next().and_then(|v| v.parse().ok());
|
|
|
|
let time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok());
|
|
|
|
|
|
|
|
if let (Some(auth), Some(time)) = (auth, time) {
|
|
|
|
// Check if the code is valid
|
|
|
|
return authcodes::AuthCodes::from_file(codes_path)
|
|
|
|
.ok()
|
|
|
|
.and_then(|mut codes| {
|
|
|
|
// remove old tokens
|
|
|
|
codes.clear_garbage();
|
|
|
|
|
|
|
|
let res = codes.is_valid(&auth, time);
|
2017-06-19 18:35:56 +02:00
|
|
|
|
|
|
|
if save_file {
|
|
|
|
// make sure to save back authcodes - it might have been modified
|
|
|
|
if codes.to_file(codes_path).is_err() {
|
|
|
|
warn!(target: "signer", "Couldn't save authorization codes to file.");
|
|
|
|
}
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if res {
|
|
|
|
Some(auth)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// WebSockets RPC usage statistics.
|
|
|
|
pub struct WsStats {
|
|
|
|
stats: Arc<RpcStats>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WsStats {
|
|
|
|
/// Creates new WS usage tracker.
|
|
|
|
pub fn new(stats: Arc<RpcStats>) -> Self {
|
|
|
|
WsStats {
|
2019-03-22 12:01:11 +01:00
|
|
|
stats,
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ws::SessionStats for WsStats {
|
|
|
|
fn open_session(&self, _id: ws::SessionId) {
|
|
|
|
self.stats.open_session()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn close_session(&self, _id: ws::SessionId) {
|
|
|
|
self.stats.close_session()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// WebSockets middleware dispatching requests to different handles dependning on metadata.
|
|
|
|
pub struct WsDispatcher<M: core::Middleware<Metadata>> {
|
|
|
|
full_handler: core::MetaIoHandler<Metadata, M>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<M: core::Middleware<Metadata>> WsDispatcher<M> {
|
|
|
|
/// Create new `WsDispatcher` with given full handler.
|
|
|
|
pub fn new(full_handler: core::MetaIoHandler<Metadata, M>) -> Self {
|
|
|
|
WsDispatcher {
|
2019-03-22 12:01:11 +01:00
|
|
|
full_handler,
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<M: core::Middleware<Metadata>> core::Middleware<Metadata> for WsDispatcher<M> {
|
2018-10-22 09:40:50 +02:00
|
|
|
type Future = Either<
|
2018-11-05 15:39:51 +01:00
|
|
|
core::FutureRpcResult<M::Future, M::CallFuture>,
|
2017-07-11 12:22:19 +02:00
|
|
|
core::FutureResponse,
|
|
|
|
>;
|
2018-11-05 15:39:51 +01:00
|
|
|
type CallFuture = core::middleware::NoopCallFuture;
|
2017-07-11 12:22:19 +02:00
|
|
|
|
2018-10-22 09:40:50 +02:00
|
|
|
fn on_request<F, X>(&self, request: core::Request, meta: Metadata, process: F)
|
|
|
|
-> Either<Self::Future, X>
|
|
|
|
where
|
2017-07-11 12:22:19 +02:00
|
|
|
F: FnOnce(core::Request, Metadata) -> X,
|
|
|
|
X: core::futures::Future<Item=Option<core::Response>, Error=()> + Send + 'static,
|
2017-05-24 12:24:07 +02:00
|
|
|
{
|
|
|
|
let use_full = match &meta.origin {
|
2019-03-22 12:01:11 +01:00
|
|
|
Origin::Signer { .. } => true,
|
2017-05-24 12:24:07 +02:00
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
|
|
|
|
if use_full {
|
2018-10-22 09:40:50 +02:00
|
|
|
Either::A(Either::A(self.full_handler.handle_rpc_request(request, meta)))
|
2017-05-24 12:24:07 +02:00
|
|
|
} else {
|
2018-10-22 09:40:50 +02:00
|
|
|
Either::B(process(request, meta))
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::RpcExtractor;
|
|
|
|
use {HttpMetaExtractor, Origin};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_extract_rpc_origin() {
|
|
|
|
// given
|
|
|
|
let extractor = RpcExtractor;
|
|
|
|
|
|
|
|
// when
|
2018-08-07 14:52:23 +02:00
|
|
|
let meta1 = extractor.read_metadata(None, None);
|
|
|
|
let meta2 = extractor.read_metadata(None, Some("http://parity.io".to_owned()));
|
|
|
|
let meta3 = extractor.read_metadata(None, Some("http://parity.io".to_owned()));
|
2017-05-24 12:24:07 +02:00
|
|
|
|
|
|
|
// then
|
2018-08-07 14:52:23 +02:00
|
|
|
assert_eq!(meta1.origin, Origin::Rpc("unknown origin / unknown agent".into()));
|
|
|
|
assert_eq!(meta2.origin, Origin::Rpc("unknown origin / http://parity.io".into()));
|
|
|
|
assert_eq!(meta3.origin, Origin::Rpc("unknown origin / http://parity.io".into()));
|
2017-05-24 12:24:07 +02:00
|
|
|
}
|
|
|
|
}
|