extern crate iron;
extern crate urlencoded;
use super::code_grant::prelude::*;
use super::code_grant::{Authorizer, Issuer, Registrar};
use super::code_grant::frontend::{AccessFlow, AuthorizationFlow, GrantFlow, OwnerAuthorizer, WebRequest, WebResponse};
pub use super::code_grant::frontend::{AuthenticationRequest, Authentication, OAuthError};
pub use super::code_grant::Scope;
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, LockResult, MutexGuard};
use std::ops::DerefMut;
use std::marker::PhantomData;
use self::iron::prelude::*;
use self::iron::headers::{Authorization as AuthHeader};
use self::iron::modifiers::Redirect;
use self::urlencoded::{UrlEncodedBody, UrlEncodedQuery};
use url::Url;
pub struct IronGranter<R, A, I> where
R: Registrar + Send + 'static,
A: Authorizer + Send + 'static,
I: Issuer + Send + 'static
{
registrar: Arc<Mutex<R>>,
authorizer: Arc<Mutex<A>>,
issuer: Arc<Mutex<I>>,
}
pub struct IronAuthorizer<PH, R, A> where
PH: GenericOwnerAuthorizer + Send + Sync,
R: Registrar + Send + 'static,
A: Authorizer + Send + 'static,
{
page_handler: Box<PH>,
registrar: Arc<Mutex<R>>,
authorizer: Arc<Mutex<A>>,
}
pub struct IronTokenRequest<A, I> where
A: Authorizer + Send + 'static,
I: Issuer + Send + 'static
{
authorizer: Arc<Mutex<A>>,
issuer: Arc<Mutex<I>>,
}
pub struct IronGuard<I> where
I: Issuer + Send + 'static
{
scopes: Vec<Scope>,
issuer: Arc<Mutex<I>>,
}
impl iron::typemap::Key for AuthenticationRequest { type Value = AuthenticationRequest; }
impl iron::typemap::Key for Authentication { type Value = Authentication; }
pub trait GenericOwnerAuthorizer {
fn get_owner_authorization(&self, &mut iron::Request, AuthenticationRequest) -> Result<(Authentication, iron::Response), OAuthError>;
}
pub struct IronOwnerAuthorizer<A: iron::Handler>(pub A);
impl GenericOwnerAuthorizer for iron::Handler {
fn get_owner_authorization(&self, req: &mut iron::Request, auth: AuthenticationRequest)
-> Result<(Authentication, Response), OAuthError> {
req.extensions.insert::<AuthenticationRequest>(auth);
let response = self.handle(req).map_err(|_| OAuthError::Other("Internal error".to_string()))?;
match req.extensions.get::<Authentication>() {
None => return Ok((Authentication::Failed, Response::with((iron::status::InternalServerError, "No authentication response")))),
Some(v) => return Ok((v.clone(), response)),
};
}
}
impl<F> GenericOwnerAuthorizer for F
where F :Fn(&mut iron::Request, AuthenticationRequest) -> Result<(Authentication, Response), OAuthError> + Send + Sync + 'static {
fn get_owner_authorization(&self, req: &mut iron::Request, auth: AuthenticationRequest)
-> Result<(Authentication, Response), OAuthError> {
self(req, auth)
}
}
impl<A: iron::Handler> GenericOwnerAuthorizer for IronOwnerAuthorizer<A> {
fn get_owner_authorization(&self, req: &mut iron::Request, auth: AuthenticationRequest)
-> Result<(Authentication, Response), OAuthError> {
(&self.0 as &iron::Handler).get_owner_authorization(req, auth)
}
}
struct SpecificOwnerAuthorizer<'l, 'a, 'b: 'a>(&'l GenericOwnerAuthorizer, PhantomData<iron::Request<'a, 'b>>);
impl<'l, 'a, 'b: 'a> OwnerAuthorizer for SpecificOwnerAuthorizer<'l, 'a, 'b> {
type Request = iron::Request<'a, 'b>;
fn get_owner_authorization(&self, req: &mut Self::Request, auth: AuthenticationRequest)
-> Result<(Authentication, Response), OAuthError> {
self.0.get_owner_authorization(req, auth)
}
}
impl<'a, 'b> WebRequest for iron::Request<'a, 'b> {
type Response = iron::Response;
fn query(&mut self) -> Result<HashMap<String, Vec<String>>, ()> {
self.get::<UrlEncodedQuery>().map_err(|_| ())
}
fn urlbody(&mut self) -> Result<&HashMap<String, Vec<String>>, ()> {
self.get_ref::<UrlEncodedBody>().map_err(|_| ())
}
fn authheader(&mut self) -> Result<Option<Cow<str>>, ()> {
let string = match self.headers.get::<AuthHeader<String>>() {
None => return Ok(None),
Some(hdr) => hdr,
};
let position = string.find(' ').ok_or(())?;
let (scheme, content) = string.split_at(position);
Ok(Some(Cow::Borrowed(&content[1..])))
}
}
impl WebResponse for iron::Response {
fn redirect(url: Url) -> Result<Response, OAuthError> {
let real_url = match iron::Url::from_generic_url(url) {
Err(_) => return Err(OAuthError::Other("Error parsing redirect target".to_string())),
Ok(v) => v,
};
Ok(Response::with((iron::status::Found, Redirect(real_url))))
}
fn text(text: &str) -> Result<Response, OAuthError> {
Ok(Response::with((iron::status::Ok, text)))
}
fn json(data: &str) -> Result<Response, OAuthError> {
Ok(Response::with((
iron::status::Ok,
iron::modifiers::Header(iron::headers::ContentType::json()),
data,
)))
}
fn as_client_error(mut self) -> Result<Self, OAuthError> {
self.status = Some(iron::status::BadRequest);
Ok(self)
}
fn as_unauthorized(mut self) -> Result<Self, OAuthError> {
self.status = Some(iron::status::Unauthorized);
Ok(self)
}
fn with_authorization(mut self, kind: &str) -> Result<Self, OAuthError> {
self.headers.set_raw("WWW-Authenticate", vec![kind.as_bytes().to_vec()]);
Ok(self)
}
}
impl<R, A, I> IronGranter<R, A, I> where
R: Registrar + Send + 'static,
A: Authorizer + Send + 'static,
I: Issuer + Send + 'static
{
pub fn new(registrar: R, data: A, issuer: I) -> IronGranter<R, A, I> {
IronGranter {
registrar: Arc::new(Mutex::new(registrar)),
authorizer: Arc::new(Mutex::new(data)),
issuer: Arc::new(Mutex::new(issuer)) }
}
pub fn authorize<H: GenericOwnerAuthorizer + Send + Sync>(&self, page_handler: H) -> IronAuthorizer<H, R, A> {
IronAuthorizer {
authorizer: self.authorizer.clone(),
page_handler: Box::new(page_handler),
registrar: self.registrar.clone() }
}
pub fn token(&self) -> IronTokenRequest<A, I> {
IronTokenRequest { authorizer: self.authorizer.clone(), issuer: self.issuer.clone() }
}
pub fn guard<S>(&self, scopes: S) -> IronGuard<I> where S: Into<Vec<Scope>> {
IronGuard { issuer: self.issuer.clone(), scopes: scopes.into() }
}
pub fn registrar(&self) -> LockResult<MutexGuard<R>> {
self.registrar.lock()
}
pub fn authorizer(&self) -> LockResult<MutexGuard<A>> {
self.authorizer.lock()
}
pub fn issuer(&self) -> LockResult<MutexGuard<I>> {
self.issuer.lock()
}
}
fn from_oauth_error(error: OAuthError) -> IronResult<Response> {
match error {
_ => Ok(Response::with(iron::status::InternalServerError))
}
}
impl From<OAuthError> for IronError {
fn from(this: OAuthError) -> IronError {
IronError::new(this, iron::status::Unauthorized)
}
}
impl<PH, R, A> iron::Handler for IronAuthorizer<PH, R, A> where
PH: GenericOwnerAuthorizer + Send + Sync + 'static,
R: Registrar + Send + 'static,
A: Authorizer + Send + 'static
{
fn handle<'a>(&'a self, req: &mut iron::Request) -> IronResult<Response> {
let prepared = match AuthorizationFlow::prepare(req).map_err(from_oauth_error) {
Err(res) => return res,
Ok(v) => v,
};
let mut locked_registrar = self.registrar.lock().unwrap();
let mut locked_authorizer = self.authorizer.lock().unwrap();
let code = CodeRef::with(locked_registrar.deref_mut(), locked_authorizer.deref_mut());
let handler = SpecificOwnerAuthorizer(self.page_handler.as_ref(), PhantomData);
AuthorizationFlow::handle(code, prepared, &handler).or_else(from_oauth_error)
}
}
impl<A, I> iron::Handler for IronTokenRequest<A, I> where
A: Authorizer + Send + 'static,
I: Issuer + Send + 'static
{
fn handle<'a>(&'a self, req: &mut iron::Request) -> IronResult<Response> {
let prepared = match GrantFlow::prepare(req).map_err(from_oauth_error) {
Err(res) => return res,
Ok(v) => v,
};
let mut locked_authorizer = self.authorizer.lock().unwrap();
let mut locked_issuer = self.issuer.lock().unwrap();
let issuer = IssuerRef::with(locked_authorizer.deref_mut(), locked_issuer.deref_mut());
GrantFlow::handle(issuer, prepared).or_else(from_oauth_error)
}
}
impl<I> iron::BeforeMiddleware for IronGuard<I> where
I: Issuer + Send + 'static
{
fn before(&self, request: &mut Request) -> IronResult<()> {
let prepared = AccessFlow::prepare(request)?;
let mut locked_issuer = self.issuer.lock().unwrap();
let guard = GuardRef::with(locked_issuer.deref_mut(), &self.scopes);
let ok = AccessFlow::handle(guard, prepared)?;
Ok(ok)
}
}
pub mod prelude {
pub use url::Url;
pub use code_grant::prelude::*;
pub use super::{IronGranter, IronOwnerAuthorizer, AuthenticationRequest, Authentication, OAuthError};
}