use async_trait::async_trait;
use crate::error::SaTokenResult;
use crate::sso::ticket_store::SsoTicketStore;
#[async_trait]
pub trait TicketChecker: Send + Sync {
async fn check_and_consume(&self, ticket_id: &str, service: &str) -> SaTokenResult<String>;
}
pub struct LocalTicketChecker {
pub store: SsoTicketStore,
}
impl std::fmt::Debug for LocalTicketChecker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("LocalTicketChecker { .. }")
}
}
#[async_trait]
impl TicketChecker for LocalTicketChecker {
async fn check_and_consume(&self, ticket_id: &str, service: &str) -> SaTokenResult<String> {
self.store.consume(ticket_id, service).await
}
}
#[cfg(feature = "sso-http")]
use std::collections::BTreeMap;
#[cfg(feature = "sso-http")]
use serde::Deserialize;
#[cfg(feature = "sso-http")]
use uuid::Uuid;
#[cfg(feature = "sso-http")]
use crate::error::SaTokenError;
#[cfg(feature = "sso-http")]
use crate::sso::sign::RequestSign;
#[cfg(feature = "sso-http")]
pub struct HttpTicketChecker {
pub check_url: String,
pub sign: RequestSign,
pub service: String,
client: reqwest::Client,
}
#[cfg(feature = "sso-http")]
impl HttpTicketChecker {
pub fn new(
check_url: impl Into<String>,
sign: RequestSign,
service: impl Into<String>,
) -> Self {
Self {
check_url: check_url.into(),
sign,
service: service.into(),
client: reqwest::Client::new(),
}
}
}
#[cfg(feature = "sso-http")]
#[derive(Deserialize)]
struct CheckTicketHttpBody {
login_id: String,
timestamp: String,
nonce: String,
sign: String,
}
#[cfg(feature = "sso-http")]
#[async_trait]
impl TicketChecker for HttpTicketChecker {
async fn check_and_consume(&self, ticket_id: &str, service: &str) -> SaTokenResult<String> {
if service != self.service {
return Err(SaTokenError::ServiceMismatch);
}
let mut params = BTreeMap::new();
params.insert("ticket".into(), ticket_id.to_string());
params.insert("service".into(), service.to_string());
params.insert(
"timestamp".into(),
chrono::Utc::now().timestamp().to_string(),
);
params.insert("nonce".into(), Uuid::new_v4().simple().to_string());
let sign = self.sign.sign_params(¶ms)?;
params.insert("sign".into(), sign);
let qs: String = params
.iter()
.map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
.collect::<Vec<_>>()
.join("&");
let url = format!("{}?{}", self.check_url, qs);
let body = self
.client
.get(&url)
.send()
.await
.map_err(|e| SaTokenError::StorageError(e.to_string()))?
.text()
.await
.map_err(|e| SaTokenError::StorageError(e.to_string()))?;
let parsed: CheckTicketHttpBody =
serde_json::from_str(&body).map_err(|_| SaTokenError::InvalidTicket)?;
let mut verify = BTreeMap::new();
verify.insert("login_id".into(), parsed.login_id.clone());
verify.insert("timestamp".into(), parsed.timestamp.clone());
verify.insert("nonce".into(), parsed.nonce.clone());
self.sign
.verify_params(&verify, &parsed.sign)
.await
.map_err(crate::sso::sign::map_sign_err_to_sso)?;
Ok(parsed.login_id)
}
}