jsonwebtoken/src/validation.rs

466 lines
15 KiB
Rust
Raw Normal View History

2019-11-11 13:47:35 -05:00
use std::collections::HashSet;
2019-11-11 14:29:57 -05:00
use std::time::{SystemTime, UNIX_EPOCH};
2019-11-11 13:47:35 -05:00
2017-04-11 01:41:44 -04:00
use serde_json::map::Map;
use serde_json::{from_value, Value};
2017-04-11 01:41:44 -04:00
2019-10-31 14:12:08 -04:00
use crate::algorithms::Algorithm;
use crate::errors::{new_error, ErrorKind, Result};
2017-04-11 01:41:44 -04:00
2019-11-14 13:43:43 -05:00
/// Contains the various validations that are applied after decoding a JWT.
2017-04-12 21:08:07 -04:00
///
2019-11-11 13:47:35 -05:00
/// All time validation happen on UTC timestamps as seconds.
2017-06-13 04:51:10 -04:00
///
2017-04-12 21:08:07 -04:00
/// ```rust
/// use jsonwebtoken::Validation;
///
/// // Default value
/// let validation = Validation::default();
2017-06-13 04:51:10 -04:00
///
2017-04-12 21:08:07 -04:00
/// // Changing one parameter
2017-08-30 05:09:57 -04:00
/// let mut validation = Validation {leeway: 60, ..Default::default()};
2017-06-13 04:51:10 -04:00
///
2017-04-12 21:08:07 -04:00
/// // Setting audience
/// let mut validation = Validation::default();
/// validation.set_audience(&["Me"]); // a single string
2017-04-12 21:08:07 -04:00
/// validation.set_audience(&["Me", "You"]); // array of strings
/// ```
2017-04-11 01:41:44 -04:00
#[derive(Debug, Clone, PartialEq)]
pub struct Validation {
2017-08-30 05:09:57 -04:00
/// Add some leeway (in seconds) to the `exp`, `iat` and `nbf` validation to
2017-04-12 21:08:07 -04:00
/// account for clock skew.
///
/// Defaults to `0`.
2019-11-11 13:47:35 -05:00
pub leeway: u64,
2017-04-12 21:08:07 -04:00
/// Whether to validate the `exp` field.
///
/// It will return an error if the time in the `exp` field is past.
///
/// Defaults to `true`.
2017-04-11 01:41:44 -04:00
pub validate_exp: bool,
2017-04-12 21:08:07 -04:00
/// Whether to validate the `nbf` field.
///
/// It will return an error if the current timestamp is before the time in the `nbf` field.
///
/// Defaults to `false`.
2017-04-11 01:41:44 -04:00
pub validate_nbf: bool,
/// If it contains a value, the validation will check that the `aud` field is a member of the
/// audience provided and will error otherwise.
2017-04-12 21:08:07 -04:00
///
2017-04-22 02:21:16 -04:00
/// Defaults to `None`.
pub aud: Option<HashSet<String>>,
2017-04-12 21:08:07 -04:00
/// If it contains a value, the validation will check that the `iss` field is the same as the
/// one provided and will error otherwise.
///
2017-04-22 02:21:16 -04:00
/// Defaults to `None`.
2017-04-11 01:41:44 -04:00
pub iss: Option<String>,
2017-04-12 21:08:07 -04:00
/// If it contains a value, the validation will check that the `sub` field is the same as the
/// one provided and will error otherwise.
///
2017-04-22 02:21:16 -04:00
/// Defaults to `None`.
2017-04-11 01:41:44 -04:00
pub sub: Option<String>,
2017-10-22 07:20:01 -04:00
/// If it contains a value, the validation will check that the `alg` of the header is contained
2017-04-22 02:21:16 -04:00
/// in the ones provided and will error otherwise.
///
2017-10-22 07:20:01 -04:00
/// Defaults to `vec![Algorithm::HS256]`.
pub algorithms: Vec<Algorithm>,
2017-04-11 01:41:44 -04:00
}
impl Validation {
2017-10-22 07:20:01 -04:00
/// Create a default validation setup allowing the given alg
pub fn new(alg: Algorithm) -> Validation {
let mut validation = Validation::default();
validation.algorithms = vec![alg];
validation
}
/// `aud` is a collection of one or more acceptable audience members
pub fn set_audience<T: ToString>(&mut self, items: &[T]) {
self.aud = Some(items.iter().map(|x| x.to_string()).collect())
2017-04-11 01:41:44 -04:00
}
}
impl Default for Validation {
fn default() -> Validation {
Validation {
leeway: 0,
validate_exp: true,
validate_nbf: false,
2017-04-11 01:41:44 -04:00
iss: None,
sub: None,
aud: None,
2017-04-22 02:21:16 -04:00
2017-10-22 07:20:01 -04:00
algorithms: vec![Algorithm::HS256],
2017-04-11 01:41:44 -04:00
}
}
}
2019-11-11 13:47:35 -05:00
fn get_current_timestamp() -> u64 {
let start = SystemTime::now();
start.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs()
}
2017-04-11 01:41:44 -04:00
pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()> {
2019-11-11 13:47:35 -05:00
let now = get_current_timestamp();
2017-04-11 01:41:44 -04:00
if options.validate_exp {
if let Some(exp) = claims.get("exp") {
2019-11-11 13:47:35 -05:00
if from_value::<u64>(exp.clone())? < now - options.leeway {
return Err(new_error(ErrorKind::ExpiredSignature));
}
} else {
2018-07-25 08:43:58 -04:00
return Err(new_error(ErrorKind::ExpiredSignature));
2017-04-11 01:41:44 -04:00
}
}
if options.validate_nbf {
if let Some(nbf) = claims.get("nbf") {
2019-11-11 13:47:35 -05:00
if from_value::<u64>(nbf.clone())? > now + options.leeway {
return Err(new_error(ErrorKind::ImmatureSignature));
}
} else {
2018-07-25 08:43:58 -04:00
return Err(new_error(ErrorKind::ImmatureSignature));
2017-04-11 01:41:44 -04:00
}
}
if let Some(ref correct_iss) = options.iss {
if let Some(iss) = claims.get("iss") {
2017-04-11 01:41:44 -04:00
if from_value::<String>(iss.clone())? != *correct_iss {
2018-07-25 08:43:58 -04:00
return Err(new_error(ErrorKind::InvalidIssuer));
2017-04-11 01:41:44 -04:00
}
} else {
return Err(new_error(ErrorKind::InvalidIssuer));
2017-04-11 01:41:44 -04:00
}
}
if let Some(ref correct_sub) = options.sub {
if let Some(sub) = claims.get("sub") {
2017-04-11 01:41:44 -04:00
if from_value::<String>(sub.clone())? != *correct_sub {
2018-07-25 08:43:58 -04:00
return Err(new_error(ErrorKind::InvalidSubject));
2017-04-11 01:41:44 -04:00
}
} else {
return Err(new_error(ErrorKind::InvalidSubject));
2017-04-11 01:41:44 -04:00
}
}
if let Some(ref correct_aud) = options.aud {
if let Some(aud) = claims.get("aud") {
match aud {
Value::String(aud_found) => {
if !correct_aud.contains(aud_found) {
return Err(new_error(ErrorKind::InvalidAudience));
}
}
Value::Array(_) => {
let provided_aud: HashSet<String> = from_value(aud.clone())?;
if provided_aud.intersection(correct_aud).count() == 0 {
return Err(new_error(ErrorKind::InvalidAudience));
}
}
2019-12-29 12:42:35 -05:00
_ => return Err(new_error(ErrorKind::InvalidAudience)),
};
} else {
return Err(new_error(ErrorKind::InvalidAudience));
2017-04-11 01:41:44 -04:00
}
}
Ok(())
}
#[cfg(test)]
mod tests {
2018-10-28 14:58:35 -04:00
use serde_json::map::Map;
use serde_json::to_value;
2017-04-11 01:41:44 -04:00
2019-11-11 14:29:57 -05:00
use super::{get_current_timestamp, validate, Validation};
2017-04-11 01:41:44 -04:00
2019-10-31 14:12:08 -04:00
use crate::errors::ErrorKind;
2017-04-11 01:41:44 -04:00
#[test]
fn exp_in_future_ok() {
let mut claims = Map::new();
2019-11-11 13:47:35 -05:00
claims.insert("exp".to_string(), to_value(get_current_timestamp() + 10000).unwrap());
2017-04-11 01:41:44 -04:00
let res = validate(&claims, &Validation::default());
assert!(res.is_ok());
}
#[test]
fn exp_in_past_fails() {
let mut claims = Map::new();
2019-11-11 13:47:35 -05:00
claims.insert("exp".to_string(), to_value(get_current_timestamp() - 100000).unwrap());
2017-04-11 01:41:44 -04:00
let res = validate(&claims, &Validation::default());
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::ExpiredSignature => (),
_ => assert!(false),
};
}
#[test]
fn exp_in_past_but_in_leeway_ok() {
let mut claims = Map::new();
2019-11-11 13:47:35 -05:00
claims.insert("exp".to_string(), to_value(get_current_timestamp() - 500).unwrap());
2018-10-28 14:58:35 -04:00
let validation = Validation { leeway: 1000 * 60, ..Default::default() };
2017-04-11 01:41:44 -04:00
let res = validate(&claims, &validation);
assert!(res.is_ok());
}
// https://github.com/Keats/jsonwebtoken/issues/51
#[test]
fn validation_called_even_if_field_is_empty() {
let claims = Map::new();
let res = validate(&claims, &Validation::default());
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::ExpiredSignature => (),
_ => assert!(false),
};
}
2017-04-11 01:41:44 -04:00
#[test]
fn nbf_in_past_ok() {
let mut claims = Map::new();
2019-11-11 13:47:35 -05:00
claims.insert("nbf".to_string(), to_value(get_current_timestamp() - 10000).unwrap());
2018-10-28 14:58:35 -04:00
let validation =
Validation { validate_exp: false, validate_nbf: true, ..Validation::default() };
let res = validate(&claims, &validation);
2017-04-11 01:41:44 -04:00
assert!(res.is_ok());
}
#[test]
fn nbf_in_future_fails() {
let mut claims = Map::new();
2019-11-11 13:47:35 -05:00
claims.insert("nbf".to_string(), to_value(get_current_timestamp() + 100000).unwrap());
2018-10-28 14:58:35 -04:00
let validation =
Validation { validate_exp: false, validate_nbf: true, ..Validation::default() };
let res = validate(&claims, &validation);
2017-04-11 01:41:44 -04:00
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::ImmatureSignature => (),
_ => assert!(false),
};
}
#[test]
fn nbf_in_future_but_in_leeway_ok() {
let mut claims = Map::new();
2019-11-11 13:47:35 -05:00
claims.insert("nbf".to_string(), to_value(get_current_timestamp() + 500).unwrap());
2017-04-11 01:41:44 -04:00
let validation = Validation {
leeway: 1000 * 60,
validate_nbf: true,
validate_exp: false,
2017-04-11 01:41:44 -04:00
..Default::default()
};
let res = validate(&claims, &validation);
assert!(res.is_ok());
}
#[test]
fn iss_ok() {
let mut claims = Map::new();
claims.insert("iss".to_string(), to_value("Keats").unwrap());
let validation = Validation {
validate_exp: false,
2017-04-11 01:41:44 -04:00
iss: Some("Keats".to_string()),
..Default::default()
};
let res = validate(&claims, &validation);
assert!(res.is_ok());
}
#[test]
fn iss_not_matching_fails() {
let mut claims = Map::new();
claims.insert("iss".to_string(), to_value("Hacked").unwrap());
let validation = Validation {
validate_exp: false,
iss: Some("Keats".to_string()),
..Default::default()
};
let res = validate(&claims, &validation);
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::InvalidIssuer => (),
_ => assert!(false),
};
}
#[test]
fn iss_missing_fails() {
let claims = Map::new();
let validation = Validation {
validate_exp: false,
2017-04-11 01:41:44 -04:00
iss: Some("Keats".to_string()),
..Default::default()
};
let res = validate(&claims, &validation);
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::InvalidIssuer => (),
_ => assert!(false),
};
}
#[test]
fn sub_ok() {
let mut claims = Map::new();
claims.insert("sub".to_string(), to_value("Keats").unwrap());
let validation = Validation {
validate_exp: false,
2017-04-11 01:41:44 -04:00
sub: Some("Keats".to_string()),
..Default::default()
};
let res = validate(&claims, &validation);
assert!(res.is_ok());
}
#[test]
fn sub_not_matching_fails() {
let mut claims = Map::new();
claims.insert("sub".to_string(), to_value("Hacked").unwrap());
let validation = Validation {
validate_exp: false,
sub: Some("Keats".to_string()),
..Default::default()
};
let res = validate(&claims, &validation);
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::InvalidSubject => (),
_ => assert!(false),
};
}
#[test]
fn sub_missing_fails() {
let claims = Map::new();
let validation = Validation {
validate_exp: false,
2017-04-11 01:41:44 -04:00
sub: Some("Keats".to_string()),
..Default::default()
};
let res = validate(&claims, &validation);
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::InvalidSubject => (),
_ => assert!(false),
};
}
#[test]
fn aud_string_ok() {
let mut claims = Map::new();
claims.insert("aud".to_string(), to_value(["Everyone"]).unwrap());
2018-10-28 14:58:35 -04:00
let mut validation = Validation { validate_exp: false, ..Validation::default() };
validation.set_audience(&["Everyone"]);
2017-04-11 01:41:44 -04:00
let res = validate(&claims, &validation);
assert!(res.is_ok());
}
#[test]
fn aud_array_of_string_ok() {
let mut claims = Map::new();
claims.insert("aud".to_string(), to_value(["UserA", "UserB"]).unwrap());
2018-10-28 14:58:35 -04:00
let mut validation = Validation { validate_exp: false, ..Validation::default() };
2017-04-11 01:41:44 -04:00
validation.set_audience(&["UserA", "UserB"]);
let res = validate(&claims, &validation);
assert!(res.is_ok());
}
#[test]
fn aud_type_mismatch_fails() {
let mut claims = Map::new();
claims.insert("aud".to_string(), to_value(["Everyone"]).unwrap());
2018-10-28 14:58:35 -04:00
let mut validation = Validation { validate_exp: false, ..Validation::default() };
2017-04-11 01:41:44 -04:00
validation.set_audience(&["UserA", "UserB"]);
let res = validate(&claims, &validation);
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::InvalidAudience => (),
_ => assert!(false),
};
}
#[test]
fn aud_correct_type_not_matching_fails() {
let mut claims = Map::new();
claims.insert("aud".to_string(), to_value(["Everyone"]).unwrap());
2018-10-28 14:58:35 -04:00
let mut validation = Validation { validate_exp: false, ..Validation::default() };
validation.set_audience(&["None"]);
let res = validate(&claims, &validation);
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::InvalidAudience => (),
_ => assert!(false),
};
}
#[test]
fn aud_missing_fails() {
let claims = Map::new();
2018-10-28 14:58:35 -04:00
let mut validation = Validation { validate_exp: false, ..Validation::default() };
validation.set_audience(&["None"]);
2017-04-11 01:41:44 -04:00
let res = validate(&claims, &validation);
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::InvalidAudience => (),
_ => assert!(false),
};
}
2019-11-03 10:36:19 -05:00
// https://github.com/Keats/jsonwebtoken/issues/51
#[test]
fn does_validation_in_right_order() {
let mut claims = Map::new();
2019-11-11 13:47:35 -05:00
claims.insert("exp".to_string(), to_value(get_current_timestamp() + 10000).unwrap());
2019-11-03 10:36:19 -05:00
let v = Validation {
leeway: 5,
validate_exp: true,
iss: Some("iss no check".to_string()),
sub: Some("sub no check".to_string()),
..Validation::default()
};
let res = validate(&claims, &v);
// It errors because it needs to validate iss/sub which are missing
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::InvalidIssuer => (),
t @ _ => {
println!("{:?}", t);
assert!(false)
}
};
}
// https://github.com/Keats/jsonwebtoken/issues/110
#[test]
fn aud_use_validation_struct() {
let mut claims = Map::new();
2019-12-29 12:42:35 -05:00
claims.insert(
"aud".to_string(),
to_value("my-googleclientid1234.apps.googleusercontent.com").unwrap(),
);
let aud = "my-googleclientid1234.apps.googleusercontent.com".to_string();
let mut aud_hashset = std::collections::HashSet::new();
aud_hashset.insert(aud);
2019-12-29 12:42:35 -05:00
let validation =
Validation { aud: Some(aud_hashset), validate_exp: false, ..Validation::default() };
let res = validate(&claims, &validation);
println!("{:?}", res);
assert!(res.is_ok());
}
2017-04-11 01:41:44 -04:00
}