extern crate serde;
extern crate serde_json;
extern crate yaml_rust;
extern crate chrono;
extern crate jsonwebtoken as jwt;
extern crate uuid;
use core::chrono::prelude::*;
use core::uuid::Uuid;
use std::collections::HashMap;
use std::error;
use std::fmt;
use std::result;
type Result<A> = result::Result<A, Error>;
#[derive(Debug)]
pub enum Error {
JWTError(jwt::errors::Error),
UnknownToken,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::JWTError(err) => write!(f, "JWT failed to decode: {}", err),
Error::UnknownToken => write!(f, "Token not recognized"),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match self {
Error::JWTError(ref err) => err.description(),
Error::UnknownToken => "Token not recognized",
}
}
fn cause(&self) -> Option<&error::Error> {
match self {
Error::JWTError(ref err) => Some(err),
Error::UnknownToken => None,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct ResourceName(pub String);
#[derive(Debug, PartialEq, Clone)]
pub struct Permissions(pub Vec<String>);
#[derive(Debug, PartialEq, Clone)]
pub struct Issuer(pub String);
#[derive(Debug, PartialEq, Clone)]
pub struct TTL(pub chrono::Duration);
#[derive(Debug, PartialEq, Clone)]
pub struct Username(pub String);
#[derive(Debug, PartialEq, Clone)]
pub struct Secret(pub Vec<u8>);
#[derive(Debug, PartialEq, Clone)]
pub struct ClaimSet {
pub id: String,
pub audience: Username,
pub expiration: Option<DateTime<Utc>>,
pub issuer: Issuer,
pub issued_at: DateTime<Utc>,
pub resource: ResourceName,
pub permissions: Permissions,
}
impl ClaimSet {
pub fn new(
issuer: Issuer,
ttl: Option<TTL>,
resource_name: ResourceName,
user_name: Username,
perms: Permissions,
) -> ClaimSet {
let issued_at: DateTime<Utc> = Utc::now().with_nanosecond(0).unwrap();
let expiration = match ttl {
Some(TTL(ttl_)) => issued_at.checked_add_signed(ttl_),
None => None,
};
ClaimSet {
id: String::from(Uuid::new_v4().hyphenated().to_string()),
audience: user_name,
expiration,
issuer,
issued_at,
resource: resource_name,
permissions: perms,
}
}
pub fn to_json(&self) -> result::Result<String, serde_json::Error> {
serde_json::to_string(&(ClaimSetJS::from_claimset(self)))
}
pub fn from_json(text: &String) -> result::Result<ClaimSet, serde_json::Error> {
serde_json::from_str(&text).map(|x| ClaimSetJS::to_claimset(&x))
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct ClaimSetJS {
jti: String,
aud: String,
exp: Option<i64>,
iss: String,
iat: i64,
sub: String,
perms: Vec<String>,
}
impl ClaimSetJS {
pub fn from_claimset(claims: &ClaimSet) -> ClaimSetJS {
ClaimSetJS {
jti: claims.id.clone(),
aud: claims.audience.0.clone(),
exp: claims.expiration.map(|t| t.timestamp()),
iss: claims.issuer.0.clone(),
iat: claims.issued_at.timestamp(),
sub: claims.resource.0.clone(),
perms: claims.permissions.0.clone(),
}
}
pub fn to_claimset(&self) -> ClaimSet {
ClaimSet {
id: self.jti.clone(),
audience: Username(self.aud.clone()),
expiration: self.exp.map(|t| Utc.timestamp(t, 0)),
issuer: Issuer(self.iss.clone()),
issued_at: Utc.timestamp(self.iat, 0),
resource: ResourceName(self.sub.clone()),
permissions: Permissions(self.perms.clone()),
}
}
}
pub struct OrizenticCtx(Secret, HashMap<String, ClaimSet>);
#[derive(Debug)]
pub struct UnverifiedToken {
pub text: String,
pub claims: ClaimSet,
}
impl UnverifiedToken {
pub fn decode_text(text: &String) -> Result<UnverifiedToken> {
let res = jwt::dangerous_unsafe_decode::<ClaimSetJS>(text);
match res {
Ok(res_) => Ok(UnverifiedToken {
text: text.clone(),
claims: res_.claims.to_claimset(),
}),
Err(err) => Err(Error::JWTError(err)),
}
}
}
#[derive(Debug)]
pub struct VerifiedToken {
pub text: String,
pub claims: ClaimSet,
}
impl VerifiedToken {
pub fn check_authorizations<F: FnOnce(&ResourceName, &Permissions) -> bool>(
&self,
f: F,
) -> bool {
f(&self.claims.resource, &self.claims.permissions)
}
}
impl OrizenticCtx {
pub fn new(secret: Secret, claims_lst: Vec<ClaimSet>) -> OrizenticCtx {
let mut hm = HashMap::new();
for claimset in claims_lst {
hm.insert(claimset.id.clone(), claimset);
}
OrizenticCtx(secret, hm)
}
pub fn validate_token(&self, token: &UnverifiedToken) -> Result<VerifiedToken> {
let validator = match token.claims.expiration {
Some(_) => jwt::Validation::default(),
None => jwt::Validation {
validate_exp: false,
..jwt::Validation::default()
},
};
let res = jwt::decode::<ClaimSetJS>(&token.text, &(self.0).0, &validator);
match res {
Ok(res_) => {
let claims = res_.claims;
let in_db = self.1.get(&claims.jti);
if in_db.is_some() {
Ok(VerifiedToken {
text: token.text.clone(),
claims: claims.to_claimset(),
})
} else {
Err(Error::UnknownToken)
}
}
Err(err) => Err(Error::JWTError(err)),
}
}
pub fn decode_and_validate_text(&self, text: &String) -> Result<VerifiedToken> {
match UnverifiedToken::decode_text(text) {
Ok(unverified) => self.validate_token(&unverified),
Err(err) => Err(err),
}
}
pub fn add_claimset(&mut self, claimset: ClaimSet) {
self.1.insert(claimset.id.clone(), claimset);
}
pub fn revoke_claimset(&mut self, claim: &ClaimSet) {
self.1.remove(&claim.id);
}
pub fn revoke_by_uuid(&mut self, claim_id: &String) {
self.1.remove(claim_id);
}
pub fn replace_claimsets(&mut self, _claims_lst: Vec<ClaimSet>) {
unimplemented!()
}
pub fn list_claimsets(&self) -> Vec<&ClaimSet> {
self.1.values().map(|item| item).collect()
}
pub fn find_claimset(&self, claims_id: &String) -> Option<&ClaimSet> {
self.1.get(claims_id)
}
pub fn encode_claimset(&self, claims: &ClaimSet) -> Result<VerifiedToken> {
let in_db = self.1.get(&claims.id);
if in_db.is_some() {
let text = jwt::encode(
&jwt::Header::default(),
&ClaimSetJS::from_claimset(&claims),
&(self.0).0,
);
match text {
Ok(text_) => Ok(VerifiedToken {
text: text_,
claims: claims.clone(),
}),
Err(err) => Err(Error::JWTError(err)),
}
} else {
Err(Error::UnknownToken)
}
}
}