use crate::core::TdsResult;
use crate::error::Error;
use crate::security::{
IntegratedAuthConfig, SecurityContext, SecurityError, create_security_context,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SspiAuthState {
Initial,
WaitingForChallenge,
SendingResponse,
Complete,
Failed,
}
pub(crate) struct SspiAuthHandler {
security_context: Box<dyn SecurityContext>,
state: SspiAuthState,
#[allow(dead_code)] target_spn: String,
}
impl SspiAuthHandler {
pub(crate) fn new(config: &IntegratedAuthConfig, server: &str, port: u16) -> TdsResult<Self> {
let security_context = create_security_context(config, server, port)?;
let target_spn = security_context.spn().to_string();
Ok(Self {
security_context,
state: SspiAuthState::Initial,
target_spn,
})
}
#[cfg(test)]
pub(crate) fn with_context(
security_context: Box<dyn SecurityContext>,
target_spn: String,
) -> Self {
Self {
security_context,
state: SspiAuthState::Initial,
target_spn,
}
}
pub(crate) fn get_initial_token(&mut self) -> TdsResult<Vec<u8>> {
if self.state != SspiAuthState::Initial {
return Err(Error::Security(SecurityError::InitContextFailed {
message: "get_initial_token called in wrong state".to_string(),
code: 0,
}));
}
let result = self.security_context.generate_token(None)?;
if result.is_complete {
self.state = SspiAuthState::Complete;
} else {
self.state = SspiAuthState::WaitingForChallenge;
}
Ok(result.data)
}
pub(crate) fn process_challenge(&mut self, challenge: &[u8]) -> TdsResult<Option<Vec<u8>>> {
match self.state {
SspiAuthState::WaitingForChallenge | SspiAuthState::SendingResponse => {}
SspiAuthState::Complete => return Ok(None),
SspiAuthState::Initial => {
return Err(Error::Security(SecurityError::InitContextFailed {
message: "process_challenge called before get_initial_token".to_string(),
code: 0,
}));
}
SspiAuthState::Failed => {
return Err(Error::Security(SecurityError::InitContextFailed {
message: "Authentication already failed".to_string(),
code: 0,
}));
}
}
self.state = SspiAuthState::SendingResponse;
let result = match self.security_context.generate_token(Some(challenge)) {
Ok(r) => r,
Err(e) => {
self.state = SspiAuthState::Failed;
return Err(Error::Security(e));
}
};
if result.is_complete {
self.state = SspiAuthState::Complete;
if result.data.is_empty() {
return Ok(None);
}
}
Ok(Some(result.data))
}
#[cfg(test)]
pub(crate) fn is_complete(&self) -> bool {
self.state == SspiAuthState::Complete
}
#[cfg(test)]
pub(crate) fn state(&self) -> SspiAuthState {
self.state
}
#[cfg(test)]
pub(crate) fn target_spn(&self) -> &str {
&self.target_spn
}
pub(crate) fn package_name(&self) -> &str {
self.security_context.package_name()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::security::mock::MockSecurityContext;
#[test]
fn test_sspi_handler_single_round() {
let mock = MockSecurityContext::single_round("test_token".as_bytes().to_vec());
let mut handler =
SspiAuthHandler::with_context(Box::new(mock), "MSSQLSvc/server:1433".to_string());
assert_eq!(handler.state(), SspiAuthState::Initial);
let initial_token = handler.get_initial_token().unwrap();
assert_eq!(initial_token, b"test_token");
assert!(handler.is_complete());
}
#[test]
fn test_sspi_handler_multi_round() {
let mock = MockSecurityContext::multi_round(
b"ntlm_type1".to_vec(), b"ntlm_type3".to_vec(), );
let mut handler =
SspiAuthHandler::with_context(Box::new(mock), "MSSQLSvc/server:1433".to_string());
let initial_token = handler.get_initial_token().unwrap();
assert_eq!(initial_token, b"ntlm_type1");
assert!(!handler.is_complete());
assert_eq!(handler.state(), SspiAuthState::WaitingForChallenge);
let response = handler.process_challenge(b"ntlm_type2_challenge").unwrap();
assert!(response.is_some());
assert_eq!(response.unwrap(), b"ntlm_type3");
assert!(handler.is_complete());
}
#[test]
fn test_sspi_handler_state_validation() {
let mock = MockSecurityContext::multi_round(b"token1".to_vec(), b"token2".to_vec());
let mut handler =
SspiAuthHandler::with_context(Box::new(mock), "MSSQLSvc/server:1433".to_string());
let result = handler.process_challenge(b"challenge");
assert!(result.is_err());
}
#[test]
fn test_sspi_handler_getters() {
let mock = MockSecurityContext::single_round(vec![]);
let handler =
SspiAuthHandler::with_context(Box::new(mock), "MSSQLSvc/server:1433".to_string());
assert_eq!(handler.target_spn(), "MSSQLSvc/server:1433");
assert!(!handler.package_name().is_empty());
}
}