diff --git a/parity/main.rs b/parity/main.rs index b9f95eee3..d9382e645 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -136,13 +136,19 @@ API and Console Options: interface. APIS is a comma-delimited list of API name. Possible name are web3, eth and net. [default: web3,eth,net,personal]. - -w --webapp Enable the web applications server (e.g. status page). + -w --webapp Enable the web applications server (e.g. + status page). --webapp-port PORT Specify the port portion of the WebApps server [default: 8080]. --webapp-interface IP Specify the hostname portion of the WebApps server, IP should be an interface's IP address, or all (all interfaces) or local [default: local]. - + --webapp-user USERNAME Specify username for WebApps server. It will be + used in HTTP Basic Authentication Scheme. + If --webapp-pass is not specified you will be + asked for password on startup. + --webapp-pass PASSWORD Specify password for WebApps server. Use only in + conjunction with --webapp-user. Sealing/Mining Options: --usd-per-tx USD Amount of USD to be paid for a basic transaction @@ -230,6 +236,8 @@ struct Args { flag_webapp: bool, flag_webapp_port: u16, flag_webapp_interface: String, + flag_webapp_user: Option, + flag_webapp_pass: Option, flag_author: String, flag_usd_per_tx: String, flag_usd_per_eth: String, @@ -288,7 +296,7 @@ fn setup_rpc_server( miner: Arc, url: &SocketAddr, cors_domain: &str, - apis: Vec<&str> + apis: Vec<&str>, ) -> RpcServer { use rpc::v1::*; @@ -321,7 +329,8 @@ fn setup_webapp_server( sync: Arc, secret_store: Arc, miner: Arc, - url: &str + url: &str, + auth: Option<(String, String)>, ) -> WebappServer { use rpc::v1::*; @@ -331,7 +340,14 @@ fn setup_webapp_server( server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate()); server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); server.add_delegate(PersonalClient::new(&secret_store).to_delegate()); - let start_result = server.start_http(url, ::num_cpus::get()); + let start_result = match auth { + None => { + server.start_unsecure_http(url, ::num_cpus::get()) + }, + Some((username, password)) => { + server.start_basic_auth_http(url, ::num_cpus::get(), &username, &password) + }, + }; match start_result { Err(webapp::WebappServerError::IoError(err)) => die_with_io_error(err), Err(e) => die!("{:?}", e), @@ -351,7 +367,7 @@ fn setup_rpc_server( _miner: Arc, _url: &str, _cors_domain: &str, - _apis: Vec<&str> + _apis: Vec<&str>, ) -> ! { die!("Your Parity version has been compiled without JSON-RPC support.") } @@ -365,7 +381,8 @@ fn setup_webapp_server( _sync: Arc, _secret_store: Arc, _miner: Arc, - _url: &str + _url: &str, + _auth: Option<(String, String)>, ) -> ! { die!("Your Parity version has been compiled without WebApps support.") } @@ -683,12 +700,24 @@ impl Configuration { }, self.args.flag_webapp_port ); + let auth = self.args.flag_webapp_user.as_ref().map(|username| { + let password = self.args.flag_webapp_pass.as_ref().map_or_else(|| { + use rpassword::read_password; + println!("Type password for WebApps server (user: {}): ", username); + let pass = read_password().unwrap(); + println!("OK, got it. Starting server..."); + pass + }, |pass| pass.to_owned()); + (username.to_owned(), password) + }); + Some(setup_webapp_server( service.client(), sync.clone(), account_service.clone(), miner.clone(), &url, + auth, )) } else { None diff --git a/webapp/src/lib.rs b/webapp/src/lib.rs index 35ebb4a44..ed9a13967 100644 --- a/webapp/src/lib.rs +++ b/webapp/src/lib.rs @@ -35,6 +35,8 @@ mod apps; mod page; mod router; +use router::auth::{Authorization, NoAuth, HttpBasicAuth}; + /// Http server. pub struct WebappServer { handler: Arc, @@ -53,14 +55,25 @@ impl WebappServer { self.handler.add_delegate(delegate); } - /// Start server asynchronously and returns result with `Listening` handle on success or an error. - pub fn start_http(&self, addr: &str, threads: usize) -> Result { + /// Asynchronously start server with no authentication, + /// return result with `Listening` handle on success or an error. + pub fn start_unsecure_http(&self, addr: &str, threads: usize) -> Result { + self.start_http(addr, threads, NoAuth) + } + + /// Asynchronously start server with `HTTP Basic Authentication`, + /// return result with `Listening` handle on success or an error. + pub fn start_basic_auth_http(&self, addr: &str, threads: usize, username: &str, password: &str) -> Result { + self.start_http(addr, threads, HttpBasicAuth::single_user(username, password)) + } + + fn start_http(&self, addr: &str, threads: usize, authorization: A) -> Result { let addr = addr.to_owned(); let handler = self.handler.clone(); let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; let rpc = ServerHandler::new(handler, cors_domain); - let router = router::Router::new(rpc, apps::main_page(), apps::all_pages()); + let router = router::Router::new(rpc, apps::main_page(), apps::all_pages(), authorization); try!(hyper::Server::http(addr.as_ref() as &str)) .handle_threads(router, threads) diff --git a/webapp/src/router/auth.rs b/webapp/src/router/auth.rs index 96a27f189..6122b9309 100644 --- a/webapp/src/router/auth.rs +++ b/webapp/src/router/auth.rs @@ -29,7 +29,7 @@ pub enum Authorized<'a, 'b> where 'b : 'a { } /// Authorization interface -pub trait Authorization { +pub trait Authorization : Send + Sync { /// Handle authorization process and return `Request` and `Response` when authorization is successful. fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b>; } diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs index a545d81c0..070f94a34 100644 --- a/webapp/src/router/mod.rs +++ b/webapp/src/router/mod.rs @@ -18,7 +18,7 @@ //! Processes request handling authorization and dispatching it to proper application. mod api; -mod auth; +pub mod auth; use std::sync::Arc; use hyper; @@ -27,22 +27,22 @@ use page::Page; use apps::Pages; use iron::request::Url; use jsonrpc_http_server::ServerHandler; -use self::auth::{Authorization, NoAuth, Authorized}; +use self::auth::{Authorization, Authorized}; -pub struct Router { - auth: NoAuth, +pub struct Router { + authorization: A, rpc: ServerHandler, api: api::RestApi, main_page: Box, pages: Arc, } -impl server::Handler for Router { +impl server::Handler for Router { fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>) { - let auth = self.auth.handle(req, res); + let auth = self.authorization.handle(req, res); if let Authorized::Yes(req, res) = auth { - let (path, req) = Router::extract_request_path(req); + let (path, req) = self.extract_request_path(req); match path { Some(ref url) if self.pages.contains_key(url) => { self.pages.get(url).unwrap().handle(req, res); @@ -59,11 +59,15 @@ impl server::Handler for Router { } } -impl Router { - pub fn new(rpc: ServerHandler, main_page: Box, pages: Pages) -> Self { +impl Router { + pub fn new( + rpc: ServerHandler, + main_page: Box, + pages: Pages, + authorization: A) -> Self { let pages = Arc::new(pages); Router { - auth: NoAuth, + authorization: authorization, rpc: rpc, api: api::RestApi { pages: pages.clone() }, main_page: main_page, @@ -71,7 +75,7 @@ impl Router { } } - fn extract_url(req: &server::Request) -> Option { + fn extract_url(&self, req: &server::Request) -> Option { match req.uri { uri::RequestUri::AbsoluteUri(ref url) => { match Url::from_generic_url(url.clone()) { @@ -97,8 +101,8 @@ impl Router { } } - fn extract_request_path<'a, 'b>(mut req: server::Request<'a, 'b>) -> (Option, server::Request<'a, 'b>) { - let url = Router::extract_url(&req); + fn extract_request_path<'a, 'b>(&self, mut req: server::Request<'a, 'b>) -> (Option, server::Request<'a, 'b>) { + let url = self.extract_url(&req); match url { Some(ref url) if url.path.len() > 1 => { let part = url.path[0].clone();