oxide-auth 0.1.0-alpha

A OAuth2 server library, for use in combination with iron or other frontends, featuring a set of configurable and pluggable backends.
Documentation
use super::{Grant, TokenGenerator};
use chrono::{Utc, TimeZone};
use std::borrow::Cow;
use rand::{thread_rng, Rng};
use ring;
use rmp_serde;
use url::Url;
use base64::{encode, decode};

pub struct RandomGenerator {
    len: usize
}

impl RandomGenerator {
    pub fn new(length: usize) -> RandomGenerator {
        RandomGenerator {len: length}
    }
}

impl TokenGenerator for RandomGenerator {
    fn generate(&self, _grant: &Grant) -> String {
        let result = thread_rng().gen_iter::<u8>().take(self.len).collect::<Vec<u8>>();
        encode(&result)
    }
}

pub struct Assertion {
    secret: ring::hmac::SigningKey,
}

#[derive(Serialize, Deserialize)]
pub struct InternalAssertionGrant<'a>(&'a str, &'a str, &'a str, &'a str, (i64, u32), &'a str);
#[derive(Serialize, Deserialize)]
pub struct AssertGrant(Vec<u8>, Vec<u8>);

pub struct TaggedAssertion<'a>(&'a Assertion, &'a str);

impl Assertion {
    pub fn new(key: ring::hmac::SigningKey) -> Assertion {
        Assertion { secret: key}
    }

    pub fn tag<'a>(&'a self, tag: &'a str) -> TaggedAssertion<'a> {
        TaggedAssertion(self, tag)
    }

    fn extract<'a>(&self, token: &'a str) -> Result<(Grant<'a>, String), ()> {
        let readbytes = decode(token).map_err(|_| ())?;
        let AssertGrant(message, digest) = rmp_serde::from_slice(&readbytes).unwrap();

        ring::hmac::verify_with_own_key(&self.secret, &message, &digest).map_err(|_| ())?;
        let InternalAssertionGrant(owner_id, client_id, redirectbytes, scope, (ts, tsnanos), tag) =
            rmp_serde::from_slice(&message).map_err(|_| ())?;

        let redirect_url = Url::parse(redirectbytes).map_err(|_| ())?;
        let scope = scope.parse().map_err(|_| ())?;
        let until = Utc::timestamp(&Utc, ts, tsnanos);
        Ok((Grant{
            owner_id: Cow::Owned(owner_id.to_string()),
            client_id: Cow::Owned(client_id.to_string()),
            redirect_url: Cow::Owned(redirect_url),
            scope: Cow::Owned(scope),
            until: Cow::Owned(until),
        }, tag.to_string()))
    }

    fn generate_tagged(&self, grant: &Grant, tag: &str) -> String {
        let tosign = rmp_serde::to_vec(&InternalAssertionGrant(
            &grant.owner_id,
            &grant.client_id,
            grant.redirect_url.as_str(),
            &grant.scope.to_string(),
            (grant.until.timestamp(), grant.until.timestamp_subsec_nanos()),
            tag)).unwrap();
        let signature = ring::hmac::sign(&self.secret, &tosign);
        encode(&rmp_serde::to_vec(&AssertGrant(tosign, signature.as_ref().to_vec())).unwrap())
    }
}

impl<'a> TaggedAssertion<'a> {
    pub fn extract<'b>(&self, token: &'b str) -> Result<Grant<'b>, ()> {
        self.0.extract(token).and_then(|(token, tag)| {
            if tag == self.1 {
                Ok(token)
            } else {
                Err(())
            }
        })
    }
}

impl<'a> TokenGenerator for TaggedAssertion<'a> {
    fn generate(&self, grant: &Grant) -> String {
        self.0.generate_tagged(grant, self.1)
    }
}