Implement Basic Auth for API and Owner API (#1566)
* Add api_secret * Add to base64 method * Add basic auth in API * Add Basic Auth to owner API * Add flag to enable disable basic auth * Add .api_secret file
This commit is contained in:
committed by
hashmap
parent
acec59e249
commit
62fd8f2124
@@ -0,0 +1,72 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use futures::future::ok;
|
||||
use hyper::header::{HeaderValue, AUTHORIZATION, WWW_AUTHENTICATE};
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use router::{Handler, HandlerObj, ResponseFuture};
|
||||
|
||||
// Basic Authentication Middleware
|
||||
pub struct BasicAuthMiddleware {
|
||||
api_basic_auth: String,
|
||||
basic_realm: String,
|
||||
}
|
||||
|
||||
impl BasicAuthMiddleware {
|
||||
pub fn new(api_basic_auth: String, basic_realm: String) -> BasicAuthMiddleware {
|
||||
BasicAuthMiddleware {
|
||||
api_basic_auth,
|
||||
basic_realm,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler for BasicAuthMiddleware {
|
||||
fn call(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
mut handlers: Box<Iterator<Item = HandlerObj>>,
|
||||
) -> ResponseFuture {
|
||||
if req.headers().contains_key(AUTHORIZATION) {
|
||||
if req.headers()[AUTHORIZATION] == self.api_basic_auth {
|
||||
handlers.next().unwrap().call(req, handlers)
|
||||
} else {
|
||||
// Forbidden 403
|
||||
forbidden_response()
|
||||
}
|
||||
} else {
|
||||
// Unauthorized 401
|
||||
unauthorized_response(&self.basic_realm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unauthorized_response(basic_realm: &str) -> ResponseFuture {
|
||||
let response = Response::builder()
|
||||
.status(StatusCode::UNAUTHORIZED)
|
||||
.header(
|
||||
WWW_AUTHENTICATE,
|
||||
HeaderValue::from_str(basic_realm).unwrap(),
|
||||
).body(Body::empty())
|
||||
.unwrap();
|
||||
Box::new(ok(response))
|
||||
}
|
||||
|
||||
fn forbidden_response() -> ResponseFuture {
|
||||
let response = Response::builder()
|
||||
.status(StatusCode::FORBIDDEN)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
Box::new(ok(response))
|
||||
}
|
||||
+39
-20
@@ -16,7 +16,7 @@
|
||||
|
||||
use failure::{Fail, ResultExt};
|
||||
use http::uri::{InvalidUri, Uri};
|
||||
use hyper::header::{ACCEPT, USER_AGENT};
|
||||
use hyper::header::{ACCEPT, AUTHORIZATION, USER_AGENT};
|
||||
use hyper::rt::{Future, Stream};
|
||||
use hyper::{Body, Client, Request};
|
||||
use hyper_tls;
|
||||
@@ -27,27 +27,28 @@ use futures::future::{err, ok, Either};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use rest::{Error, ErrorKind};
|
||||
use util::to_base64;
|
||||
|
||||
pub type ClientResponseFuture<T> = Box<Future<Item = T, Error = Error> + Send>;
|
||||
|
||||
/// Helper function to easily issue a HTTP GET request against a given URL that
|
||||
/// returns a JSON object. Handles request building, JSON deserialization and
|
||||
/// response code checking.
|
||||
pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
|
||||
pub fn get<'a, T>(url: &'a str, api_secret: Option<String>) -> Result<T, Error>
|
||||
where
|
||||
for<'de> T: Deserialize<'de>,
|
||||
{
|
||||
handle_request(build_request(url, "GET", None)?)
|
||||
handle_request(build_request(url, "GET", api_secret, None)?)
|
||||
}
|
||||
|
||||
/// Helper function to easily issue an async HTTP GET request against a given
|
||||
/// URL that returns a future. Handles request building, JSON deserialization
|
||||
/// and response code checking.
|
||||
pub fn get_async<'a, T>(url: &'a str) -> ClientResponseFuture<T>
|
||||
pub fn get_async<'a, T>(url: &'a str, api_secret: Option<String>) -> ClientResponseFuture<T>
|
||||
where
|
||||
for<'de> T: Deserialize<'de> + Send + 'static,
|
||||
{
|
||||
match build_request(url, "GET", None) {
|
||||
match build_request(url, "GET", api_secret, None) {
|
||||
Ok(req) => Box::new(handle_request_async(req)),
|
||||
Err(e) => Box::new(err(e)),
|
||||
}
|
||||
@@ -57,12 +58,12 @@ where
|
||||
/// object as body on a given URL that returns a JSON object. Handles request
|
||||
/// building, JSON serialization and deserialization, and response code
|
||||
/// checking.
|
||||
pub fn post<IN, OUT>(url: &str, input: &IN) -> Result<OUT, Error>
|
||||
pub fn post<IN, OUT>(url: &str, api_secret: Option<String>, input: &IN) -> Result<OUT, Error>
|
||||
where
|
||||
IN: Serialize,
|
||||
for<'de> OUT: Deserialize<'de>,
|
||||
{
|
||||
let req = create_post_request(url, input)?;
|
||||
let req = create_post_request(url, api_secret, input)?;
|
||||
handle_request(req)
|
||||
}
|
||||
|
||||
@@ -70,13 +71,17 @@ where
|
||||
/// provided JSON object as body on a given URL that returns a future. Handles
|
||||
/// request building, JSON serialization and deserialization, and response code
|
||||
/// checking.
|
||||
pub fn post_async<IN, OUT>(url: &str, input: &IN) -> ClientResponseFuture<OUT>
|
||||
pub fn post_async<IN, OUT>(
|
||||
url: &str,
|
||||
input: &IN,
|
||||
api_secret: Option<String>,
|
||||
) -> ClientResponseFuture<OUT>
|
||||
where
|
||||
IN: Serialize,
|
||||
OUT: Send + 'static,
|
||||
for<'de> OUT: Deserialize<'de>,
|
||||
{
|
||||
match create_post_request(url, input) {
|
||||
match create_post_request(url, api_secret, input) {
|
||||
Ok(req) => Box::new(handle_request_async(req)),
|
||||
Err(e) => Box::new(err(e)),
|
||||
}
|
||||
@@ -86,11 +91,11 @@ where
|
||||
/// object as body on a given URL that returns nothing. Handles request
|
||||
/// building, JSON serialization, and response code
|
||||
/// checking.
|
||||
pub fn post_no_ret<IN>(url: &str, input: &IN) -> Result<(), Error>
|
||||
pub fn post_no_ret<IN>(url: &str, api_secret: Option<String>, input: &IN) -> Result<(), Error>
|
||||
where
|
||||
IN: Serialize,
|
||||
{
|
||||
let req = create_post_request(url, input)?;
|
||||
let req = create_post_request(url, api_secret, input)?;
|
||||
send_request(req)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -99,11 +104,15 @@ where
|
||||
/// provided JSON object as body on a given URL that returns a future. Handles
|
||||
/// request building, JSON serialization and deserialization, and response code
|
||||
/// checking.
|
||||
pub fn post_no_ret_async<IN>(url: &str, input: &IN) -> ClientResponseFuture<()>
|
||||
pub fn post_no_ret_async<IN>(
|
||||
url: &str,
|
||||
api_secret: Option<String>,
|
||||
input: &IN,
|
||||
) -> ClientResponseFuture<()>
|
||||
where
|
||||
IN: Serialize,
|
||||
{
|
||||
match create_post_request(url, input) {
|
||||
match create_post_request(url, api_secret, input) {
|
||||
Ok(req) => Box::new(send_request_async(req).and_then(|_| ok(()))),
|
||||
Err(e) => Box::new(err(e)),
|
||||
}
|
||||
@@ -112,13 +121,21 @@ where
|
||||
fn build_request<'a>(
|
||||
url: &'a str,
|
||||
method: &str,
|
||||
api_secret: Option<String>,
|
||||
body: Option<String>,
|
||||
) -> Result<Request<Body>, Error> {
|
||||
let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| {
|
||||
e.context(ErrorKind::Argument(format!("Invalid url {}", url)))
|
||||
.into()
|
||||
})?;
|
||||
Request::builder()
|
||||
let mut builder = Request::builder();
|
||||
if api_secret.is_some() {
|
||||
let basic_auth =
|
||||
"Basic ".to_string() + &to_base64(&("grin:".to_string() + &api_secret.unwrap()));
|
||||
builder.header(AUTHORIZATION, basic_auth);
|
||||
}
|
||||
|
||||
builder
|
||||
.method(method)
|
||||
.uri(uri)
|
||||
.header(USER_AGENT, "grin-client")
|
||||
@@ -126,20 +143,23 @@ fn build_request<'a>(
|
||||
.body(match body {
|
||||
None => Body::empty(),
|
||||
Some(json) => json.into(),
|
||||
})
|
||||
.map_err(|e| {
|
||||
}).map_err(|e| {
|
||||
ErrorKind::RequestError(format!("Bad request {} {}: {}", method, url, e)).into()
|
||||
})
|
||||
}
|
||||
|
||||
fn create_post_request<IN>(url: &str, input: &IN) -> Result<Request<Body>, Error>
|
||||
fn create_post_request<IN>(
|
||||
url: &str,
|
||||
api_secret: Option<String>,
|
||||
input: &IN,
|
||||
) -> Result<Request<Body>, Error>
|
||||
where
|
||||
IN: Serialize,
|
||||
{
|
||||
let json = serde_json::to_string(input).context(ErrorKind::Internal(
|
||||
"Could not serialize data to JSON".to_owned(),
|
||||
))?;
|
||||
build_request(url, "POST", Some(json))
|
||||
build_request(url, "POST", api_secret, Some(json))
|
||||
}
|
||||
|
||||
fn handle_request<T>(req: Request<Body>) -> Result<T, Error>
|
||||
@@ -183,8 +203,7 @@ fn send_request_async(req: Request<Body>) -> Box<Future<Item = String, Error = E
|
||||
.map_err(|e| {
|
||||
ErrorKind::RequestError(format!("Cannot read response body: {}", e))
|
||||
.into()
|
||||
})
|
||||
.concat2()
|
||||
}).concat2()
|
||||
.and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())),
|
||||
)
|
||||
}
|
||||
|
||||
+11
-3
@@ -21,6 +21,7 @@ use futures::future::ok;
|
||||
use futures::Future;
|
||||
use hyper::{Body, Request, StatusCode};
|
||||
|
||||
use auth::BasicAuthMiddleware;
|
||||
use chain;
|
||||
use core::core::hash::{Hash, Hashed};
|
||||
use core::core::{OutputFeatures, OutputIdentifier, Transaction};
|
||||
@@ -795,10 +796,17 @@ pub fn start_rest_apis(
|
||||
chain: Weak<chain::Chain>,
|
||||
tx_pool: Weak<RwLock<pool::TransactionPool>>,
|
||||
peers: Weak<p2p::Peers>,
|
||||
api_secret: Option<String>,
|
||||
) -> bool {
|
||||
let mut apis = ApiServer::new();
|
||||
|
||||
let router = build_router(chain, tx_pool, peers).expect("unable to build API router");
|
||||
let mut router = build_router(chain, tx_pool, peers).expect("unable to build API router");
|
||||
if api_secret.is_some() {
|
||||
let api_basic_auth =
|
||||
"Basic ".to_string() + &util::to_base64(&("grin:".to_string() + &api_secret.unwrap()));
|
||||
let basic_realm = "Basic realm=GrinAPI".to_string();
|
||||
let basic_auth_middleware = Arc::new(BasicAuthMiddleware::new(api_basic_auth, basic_realm));
|
||||
router.add_middleware(basic_auth_middleware);
|
||||
}
|
||||
|
||||
info!(LOGGER, "Starting HTTP API server at {}.", addr);
|
||||
let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address");
|
||||
@@ -875,7 +883,7 @@ pub fn build_router(
|
||||
};
|
||||
|
||||
let mut router = Router::new();
|
||||
// example how we can use midlleware
|
||||
|
||||
router.add_route("/v1/", Arc::new(index_handler))?;
|
||||
router.add_route("/v1/blocks/*", Arc::new(block_handler))?;
|
||||
router.add_route("/v1/headers/*", Arc::new(header_handler))?;
|
||||
|
||||
@@ -41,6 +41,7 @@ extern crate tokio;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_tls;
|
||||
|
||||
pub mod auth;
|
||||
pub mod client;
|
||||
mod handlers;
|
||||
mod rest;
|
||||
@@ -48,6 +49,7 @@ mod router;
|
||||
mod types;
|
||||
mod web;
|
||||
|
||||
pub use auth::BasicAuthMiddleware;
|
||||
pub use handlers::start_rest_apis;
|
||||
pub use rest::*;
|
||||
pub use router::*;
|
||||
|
||||
+4
-8
@@ -132,8 +132,7 @@ impl ApiServer {
|
||||
.map_err(|e| eprintln!("HTTP API server error: {}", e));
|
||||
|
||||
rt::run(server);
|
||||
})
|
||||
.map_err(|_| ErrorKind::Internal("failed to spawn API thread".to_string()).into())
|
||||
}).map_err(|_| ErrorKind::Internal("failed to spawn API thread".to_string()).into())
|
||||
}
|
||||
|
||||
/// Starts the TLS ApiServer at the provided address.
|
||||
@@ -165,15 +164,13 @@ impl ApiServer {
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}),
|
||||
router,
|
||||
)
|
||||
.then(|res| match res {
|
||||
).then(|res| match res {
|
||||
Ok(conn) => Ok(Some(conn)),
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.for_each(|conn_opt| {
|
||||
}).for_each(|conn_opt| {
|
||||
if let Some(conn) = conn_opt {
|
||||
rt::spawn(
|
||||
conn.and_then(|c| c.map_err(|e| panic!("Hyper error {}", e)))
|
||||
@@ -184,8 +181,7 @@ impl ApiServer {
|
||||
});
|
||||
|
||||
rt::run(server);
|
||||
})
|
||||
.map_err(|_| ErrorKind::Internal("failed to spawn API thread".to_string()).into())
|
||||
}).map_err(|_| ErrorKind::Internal("failed to spawn API thread".to_string()).into())
|
||||
}
|
||||
|
||||
/// Stops the API server, it panics in case of error
|
||||
|
||||
+15
-2
@@ -1,3 +1,17 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use futures::future;
|
||||
use hyper;
|
||||
use hyper::rt::Future;
|
||||
@@ -133,8 +147,7 @@ impl Router {
|
||||
.find(|&id| {
|
||||
let node_key = self.node(*id).key;
|
||||
node_key == key || node_key == *WILDCARD_HASH || node_key == *WILDCARD_STOP_HASH
|
||||
})
|
||||
.cloned()
|
||||
}).cloned()
|
||||
}
|
||||
|
||||
fn add_empty_node(&mut self, parent: NodeId, key: u64) -> NodeId {
|
||||
|
||||
+3
-4
@@ -280,8 +280,8 @@ impl OutputPrintable {
|
||||
let mut merkle_proof = None;
|
||||
if output
|
||||
.features
|
||||
.contains(core::transaction::OutputFeatures::COINBASE_OUTPUT) && !spent
|
||||
&& block_header.is_some()
|
||||
.contains(core::transaction::OutputFeatures::COINBASE_OUTPUT)
|
||||
&& !spent && block_header.is_some()
|
||||
{
|
||||
merkle_proof = chain.get_merkle_proof(&out_id, &block_header.unwrap()).ok()
|
||||
};
|
||||
@@ -564,8 +564,7 @@ impl BlockPrintable {
|
||||
Some(&block.header),
|
||||
include_proof,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
}).collect();
|
||||
let kernels = block
|
||||
.kernels()
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user