use std::{fmt, mem};
use std::borrow::Cow;
use chrono::Utc;
use crate::primitives::issuer::Issuer;
use crate::primitives::grant::Grant;
use crate::primitives::scope::Scope;
#[derive(Clone, Debug)]
pub struct AccessFailure {
pub code: Option<ErrorCode>,
}
#[derive(Debug, Clone, Copy)]
pub enum ErrorCode {
InvalidRequest,
InsufficientScope,
InvalidToken,
}
#[derive(Clone, Debug)]
pub struct Authenticate {
pub realm: Option<String>,
pub scope: Option<Scope>,
}
#[derive(Clone, 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 struct Resource {
state: ResourceState,
}
enum ResourceState {
New,
Internalized { token: String },
Recovering { token: String, scopes: Vec<Scope> },
Err(Error),
}
#[derive(Clone)]
pub enum Input<'req> {
Recovered(Option<Grant>),
Scopes(&'req [Scope]),
Request {
request: &'req dyn Request,
},
None,
}
#[derive(Clone, Debug)]
pub enum Output<'machine> {
GetRequest,
Recover {
token: &'machine str,
},
DetermineScopes,
Ok(Box<Grant>),
Err(Error),
}
impl Resource {
pub fn new() -> Self {
Resource {
state: ResourceState::New,
}
}
pub fn advance(&mut self, input: Input) -> Output<'_> {
self.state = match (self.take(), input) {
(any, Input::None) => any,
(ResourceState::New, Input::Request { request }) => {
validate(request).unwrap_or_else(ResourceState::Err)
}
(ResourceState::Internalized { token }, Input::Scopes(scopes)) => get_scopes(token, scopes),
(ResourceState::Recovering { token: _, scopes }, Input::Recovered(grant)) => {
match recovered(grant, scopes) {
Ok(grant) => return Output::Ok(Box::new(grant)),
Err(err) => ResourceState::Err(err),
}
}
_ => return Output::Err(Error::PrimitiveError),
};
self.output()
}
fn output(&self) -> Output<'_> {
match &self.state {
ResourceState::New => Output::GetRequest,
ResourceState::Internalized { .. } => Output::DetermineScopes,
ResourceState::Recovering { token, .. } => Output::Recover { token },
ResourceState::Err(error) => Output::Err(error.clone()),
}
}
fn take(&mut self) -> ResourceState {
mem::replace(&mut self.state, ResourceState::Err(Error::PrimitiveError))
}
}
pub fn protect(handler: &mut dyn Endpoint, req: &dyn Request) -> Result<Grant> {
enum Requested {
None,
Request,
Scopes,
Grant(String),
}
let mut resource = Resource::new();
let mut requested = Requested::None;
loop {
let input = match requested {
Requested::None => Input::None,
Requested::Request => Input::Request { request: req },
Requested::Scopes => Input::Scopes(handler.scopes()),
Requested::Grant(token) => {
let grant = handler
.issuer()
.recover_token(&token)
.map_err(|_| Error::PrimitiveError)?;
Input::Recovered(grant)
}
};
requested = match resource.advance(input) {
Output::Err(error) => return Err(error),
Output::Ok(grant) => return Ok(*grant),
Output::GetRequest => Requested::Request,
Output::DetermineScopes => Requested::Scopes,
Output::Recover { token } => Requested::Grant(token.to_string()),
};
}
}
fn validate(request: &'_ dyn Request) -> Result<ResourceState> {
if !request.valid() {
return Err(Error::InvalidRequest {
authenticate: Authenticate::empty(),
});
}
let client_token = match request.token() {
Some(token) => token,
None => {
return Err(Error::NoAuthentication {
authenticate: Authenticate::empty(),
})
}
};
if !client_token
.to_uppercase()
.starts_with(&BEARER_START.to_uppercase())
{
return Err(Error::InvalidRequest {
authenticate: Authenticate::empty(),
});
}
let token = match client_token {
Cow::Borrowed(token) => token[BEARER_START.len()..].to_string(),
Cow::Owned(mut token) => token.split_off(BEARER_START.len()),
};
Ok(ResourceState::Internalized { token })
}
fn get_scopes(token: String, scopes: &'_ [Scope]) -> ResourceState {
ResourceState::Recovering {
token,
scopes: scopes.to_owned(),
}
}
fn recovered(grant: Option<Grant>, mut scopes: Vec<Scope>) -> Result<Grant> {
let grant = match grant {
Some(grant) => grant,
None => {
return Err(Error::AccessDenied {
failure: AccessFailure {
code: Some(ErrorCode::InvalidRequest),
},
authenticate: Authenticate {
realm: None,
scope: scopes.drain(..).next(),
},
});
}
};
if grant.until < Utc::now() {
return Err(Error::AccessDenied {
failure: AccessFailure {
code: Some(ErrorCode::InvalidToken),
},
authenticate: Authenticate::empty(),
});
}
let allowing = scopes
.iter()
.find(|resource_scope| resource_scope.allow_access(&grant.scope));
if allowing.is_none() {
return Err(Error::AccessDenied {
failure: AccessFailure {
code: Some(ErrorCode::InsufficientScope),
},
authenticate: Authenticate {
realm: None,
scope: scopes.drain(..).next(),
},
});
}
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 add_kvp(&mut self, key: &'static str, value: Option<impl fmt::Display>) {
if let Some(value) = value {
self.add_option(format_args!("{}=\"{}\"", key, value));
}
}
fn finalize(self) -> String {
self.content
}
}
impl Authenticate {
fn empty() -> Self {
Authenticate {
realm: None,
scope: None,
}
}
fn extend_header(self, header: &mut BearerHeader) {
header.add_kvp("realm", self.realm);
header.add_kvp("scope", self.scope);
}
}
impl AccessFailure {
fn extend_header(self, header: &mut BearerHeader) {
header.add_kvp("error", self.code.map(ErrorCode::description));
}
}
impl Error {
pub 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()
}
}