use std::borrow::Cow;
use std::fmt;
use chrono::Utc;
use primitives::issuer::Issuer;
use primitives::grant::Grant;
use primitives::scope::Scope;
#[derive(Debug)]
pub struct AccessFailure {
pub code: Option<ErrorCode>,
}
#[derive(Debug, Clone, Copy)]
pub enum ErrorCode {
InvalidRequest,
InsufficientScope,
InvalidToken,
}
#[derive(Debug)]
pub struct Authenticate {
pub realm: Option<String>,
pub scope: Option<Scope>,
}
#[derive(Debug)]
pub enum Error {
AccessDenied {
failure: AccessFailure,
authenticate: Authenticate,
},
NoAuthentication {
authenticate: Authenticate,
},
InvalidRequest {
authenticate: Authenticate,
},
PrimitiveError,
}
const BEARER_START: &str = "Bearer ";
type Result<T> = std::result::Result<T, Error>;
pub trait Request {
fn valid(&self) -> bool;
fn token(&self) -> Option<Cow<str>>;
}
pub trait Endpoint {
fn scopes(&mut self) -> &[Scope];
fn issuer(&mut self) -> &dyn Issuer;
}
pub fn protect(handler: &mut dyn Endpoint, req: &dyn Request) -> Result<Grant> {
let authenticate = Authenticate {
realm: None,
scope: handler.scopes().get(0).cloned(),
};
if !req.valid() {
return Err(Error::InvalidRequest {
authenticate
});
}
let token = match req.token() {
Some(token) => token,
None => return Err(Error::NoAuthentication {
authenticate,
}),
};
if !token.starts_with(BEARER_START) {
return Err(Error::InvalidRequest {
authenticate,
})
}
let token = &token[BEARER_START.len()..];
let grant = match handler.issuer().recover_token(token) {
Err(()) => return Err(Error::PrimitiveError),
Ok(Some(grant)) => grant,
Ok(None) => return Err(Error::AccessDenied {
failure: AccessFailure {
code: Some(ErrorCode::InvalidRequest),
},
authenticate,
}),
};
if grant.until < Utc::now() {
return Err(Error::AccessDenied {
failure: AccessFailure {
code: Some(ErrorCode::InvalidToken),
},
authenticate,
});
}
if !handler.scopes().iter()
.any(|resource_scope| resource_scope.allow_access(&grant.scope)) {
return Err(Error::AccessDenied {
failure: AccessFailure {
code: Some(ErrorCode::InsufficientScope),
},
authenticate,
});
}
Ok(grant)
}
impl ErrorCode {
fn description(self) -> &'static str {
match self {
ErrorCode::InvalidRequest => "invalid_request",
ErrorCode::InsufficientScope => "insufficient_scope",
ErrorCode::InvalidToken => "invalid_token",
}
}
}
struct BearerHeader {
content: String,
first_option: bool,
}
impl BearerHeader {
fn new() -> Self {
BearerHeader {
content: "Bearer".to_string(),
first_option: true,
}
}
fn add_option(&mut self, args: fmt::Arguments) {
if self.first_option {
self.content.push(' ');
} else {
self.content.push(',');
}
fmt::write(&mut self.content, args).unwrap();
}
fn finalize(self) -> String {
self.content
}
}
impl Authenticate {
fn extend_header(self, header: &mut BearerHeader) {
self.realm.map(|realm| header.add_option(format_args!("realm=\"{}\"", realm)));
self.scope.map(|scope| header.add_option(format_args!("scope=\"{}\"", scope)));
}
}
impl AccessFailure {
fn extend_header(self, header: &mut BearerHeader) {
self.code.map(|code| header.add_option(format_args!("error=\"{}\"", code.description())));
}
}
impl Error {
pub(crate) fn www_authenticate(self) -> String {
let mut header = BearerHeader::new();
match self {
Error::AccessDenied { failure, authenticate, } => {
failure.extend_header(&mut header);
authenticate.extend_header(&mut header);
},
Error::NoAuthentication { authenticate, } => {
authenticate.extend_header(&mut header);
},
Error::InvalidRequest { authenticate, } => {
authenticate.extend_header(&mut header);
},
Error::PrimitiveError => (),
}
header.finalize()
}
}