use super::ports::{Available, DynamicRegistrar, MetadataDocumentLoader};
use super::{ClientIdentity, RegistrarError, RegistrationError, RegistrationPlan};
use crate::domain::mcp::authorization::{AuthorizationModel, ClientRegistration};
use crate::domain::oauth::registration::{
RegisteredClient, RegistrationDecision, decide_registration,
};
pub fn plan_registration(
model: &AuthorizationModel,
issuer: &str,
identity: &ClientIdentity,
stored: Option<&RegisteredClient>,
available: Available,
) -> Result<RegistrationPlan, RegistrationError> {
if let Some(stored) = stored {
match decide_registration(model, issuer, Some(stored)) {
RegistrationDecision::Reuse => {
return Ok(RegistrationPlan::Reuse(stored.clone()));
}
RegistrationDecision::ReRegister => {
return Err(RegistrationError::IssuerBound);
}
RegistrationDecision::Register => {}
}
}
let mechanism = model.client_registration;
if mechanism == ClientRegistration::None {
return Err(RegistrationError::NoMechanism);
}
if let (true, Some(client_id)) = (
mechanism.supports_metadata_document() && available.metadata_document(),
identity.document(),
) {
let fallback = identity
.metadata()
.filter(|_| mechanism.supports_dynamic() && available.dynamic())
.cloned();
return Ok(RegistrationPlan::FetchDocument {
client_id: client_id.clone(),
fallback,
});
}
if let (true, Some(metadata)) = (mechanism.supports_dynamic(), identity.metadata()) {
if mechanism.application_type_required() && metadata.application_type().is_none() {
return Err(RegistrationError::ApplicationTypeRequired);
}
return Ok(RegistrationPlan::RegisterDynamic {
metadata: metadata.clone(),
});
}
Err(RegistrationError::NoUsableMechanism)
}
pub async fn acquire_client(
model: &AuthorizationModel,
issuer: &str,
identity: &ClientIdentity,
stored: Option<&RegisteredClient>,
dynamic: Option<&(impl DynamicRegistrar + Sync)>,
documents: Option<&(impl MetadataDocumentLoader + Sync)>,
) -> Result<RegisteredClient, RegistrationError> {
let available = match (dynamic.is_some(), documents.is_some()) {
(true, true) => Available::both(),
(true, false) => Available::dynamic_only(),
(false, true) => Available::metadata_document_only(),
(false, false) => Available::neither(),
};
match plan_registration(model, issuer, identity, stored, available)? {
RegistrationPlan::Reuse(client) => Ok(client),
RegistrationPlan::FetchDocument {
client_id,
fallback,
} => {
let loader = documents.ok_or(RegistrationError::NoUsableMechanism)?;
match loader.fetch_metadata_document(issuer, &client_id).await {
Ok(client) => Ok(client),
Err(failure @ RegistrarError::Document(_)) => {
Err(RegistrationError::DocumentUnavailable(failure))
}
Err(failure) => match fallback {
Some(metadata) => {
let registrar = dynamic.ok_or(RegistrationError::NoUsableMechanism)?;
registrar
.register_dynamic(issuer, &metadata)
.await
.map_err(RegistrationError::DynamicFailed)
}
None => Err(RegistrationError::DocumentUnavailable(failure)),
},
}
}
RegistrationPlan::RegisterDynamic { metadata } => {
let registrar = dynamic.ok_or(RegistrationError::NoUsableMechanism)?;
registrar
.register_dynamic(issuer, &metadata)
.await
.map_err(RegistrationError::DynamicFailed)
}
}
}