use std::mem;
use std::borrow::Cow;
use chrono::{Utc, Duration};
use crate::code_grant::accesstoken::BearerToken;
use crate::code_grant::error::{AccessTokenError, AccessTokenErrorType};
use crate::endpoint::{Scope, Solicitation};
use crate::primitives::issuer::Issuer;
use crate::primitives::grant::{Extensions, Grant};
use crate::primitives::registrar::{Registrar, RegistrarError, BoundClient, PreGrant, ClientUrl};
use super::accesstoken::{ErrorDescription, PrimitiveError};
pub trait Request {
fn valid(&self) -> bool;
fn authorization(&self) -> Option<(Cow<str>, Cow<[u8]>)>;
fn scope(&self) -> Option<Cow<str>>;
fn grant_type(&self) -> Option<Cow<str>>;
fn extension(&self, key: &str) -> Option<Cow<str>>;
fn allow_credentials_in_body(&self) -> bool {
false
}
fn allow_refresh_token(&self) -> bool {
false
}
}
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 issuer(&mut self) -> &mut dyn Issuer;
fn extension(&mut self) -> &mut dyn Extension;
}
enum Credentials<'a> {
None,
Authenticated {
client_id: &'a str,
passphrase: &'a [u8],
},
Unauthenticated,
Duplicate,
}
pub struct ClientCredentials {
state: ClientCredentialsState,
scope: Option<Scope>,
}
enum ClientCredentialsState {
Authenticate {
client: String,
passdata: Vec<u8>,
},
Binding {
client_id: String,
},
Extend {
bound_client: BoundClient<'static>,
},
Negotiating {
bound_client: BoundClient<'static>,
extensions: Extensions,
},
Issue {
pre_grant: PreGrant,
extensions: Extensions,
},
Err(Error),
}
pub enum Input {
Authenticated,
Bound {
bound_client: BoundClient<'static>,
},
Extended {
extensions: Extensions,
},
Negotiated {
pre_grant: PreGrant,
},
None,
}
pub enum Output<'machine> {
Authenticate {
client: &'machine str,
passdata: &'machine [u8],
},
Binding {
client_id: &'machine str,
},
Extend,
Negotiate {
bound_client: &'machine BoundClient<'static>,
scope: Option<Scope>,
},
Ok {
pre_grant: &'machine PreGrant,
extensions: &'machine Extensions,
},
Err(Box<Error>),
}
impl ClientCredentials {
pub fn new(request: &dyn Request) -> Self {
let (state, scope) =
Self::validate(request).unwrap_or_else(|err| (ClientCredentialsState::Err(err), None));
ClientCredentials { state, scope }
}
pub fn advance(&mut self, input: Input) -> Output<'_> {
self.state = match (self.take(), input) {
(current, Input::None) => current,
(ClientCredentialsState::Authenticate { client, .. }, Input::Authenticated) => {
Self::authenticated(client)
}
(ClientCredentialsState::Binding { .. }, Input::Bound { bound_client }) => {
Self::bound(bound_client)
}
(ClientCredentialsState::Extend { bound_client }, Input::Extended { extensions }) => {
Self::extended(bound_client, extensions)
}
(
ClientCredentialsState::Negotiating { extensions, .. },
Input::Negotiated { pre_grant },
) => Self::negotiated(pre_grant, extensions),
(ClientCredentialsState::Err(err), _) => ClientCredentialsState::Err(err),
(_, _) => ClientCredentialsState::Err(Error::Primitive(Box::new(PrimitiveError::empty()))),
};
self.output()
}
fn output(&self) -> Output<'_> {
match &self.state {
ClientCredentialsState::Err(err) => Output::Err(Box::new(err.clone())),
ClientCredentialsState::Authenticate { client, passdata, .. } => Output::Authenticate {
client,
passdata: passdata.as_slice(),
},
ClientCredentialsState::Binding { client_id } => Output::Binding { client_id },
ClientCredentialsState::Extend { .. } => Output::Extend,
ClientCredentialsState::Negotiating { bound_client, .. } => Output::Negotiate {
bound_client,
scope: self.scope.clone(),
},
ClientCredentialsState::Issue {
pre_grant,
extensions,
} => Output::Ok {
pre_grant,
extensions,
},
}
}
fn take(&mut self) -> ClientCredentialsState {
mem::replace(
&mut self.state,
ClientCredentialsState::Err(Error::Primitive(Box::new(PrimitiveError::empty()))),
)
}
fn validate(request: &dyn Request) -> Result<(ClientCredentialsState, Option<Scope>)> {
if !request.valid() {
return Err(Error::invalid());
}
let authorization = request.authorization();
let client_id = request.extension("client_id");
let client_secret = request.extension("client_secret");
let mut credentials = Credentials::None;
if let Some((client_id, auth)) = &authorization {
credentials.authenticate(client_id.as_ref(), auth.as_ref());
}
match (&client_id, &client_secret) {
(Some(client_id), Some(client_secret)) if request.allow_credentials_in_body() => {
credentials.authenticate(client_id.as_ref(), client_secret.as_ref().as_bytes())
}
(None, None) => {}
_ => credentials.unauthenticated(),
}
let scope = match request.scope().map(|scope| scope.as_ref().parse()) {
None => None,
Some(Err(_)) => return Err(Error::invalid()),
Some(Ok(scope)) => Some(scope),
};
match request.grant_type() {
Some(ref cow) if cow == "client_credentials" => (),
None => return Err(Error::invalid()),
Some(_) => return Err(Error::invalid_with(AccessTokenErrorType::UnsupportedGrantType)),
};
let (client_id, passdata) = credentials.into_client().ok_or_else(Error::invalid)?;
Ok((
ClientCredentialsState::Authenticate {
client: client_id.to_string(),
passdata: Vec::from(passdata),
},
scope,
))
}
fn authenticated(client_id: String) -> ClientCredentialsState {
ClientCredentialsState::Binding { client_id }
}
fn bound(bound_client: BoundClient<'static>) -> ClientCredentialsState {
ClientCredentialsState::Extend { bound_client }
}
fn extended(bound_client: BoundClient<'static>, extensions: Extensions) -> ClientCredentialsState {
ClientCredentialsState::Negotiating {
bound_client,
extensions,
}
}
fn negotiated(pre_grant: PreGrant, extensions: Extensions) -> ClientCredentialsState {
ClientCredentialsState::Issue {
pre_grant,
extensions,
}
}
}
pub struct Pending {
pre_grant: PreGrant,
extensions: Extensions,
}
impl Pending {
pub fn as_solicitation(&self) -> Solicitation<'_> {
Solicitation {
grant: Cow::Borrowed(&self.pre_grant),
state: None,
}
}
pub fn issue(
self, handler: &mut dyn Endpoint, owner_id: String, allow_refresh_token: bool,
) -> Result<BearerToken> {
let mut token = handler
.issuer()
.issue(Grant {
owner_id,
client_id: self.pre_grant.client_id,
redirect_uri: self.pre_grant.redirect_uri.into_url(),
scope: self.pre_grant.scope.clone(),
until: Utc::now() + Duration::minutes(10),
extensions: self.extensions,
})
.map_err(|()| Error::Primitive(Box::new(PrimitiveError::empty())))?;
if !allow_refresh_token {
token.refresh = None;
}
Ok(BearerToken(token, self.pre_grant.scope.clone()))
}
}
pub fn client_credentials(handler: &mut dyn Endpoint, request: &dyn Request) -> Result<Pending> {
enum Requested {
None,
Authenticate {
client: String,
passdata: Vec<u8>,
},
Bind {
client_id: String,
},
Extend,
Negotiate {
bound_client: BoundClient<'static>,
scope: Option<Scope>,
},
}
let mut client_credentials = ClientCredentials::new(request);
let mut requested = Requested::None;
loop {
let input = match requested {
Requested::None => Input::None,
Requested::Authenticate { client, passdata } => {
handler
.registrar()
.check(&client, Some(passdata.as_slice()))
.map_err(|err| match err {
RegistrarError::Unspecified => Error::unauthorized("basic"),
RegistrarError::PrimitiveError => Error::Primitive(Box::new(PrimitiveError {
grant: None,
extensions: None,
})),
})?;
Input::Authenticated
}
Requested::Bind { client_id } => {
let client_url = ClientUrl {
client_id: Cow::Owned(client_id),
redirect_uri: None,
};
let bound_client = match handler.registrar().bound_redirect(client_url) {
Err(RegistrarError::Unspecified) => return Err(Error::Ignore),
Err(RegistrarError::PrimitiveError) => {
return Err(Error::Primitive(Box::new(PrimitiveError {
grant: None,
extensions: None,
})));
}
Ok(pre_grant) => pre_grant,
};
Input::Bound { bound_client }
}
Requested::Extend => {
let extensions = handler
.extension()
.extend(request)
.map_err(|_| Error::invalid())?;
Input::Extended { extensions }
}
Requested::Negotiate { bound_client, scope } => {
let pre_grant = handler
.registrar()
.negotiate(bound_client.clone(), scope.clone())
.map_err(|err| match err {
RegistrarError::PrimitiveError => Error::Primitive(Box::new(PrimitiveError {
grant: None,
extensions: None,
})),
RegistrarError::Unspecified => Error::Ignore,
})?;
Input::Negotiated { pre_grant }
}
};
requested = match client_credentials.advance(input) {
Output::Authenticate { client, passdata } => Requested::Authenticate {
client: client.to_owned(),
passdata: passdata.to_vec(),
},
Output::Binding { client_id } => Requested::Bind {
client_id: client_id.to_owned(),
},
Output::Extend => Requested::Extend,
Output::Negotiate { bound_client, scope } => Requested::Negotiate {
bound_client: bound_client.clone(),
scope,
},
Output::Ok {
pre_grant,
extensions,
} => {
return Ok(Pending {
pre_grant: pre_grant.clone(),
extensions: extensions.clone(),
})
}
Output::Err(e) => return Err(*e),
};
}
}
impl<'a> Credentials<'a> {
pub fn authenticate(&mut self, client_id: &'a str, passphrase: &'a [u8]) {
self.add(Credentials::Authenticated {
client_id,
passphrase,
})
}
pub fn unauthenticated(&mut self) {
self.add(Credentials::Unauthenticated)
}
pub fn into_client(self) -> Option<(&'a str, &'a [u8])> {
match self {
Credentials::Authenticated {
client_id,
passphrase,
} => Some((client_id, passphrase)),
Credentials::Unauthenticated { .. } => None,
_ => None,
}
}
fn add(&mut self, new: Self) {
*self = match self {
Credentials::None => new,
_ => Credentials::Duplicate,
};
}
}
#[derive(Clone)]
pub enum Error {
Ignore,
Invalid(ErrorDescription),
Unauthorized(ErrorDescription, String),
Primitive(Box<PrimitiveError>),
}
type Result<T> = std::result::Result<T, Error>;
impl Error {
pub fn invalid() -> Self {
Error::Invalid(ErrorDescription {
error: AccessTokenError::default(),
})
}
fn invalid_with(with_type: AccessTokenErrorType) -> Self {
Error::Invalid(ErrorDescription {
error: {
let mut error = AccessTokenError::default();
error.set_type(with_type);
error
},
})
}
pub fn unauthorized(authtype: &str) -> Error {
Error::Unauthorized(
ErrorDescription {
error: {
let mut error = AccessTokenError::default();
error.set_type(AccessTokenErrorType::InvalidClient);
error
},
},
authtype.to_string(),
)
}
pub fn description(&mut self) -> Option<&mut AccessTokenError> {
match self {
Error::Ignore => None,
Error::Invalid(description) => Some(description.description()),
Error::Unauthorized(description, _) => Some(description.description()),
Error::Primitive(_) => None,
}
}
}