New signer token RPC & Initial signer connection without token. (#2096)

* Allowing signer to use initial token

* Generating new tokens via RPC

* Fixing RPC tests

* Fixing signer doctest

* whitespace

[ci:skip]

* whitespace

[ci:skip]
This commit is contained in:
Tomasz Drwięga
2016-09-21 12:44:49 +02:00
committed by Gav Wood
parent 93f82a1164
commit b7e2afd5c0
21 changed files with 377 additions and 94 deletions

View File

@@ -48,6 +48,7 @@ impl TimeProvider for DefaultTimeProvider {
/// No of seconds the hash is valid
const TIME_THRESHOLD: u64 = 7;
const TOKEN_LENGTH: usize = 16;
const INITIAL_TOKEN: &'static str = "initial";
/// Manages authorization codes for `SignerUIs`
pub struct AuthCodes<T: TimeProvider = DefaultTimeProvider> {
@@ -98,7 +99,7 @@ impl<T: TimeProvider> AuthCodes<T> {
}
/// Checks if given hash is correct identifier of `SignerUI`
pub fn is_valid(&self, hash: &H256, time: u64) -> bool {
pub fn is_valid(&mut self, hash: &H256, time: u64) -> bool {
let now = self.now.now();
// check time
if time >= now + TIME_THRESHOLD || time <= now - TIME_THRESHOLD {
@@ -106,9 +107,21 @@ impl<T: TimeProvider> AuthCodes<T> {
return false;
}
let as_token = |code| format!("{}:{}", code, time).sha3();
// Check if it's the initial token.
if self.is_empty() {
let initial = &as_token(INITIAL_TOKEN) == hash;
// Initial token can be used only once.
if initial {
let _ = self.generate_new();
}
return initial;
}
// look for code
self.codes.iter()
.any(|code| &format!("{}:{}", code, time).sha3() == hash)
.any(|code| &as_token(code) == hash)
}
/// Generates and returns a new code that can be used by `SignerUIs`
@@ -124,6 +137,11 @@ impl<T: TimeProvider> AuthCodes<T> {
self.codes.push(code);
Ok(readable_code)
}
/// Returns true if there are no tokens in this store
pub fn is_empty(&self) -> bool {
self.codes.is_empty()
}
}
@@ -137,12 +155,28 @@ mod tests {
format!("{}:{}", val, time).sha3()
}
#[test]
fn should_return_true_if_code_is_initial_and_store_is_empty() {
// given
let code = "initial";
let time = 99;
let mut codes = AuthCodes::new(vec![], || 100);
// when
let res1 = codes.is_valid(&generate_hash(code, time), time);
let res2 = codes.is_valid(&generate_hash(code, time), time);
// then
assert_eq!(res1, true);
assert_eq!(res2, false);
}
#[test]
fn should_return_true_if_hash_is_valid() {
// given
let code = "23521352asdfasdfadf";
let time = 99;
let codes = AuthCodes::new(vec![code.into()], || 100);
let mut codes = AuthCodes::new(vec![code.into()], || 100);
// when
let res = codes.is_valid(&generate_hash(code, time), time);
@@ -156,7 +190,7 @@ mod tests {
// given
let code = "23521352asdfasdfadf";
let time = 99;
let codes = AuthCodes::new(vec!["1".into()], || 100);
let mut codes = AuthCodes::new(vec!["1".into()], || 100);
// when
let res = codes.is_valid(&generate_hash(code, time), time);
@@ -171,7 +205,7 @@ mod tests {
let code = "23521352asdfasdfadf";
let time = 107;
let time2 = 93;
let codes = AuthCodes::new(vec![code.into()], || 100);
let mut codes = AuthCodes::new(vec![code.into()], || 100);
// when
let res1 = codes.is_valid(&generate_hash(code, time), time);

View File

@@ -14,24 +14,48 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::env;
use std::ops::{Deref, DerefMut};
use std::thread;
use std::time::Duration;
use std::time::{self, Duration};
use std::sync::Arc;
use devtools::http_client;
use devtools::{http_client, RandomTempPath};
use rpc::ConfirmationsQueue;
use util::Hashable;
use rand;
use ServerBuilder;
use Server;
use AuthCodes;
pub fn serve() -> Server {
pub struct GuardedAuthCodes {
authcodes: AuthCodes,
path: RandomTempPath,
}
impl Deref for GuardedAuthCodes {
type Target = AuthCodes;
fn deref(&self) -> &Self::Target {
&self.authcodes
}
}
impl DerefMut for GuardedAuthCodes {
fn deref_mut(&mut self) -> &mut AuthCodes {
&mut self.authcodes
}
}
pub fn serve() -> (Server, usize, GuardedAuthCodes) {
let mut path = RandomTempPath::new();
path.panic_on_drop_failure = false;
let queue = Arc::new(ConfirmationsQueue::default());
let builder = ServerBuilder::new(queue, env::temp_dir());
let builder = ServerBuilder::new(queue, path.to_path_buf());
let port = 35000 + rand::random::<usize>() % 10000;
let res = builder.start(format!("127.0.0.1:{}", port).parse().unwrap()).unwrap();
thread::sleep(Duration::from_millis(25));
res
(res, port, GuardedAuthCodes {
authcodes: AuthCodes::from_file(&path).unwrap(),
path: path,
})
}
pub fn request(server: Server, request: &str) -> http_client::Response {
@@ -41,7 +65,7 @@ pub fn request(server: Server, request: &str) -> http_client::Response {
#[test]
fn should_reject_invalid_host() {
// given
let server = serve();
let server = serve().0;
// when
let response = request(server,
@@ -62,7 +86,7 @@ fn should_reject_invalid_host() {
#[test]
fn should_serve_styles_even_on_disallowed_domain() {
// given
let server = serve();
let server = serve().0;
// when
let response = request(server,
@@ -79,3 +103,103 @@ fn should_serve_styles_even_on_disallowed_domain() {
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
}
#[test]
fn should_block_if_authorization_is_incorrect() {
// given
let (server, port, _) = serve();
// when
let response = request(server,
&format!("\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:{}\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
Sec-WebSocket-Protocol: wrong\r\n\
Sec-WebSocket-Version: 13\r\n\
\r\n\
{{}}
", port)
);
// then
assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
}
#[test]
fn should_allow_if_authorization_is_correct() {
// given
let (server, port, mut authcodes) = serve();
let code = authcodes.generate_new().unwrap().replace("-", "");
authcodes.to_file(&authcodes.path).unwrap();
let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs();
// when
let response = request(server,
&format!("\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:{}\r\n\
Connection: Close\r\n\
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
Sec-WebSocket-Protocol: {:?}_{}\r\n\
Sec-WebSocket-Version: 13\r\n\
\r\n\
{{}}
",
port,
format!("{}:{}", code, timestamp).sha3(),
timestamp,
)
);
// then
assert_eq!(response.status, "HTTP/1.1 101 Switching Protocols".to_owned());
}
#[test]
fn should_allow_initial_connection_but_only_once() {
// given
let (server, port, authcodes) = serve();
let code = "initial";
let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs();
assert!(authcodes.is_empty());
// when
let response1 = http_client::request(server.addr(),
&format!("\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:{}\r\n\
Connection: Close\r\n\
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
Sec-WebSocket-Protocol:{:?}_{}\r\n\
Sec-WebSocket-Version: 13\r\n\
\r\n\
{{}}
",
port,
format!("{}:{}", code, timestamp).sha3(),
timestamp,
)
);
let response2 = http_client::request(server.addr(),
&format!("\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:{}\r\n\
Connection: Close\r\n\
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
Sec-WebSocket-Protocol:{:?}_{}\r\n\
Sec-WebSocket-Version: 13\r\n\
\r\n\
{{}}
",
port,
format!("{}:{}", code, timestamp).sha3(),
timestamp,
)
);
// then
assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned());
assert_eq!(response2.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
}

View File

@@ -180,7 +180,6 @@ impl Drop for Server {
self.queue.finish();
self.broadcaster_handle.take().unwrap().join().unwrap();
self.handle.take().unwrap().join().unwrap();
}
}

View File

@@ -59,7 +59,7 @@ fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool {
}
}
fn auth_is_valid(codes: &Path, protocols: ws::Result<Vec<&str>>) -> bool {
fn auth_is_valid(codes_path: &Path, protocols: ws::Result<Vec<&str>>) -> bool {
match protocols {
Ok(ref protocols) if protocols.len() == 1 => {
protocols.iter().any(|protocol| {
@@ -69,8 +69,15 @@ fn auth_is_valid(codes: &Path, protocols: ws::Result<Vec<&str>>) -> bool {
if let (Some(auth), Some(time)) = (auth, time) {
// Check if the code is valid
AuthCodes::from_file(codes)
.map(|codes| codes.is_valid(&auth, time))
AuthCodes::from_file(codes_path)
.map(|mut codes| {
let res = codes.is_valid(&auth, time);
// make sure to save back authcodes - it might have been modified
if let Err(_) = codes.to_file(codes_path) {
warn!(target: "signer", "Couldn't save authorization codes to file.");
}
res
})
.unwrap_or(false)
} else {
false