diff --git a/Cargo.lock b/Cargo.lock
index 210b23666..f0f64af94 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -321,7 +321,7 @@ dependencies = [
"jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git?branch=old-hyper)",
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "parity-status 0.1.4 (git+https://github.com/tomusdrw/parity-status.git)",
+ "parity-status 0.1.5 (git+https://github.com/tomusdrw/parity-status.git)",
"parity-wallet 0.1.0 (git+https://github.com/tomusdrw/parity-wallet.git)",
"parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)",
]
@@ -727,8 +727,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "parity-status"
-version = "0.1.4"
-source = "git+https://github.com/tomusdrw/parity-status.git#380d13c8aafc3847a731968a6532edb09c78f2cf"
+version = "0.1.5"
+source = "git+https://github.com/tomusdrw/parity-status.git#6a075228e9248055a37c55dec41461856f5a9f19"
dependencies = [
"parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)",
]
diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml
index 59452f32d..126c5fbfb 100644
--- a/webapp/Cargo.toml
+++ b/webapp/Cargo.toml
@@ -17,7 +17,7 @@ ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" }
parity-webapp = { git = "https://github.com/tomusdrw/parity-webapp.git" }
# List of apps
-parity-status = { git = "https://github.com/tomusdrw/parity-status.git", version = "0.1.4" }
+parity-status = { git = "https://github.com/tomusdrw/parity-status.git", version = "0.1.5" }
parity-wallet = { git = "https://github.com/tomusdrw/parity-wallet.git", optional = true }
clippy = { version = "0.0.61", optional = true}
diff --git a/webapp/src/router/auth.rs b/webapp/src/router/auth.rs
new file mode 100644
index 000000000..96a27f189
--- /dev/null
+++ b/webapp/src/router/auth.rs
@@ -0,0 +1,120 @@
+// Copyright 2015, 2016 Ethcore (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 Authorization implementations
+
+use std::collections::HashMap;
+use hyper::{header, server};
+use hyper::status::StatusCode;
+
+/// Authorization result
+pub enum Authorized<'a, 'b> where 'b : 'a {
+ /// Authorization was successful. Request and Response are returned for further processing.
+ Yes(server::Request<'a, 'b>, server::Response<'a>),
+ /// Unsuccessful authorization. Request and Response has been consumed.
+ No,
+}
+
+/// Authorization interface
+pub trait Authorization {
+ /// 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>;
+}
+
+/// HTTP Basic Authorization handler
+pub struct HttpBasicAuth {
+ users: HashMap,
+}
+
+/// No-authorization implementation (authorization disabled)
+pub struct NoAuth;
+
+impl Authorization for NoAuth {
+ fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b> {
+ Authorized::Yes(req, res)
+ }
+}
+
+impl Authorization for HttpBasicAuth {
+
+ fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b> {
+ let auth = self.check_auth(&req);
+
+ match auth {
+ Access::Denied => {
+ self.respond_with_unauthorized(res);
+ Authorized::No
+ },
+ Access::AuthRequired => {
+ self.respond_with_auth_required(res);
+ Authorized::No
+ },
+ Access::Granted => {
+ Authorized::Yes(req, res)
+ },
+ }
+ }
+}
+
+enum Access {
+ Granted,
+ Denied,
+ AuthRequired,
+}
+
+impl HttpBasicAuth {
+ /// Creates `HttpBasicAuth` instance with only one user.
+ pub fn single_user(username: &str, password: &str) -> Self {
+ let mut users = HashMap::new();
+ users.insert(username.to_owned(), password.to_owned());
+ HttpBasicAuth {
+ users: users
+ }
+ }
+
+ fn is_authorized(&self, username: &str, password: &str) -> bool {
+ self.users.get(&username.to_owned()).map_or(false, |pass| pass == password)
+ }
+
+ fn check_auth(&self, req: &server::Request) -> Access {
+ match req.headers.get::>() {
+ Some(&header::Authorization(header::Basic { ref username, password: Some(ref password) })) => {
+ if self.is_authorized(username, password) {
+ Access::Granted
+ } else {
+ Access::Denied
+ }
+ },
+ Some(&header::Authorization(header::Basic { username: _, password: None })) => {
+ Access::Denied
+ },
+ None => {
+ Access::AuthRequired
+ },
+ }
+ }
+
+ fn respond_with_unauthorized(&self, mut res: server::Response) {
+ *res.status_mut() = StatusCode::Unauthorized;
+ let _ = res.send(b"Unauthorized");
+ }
+
+ fn respond_with_auth_required(&self, mut res: server::Response) {
+ *res.status_mut() = StatusCode::Unauthorized;
+ res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]);
+ }
+}
+
diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs
index bd0d2ff18..a545d81c0 100644
--- a/webapp/src/router/mod.rs
+++ b/webapp/src/router/mod.rs
@@ -15,37 +15,46 @@
// along with Parity. If not, see .
//! Router implementation
+//! Processes request handling authorization and dispatching it to proper application.
+
+mod api;
+mod auth;
use std::sync::Arc;
use hyper;
+use hyper::{server, uri, header};
use page::Page;
use apps::Pages;
use iron::request::Url;
use jsonrpc_http_server::ServerHandler;
-
-mod api;
+use self::auth::{Authorization, NoAuth, Authorized};
pub struct Router {
+ auth: NoAuth,
rpc: ServerHandler,
api: api::RestApi,
main_page: Box,
pages: Arc,
}
-impl hyper::server::Handler for Router {
- fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, res: hyper::server::Response<'a>) {
- let (path, req) = Router::extract_request_path(req);
- match path {
- Some(ref url) if self.pages.contains_key(url) => {
- self.pages.get(url).unwrap().handle(req, res);
- },
- Some(ref url) if url == "api" => {
- self.api.handle(req, res);
- },
- _ if req.method == hyper::method::Method::Post => {
- self.rpc.handle(req, res)
- },
- _ => self.main_page.handle(req, res),
+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);
+
+ if let Authorized::Yes(req, res) = auth {
+ let (path, req) = Router::extract_request_path(req);
+ match path {
+ Some(ref url) if self.pages.contains_key(url) => {
+ self.pages.get(url).unwrap().handle(req, res);
+ },
+ Some(ref url) if url == "api" => {
+ self.api.handle(req, res);
+ },
+ _ if req.method == hyper::method::Method::Post => {
+ self.rpc.handle(req, res)
+ },
+ _ => self.main_page.handle(req, res),
+ }
}
}
}
@@ -54,6 +63,7 @@ impl Router {
pub fn new(rpc: ServerHandler, main_page: Box, pages: Pages) -> Self {
let pages = Arc::new(pages);
Router {
+ auth: NoAuth,
rpc: rpc,
api: api::RestApi { pages: pages.clone() },
main_page: main_page,
@@ -61,17 +71,17 @@ impl Router {
}
}
- fn extract_url(req: &hyper::server::Request) -> Option {
+ fn extract_url(req: &server::Request) -> Option {
match req.uri {
- hyper::uri::RequestUri::AbsoluteUri(ref url) => {
+ uri::RequestUri::AbsoluteUri(ref url) => {
match Url::from_generic_url(url.clone()) {
Ok(url) => Some(url),
_ => None,
}
},
- hyper::uri::RequestUri::AbsolutePath(ref path) => {
+ uri::RequestUri::AbsolutePath(ref path) => {
// Attempt to prepend the Host header (mandatory in HTTP/1.1)
- let url_string = match req.headers.get::() {
+ let url_string = match req.headers.get::() {
Some(ref host) => {
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
},
@@ -87,18 +97,18 @@ impl Router {
}
}
- fn extract_request_path<'a, 'b>(mut req: hyper::server::Request<'a, 'b>) -> (Option, hyper::server::Request<'a, 'b>) {
+ fn extract_request_path<'a, 'b>(mut req: server::Request<'a, 'b>) -> (Option, server::Request<'a, 'b>) {
let url = Router::extract_url(&req);
match url {
Some(ref url) if url.path.len() > 1 => {
let part = url.path[0].clone();
let url = url.path[1..].join("/");
- req.uri = hyper::uri::RequestUri::AbsolutePath(url);
+ req.uri = uri::RequestUri::AbsolutePath(url);
(Some(part), req)
},
Some(url) => {
let url = url.path.join("/");
- req.uri = hyper::uri::RequestUri::AbsolutePath(url);
+ req.uri = uri::RequestUri::AbsolutePath(url);
(None, req)
},
_ => {