HTTP Authorization support in router
This commit is contained in:
parent
f9f11b6f74
commit
8f16515d82
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -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)",
|
||||
]
|
||||
|
@ -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}
|
||||
|
||||
|
120
webapp/src/router/auth.rs
Normal file
120
webapp/src/router/auth.rs
Normal file
@ -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://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<String, String>,
|
||||
}
|
||||
|
||||
/// 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::<header::Authorization<header::Basic>>() {
|
||||
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()]);
|
||||
}
|
||||
}
|
||||
|
@ -15,25 +15,33 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<Page>,
|
||||
pages: Arc<Pages>,
|
||||
}
|
||||
|
||||
impl hyper::server::Handler for Router {
|
||||
fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, res: hyper::server::Response<'a>) {
|
||||
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) => {
|
||||
@ -48,12 +56,14 @@ impl hyper::server::Handler for Router {
|
||||
_ => self.main_page.handle(req, res),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Router {
|
||||
pub fn new(rpc: ServerHandler, main_page: Box<Page>, 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<Url> {
|
||||
fn extract_url(req: &server::Request) -> Option<Url> {
|
||||
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::<hyper::header::Host>() {
|
||||
let url_string = match req.headers.get::<header::Host>() {
|
||||
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<String>, hyper::server::Request<'a, 'b>) {
|
||||
fn extract_request_path<'a, 'b>(mut req: server::Request<'a, 'b>) -> (Option<String>, 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)
|
||||
},
|
||||
_ => {
|
||||
|
Loading…
Reference in New Issue
Block a user