HTTP Basic Authorization for WebApps server.

This commit is contained in:
Tomasz Drwięga 2016-04-08 16:11:58 +02:00
parent 8f16515d82
commit dab54cf2a7
4 changed files with 70 additions and 24 deletions

View File

@ -136,13 +136,19 @@ API and Console Options:
interface. APIS is a comma-delimited list of API interface. APIS is a comma-delimited list of API
name. Possible name are web3, eth and net. name. Possible name are web3, eth and net.
[default: web3,eth,net,personal]. [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 --webapp-port PORT Specify the port portion of the WebApps server
[default: 8080]. [default: 8080].
--webapp-interface IP Specify the hostname portion of the WebApps --webapp-interface IP Specify the hostname portion of the WebApps
server, IP should be an interface's IP address, or server, IP should be an interface's IP address, or
all (all interfaces) or local [default: local]. 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: Sealing/Mining Options:
--usd-per-tx USD Amount of USD to be paid for a basic transaction --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: bool,
flag_webapp_port: u16, flag_webapp_port: u16,
flag_webapp_interface: String, flag_webapp_interface: String,
flag_webapp_user: Option<String>,
flag_webapp_pass: Option<String>,
flag_author: String, flag_author: String,
flag_usd_per_tx: String, flag_usd_per_tx: String,
flag_usd_per_eth: String, flag_usd_per_eth: String,
@ -288,7 +296,7 @@ fn setup_rpc_server(
miner: Arc<Miner>, miner: Arc<Miner>,
url: &SocketAddr, url: &SocketAddr,
cors_domain: &str, cors_domain: &str,
apis: Vec<&str> apis: Vec<&str>,
) -> RpcServer { ) -> RpcServer {
use rpc::v1::*; use rpc::v1::*;
@ -321,7 +329,8 @@ fn setup_webapp_server(
sync: Arc<EthSync>, sync: Arc<EthSync>,
secret_store: Arc<AccountService>, secret_store: Arc<AccountService>,
miner: Arc<Miner>, miner: Arc<Miner>,
url: &str url: &str,
auth: Option<(String, String)>,
) -> WebappServer { ) -> WebappServer {
use rpc::v1::*; 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(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate());
server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate());
server.add_delegate(PersonalClient::new(&secret_store).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 { match start_result {
Err(webapp::WebappServerError::IoError(err)) => die_with_io_error(err), Err(webapp::WebappServerError::IoError(err)) => die_with_io_error(err),
Err(e) => die!("{:?}", e), Err(e) => die!("{:?}", e),
@ -351,7 +367,7 @@ fn setup_rpc_server(
_miner: Arc<Miner>, _miner: Arc<Miner>,
_url: &str, _url: &str,
_cors_domain: &str, _cors_domain: &str,
_apis: Vec<&str> _apis: Vec<&str>,
) -> ! { ) -> ! {
die!("Your Parity version has been compiled without JSON-RPC support.") die!("Your Parity version has been compiled without JSON-RPC support.")
} }
@ -365,7 +381,8 @@ fn setup_webapp_server(
_sync: Arc<EthSync>, _sync: Arc<EthSync>,
_secret_store: Arc<AccountService>, _secret_store: Arc<AccountService>,
_miner: Arc<Miner>, _miner: Arc<Miner>,
_url: &str _url: &str,
_auth: Option<(String, String)>,
) -> ! { ) -> ! {
die!("Your Parity version has been compiled without WebApps support.") die!("Your Parity version has been compiled without WebApps support.")
} }
@ -683,12 +700,24 @@ impl Configuration {
}, },
self.args.flag_webapp_port 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( Some(setup_webapp_server(
service.client(), service.client(),
sync.clone(), sync.clone(),
account_service.clone(), account_service.clone(),
miner.clone(), miner.clone(),
&url, &url,
auth,
)) ))
} else { } else {
None None

View File

@ -35,6 +35,8 @@ mod apps;
mod page; mod page;
mod router; mod router;
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
/// Http server. /// Http server.
pub struct WebappServer { pub struct WebappServer {
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
@ -53,14 +55,25 @@ impl WebappServer {
self.handler.add_delegate(delegate); self.handler.add_delegate(delegate);
} }
/// Start server asynchronously and returns result with `Listening` handle on success or an error. /// Asynchronously start server with no authentication,
pub fn start_http(&self, addr: &str, threads: usize) -> Result<Listening, WebappServerError> { /// return result with `Listening` handle on success or an error.
pub fn start_unsecure_http(&self, addr: &str, threads: usize) -> Result<Listening, WebappServerError> {
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<Listening, WebappServerError> {
self.start_http(addr, threads, HttpBasicAuth::single_user(username, password))
}
fn start_http<A: Authorization + 'static>(&self, addr: &str, threads: usize, authorization: A) -> Result<Listening, WebappServerError> {
let addr = addr.to_owned(); let addr = addr.to_owned();
let handler = self.handler.clone(); let handler = self.handler.clone();
let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null;
let rpc = ServerHandler::new(handler, cors_domain); 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)) try!(hyper::Server::http(addr.as_ref() as &str))
.handle_threads(router, threads) .handle_threads(router, threads)

View File

@ -29,7 +29,7 @@ pub enum Authorized<'a, 'b> where 'b : 'a {
} }
/// Authorization interface /// Authorization interface
pub trait Authorization { pub trait Authorization : Send + Sync {
/// Handle authorization process and return `Request` and `Response` when authorization is successful. /// 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>; fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b>;
} }

View File

@ -18,7 +18,7 @@
//! Processes request handling authorization and dispatching it to proper application. //! Processes request handling authorization and dispatching it to proper application.
mod api; mod api;
mod auth; pub mod auth;
use std::sync::Arc; use std::sync::Arc;
use hyper; use hyper;
@ -27,22 +27,22 @@ use page::Page;
use apps::Pages; use apps::Pages;
use iron::request::Url; use iron::request::Url;
use jsonrpc_http_server::ServerHandler; use jsonrpc_http_server::ServerHandler;
use self::auth::{Authorization, NoAuth, Authorized}; use self::auth::{Authorization, Authorized};
pub struct Router { pub struct Router<A: Authorization> {
auth: NoAuth, authorization: A,
rpc: ServerHandler, rpc: ServerHandler,
api: api::RestApi, api: api::RestApi,
main_page: Box<Page>, main_page: Box<Page>,
pages: Arc<Pages>, pages: Arc<Pages>,
} }
impl server::Handler for Router { impl<A: Authorization> server::Handler for Router<A> {
fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>) { 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 { 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 { match path {
Some(ref url) if self.pages.contains_key(url) => { Some(ref url) if self.pages.contains_key(url) => {
self.pages.get(url).unwrap().handle(req, res); self.pages.get(url).unwrap().handle(req, res);
@ -59,11 +59,15 @@ impl server::Handler for Router {
} }
} }
impl Router { impl<A: Authorization> Router<A> {
pub fn new(rpc: ServerHandler, main_page: Box<Page>, pages: Pages) -> Self { pub fn new(
rpc: ServerHandler,
main_page: Box<Page>,
pages: Pages,
authorization: A) -> Self {
let pages = Arc::new(pages); let pages = Arc::new(pages);
Router { Router {
auth: NoAuth, authorization: authorization,
rpc: rpc, rpc: rpc,
api: api::RestApi { pages: pages.clone() }, api: api::RestApi { pages: pages.clone() },
main_page: main_page, main_page: main_page,
@ -71,7 +75,7 @@ impl Router {
} }
} }
fn extract_url(req: &server::Request) -> Option<Url> { fn extract_url(&self, req: &server::Request) -> Option<Url> {
match req.uri { match req.uri {
uri::RequestUri::AbsoluteUri(ref url) => { uri::RequestUri::AbsoluteUri(ref url) => {
match Url::from_generic_url(url.clone()) { 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<String>, server::Request<'a, 'b>) { fn extract_request_path<'a, 'b>(&self, mut req: server::Request<'a, 'b>) -> (Option<String>, server::Request<'a, 'b>) {
let url = Router::extract_url(&req); let url = self.extract_url(&req);
match url { match url {
Some(ref url) if url.path.len() > 1 => { Some(ref url) if url.path.len() > 1 => {
let part = url.path[0].clone(); let part = url.path[0].clone();