use super::{Authorizer, Registrar, RegistrarError};
use super::{Negotiated, NegotiationParameter};
use super::{Issuer, IssuedToken, Request};
use super::{Scope};
use super::error::{AccessTokenError, AccessTokenErrorExt, AccessTokenErrorType};
use super::error::{AuthorizationError, AuthorizationErrorExt, AuthorizationErrorType};
use std::borrow::Cow;
use std::collections::HashMap;
use url::Url;
use chrono::Utc;
use serde_json;
pub enum CodeError {
Ignore ,
Redirect(ErrorUrl) ,
}
pub struct ErrorUrl {
base_url: Url,
error: AuthorizationError,
}
pub enum IssuerError {
Invalid(ErrorDescription),
Unauthorized(ErrorDescription, String),
}
pub struct ErrorDescription {
error: AccessTokenError,
}
pub enum AccessError {
InvalidRequest,
AccessDenied,
}
type CodeResult<T> = Result<T, CodeError>;
type AccessTokenResult<T> = Result<T, IssuerError>;
type AccessResult<T> = Result<T, AccessError>;
impl ErrorUrl {
fn new<S>(mut url: Url, state: Option<S>, error: AuthorizationError) -> ErrorUrl where S: AsRef<str> {
url.query_pairs_mut()
.extend_pairs(state.as_ref().map(|st| ("state", st.as_ref())));
ErrorUrl{ base_url: url, error: error }
}
pub fn with_mut<M>(&mut self, modifier: M) where M: AuthorizationErrorExt {
modifier.modify(&mut self.error);
}
pub fn with<M>(mut self, modifier: M) -> Self where M: AuthorizationErrorExt {
modifier.modify(&mut self.error);
self
}
}
impl Into<Url> for ErrorUrl {
fn into(self) -> Url {
let mut url = self.base_url;
url.query_pairs_mut()
.extend_pairs(self.error.into_iter());
url
}
}
impl IssuerError {
fn invalid<Mod>(modifier: Mod) -> IssuerError where Mod: AccessTokenErrorExt {
IssuerError::Invalid(ErrorDescription{
error: AccessTokenError::with((AccessTokenErrorType::InvalidRequest, modifier))
})
}
fn unauthorized<Mod>(modifier: Mod, authtype: &str) -> IssuerError where Mod: AccessTokenErrorExt {
IssuerError::Unauthorized(
ErrorDescription{error: AccessTokenError::with((AccessTokenErrorType::InvalidClient, modifier))},
authtype.to_string())
}
}
impl ErrorDescription {
pub fn to_json(self) -> String {
use std::iter::IntoIterator;
use std::collections::HashMap;
let asmap = self.error.into_iter()
.map(|(k, v)| (k.to_string(), v.into_owned()))
.collect::<HashMap<String, String>>();
serde_json::to_string(&asmap).unwrap()
}
}
pub struct BearerToken(IssuedToken, String);
impl BearerToken {
pub fn to_json(self) -> String {
let remaining = self.0.until.signed_duration_since(Utc::now());
let kvmap: HashMap<_, _> = vec![
("access_token", self.0.token),
("refresh_token", self.0.refresh),
("token_type", "bearer".to_string()),
("expires_in", remaining.num_seconds().to_string()),
("scope", self.1)].into_iter().collect();
serde_json::to_string(&kvmap).unwrap()
}
}
pub trait CodeRequest {
fn valid(&self) -> bool;
fn client_id(&self) -> Option<Cow<str>>;
fn scope(&self) -> Option<Cow<str>>;
fn redirect_url(&self) -> Option<Cow<str>>;
fn state(&self) -> Option<Cow<str>>;
}
pub struct CodeRef<'a> {
registrar: &'a Registrar,
authorizer: &'a mut Authorizer,
}
pub struct AuthorizationRequest<'a> {
negotiated: Negotiated<'a>,
code: CodeRef<'a>,
request: &'a CodeRequest,
}
impl<'u> CodeRef<'u> {
pub fn negotiate<'r>(self, request: &'r CodeRequest)
-> CodeResult<AuthorizationRequest<'r>> where 'u: 'r {
if !request.valid() {
return Err(CodeError::Ignore)
}
let client_id = request.client_id().ok_or(CodeError::Ignore)?;
let redirect_url = request.redirect_url().ok_or(CodeError::Ignore)?;
let redirect_url = Url::parse(redirect_url.as_ref()).map_err(|_| CodeError::Ignore)?;
let redirect_url: Cow<Url> = Cow::Owned(redirect_url);
let state = request.state();
let error_url = redirect_url.clone().into_owned();
let prepared_error = ErrorUrl::new(error_url.clone(), state,
AuthorizationError::with(()));
let scope = request.scope();
let scope = match scope.map(|scope| scope.as_ref().parse()) {
None => None,
Some(Err(_)) =>
return Err(CodeError::Redirect(prepared_error.with(AuthorizationErrorType::InvalidScope))),
Some(Ok(scope)) => Some(Cow::Owned(scope)),
};
let parameter = NegotiationParameter {
client_id: client_id.clone(),
scope: scope,
redirect_url: redirect_url.clone(),
};
let scope = match self.registrar.negotiate(parameter) {
Err(RegistrarError::Unregistered) => return Err(CodeError::Ignore),
Err(RegistrarError::MismatchedRedirect) => return Err(CodeError::Ignore),
Err(RegistrarError::Error(err)) => {
let error = prepared_error.with(err);
return Err(CodeError::Redirect(error))
}
Ok(negotiated) => negotiated,
};
let negotiated = Negotiated {
client_id,
redirect_url: redirect_url.into_owned(),
scope
};
Ok(AuthorizationRequest {
negotiated,
code: CodeRef { registrar: self.registrar, authorizer: self.authorizer },
request,
})
}
fn authorize<'a>(&'a mut self, owner_id: Cow<'a, str>, negotiated: Negotiated<'a>, request: &'a CodeRequest)
-> Result<Url, CodeError> {
let grant = self.authorizer.authorize(Request{
owner_id: &owner_id,
client_id: &negotiated.client_id,
redirect_url: &negotiated.redirect_url,
scope: &negotiated.scope});
let mut url = negotiated.redirect_url;
url.query_pairs_mut()
.append_pair("code", grant.as_str())
.extend_pairs(request.state().map(|v| ("state", v)))
.finish();
Ok(url)
}
pub fn with(registrar: &'u Registrar, t: &'u mut Authorizer) -> Self {
CodeRef { registrar, authorizer: t }
}
}
impl<'a> AuthorizationRequest<'a> {
pub fn deny(self) -> CodeResult<Url> {
let url = self.negotiated.redirect_url;
let error = AuthorizationError::with(AuthorizationErrorType::AccessDenied);
let error = ErrorUrl::new(url, self.request.state(), error);
Err(CodeError::Redirect(error))
}
pub fn authorize(mut self, owner_id: Cow<'a, str>) -> CodeResult<Url> {
self.code.authorize(owner_id, self.negotiated, self.request)
}
pub fn negotiated(&self) -> &Negotiated<'a> {
&self.negotiated
}
}
pub struct IssuerRef<'a> {
authorizer: &'a mut Authorizer,
issuer: &'a mut Issuer,
}
pub trait AccessTokenRequest {
fn valid(&self) -> bool;
fn code(&self) -> Option<Cow<str>>;
fn authorization(&self) -> Option<(Cow<str>, Cow<str>)>;
fn client_id(&self) -> Option<Cow<str>>;
fn redirect_url(&self) -> Option<Cow<str>>;
fn grant_type(&self) -> Option<Cow<str>>;
}
impl<'u> IssuerRef<'u> {
pub fn use_code<'r>(&mut self, request: &'r AccessTokenRequest)
-> AccessTokenResult<BearerToken> where 'u: 'r {
if !request.valid() {
return Err(IssuerError::invalid(()))
}
match request.grant_type() {
Some(ref cow) if cow == "authorization_code" => (),
None => return Err(IssuerError::invalid(())),
Some(_) => return Err(IssuerError::invalid(AccessTokenErrorType::UnsupportedGrantType)),
};
let code = request.code()
.ok_or(IssuerError::invalid(()))?;
let code = code.as_ref();
let saved_params = match self.authorizer.extract(code) {
None => return Err(IssuerError::invalid(())),
Some(v) => v,
};
let redirect_url = request.redirect_url()
.ok_or(IssuerError::invalid(()))?;
let redirect_url = redirect_url.as_ref();
let client = match request.authorization() {
Some((_client, _pass)) => Err(())
.map_err(|_| IssuerError::unauthorized((), "basic"))?,
None => request.client_id()
.ok_or(IssuerError::invalid(()))?,
};
if (saved_params.client_id.as_ref(), saved_params.redirect_url.as_str()) != (&client, redirect_url) {
return Err(IssuerError::invalid(AccessTokenErrorType::InvalidGrant))
}
if *saved_params.until.as_ref() < Utc::now() {
return Err(IssuerError::invalid((AccessTokenErrorType::InvalidGrant, "Grant expired")).into())
}
let token = self.issuer.issue(Request{
client_id: &saved_params.client_id,
owner_id: &saved_params.owner_id,
redirect_url: &saved_params.redirect_url,
scope: &saved_params.scope,
});
Ok(BearerToken{0: token, 1: saved_params.scope.as_ref().to_string()})
}
pub fn with(t: &'u mut Authorizer, i: &'u mut Issuer) -> Self {
IssuerRef { authorizer: t, issuer: i }
}
}
pub struct GuardRef<'a> {
scopes: &'a [Scope],
issuer: &'a mut Issuer,
}
pub trait GuardRequest {
fn valid(&self) -> bool;
fn token(&self) -> Option<Cow<str>>;
}
impl<'a> GuardRef<'a> {
pub fn protect<'r>(&self, req: &'r GuardRequest)
-> AccessResult<()> where 'a: 'r {
if !req.valid() {
return Err(AccessError::InvalidRequest)
}
let token = req.token()
.ok_or(AccessError::AccessDenied)?;
let grant = self.issuer.recover_token(&token)
.ok_or(AccessError::AccessDenied)?;
if *grant.until.as_ref() < Utc::now() {
return Err(AccessError::AccessDenied);
}
if !self.scopes.iter()
.any(|scope| grant.scope.as_ref() <= scope) {
return Err(AccessError::AccessDenied);
}
return Ok(())
}
pub fn with<S>(issuer: &'a mut Issuer, scopes: &'a S) -> Self
where S: AsRef<[Scope]> {
GuardRef { scopes: scopes.as_ref(), issuer: issuer }
}
}