Migrated to anyhow and chrono from failure and time crates.

This commit is contained in:
Thomas Gideon 2020-06-22 11:52:53 -04:00
parent 30ff924e9a
commit 4776132e51
8 changed files with 76 additions and 90 deletions

View file

@ -9,17 +9,17 @@ license = "MIT"
name = "medallion" name = "medallion"
readme = "README.md" readme = "README.md"
repository = "http://github.com/cmdln/medallion" repository = "http://github.com/cmdln/medallion"
version = "2.3.1" version = "2.4.0"
edition = "2018"
[badges] [badges]
[badges.travis-ci] [badges.travis-ci]
branch = "master" branch = "master"
repository = "https://travis-ci.org/cmdln/medallion" repository = "https://travis-ci.org/cmdln/medallion"
[dependencies] [dependencies]
base64 = "~0.10.0" base64 = "~0.12.2"
failure = "~0.1.3"
openssl = "~0.10.15" openssl = "~0.10.15"
serde = "^1.0.80" serde = { version = "^1.0.114", features = [ "derive" ] }
serde_derive = "^1.0.80" serde_json = "^1.0.55"
serde_json = "^1.0.33" anyhow = "^1.0.31"
time = "~0.1.40" chrono = "~0.4.11"

View file

@ -1,9 +1,5 @@
// need this for custom derivation
#[macro_use]
extern crate serde_derive;
extern crate medallion;
use medallion::{Header, Payload, Token}; use medallion::{Header, Payload, Token};
use serde::{Deserialize, Serialize};
#[derive(Default, Serialize, Deserialize, PartialEq, Debug)] #[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
struct Custom { struct Custom {

View file

@ -1,9 +1,5 @@
// need this for custom derivation
#[macro_use]
extern crate serde_derive;
extern crate medallion;
use medallion::{DefaultPayload, DefaultToken, Header}; use medallion::{DefaultPayload, DefaultToken, Header};
use serde::{Deserialize, Serialize};
#[derive(Default, Serialize, Deserialize, PartialEq, Debug)] #[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
struct Custom { struct Custom {

View file

@ -1,5 +1,6 @@
use super::Result;
use crate::header::Algorithm;
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD}; use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
use header::Algorithm;
use openssl::{ use openssl::{
hash::MessageDigest, hash::MessageDigest,
memcmp, memcmp,
@ -8,8 +9,6 @@ use openssl::{
sign::{Signer, Verifier}, sign::{Signer, Verifier},
}; };
use super::Result;
pub fn sign(data: &str, key: &[u8], algorithm: &Algorithm) -> Result<String> { pub fn sign(data: &str, key: &[u8], algorithm: &Algorithm) -> Result<String> {
match *algorithm { match *algorithm {
Algorithm::HS256 => sign_hmac(data, key, MessageDigest::sha256()), Algorithm::HS256 => sign_hmac(data, key, MessageDigest::sha256()),
@ -76,8 +75,7 @@ fn verify_rsa(signature: &str, data: &str, key: &[u8], digest: MessageDigest) ->
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::{sign, verify}; use super::{sign, verify};
use header::Algorithm; use crate::header::Algorithm;
use openssl;
#[test] #[test]
pub fn sign_data_hmac() { pub fn sign_data_hmac() {
@ -104,16 +102,16 @@ pub mod tests {
&*data, &*data,
&keypair.private_key_to_pem().unwrap(), &keypair.private_key_to_pem().unwrap(),
&Algorithm::RS256, &Algorithm::RS256,
).unwrap(); )
.unwrap();
assert!( assert!(verify(
verify(
&sig, &sig,
&*data, &*data,
&keypair.public_key_to_pem().unwrap(), &keypair.public_key_to_pem().unwrap(),
&Algorithm::RS256 &Algorithm::RS256
).unwrap() )
); .unwrap());
} }
#[test] #[test]

View file

@ -1,3 +0,0 @@
use failure::Error;
pub type Result<T> = std::result::Result<T, Error>;

View file

@ -1,8 +1,8 @@
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::{self, Value};
use super::Result; use super::Result;
use anyhow::format_err;
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{self, Value};
/// An extensible Header that provides only algorithm field and allows for additional fields to be /// An extensible Header that provides only algorithm field and allows for additional fields to be
/// passed in via a struct that can be serialized and deserialized. Unlike the Claims struct, there /// passed in via a struct that can be serialized and deserialized. Unlike the Claims struct, there
@ -79,6 +79,7 @@ impl<T> Default for Header<T> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Algorithm, Header}; use super::{Algorithm, Header};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
struct CustomHeaders { struct CustomHeaders {

View file

@ -6,26 +6,15 @@
///! ///!
///! Tries to support the standard uses for JWTs while providing reasonable ways to extend, ///! Tries to support the standard uses for JWTs while providing reasonable ways to extend,
///! primarily by adding custom headers and claims to tokens. ///! primarily by adding custom headers and claims to tokens.
extern crate base64;
#[macro_use]
extern crate failure;
extern crate openssl;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate time;
pub use header::{Algorithm, Header}; pub use header::{Algorithm, Header};
pub use payload::{DefaultPayload, Payload}; pub use payload::{DefaultPayload, Payload};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
mod crypt; mod crypt;
mod error;
mod header; mod header;
mod payload; mod payload;
pub use error::Result; pub use anyhow::Result;
/// A convenient type that binds the same type parameter for the custom claims, an empty tuple, as /// A convenient type that binds the same type parameter for the custom claims, an empty tuple, as
/// `DefaultPayload` so that the two aliases may be used together to reduce boilerplate when no /// `DefaultPayload` so that the two aliases may be used together to reduce boilerplate when no
@ -104,10 +93,10 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Algorithm::{HS256, RS512}; use super::Algorithm::{HS256, RS512};
use openssl; use crate::{DefaultPayload, DefaultToken, Header, Payload, Token};
use std::default::Default; use anyhow::Result;
use time::{self, Duration, Tm}; use chrono::{prelude::*, Duration};
use {DefaultPayload, DefaultToken, Header, Payload, Token}; use std::convert::TryInto;
#[test] #[test]
pub fn raw_data() { pub fn raw_data() {
@ -122,11 +111,11 @@ mod tests {
#[test] #[test]
pub fn roundtrip_hmac() { pub fn roundtrip_hmac() {
let now = time::now(); let now = Utc::now();
let header: Header<()> = Header::default(); let header: Header<()> = Header::default();
let payload = DefaultPayload { let payload = DefaultPayload {
nbf: Some(now.to_timespec().sec as u64), nbf: Some(now.timestamp().try_into().unwrap()),
exp: Some((now + Duration::minutes(5)).to_timespec().sec as u64), exp: Some((now + Duration::minutes(5)).timestamp().try_into().unwrap()),
..DefaultPayload::default() ..DefaultPayload::default()
}; };
let token = Token::new(header, payload); let token = Token::new(header, payload);
@ -139,27 +128,29 @@ mod tests {
} }
#[test] #[test]
pub fn roundtrip_expired() { pub fn roundtrip_expired() -> Result<()> {
let now = time::now(); let now = Utc::now();
let token = create_for_range(now, now + Duration::minutes(-5)); let token = create_for_range(now, now + Duration::minutes(-5))?;
let key = b"secret"; let key = b"secret";
let raw = token.sign(key).unwrap(); let raw = token.sign(key)?;
let same = Token::parse(&*raw).unwrap(); let same = Token::parse(&*raw).unwrap();
assert_eq!(token, same); assert_eq!(token, same);
assert_eq!(false, same.verify(key).unwrap()); assert_eq!(false, same.verify(key).unwrap());
Ok(())
} }
#[test] #[test]
pub fn roundtrip_not_yet_valid() { pub fn roundtrip_not_yet_valid() -> Result<()> {
let now = time::now(); let now = Utc::now();
let token = create_for_range(now + Duration::minutes(5), now + Duration::minutes(10)); let token = create_for_range(now + Duration::minutes(5), now + Duration::minutes(10))?;
let key = b"secret"; let key = b"secret";
let raw = token.sign(key).unwrap(); let raw = token.sign(key)?;
let same = Token::parse(&*raw).unwrap(); let same = Token::parse(&*raw).unwrap();
assert_eq!(token, same); assert_eq!(token, same);
assert_eq!(false, same.verify(key).unwrap()); assert_eq!(false, same.verify(key).unwrap());
Ok(())
} }
#[test] #[test]
@ -179,19 +170,18 @@ mod tests {
let same = Token::parse(&*raw).unwrap(); let same = Token::parse(&*raw).unwrap();
assert_eq!(token, same); assert_eq!(token, same);
assert!( assert!(same
same.verify(&rsa_keypair.public_key_to_pem().unwrap()) .verify(&rsa_keypair.public_key_to_pem().unwrap())
.unwrap() .unwrap());
);
} }
fn create_for_range(nbf: Tm, exp: Tm) -> Token { fn create_for_range(nbf: DateTime<Utc>, exp: DateTime<Utc>) -> Result<Token> {
let header: Header = Header::default(); let header: Header = Header::default();
let payload = Payload { let payload = Payload {
nbf: Some(nbf.to_timespec().sec as u64), nbf: Some(nbf.timestamp().try_into()?),
exp: Some(exp.to_timespec().sec as u64), exp: Some(exp.timestamp().try_into()?),
..Payload::default() ..Payload::default()
}; };
Token::new(header, payload) Ok(Token::new(header, payload))
} }
} }

View file

@ -1,10 +1,9 @@
use super::Result; use super::Result;
use anyhow::format_err;
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD}; use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
use serde::de::DeserializeOwned; use chrono::{DateTime, NaiveDateTime, Utc};
use serde::Serialize; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json;
use serde_json::value::Value; use serde_json::value::Value;
use time::{self, Timespec};
/// A default claim set, including the standard, or registered, claims and the ability to specify /// A default claim set, including the standard, or registered, claims and the ability to specify
/// your own as custom claims. /// your own as custom claims.
@ -73,7 +72,7 @@ impl<T: Serialize + DeserializeOwned> Payload<T> {
None => { None => {
let s = serde_json::to_string(&claims_map)?; let s = serde_json::to_string(&claims_map)?;
let enc = encode_config((&*s).as_bytes(), URL_SAFE_NO_PAD); let enc = encode_config((&*s).as_bytes(), URL_SAFE_NO_PAD);
return Ok(enc); Ok(enc)
} }
} }
} else { } else {
@ -82,13 +81,25 @@ impl<T: Serialize + DeserializeOwned> Payload<T> {
} }
pub fn verify(&self) -> bool { pub fn verify(&self) -> bool {
let now = time::now().to_timespec(); let now = Utc::now();
let nbf_verified = match self.nbf { let nbf_verified = match self.nbf {
Some(nbf_sec) => Timespec::new(nbf_sec as i64, 0) < now, Some(nbf_sec) => {
let nbf = DateTime::<Utc>::from_utc(
NaiveDateTime::from_timestamp(nbf_sec as i64, 0),
Utc,
);
nbf < now
}
None => true, None => true,
}; };
let exp_verified = match self.exp { let exp_verified = match self.exp {
Some(exp_sec) => now < Timespec::new(exp_sec as i64, 0), Some(exp_sec) => {
let exp = DateTime::<Utc>::from_utc(
NaiveDateTime::from_timestamp(exp_sec as i64, 0),
Utc,
);
now < exp
}
None => true, None => true,
}; };
nbf_verified && exp_verified nbf_verified && exp_verified
@ -98,8 +109,9 @@ impl<T: Serialize + DeserializeOwned> Payload<T> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{DefaultPayload, Payload}; use super::{DefaultPayload, Payload};
use chrono::{prelude::*, Duration};
use serde::{Deserialize, Serialize};
use std::default::Default; use std::default::Default;
use time::{self, Duration};
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] #[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
struct CustomClaims { struct CustomClaims {
@ -199,7 +211,7 @@ mod tests {
} }
fn create_with_nbf(offset: i64) -> DefaultPayload { fn create_with_nbf(offset: i64) -> DefaultPayload {
let nbf = (time::now() - Duration::minutes(offset)).to_timespec().sec; let nbf = (Utc::now() - Duration::minutes(offset)).timestamp();
DefaultPayload { DefaultPayload {
nbf: Some(nbf as u64), nbf: Some(nbf as u64),
..Default::default() ..Default::default()
@ -207,7 +219,7 @@ mod tests {
} }
fn create_with_exp(offset: i64) -> DefaultPayload { fn create_with_exp(offset: i64) -> DefaultPayload {
let exp = (time::now() + Duration::minutes(offset)).to_timespec().sec; let exp = (Utc::now() + Duration::minutes(offset)).timestamp();
DefaultPayload { DefaultPayload {
exp: Some(exp as u64), exp: Some(exp as u64),
..Default::default() ..Default::default()
@ -215,12 +227,8 @@ mod tests {
} }
fn create_with_nbf_exp(nbf_offset: i64, exp_offset: i64) -> DefaultPayload { fn create_with_nbf_exp(nbf_offset: i64, exp_offset: i64) -> DefaultPayload {
let nbf = (time::now() - Duration::minutes(nbf_offset)) let nbf = (Utc::now() - Duration::minutes(nbf_offset)).timestamp();
.to_timespec() let exp = (Utc::now() + Duration::minutes(exp_offset)).timestamp();
.sec;
let exp = (time::now() + Duration::minutes(exp_offset))
.to_timespec()
.sec;
DefaultPayload { DefaultPayload {
nbf: Some(nbf as u64), nbf: Some(nbf as u64),
exp: Some(exp as u64), exp: Some(exp as u64),