use serde::Serialize;
use snafu::prelude::*;
use crate::{
core::{
EndpointUrl,
client_auth::{AuthenticationParams, ClientAuthentication},
dpop::AuthorizationServerDPoP,
http::HttpClient,
platform::{MaybeSend, MaybeSendSync},
},
grant::{
core::{
ExchangeError,
form::{OAuth2FormError, OAuth2FormRequest},
token_response::{InvalidTokenResponse, RawTokenResponse, TokenResponse},
},
refresh::RefreshGrant,
},
};
pub trait OAuth2ExchangeGrant: MaybeSendSync {
type Parameters: Clone + MaybeSendSync;
type ClientAuth: ClientAuthentication + 'static;
type DPoP: AuthorizationServerDPoP + 'static;
type Form<'a>: MaybeSendSync + Serialize
where
Self: 'a;
fn client_id(&self) -> &str;
fn issuer(&self) -> Option<&str>;
fn client_auth(&self) -> &Self::ClientAuth;
fn bound_dpop_jkt(_params: &Self::Parameters) -> Option<&str> {
None
}
fn token_endpoint(&self) -> &EndpointUrl;
fn mtls_token_endpoint(&self) -> Option<&EndpointUrl> {
None
}
fn effective_token_endpoint(&self, uses_mtls: bool) -> &EndpointUrl {
if uses_mtls {
self.mtls_token_endpoint()
.unwrap_or_else(|| self.token_endpoint())
} else {
self.token_endpoint()
}
}
fn dpop(&self) -> &Self::DPoP;
fn build_form(&self, params: Self::Parameters) -> Self::Form<'_>;
fn allowed_auth_methods(&self) -> Option<&[String]>;
fn authentication_params(
&self,
) -> impl Future<
Output = Result<
AuthenticationParams<'_>,
<Self::ClientAuth as ClientAuthentication>::Error,
>,
> + MaybeSend {
async {
self.client_auth()
.authentication_params(
self.client_id(),
self.issuer(),
self.token_endpoint().as_uri(),
self.allowed_auth_methods(),
)
.await
}
}
fn exchange<C: HttpClient>(
&self,
http_client: &C,
params: Self::Parameters,
) -> impl Future<Output = Result<TokenResponse, ExchangeError<C, Self>>> + MaybeSend {
async move {
let dpop_jkt = Self::bound_dpop_jkt(¶ms)
.map(ToString::to_string)
.or_else(|| self.dpop().get_current_thumbprint());
let effective_endpoint = self.effective_token_endpoint(http_client.uses_mtls());
let auth_params = self
.client_auth()
.authentication_params(
self.client_id(),
self.issuer(),
effective_endpoint.as_uri(),
self.allowed_auth_methods(),
)
.await
.context(AuthSnafu)?;
let form = self.build_form(params);
let raw_token_response: RawTokenResponse = OAuth2FormRequest::builder()
.auth_params(auth_params)
.dpop(self.dpop())
.maybe_dpop_jkt(dpop_jkt.as_deref())
.form(&form)
.uri(effective_endpoint.as_uri())
.build()
.execute(http_client)
.await
.context(OAuth2FormSnafu)?;
raw_token_response
.into_token_response(dpop_jkt, crate::core::platform::SystemTime::now())
.context(InvalidTokenResponseSnafu)
}
}
fn to_refresh_grant(&self) -> RefreshGrant<Self::ClientAuth, Self::DPoP>;
}
#[derive(Debug, Snafu)]
pub enum OAuth2ExchangeGrantError<
HttpReqErr: crate::core::Error,
HttpRespErr: crate::core::Error,
AuthErr: crate::core::Error,
DPoPErr: crate::core::Error,
> {
Auth {
source: AuthErr,
},
OAuth2Form {
source: OAuth2FormError<HttpReqErr, HttpRespErr, DPoPErr>,
},
InvalidTokenResponse {
source: InvalidTokenResponse,
},
}
impl<
HttpErr: crate::core::Error,
HttpRespErr: crate::core::Error,
AuthErr: crate::core::Error,
DPoPErr: crate::core::Error,
> crate::core::Error for OAuth2ExchangeGrantError<HttpErr, HttpRespErr, AuthErr, DPoPErr>
{
fn is_retryable(&self) -> bool {
match self {
Self::Auth { source } => source.is_retryable(),
Self::OAuth2Form { source } => source.is_retryable(),
Self::InvalidTokenResponse { source } => source.is_retryable(),
}
}
}