use std::borrow::Cow;
use std::collections::HashMap;
use std::error;
use std::fmt;
use std::str::from_utf8;
use primitives::authorizer::Authorizer;
use primitives::issuer::Issuer;
use primitives::registrar::{Registrar, PreGrant};
use primitives::scope::Scope;
use super::backend::{AccessTokenRequest, CodeRequest, CodeError, IssuerError};
use super::backend::{AccessError, GuardRequest};
use super::extensions::{AccessTokenExtension, CodeExtension};
pub use super::backend::{CodeRef, ErrorUrl, IssuerRef, GuardRef};
use url::Url;
use base64;
struct AuthorizationParameter<'a> {
valid: bool,
method: Option<Cow<'a, str>>,
client_id: Option<Cow<'a, str>>,
scope: Option<Cow<'a, str>>,
redirect_uri: Option<Cow<'a, str>>,
state: Option<Cow<'a, str>>,
extensions: HashMap<Cow<'a, str>, Cow<'a, str>>,
}
#[derive(Clone)]
pub enum Authentication {
Failed,
InProgress,
Authenticated(String),
}
struct AccessTokenParameter<'a> {
valid: bool,
client_id: Option<Cow<'a, str>>,
redirect_uri: Option<Cow<'a, str>>,
grant_type: Option<Cow<'a, str>>,
code: Option<Cow<'a, str>>,
authorization: Option<(String, Vec<u8>)>,
extensions: HashMap<Cow<'a, str>, Cow<'a, str>>,
}
struct GuardParameter<'a> {
valid: bool,
token: Option<Cow<'a, str>>,
}
pub trait WebRequest {
type Error: From<OAuthError>;
type Response: WebResponse<Error=Self::Error>;
fn query(&mut self) -> Result<Cow<HashMap<String, Vec<String>>>, ()>;
fn urlbody(&mut self) -> Result<Cow<HashMap<String, Vec<String>>>, ()>;
fn authheader(&mut self) -> Result<Option<Cow<str>>, ()>;
}
pub trait WebResponse where Self: Sized {
type Error: From<OAuthError>;
fn redirect(url: Url) -> Result<Self, Self::Error>;
fn text(text: &str) -> Result<Self, Self::Error>;
fn json(data: &str) -> Result<Self, Self::Error>;
fn redirect_error(target: ErrorUrl) -> Result<Self, Self::Error> {
Self::redirect(target.into())
}
fn as_client_error(self) -> Result<Self, Self::Error>;
fn as_unauthorized(self) -> Result<Self, Self::Error>;
fn with_authorization(self, kind: &str) -> Result<Self, Self::Error>;
}
pub trait OwnerAuthorizer {
type Request: WebRequest;
fn get_owner_authorization(&self, &mut Self::Request, &PreGrant)
-> Result<(Authentication, <Self::Request as WebRequest>::Response), <Self::Request as WebRequest>::Error>;
}
fn extract_single_parameters<'l>(params: Cow<'l, HashMap<String, Vec<String>>>)
-> HashMap<Cow<'l, str>, Cow<'l, str>> {
match params {
Cow::Owned(map) => map.into_iter()
.filter_map(|(k, mut v)|
if v.len() < 2 {
v.pop().map(|v| (k, v))
} else { None })
.map(|(k, v)| (k.into(), v.into()))
.collect::<HashMap<_, _>>(),
Cow::Borrowed(map) => map.iter()
.filter_map(|(ref k, ref v)|
if v.len() == 1 {
Some((k.as_str().into(), v[0].as_str().into()))
} else { None })
.collect::<HashMap<_, _>>(),
}
}
impl<'l, 'c: 'l, W: WebRequest> From<&'l mut &'c mut W> for AuthorizationParameter<'l> {
fn from(val: &'l mut &'c mut W) -> Self {
let mut params = match val.query() {
Err(()) => return Self::invalid(),
Ok(query) => extract_single_parameters(query),
};
AuthorizationParameter {
valid: true,
client_id: params.remove("client_id"),
scope: params.remove("scope"),
redirect_uri: params.remove("redirect_uri"),
state: params.remove("state"),
method: params.remove("response_type"),
extensions: params,
}
}
}
impl<'l> CodeRequest for AuthorizationParameter<'l> {
fn valid(&self) -> bool {
self.valid
}
fn client_id(&self) -> Option<Cow<str>> {
self.client_id.clone()
}
fn scope(&self) -> Option<Cow<str>> {
self.scope.clone()
}
fn redirect_uri(&self) -> Option<Cow<str>> {
self.redirect_uri.clone()
}
fn state(&self) -> Option<Cow<str>> {
self.state.clone()
}
fn method(&self) -> Option<Cow<str>> {
self.method.clone()
}
fn extension(&self, key: &str) -> Option<Cow<str>> {
self.extensions.get(key).cloned()
}
}
impl<'l> AuthorizationParameter<'l> {
fn invalid() -> Self {
AuthorizationParameter {
valid: false,
method: None,
client_id: None,
scope: None,
redirect_uri: None,
state: None,
extensions: HashMap::new()
}
}
}
pub struct AuthorizationFlow<'a> {
backend: CodeRef<'a>,
extensions: Vec<&'a CodeExtension>,
}
impl<'a> AuthorizationFlow<'a> {
pub fn new(registrar: &'a Registrar, authorizer: &'a mut Authorizer) -> Self {
AuthorizationFlow {
backend: CodeRef::with(registrar, authorizer),
extensions: Vec::new(),
}
}
pub fn with_extension(mut self, extension: &'a CodeExtension) -> Self {
self.extensions.push(extension);
self
}
pub fn handle<Req>(self, mut request: &mut Req, page_handler: &OwnerAuthorizer<Request=Req>)
-> Result<Req::Response, Req::Error> where
Req: WebRequest,
{
let negotiated = {
let urldecoded = AuthorizationParameter::from(&mut request);
let negotiated = match self.backend.negotiate(&urldecoded, self.extensions.as_slice()) {
Err(CodeError::Ignore) => return Err(OAuthError::InternalCodeError().into()),
Err(CodeError::Redirect(url)) => return Req::Response::redirect_error(url),
Ok(v) => v,
};
negotiated
};
let authorization = match page_handler.get_owner_authorization(request, negotiated.pre_grant())? {
(Authentication::Failed, _)
=> negotiated.deny(),
(Authentication::InProgress, response)
=> return Ok(response),
(Authentication::Authenticated(owner), _)
=> negotiated.authorize(owner.into()),
};
let redirect_to = match authorization {
Err(CodeError::Ignore) => return Err(OAuthError::InternalCodeError().into()),
Err(CodeError::Redirect(url)) => return Req::Response::redirect_error(url),
Ok(v) => v,
};
Req::Response::redirect(redirect_to)
}
}
pub struct GrantFlow<'a> {
backend: IssuerRef<'a>,
extensions: Vec<&'a AccessTokenExtension>,
}
impl<'l> From<HashMap<Cow<'l, str>, Cow<'l, str>>> for AccessTokenParameter<'l> {
fn from(mut map: HashMap<Cow<'l, str>, Cow<'l, str>>) -> AccessTokenParameter<'l> {
AccessTokenParameter {
valid: true,
client_id: map.remove("client_id"),
code: map.remove("code"),
redirect_uri: map.remove("redirect_uri"),
grant_type: map.remove("grant_type"),
authorization: None,
extensions: map,
}
}
}
impl<'l> AccessTokenRequest for AccessTokenParameter<'l> {
fn valid(&self) -> bool {
self.valid
}
fn code(&self) -> Option<Cow<str>> {
self.code.clone()
}
fn client_id(&self) -> Option<Cow<str>> {
self.client_id.clone()
}
fn redirect_uri(&self) -> Option<Cow<str>> {
self.redirect_uri.clone()
}
fn grant_type(&self) -> Option<Cow<str>> {
self.grant_type.clone()
}
fn authorization(&self) -> Option<(Cow<str>, Cow<[u8]>)> {
match self.authorization {
None => None,
Some((ref id, ref pass))
=> Some((id.as_str().into(), pass.as_slice().into())),
}
}
fn extension(&self, key: &str) -> Option<Cow<str>> {
self.extensions.get(key).cloned()
}
}
impl<'l> AccessTokenParameter<'l> {
fn invalid() -> Self {
AccessTokenParameter {
valid: false,
code: None,
client_id: None,
redirect_uri: None,
grant_type: None,
authorization: None,
extensions: HashMap::new(),
}
}
}
impl<'a> GrantFlow<'a> {
pub fn new(registrar: &'a Registrar, authorizer: &'a mut Authorizer, issuer: &'a mut Issuer) -> Self {
GrantFlow {
backend: IssuerRef::with(registrar, authorizer, issuer),
extensions: Vec::new(),
}
}
pub fn with_extension(mut self, extension: &'a AccessTokenExtension) -> Self {
self.extensions.push(extension);
self
}
fn create_valid_params<'w, W: WebRequest>(req: &'w mut W) -> Option<AccessTokenParameter<'w>> {
let authorization = match req.authheader() {
Err(_) => return None,
Ok(None) => None,
Ok(Some(ref header)) => {
if !header.starts_with("Basic ") {
return None
}
let combined = match base64::decode(&header[6..]) {
Err(_) => return None,
Ok(vec) => vec,
};
let mut split = combined.splitn(2, |&c| c == b':');
let client_bin = match split.next() {
None => return None,
Some(client) => client,
};
let passwd = match split.next() {
None => return None,
Some(passwd64) => passwd64,
};
let client = match from_utf8(client_bin) {
Err(_) => return None,
Ok(client) => client,
};
Some((client.to_string(), passwd.to_vec()))
},
};
let mut params: AccessTokenParameter<'w> = match req.urlbody() {
Err(_) => return None,
Ok(body) => extract_single_parameters(body).into(),
};
params.authorization = authorization;
Some(params)
}
pub fn handle<Req>(mut self, request: &mut Req)
-> Result<Req::Response, Req::Error> where Req: WebRequest
{
let params = GrantFlow::create_valid_params(request)
.unwrap_or(AccessTokenParameter::invalid());
match self.backend.use_code(¶ms, self.extensions.as_slice()) {
Err(IssuerError::Invalid(json_data))
=> return Req::Response::json(&json_data.to_json())?.as_client_error(),
Err(IssuerError::Unauthorized(json_data, scheme))
=> return Req::Response::json(&json_data.to_json())?.as_unauthorized()?.with_authorization(&scheme),
Ok(token) => Req::Response::json(&token.to_json()),
}
}
}
pub struct AccessFlow<'a> {
backend: GuardRef<'a>,
}
impl<'l> GuardRequest for GuardParameter<'l> {
fn valid(&self) -> bool {
self.valid
}
fn token(&self) -> Option<Cow<str>> {
self.token.clone()
}
}
impl<'l> GuardParameter<'l> {
fn invalid() -> Self {
GuardParameter {
valid: false,
token: None
}
}
}
impl<'a> AccessFlow<'a> {
pub fn new(issuer: &'a mut Issuer, scopes: &'a [Scope]) -> Self {
AccessFlow {
backend: GuardRef::with(issuer, scopes)
}
}
fn create_valid_params<W: WebRequest>(req: &mut W) -> Option<GuardParameter> {
let token = match req.authheader() {
Err(_) => return None,
Ok(None) => None,
Ok(Some(header)) => {
if !header.starts_with("Bearer ") {
return None
}
match header {
Cow::Borrowed(v) => Some(Cow::Borrowed(&v[7..])),
Cow::Owned(v) => Some(Cow::Owned(v[7..].to_string())),
}
}
};
Some(GuardParameter { valid: true, token })
}
pub fn handle<R>(&self, request: &mut R)
-> Result<(), R::Error> where R: WebRequest {
let params = AccessFlow::create_valid_params(request)
.unwrap_or_else(|| GuardParameter::invalid());
self.backend.protect(¶ms).map_err(|err| {
match err {
AccessError::InvalidRequest => OAuthError::InternalAccessError(),
AccessError::AccessDenied => OAuthError::AccessDenied,
}.into()
})
}
}
#[derive(Debug)]
pub enum OAuthError {
InternalCodeError(),
InternalAccessError(),
AccessDenied,
}
impl fmt::Display for OAuthError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt.write_str("OAuthError")
}
}
impl error::Error for OAuthError {
fn description(&self) -> &str {
"OAuthError"
}
}