2021-09-28 12:16:51 -04:00
|
|
|
use std::borrow::Cow;
|
2019-11-11 13:47:35 -05:00
|
|
|
use std::collections::HashSet;
|
2021-11-01 09:25:59 -04:00
|
|
|
use std::fmt;
|
|
|
|
use std::marker::PhantomData;
|
2019-11-11 14:29:57 -05:00
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
2019-11-11 13:47:35 -05:00
|
|
|
|
2021-11-01 09:25:59 -04:00
|
|
|
use serde::de::{self, Visitor};
|
|
|
|
use serde::{Deserialize, Deserializer};
|
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
|
2021-09-28 04:04:51 -04:00
|
|
|
/// use jsonwebtoken::{Validation, Algorithm};
|
2017-06-13 04:51:10 -04:00
|
|
|
///
|
2021-09-28 04:04:51 -04:00
|
|
|
/// let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
/// validation.leeway = 5;
|
2017-04-12 21:08:07 -04:00
|
|
|
/// // Setting audience
|
2019-10-28 11:49:02 -04:00
|
|
|
/// validation.set_audience(&["Me"]); // a single string
|
2017-04-12 21:08:07 -04:00
|
|
|
/// validation.set_audience(&["Me", "You"]); // array of strings
|
2021-09-28 04:04:51 -04:00
|
|
|
/// // or issuer
|
2021-12-03 13:57:42 -05:00
|
|
|
/// validation.set_issuer(&["Me"]); // a single string
|
|
|
|
/// validation.set_issuer(&["Me", "You"]); // array of strings
|
2017-04-12 21:08:07 -04:00
|
|
|
/// ```
|
2017-04-11 01:41:44 -04:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
pub struct Validation {
|
2021-12-15 13:54:54 -05:00
|
|
|
/// Which claims are required to be present before starting the validation.
|
|
|
|
/// This does not interact with the various `validate_*`. If you remove `exp` from that list, you still need
|
|
|
|
/// to set `validate_exp` to `false`.
|
|
|
|
/// The only value that will be used are "exp", "nbf", "aud", "iss", "sub". Anything else will be ignored.
|
2021-12-03 14:42:05 -05:00
|
|
|
///
|
|
|
|
/// Defaults to `{"exp"}`
|
2021-12-15 13:54:54 -05:00
|
|
|
pub required_spec_claims: HashSet<String>,
|
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.
|
|
|
|
///
|
2019-01-18 02:31:56 -05:00
|
|
|
/// Defaults to `false`.
|
2017-04-11 01:41:44 -04:00
|
|
|
pub validate_nbf: bool,
|
2019-10-28 11:49:02 -04:00
|
|
|
/// If it contains a value, the validation will check that the `aud` field is a member of the
|
|
|
|
/// audience provided and will error otherwise.
|
2021-12-03 13:57:42 -05:00
|
|
|
/// Use `set_audience` to set it
|
2017-04-12 21:08:07 -04:00
|
|
|
///
|
2017-04-22 02:21:16 -04:00
|
|
|
/// Defaults to `None`.
|
2019-10-27 15:14:52 -04:00
|
|
|
pub aud: Option<HashSet<String>>,
|
2021-02-21 09:01:28 -05:00
|
|
|
/// If it contains a value, the validation will check that the `iss` field is a member of the
|
|
|
|
/// iss provided and will error otherwise.
|
2021-12-03 13:57:42 -05:00
|
|
|
/// Use `set_issuer` to set it
|
2017-04-12 21:08:07 -04:00
|
|
|
///
|
2017-04-22 02:21:16 -04:00
|
|
|
/// Defaults to `None`.
|
2021-02-21 09:01:28 -05:00
|
|
|
pub iss: Option<HashSet<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>,
|
2021-03-22 16:24:20 -04:00
|
|
|
/// The validation will check that the `alg` of the header is contained
|
|
|
|
/// in the ones provided and will error otherwise. Will error if it is empty.
|
2017-04-22 02:21:16 -04:00
|
|
|
///
|
2017-10-22 07:20:01 -04:00
|
|
|
/// Defaults to `vec![Algorithm::HS256]`.
|
|
|
|
pub algorithms: Vec<Algorithm>,
|
2021-09-28 04:04:51 -04:00
|
|
|
|
|
|
|
/// Whether to validate the JWT signature. Very insecure to turn that off
|
|
|
|
pub(crate) validate_signature: bool,
|
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 {
|
2021-10-10 14:44:53 -04:00
|
|
|
Validation { algorithms: vec![alg], ..Default::default() }
|
2017-10-22 07:20:01 -04:00
|
|
|
}
|
|
|
|
|
2019-10-28 11:49:02 -04:00
|
|
|
/// `aud` is a collection of one or more acceptable audience members
|
2021-12-03 13:57:42 -05:00
|
|
|
/// The simple usage is `set_audience(&["some aud name"])`
|
2019-10-28 11:49:02 -04:00
|
|
|
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
|
|
|
}
|
2021-02-21 09:01:28 -05:00
|
|
|
|
2021-12-03 13:57:42 -05:00
|
|
|
/// `iss` is a collection of one or more acceptable issuers members
|
|
|
|
/// The simple usage is `set_issuer(&["some iss name"])`
|
|
|
|
pub fn set_issuer<T: ToString>(&mut self, items: &[T]) {
|
2021-02-21 09:01:28 -05:00
|
|
|
self.iss = Some(items.iter().map(|x| x.to_string()).collect())
|
|
|
|
}
|
2017-04-22 02:21:16 -04:00
|
|
|
|
2021-12-15 13:54:54 -05:00
|
|
|
/// Which claims are required to be present for this JWT to be considered valid.
|
|
|
|
/// The only values that will be considered are "exp", "nbf", "aud", "iss", "sub".
|
|
|
|
/// The simple usage is `set_required_claims(&["exp", "nbf"])`.
|
|
|
|
/// If you want to have an empty set, do not use this function - set an empty set on the struct
|
|
|
|
/// param directly.
|
|
|
|
pub fn set_required_spec_claims<T: ToString>(&mut self, items: &[T]) {
|
|
|
|
self.required_spec_claims = items.iter().map(|x| x.to_string()).collect();
|
2021-12-03 14:42:05 -05:00
|
|
|
}
|
|
|
|
|
2021-09-28 04:04:51 -04:00
|
|
|
/// Whether to validate the JWT cryptographic signature
|
|
|
|
/// Very insecure to turn that off, only do it if you know what you're doing.
|
|
|
|
/// With this flag turned off, you should not trust any of the values of the claims.
|
|
|
|
pub fn insecure_disable_signature_validation(&mut self) {
|
|
|
|
self.validate_signature = false;
|
2017-04-11 01:41:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-10 14:44:53 -04:00
|
|
|
impl Default for Validation {
|
|
|
|
fn default() -> Self {
|
2021-12-03 14:42:05 -05:00
|
|
|
let mut required_claims = HashSet::with_capacity(1);
|
|
|
|
required_claims.insert("exp".to_owned());
|
|
|
|
|
2021-10-10 14:44:53 -04:00
|
|
|
Validation {
|
2021-12-15 13:54:54 -05:00
|
|
|
required_spec_claims: required_claims,
|
2021-10-10 14:44:53 -04:00
|
|
|
algorithms: vec![Algorithm::HS256],
|
2021-11-19 14:11:17 -05:00
|
|
|
leeway: 60,
|
2021-10-10 14:44:53 -04:00
|
|
|
|
|
|
|
validate_exp: true,
|
|
|
|
validate_nbf: false,
|
|
|
|
|
|
|
|
iss: None,
|
|
|
|
sub: None,
|
|
|
|
aud: None,
|
|
|
|
|
|
|
|
validate_signature: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-19 15:08:30 -05:00
|
|
|
/// Gets the current timestamp in the format JWT expect
|
|
|
|
pub fn get_current_timestamp() -> u64 {
|
2019-11-11 13:47:35 -05:00
|
|
|
let start = SystemTime::now();
|
|
|
|
start.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs()
|
|
|
|
}
|
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub(crate) struct ClaimsForValidation<'a> {
|
2021-11-01 09:25:59 -04:00
|
|
|
#[serde(deserialize_with = "numeric_type", default)]
|
2021-09-28 12:16:51 -04:00
|
|
|
exp: TryParse<u64>,
|
2021-11-01 09:25:59 -04:00
|
|
|
#[serde(deserialize_with = "numeric_type", default)]
|
2021-09-28 12:16:51 -04:00
|
|
|
nbf: TryParse<u64>,
|
|
|
|
#[serde(borrow)]
|
|
|
|
sub: TryParse<Cow<'a, str>>,
|
|
|
|
#[serde(borrow)]
|
2021-12-03 14:42:05 -05:00
|
|
|
iss: TryParse<Issuer<'a>>,
|
2021-09-28 12:16:51 -04:00
|
|
|
#[serde(borrow)]
|
|
|
|
aud: TryParse<Audience<'a>>,
|
|
|
|
}
|
2021-11-01 09:25:59 -04:00
|
|
|
#[derive(Debug)]
|
2021-09-28 12:16:51 -04:00
|
|
|
enum TryParse<T> {
|
|
|
|
Parsed(T),
|
|
|
|
FailedToParse,
|
|
|
|
NotPresent,
|
|
|
|
}
|
|
|
|
impl<'de, T: Deserialize<'de>> Deserialize<'de> for TryParse<T> {
|
|
|
|
fn deserialize<D: serde::Deserializer<'de>>(
|
|
|
|
deserializer: D,
|
|
|
|
) -> std::result::Result<Self, D::Error> {
|
|
|
|
Ok(match Option::<T>::deserialize(deserializer) {
|
|
|
|
Ok(Some(value)) => TryParse::Parsed(value),
|
|
|
|
Ok(None) => TryParse::NotPresent,
|
|
|
|
Err(_) => TryParse::FailedToParse,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-11-01 09:25:59 -04:00
|
|
|
impl<T> Default for TryParse<T> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::NotPresent
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
enum Audience<'a> {
|
|
|
|
Single(#[serde(borrow)] Cow<'a, str>),
|
|
|
|
Multiple(#[serde(borrow)] HashSet<BorrowedCowIfPossible<'a>>),
|
|
|
|
}
|
2021-12-03 14:42:05 -05:00
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
enum Issuer<'a> {
|
|
|
|
Single(#[serde(borrow)] Cow<'a, str>),
|
|
|
|
Multiple(#[serde(borrow)] HashSet<BorrowedCowIfPossible<'a>>),
|
|
|
|
}
|
2021-12-15 13:54:54 -05:00
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
/// Usually #[serde(borrow)] on `Cow` enables deserializing with no allocations where
|
|
|
|
/// possible (no escapes in the original str) but it does not work on e.g. `HashSet<Cow<str>>`
|
|
|
|
/// We use this struct in this case.
|
|
|
|
#[derive(Deserialize, PartialEq, Eq, Hash)]
|
|
|
|
struct BorrowedCowIfPossible<'a>(#[serde(borrow)] Cow<'a, str>);
|
|
|
|
impl std::borrow::Borrow<str> for BorrowedCowIfPossible<'_> {
|
|
|
|
fn borrow(&self) -> &str {
|
|
|
|
&*self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-03 14:42:05 -05:00
|
|
|
fn is_subset(reference: &HashSet<String>, given: &HashSet<BorrowedCowIfPossible<'_>>) -> bool {
|
|
|
|
// Check that intersection is non-empty, favoring iterating on smallest
|
|
|
|
if reference.len() < given.len() {
|
|
|
|
reference.iter().any(|a| given.contains(&**a))
|
|
|
|
} else {
|
|
|
|
given.iter().any(|a| reference.contains(&*a.0))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> Result<()> {
|
2019-11-11 13:47:35 -05:00
|
|
|
let now = get_current_timestamp();
|
2017-04-11 01:41:44 -04:00
|
|
|
|
2021-12-15 13:54:54 -05:00
|
|
|
for required_claim in &options.required_spec_claims {
|
|
|
|
let present = match required_claim.as_str() {
|
|
|
|
"exp" => matches!(claims.exp, TryParse::Parsed(_)),
|
|
|
|
"sub" => matches!(claims.sub, TryParse::Parsed(_)),
|
|
|
|
"iss" => matches!(claims.iss, TryParse::Parsed(_)),
|
|
|
|
"aud" => matches!(claims.aud, TryParse::Parsed(_)),
|
|
|
|
"nbf" => matches!(claims.nbf, TryParse::Parsed(_)),
|
|
|
|
_ => continue,
|
|
|
|
};
|
|
|
|
|
|
|
|
if !present {
|
|
|
|
return Err(new_error(ErrorKind::MissingRequiredClaim(required_claim.clone())));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
if options.validate_exp
|
|
|
|
&& !matches!(claims.exp, TryParse::Parsed(exp) if exp >= now-options.leeway)
|
|
|
|
{
|
|
|
|
return Err(new_error(ErrorKind::ExpiredSignature));
|
2017-04-11 01:41:44 -04:00
|
|
|
}
|
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
if options.validate_nbf
|
|
|
|
&& !matches!(claims.nbf, TryParse::Parsed(nbf) if nbf <= now + options.leeway)
|
|
|
|
{
|
|
|
|
return Err(new_error(ErrorKind::ImmatureSignature));
|
2017-04-11 01:41:44 -04:00
|
|
|
}
|
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
if let Some(correct_sub) = options.sub.as_deref() {
|
|
|
|
if !matches!(claims.sub, TryParse::Parsed(sub) if sub == correct_sub) {
|
2018-07-25 09:42:00 -04:00
|
|
|
return Err(new_error(ErrorKind::InvalidSubject));
|
2017-04-11 01:41:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 09:01:28 -05:00
|
|
|
if let Some(ref correct_iss) = options.iss {
|
2021-12-03 14:42:05 -05:00
|
|
|
let is_valid = match claims.iss {
|
|
|
|
TryParse::Parsed(Issuer::Single(iss)) if correct_iss.contains(&*iss) => true,
|
|
|
|
TryParse::Parsed(Issuer::Multiple(iss)) => is_subset(correct_iss, &iss),
|
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
|
|
|
|
if !is_valid {
|
2021-02-21 09:01:28 -05:00
|
|
|
return Err(new_error(ErrorKind::InvalidIssuer));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-25 09:42:00 -04:00
|
|
|
if let Some(ref correct_aud) = options.aud {
|
2021-12-03 14:42:05 -05:00
|
|
|
let is_valid = match claims.aud {
|
|
|
|
TryParse::Parsed(Audience::Single(aud)) if correct_aud.contains(&*aud) => true,
|
|
|
|
TryParse::Parsed(Audience::Multiple(aud)) => is_subset(correct_aud, &aud),
|
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
|
|
|
|
if !is_valid {
|
|
|
|
return Err(new_error(ErrorKind::InvalidAudience));
|
2017-04-11 01:41:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-11-01 09:25:59 -04:00
|
|
|
fn numeric_type<'de, D>(deserializer: D) -> std::result::Result<TryParse<u64>, D::Error>
|
|
|
|
where
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
{
|
|
|
|
struct NumericType(PhantomData<fn() -> TryParse<u64>>);
|
|
|
|
|
|
|
|
impl<'de> Visitor<'de> for NumericType {
|
|
|
|
type Value = TryParse<u64>;
|
|
|
|
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
formatter.write_str("A NumericType that can be reasonably coerced into a u64")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_f64<E>(self, value: f64) -> std::result::Result<Self::Value, E>
|
|
|
|
where
|
|
|
|
E: de::Error,
|
|
|
|
{
|
|
|
|
if value.is_finite() && value >= 0.0 && value < (u64::MAX as f64) {
|
|
|
|
Ok(TryParse::Parsed(value.round() as u64))
|
|
|
|
} else {
|
|
|
|
Err(serde::de::Error::custom("NumericType must be representable as a u64"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_u64<E>(self, value: u64) -> std::result::Result<Self::Value, E>
|
|
|
|
where
|
|
|
|
E: de::Error,
|
|
|
|
{
|
|
|
|
Ok(TryParse::Parsed(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match deserializer.deserialize_any(NumericType(PhantomData)) {
|
|
|
|
Ok(ok) => Ok(ok),
|
|
|
|
Err(_) => Ok(TryParse::FailedToParse),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-11 01:41:44 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-09-28 12:16:51 -04:00
|
|
|
use serde_json::json;
|
2017-04-11 01:41:44 -04:00
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
use super::{get_current_timestamp, validate, ClaimsForValidation, Validation};
|
2017-04-11 01:41:44 -04:00
|
|
|
|
2019-10-31 14:12:08 -04:00
|
|
|
use crate::errors::ErrorKind;
|
2021-09-28 04:04:51 -04:00
|
|
|
use crate::Algorithm;
|
2021-12-15 13:54:54 -05:00
|
|
|
use std::collections::HashSet;
|
2017-04-11 01:41:44 -04:00
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
fn deserialize_claims(claims: &serde_json::Value) -> ClaimsForValidation {
|
|
|
|
serde::Deserialize::deserialize(claims).unwrap()
|
|
|
|
}
|
|
|
|
|
2017-04-11 01:41:44 -04:00
|
|
|
#[test]
|
|
|
|
fn exp_in_future_ok() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({ "exp": get_current_timestamp() + 10000 });
|
|
|
|
let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
2021-11-01 09:25:59 -04:00
|
|
|
#[test]
|
|
|
|
fn exp_float_in_future_ok() {
|
|
|
|
let claims = json!({ "exp": (get_current_timestamp() as f64) + 10000.123 });
|
|
|
|
let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
|
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
2017-04-11 01:41:44 -04:00
|
|
|
#[test]
|
|
|
|
fn exp_in_past_fails() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({ "exp": get_current_timestamp() - 100000 });
|
|
|
|
let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_err());
|
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::ExpiredSignature => (),
|
|
|
|
_ => unreachable!(),
|
2017-04-11 01:41:44 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-11-01 09:25:59 -04:00
|
|
|
#[test]
|
|
|
|
fn exp_float_in_past_fails() {
|
|
|
|
let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 });
|
|
|
|
let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
|
|
|
|
assert!(res.is_err());
|
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
|
|
|
ErrorKind::ExpiredSignature => (),
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-04-11 01:41:44 -04:00
|
|
|
#[test]
|
|
|
|
fn exp_in_past_but_in_leeway_ok() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({ "exp": get_current_timestamp() - 500 });
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.leeway = 1000 * 60;
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
2018-07-25 09:42:00 -04:00
|
|
|
// https://github.com/Keats/jsonwebtoken/issues/51
|
|
|
|
#[test]
|
|
|
|
fn validation_called_even_if_field_is_empty() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({});
|
2021-12-15 13:54:54 -05:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.required_spec_claims = HashSet::new();
|
|
|
|
let res = validate(deserialize_claims(&claims), &validation).unwrap_err();
|
|
|
|
assert_eq!(res.kind(), &ErrorKind::ExpiredSignature);
|
2018-07-25 09:42:00 -04:00
|
|
|
}
|
|
|
|
|
2017-04-11 01:41:44 -04:00
|
|
|
#[test]
|
|
|
|
fn nbf_in_past_ok() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({ "nbf": get_current_timestamp() - 10000 });
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.validate_exp = false;
|
2021-11-01 09:25:59 -04:00
|
|
|
validation.validate_nbf = true;
|
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn nbf_float_in_past_ok() {
|
|
|
|
let claims = json!({ "nbf": (get_current_timestamp() as f64) - 10000.1234 });
|
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-11-01 09:25:59 -04:00
|
|
|
validation.validate_exp = false;
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.validate_nbf = true;
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn nbf_in_future_fails() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({ "nbf": get_current_timestamp() + 100000 });
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.validate_exp = false;
|
|
|
|
validation.validate_nbf = true;
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_err());
|
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::ImmatureSignature => (),
|
|
|
|
_ => unreachable!(),
|
2017-04-11 01:41:44 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn nbf_in_future_but_in_leeway_ok() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({ "nbf": get_current_timestamp() + 500 });
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.validate_exp = false;
|
|
|
|
validation.validate_nbf = true;
|
|
|
|
validation.leeway = 1000 * 60;
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-12-03 14:42:05 -05:00
|
|
|
fn iss_string_ok() {
|
|
|
|
let claims = json!({"iss": ["Keats"]});
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.validate_exp = false;
|
2021-12-03 13:57:42 -05:00
|
|
|
validation.set_issuer(&["Keats"]);
|
2021-12-03 14:42:05 -05:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
2021-09-28 12:16:51 -04:00
|
|
|
|
2021-12-03 14:42:05 -05:00
|
|
|
#[test]
|
|
|
|
fn iss_array_of_string_ok() {
|
|
|
|
let claims = json!({"iss": ["UserA", "UserB"]});
|
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-12-03 14:42:05 -05:00
|
|
|
validation.validate_exp = false;
|
|
|
|
validation.set_issuer(&["UserA", "UserB"]);
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn iss_not_matching_fails() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({"iss": "Hacked"});
|
2021-02-21 09:01:28 -05:00
|
|
|
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.validate_exp = false;
|
2021-12-03 13:57:42 -05:00
|
|
|
validation.set_issuer(&["Keats"]);
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2018-07-25 09:42:00 -04:00
|
|
|
assert!(res.is_err());
|
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::InvalidIssuer => (),
|
|
|
|
_ => unreachable!(),
|
2018-07-25 09:42:00 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn iss_missing_fails() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({});
|
2021-02-21 09:01:28 -05:00
|
|
|
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.validate_exp = false;
|
2021-12-03 13:57:42 -05:00
|
|
|
validation.set_issuer(&["Keats"]);
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::InvalidIssuer => (),
|
|
|
|
_ => unreachable!(),
|
2017-04-11 01:41:44 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn sub_ok() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({"sub": "Keats"});
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.validate_exp = false;
|
|
|
|
validation.sub = Some("Keats".to_owned());
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn sub_not_matching_fails() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({"sub": "Hacked"});
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.validate_exp = false;
|
|
|
|
validation.sub = Some("Keats".to_owned());
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2018-07-25 09:42:00 -04:00
|
|
|
assert!(res.is_err());
|
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::InvalidSubject => (),
|
|
|
|
_ => unreachable!(),
|
2018-07-25 09:42:00 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn sub_missing_fails() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({});
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.validate_exp = false;
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.sub = Some("Keats".to_owned());
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_err());
|
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::InvalidSubject => (),
|
|
|
|
_ => unreachable!(),
|
2017-04-11 01:41:44 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn aud_string_ok() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({"aud": ["Everyone"]});
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.validate_exp = false;
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2019-10-28 11:49:02 -04:00
|
|
|
validation.set_audience(&["Everyone"]);
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn aud_array_of_string_ok() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({"aud": ["UserA", "UserB"]});
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.validate_exp = false;
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2017-04-11 01:41:44 -04:00
|
|
|
validation.set_audience(&["UserA", "UserB"]);
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn aud_type_mismatch_fails() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({"aud": ["Everyone"]});
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.validate_exp = false;
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2017-04-11 01:41:44 -04:00
|
|
|
validation.set_audience(&["UserA", "UserB"]);
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_err());
|
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::InvalidAudience => (),
|
|
|
|
_ => unreachable!(),
|
2017-04-11 01:41:44 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn aud_correct_type_not_matching_fails() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({"aud": ["Everyone"]});
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.validate_exp = false;
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2019-10-28 11:49:02 -04:00
|
|
|
validation.set_audience(&["None"]);
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2018-07-25 09:42:00 -04:00
|
|
|
assert!(res.is_err());
|
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::InvalidAudience => (),
|
|
|
|
_ => unreachable!(),
|
2018-07-25 09:42:00 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn aud_missing_fails() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({});
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.validate_exp = false;
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2019-10-28 11:49:02 -04:00
|
|
|
validation.set_audience(&["None"]);
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2017-04-11 01:41:44 -04:00
|
|
|
assert!(res.is_err());
|
|
|
|
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::InvalidAudience => (),
|
|
|
|
_ => unreachable!(),
|
2017-04-11 01:41:44 -04:00
|
|
|
};
|
|
|
|
}
|
2019-11-03 10:36:19 -05:00
|
|
|
|
|
|
|
// https://github.com/Keats/jsonwebtoken/issues/51
|
|
|
|
#[test]
|
|
|
|
fn does_validation_in_right_order() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({ "exp": get_current_timestamp() + 10000 });
|
2021-02-21 09:01:28 -05:00
|
|
|
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.leeway = 5;
|
2021-12-03 13:57:42 -05:00
|
|
|
validation.set_issuer(&["iss no check"]);
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.set_audience(&["iss no check"]);
|
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2019-11-03 10:36:19 -05:00
|
|
|
// It errors because it needs to validate iss/sub which are missing
|
|
|
|
assert!(res.is_err());
|
|
|
|
match res.unwrap_err().kind() {
|
2020-08-31 06:04:57 -04:00
|
|
|
ErrorKind::InvalidIssuer => (),
|
|
|
|
t => panic!("{:?}", t),
|
2019-11-03 10:36:19 -05:00
|
|
|
};
|
|
|
|
}
|
2019-11-28 13:27:08 -05:00
|
|
|
|
|
|
|
// https://github.com/Keats/jsonwebtoken/issues/110
|
|
|
|
#[test]
|
|
|
|
fn aud_use_validation_struct() {
|
2021-09-28 12:16:51 -04:00
|
|
|
let claims = json!({"aud": "my-googleclientid1234.apps.googleusercontent.com"});
|
2019-11-28 13:27:08 -05:00
|
|
|
|
|
|
|
let aud = "my-googleclientid1234.apps.googleusercontent.com".to_string();
|
|
|
|
let mut aud_hashset = std::collections::HashSet::new();
|
|
|
|
aud_hashset.insert(aud);
|
2021-09-28 04:04:51 -04:00
|
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
|
|
|
validation.validate_exp = false;
|
2021-12-15 13:54:54 -05:00
|
|
|
validation.required_spec_claims = HashSet::new();
|
2021-09-28 04:04:51 -04:00
|
|
|
validation.set_audience(&["my-googleclientid1234.apps.googleusercontent.com"]);
|
2019-11-28 13:27:08 -05:00
|
|
|
|
2021-09-28 12:16:51 -04:00
|
|
|
let res = validate(deserialize_claims(&claims), &validation);
|
2019-11-28 13:27:08 -05:00
|
|
|
assert!(res.is_ok());
|
|
|
|
}
|
2021-12-03 14:42:05 -05:00
|
|
|
|
|
|
|
#[test]
|
2021-12-15 13:54:54 -05:00
|
|
|
fn errors_when_required_claim_is_missing() {
|
|
|
|
let claims = json!({});
|
|
|
|
let res = validate(deserialize_claims(&claims), &Validation::default()).unwrap_err();
|
|
|
|
assert_eq!(res.kind(), &ErrorKind::MissingRequiredClaim("exp".to_owned()));
|
2021-12-03 14:42:05 -05:00
|
|
|
}
|
2017-04-11 01:41:44 -04:00
|
|
|
}
|