use std::borrow::Cow;
use std::result::Result as StdResult;
use url::Url;
use chrono::{Duration, Utc};
use code_grant::error::{AuthorizationError, AuthorizationErrorType};
use primitives::authorizer::Authorizer;
use primitives::registrar::{ClientUrl, Registrar, RegistrarError, PreGrant};
use primitives::grant::{Extensions, Grant};
pub trait Request {
fn valid(&self) -> bool;
fn client_id(&self) -> Option<Cow<str>>;
fn scope(&self) -> Option<Cow<str>>;
fn redirect_uri(&self) -> Option<Cow<str>>;
fn state(&self) -> Option<Cow<str>>;
fn response_type(&self) -> Option<Cow<str>>;
fn extension(&self, key: &str) -> Option<Cow<str>>;
}
pub trait Extension {
fn extend(&mut self, request: &dyn Request) -> std::result::Result<Extensions, ()>;
}
impl Extension for () {
fn extend(&mut self, _: &dyn Request) -> std::result::Result<Extensions, ()> {
Ok(Extensions::new())
}
}
pub trait Endpoint {
fn registrar(&self) -> &dyn Registrar;
fn authorizer(&mut self) -> &mut dyn Authorizer;
fn extension(&mut self) -> &mut dyn Extension;
}
pub fn authorization_code(handler: &mut dyn Endpoint, request: &dyn Request)
-> self::Result<Pending> {
if !request.valid() {
return Err(Error::Ignore)
}
let client_id = request.client_id().ok_or(Error::Ignore)?;
let redirect_uri = match request.redirect_uri() {
None => None,
Some(ref uri) => {
let parsed = Url::parse(&uri).map_err(|_| Error::Ignore)?;
Some(Cow::Owned(parsed))
},
};
let client_url = ClientUrl {
client_id,
redirect_uri,
};
let bound_client = match handler.registrar().bound_redirect(client_url) {
Err(RegistrarError::Unspecified) => return Err(Error::Ignore),
Err(RegistrarError::PrimitiveError) => return Err(Error::PrimitiveError),
Ok(pre_grant) => pre_grant,
};
let state = request.state();
let error_uri = bound_client.redirect_uri.clone().into_owned();
let mut prepared_error = ErrorUrl::new(error_uri.clone(), state.clone(),
AuthorizationError::default());
match request.response_type() {
Some(ref method) if method.as_ref() == "code"
=> (),
_ => {
prepared_error.description().set_type(AuthorizationErrorType::UnsupportedResponseType);
return Err(Error::Redirect(prepared_error))
}
}
let scope = request.scope();
let scope = match scope.map(|scope| scope.as_ref().parse()) {
None => None,
Some(Err(_)) => {
prepared_error.description().set_type(AuthorizationErrorType::InvalidScope);
return Err(Error::Redirect(prepared_error))
},
Some(Ok(scope)) => Some(scope),
};
let grant_extension = match handler.extension().extend(request) {
Ok(extension_data) => extension_data,
Err(()) => {
prepared_error.description().set_type(AuthorizationErrorType::InvalidRequest);
return Err(Error::Redirect(prepared_error))
},
};
let pre_grant = handler.registrar()
.negotiate(bound_client, scope)
.map_err(|err| match err {
RegistrarError::PrimitiveError => Error::PrimitiveError,
RegistrarError::Unspecified => {
prepared_error.description().set_type(AuthorizationErrorType::InvalidScope);
Error::Redirect(prepared_error)
},
})?;
Ok(Pending {
pre_grant,
state: state.map(Cow::into_owned),
extensions: grant_extension,
})
}
pub struct Pending {
pre_grant: PreGrant,
state: Option<String>,
extensions: Extensions,
}
impl Pending {
pub fn deny(self) -> Result<Url> {
let url = self.pre_grant.redirect_uri;
let mut error = AuthorizationError::default();
error.set_type(AuthorizationErrorType::AccessDenied);
let error = ErrorUrl::new(url, self.state, error);
Err(Error::Redirect(error))
}
pub fn authorize(self, handler: &mut dyn Endpoint, owner_id: Cow<str>) -> Result<Url> {
let mut url = self.pre_grant.redirect_uri.clone();
let grant = handler.authorizer().authorize(Grant {
owner_id: owner_id.into_owned(),
client_id: self.pre_grant.client_id,
redirect_uri: self.pre_grant.redirect_uri,
scope: self.pre_grant.scope,
until: Utc::now() + Duration::minutes(10),
extensions: self.extensions,
}).map_err(|()| Error::PrimitiveError)?;
url.query_pairs_mut()
.append_pair("code", grant.as_str())
.extend_pairs(self.state.map(|v| ("state", v)))
.finish();
Ok(url)
}
pub fn pre_grant(&self) -> &PreGrant {
&self.pre_grant
}
}
pub enum Error {
Ignore,
Redirect(ErrorUrl),
PrimitiveError,
}
pub struct ErrorUrl {
base_uri: Url,
error: AuthorizationError,
}
type Result<T> = StdResult<T, Error>;
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_uri: url, error }
}
pub fn description(&mut self) -> &mut AuthorizationError {
&mut self.error
}
}
impl Error {
pub fn description(&mut self) -> Option<&mut AuthorizationError> {
match self {
Error::Ignore => None,
Error::Redirect(inner) => Some(inner.description()),
Error::PrimitiveError => None,
}
}
}
impl Into<Url> for ErrorUrl {
fn into(self) -> Url {
let mut url = self.base_uri;
url.query_pairs_mut()
.extend_pairs(self.error.into_iter());
url
}
}