use std::sync::Arc;
use http::{header, Method};
use rustauth_core::api::{
create_auth_endpoint, AsyncAuthEndpoint, AuthEndpointOptions, OpenApiOperation,
};
use serde_json::json;
use crate::openapi::saml_metadata_response;
use crate::options::{SamlConfig, SsoOptions};
use crate::saml_impl::metadata::service_provider_metadata;
use crate::store::SsoProviderStore;
use crate::utils;
use super::support::query_param;
pub(super) fn endpoint(options: Arc<SsoOptions>) -> AsyncAuthEndpoint {
create_auth_endpoint(
"/sso/saml2/sp/metadata",
Method::GET,
AuthEndpointOptions::new()
.operation_id("getSSOServiceProviderMetadata")
.openapi(
OpenApiOperation::new("getSSOServiceProviderMetadata")
.tag("SSO")
.response("200", saml_metadata_response()),
),
{
let options = Arc::clone(&options);
move |context, request| {
let options = Arc::clone(&options);
async move {
let Some(provider_id) = query_param(&request, "providerId") else {
return utils::json(
http::StatusCode::BAD_REQUEST,
&json!({"code": "MISSING_PROVIDER_ID"}),
);
};
let Some(adapter) = context.adapter.as_deref() else {
return utils::json(
http::StatusCode::NOT_FOUND,
&json!({"code": "PROVIDER_NOT_FOUND"}),
);
};
let Some(provider) = SsoProviderStore::new_with_options(adapter, &options)
.find_by_provider_id(&provider_id)
.await?
else {
return utils::json(
http::StatusCode::NOT_FOUND,
&json!({"code": "PROVIDER_NOT_FOUND"}),
);
};
let Some(config) = provider
.saml_config
.as_deref()
.and_then(|value| serde_json::from_str::<SamlConfig>(value).ok())
else {
return utils::json(
http::StatusCode::BAD_REQUEST,
&json!({"code": "INVALID_SAML_CONFIG"}),
);
};
let metadata = service_provider_metadata(
&provider.provider_id,
&context.base_url,
&config,
options.saml.enable_single_logout,
);
http::Response::builder()
.status(http::StatusCode::OK)
.header(header::CONTENT_TYPE, "application/xml")
.body(metadata.into_bytes())
.map_err(|error| {
rustauth_core::error::RustAuthError::Api(error.to_string())
})
}
}
},
)
}