oxide-auth 0.4.5

A OAuth2 server library, for actix, rocket, iron or other libraries, featuring a set of configurable and pluggable backends.
Documentation
use std::collections::HashMap;

use primitives::authorizer::{Authorizer, AuthMap};
use primitives::generator::RandomGenerator;
use primitives::issuer::{Issuer, TokenSigner};
use primitives::registrar::{Client, ClientMap};
use primitives::grant::{Extensions, Grant};

use endpoint::{OAuthError, OwnerConsent};
use frontends::simple::endpoint::FnSolicitor;
use frontends::simple::request::{Body, MapErr, NoError, Request, Response, Status};

use super::{AsActor, ResourceProtection, access_token, authorization, resource};
use super::actix::{Actor, Addr, System, SystemRunner};

use chrono::{Utc, Duration};
use serde_json;

struct Setup {
    authorizer: Addr<AsActor<AuthMap<RandomGenerator>>>,
    registrar: Addr<AsActor<ClientMap>>,
    issuer: Addr<AsActor<TokenSigner>>,
    runner: SystemRunner,
    valid_authorization: String,
    valid_token: String,
}

impl Setup {
    fn start() -> Self {
        use self::defaults::*;

        let scope = EXAMPLE_SCOPE.parse().unwrap();

        let mut authorizer = AuthMap::new(RandomGenerator::new(16));
        let mut registrar = ClientMap::new();
        let mut issuer = TokenSigner::ephemeral();

        registrar.register_client(Client::confidential(
            EXAMPLE_CLIENT_ID,
            EXAMPLE_REDIRECT_URI.parse().unwrap(),
            scope,
            EXAMPLE_PASSPHRASE.as_bytes(),
        ));

        let grant = Grant {
            client_id: EXAMPLE_CLIENT_ID.to_string(),
            owner_id: EXAMPLE_OWNER_ID.to_string(),
            redirect_uri: EXAMPLE_REDIRECT_URI.parse().unwrap(),
            scope: EXAMPLE_SCOPE.parse().unwrap(),
            until: Utc::now() + Duration::hours(1),
            extensions: Extensions::new(),
        };

        let valid_authorization = authorizer.authorize(grant.clone()).unwrap();
        let valid_token = issuer.issue(grant).unwrap().token;

        let runner = System::new("OAuthTestSystem");
        let authorizer = AsActor(authorizer).start();
        let registrar = AsActor(registrar).start();
        let issuer = AsActor(issuer).start();

        Setup {
            authorizer,
            registrar,
            issuer,
            runner,
            valid_authorization,
            valid_token,
        }
    }
}

mod defaults {
    pub const EXAMPLE_CLIENT_ID: &str = "ClientId";
    pub const EXAMPLE_OWNER_ID: &str = "Owner";
    pub const EXAMPLE_PASSPHRASE: &str = "VGhpcyBpcyBhIHZlcnkgc2VjdXJlIHBhc3NwaHJhc2UK";
    pub const EXAMPLE_REDIRECT_URI: &str = "https://client.example/endpoint";
    pub const EXAMPLE_SCOPE: &str = "example default";
}

#[test]
fn future_authorization() {
    let mut setup = Setup::start();

    let request = Request {
        query: vec![
            ("response_type", "code"),
            ("client_id", defaults::EXAMPLE_CLIENT_ID),
            ("redirect_uri", defaults::EXAMPLE_REDIRECT_URI)]
                .into_iter()
                .map(|(k, v)| (k.to_string(), v.to_string()))
                .collect(),
        urlbody: HashMap::new(),
        auth: None,
    };

    let response = Response::default();

    let result = setup.runner.block_on(authorization(
        setup.registrar.clone(),
        setup.authorizer.clone(),
        FnSolicitor(|_req: &mut _, _: &_| { OwnerConsent::Authorized(defaults::EXAMPLE_OWNER_ID.to_string()) }),
        MapErr::request(request, NoError::into::<OAuthError>),
        MapErr::response(response, NoError::into::<OAuthError>)));

    let result = result
        .expect("Should not be an oauth error");
    let response = result.into_inner();

    assert_eq!(response.status, Status::Redirect);

    let location = response.location.expect("Location header should be set");
    eprintln!("{:?}", &location);
    assert_eq!(location.as_str().find("error"), None);
}

#[test]
fn future_access_token() {
    let mut setup = Setup::start();

    let request = Request {
        query: HashMap::new(),
        urlbody: vec![
            ("grant_type", "authorization_code"),
            ("code", &setup.valid_authorization),
            ("redirect_uri", defaults::EXAMPLE_REDIRECT_URI)]
                .into_iter()
                .map(|(k, v)| (k.to_string(), v.to_string()))
                .collect(),
        auth: Some("Basic ".to_string() + &base64::encode(&format!("{}:{}",
            defaults::EXAMPLE_CLIENT_ID, defaults::EXAMPLE_PASSPHRASE))),
    };

    let response = Response::default();

    let result = setup.runner.block_on(access_token(
        setup.registrar.clone(),
        setup.authorizer.clone(),
        setup.issuer.clone(),
        MapErr::request(request, NoError::into::<OAuthError>),
        MapErr::response(response, NoError::into::<OAuthError>)));

    let result = result
        .expect("Should not be an oauth error");
    let response = result.into_inner();

    assert_eq!(response.status, Status::Ok);

    let body = response.body.as_ref().map(Body::as_str)
        .expect("Should have a body");
    let response = serde_json::from_str::<HashMap<String, String>>(body)
        .expect("Should decode as valid json map");

    assert!(response.get("access_token").is_some());
    assert_eq!(response.get("token_type").cloned(), Some("bearer".to_owned()));
}

#[test]
fn future_resource() {
    let mut setup = Setup::start();

    let request = Request {
        query: HashMap::new(),
        urlbody: HashMap::new(),
        auth: Some("Bearer ".to_string() + &setup.valid_token),
    };

    let response = Response::default();

    let result = setup.runner.block_on(resource(
        setup.issuer.clone(),
        vec![defaults::EXAMPLE_SCOPE.parse().unwrap()],
        MapErr::request(request, NoError::into::<OAuthError>),
        MapErr::response(response, NoError::into::<OAuthError>)));

    let () = match result {
        Ok(_grant) => (),
        Err(ResourceProtection::Respond(resp)) => panic!("Should not be a response: {:?}", resp.into_inner()),
        Err(ResourceProtection::Error(err)) => panic!("Should not be an oauth error: {:?}", err),
    };
}