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::frontend::*;
use super::backend::{CodeRef, ErrorUrl, IssuerRef, GuardRef};
use super::authorizer::Storage;
use super::issuer::TokenMap;
use super::registrar::ClientMap;
use super::scope::Scope;

use std::borrow::Cow;
use std::collections::HashMap;

use url::Url;
use serde_json;

struct CraftedRequest {
    query: Option<HashMap<String, Vec<String>>>,
    urlbody: Option<HashMap<String, Vec<String>>>,
    auth: Option<String>,
}

enum CraftedResponse {
    Redirect(Url),
    Text(String),
    Json(String),
    RedirectFromError(Url),
    ClientError(Box<CraftedResponse>),
    Unauthorized(Box<CraftedResponse>),
    Authorization(Box<CraftedResponse>, String),
}

impl WebRequest for CraftedRequest {
    type Response = CraftedResponse;

    fn query(&mut self) -> Result<HashMap<String, Vec<String>>, ()> {
        self.query.clone().ok_or(())
    }

    fn urlbody(&mut self) -> Result<&HashMap<String, Vec<String>>, ()> {
        self.urlbody.as_ref().ok_or(())
    }

    fn authheader(&mut self) -> Result<Option<Cow<str>>, ()> {
        Ok(self.auth.as_ref().map(|bearer| bearer.as_str().into()))
    }
}

impl WebResponse for CraftedResponse {
    fn redirect(url: Url) -> Result<Self, OAuthError> {
        Ok(CraftedResponse::Redirect(url))
    }

    fn text(text: &str) -> Result<Self, OAuthError> {
        Ok(CraftedResponse::Text(text.to_string()))
    }

    fn json(data: &str) -> Result<Self, OAuthError> {
        Ok(CraftedResponse::Json(data.to_string()))
    }

    fn redirect_error(target: ErrorUrl) -> Result<Self, OAuthError> {
        Ok(CraftedResponse::RedirectFromError(target.into()))
    }

    fn as_client_error(self) -> Result<Self, OAuthError> {
        Ok(CraftedResponse::ClientError(self.into()))
    }

    fn as_unauthorized(self) -> Result<Self, OAuthError> {
        Ok(CraftedResponse::Unauthorized(self.into()))
    }

    fn with_authorization(self, kind: &str) -> Result<Self, OAuthError> {
        Ok(CraftedResponse::Authorization(self.into(), kind.to_string()))
    }
}

struct TestGenerator(String);

impl super::TokenGenerator for TestGenerator {
    fn generate(&self, _grant: &super::Grant) -> String {
        self.0.clone()
    }
}

struct Allow(String);

impl OwnerAuthorizer for Allow {
    type Request = CraftedRequest;
    fn get_owner_authorization(&self, _: &mut CraftedRequest, _: AuthenticationRequest)
    -> Result<(Authentication, CraftedResponse), OAuthError> {
        Ok((Authentication::Authenticated(self.0.clone()), CraftedResponse::Text("".to_string())))
    }
}

#[test]
fn authorize_and_get() {
    let mut registrar = ClientMap::new();
    let mut authorizer = Storage::new(TestGenerator("AuthToken".to_string()));
    let mut issuer = TokenMap::new(TestGenerator("AcessToken".to_string()));

    let client_id = "ClientId";
    let owner_id = "Owner";
    let redirect_url = "https://client.example/endpoint";

    registrar.register_client(client_id, Url::parse(redirect_url).unwrap());

    let mut authrequest = CraftedRequest {
        query: Some(vec![("client_id", client_id),
                         ("redirect_url", redirect_url),
                         ("response_type", "code")]
            .into_iter()
            .map(|(k, v)| (k.to_string(), vec![v.to_string()])).collect()),
        urlbody: Some(HashMap::new()),
        auth: None,
    };

    let prepared = AuthorizationFlow::prepare(&mut authrequest).expect("Failure during authorization preparation");
    let pagehandler = Allow(owner_id.to_string());
    match AuthorizationFlow::handle(CodeRef::with(&mut registrar, &mut authorizer), prepared, &pagehandler)
          .expect("Failure during authorization handling") {
        CraftedResponse::Redirect(ref url) if url.as_str() == "https://client.example/endpoint?code=AuthToken"
            => (),
        _ => panic!()
    };

    let mut tokenrequest = CraftedRequest {
        query: Some(HashMap::new()),
        urlbody: Some(vec![("client_id", client_id),
                           ("redirect_url", redirect_url),
                           ("code", "AuthToken"),
                           ("grant_type", "authorization_code")]
            .into_iter()
            .map(|(k, v)| (k.to_string(), vec![v.to_string()])).collect()),
        auth: None,
    };

    let prepared = GrantFlow::prepare(&mut tokenrequest).expect("Failure during access token preparation");
    let (token, scope) = match GrantFlow::handle(IssuerRef::with(&mut authorizer, &mut issuer), prepared)
          .expect("Failure during access token handling") {
        CraftedResponse::Json(json)
            => {
                let parsed: HashMap<String, String> = serde_json::from_str(&json).unwrap();
                assert!(parsed.get("error").is_none());
                assert!(parsed.get("expires_in").unwrap().parse::<i32>().unwrap() > 0);
                let token = parsed.get("access_token").unwrap().to_string();
                let scope = parsed.get("scope").unwrap().to_string();
                (token, scope)
            },
        _ => panic!(),
    };

    let mut accessrequest = CraftedRequest {
        query: None,
        urlbody: None,
        auth: Some(token),
    };

    let prepared = AccessFlow::prepare(&mut accessrequest).expect("Failure during access preparation");
    let scope: [Scope; 1] = [scope.parse().unwrap()];
    AccessFlow::handle(GuardRef::with(&mut issuer, &scope), prepared).expect("Failed to authorize");
}