mod ado_net;
mod jdbc;
use std::collections::HashMap;
use std::path::PathBuf;
use super::AuthMethod;
use crate::EncryptionLevel;
use ado_net::*;
use jdbc::*;
#[derive(Clone, Debug)]
pub struct Config {
pub(crate) host: Option<String>,
pub(crate) port: Option<u16>,
pub(crate) database: Option<String>,
pub(crate) instance_name: Option<String>,
pub(crate) application_name: Option<String>,
pub(crate) encryption: EncryptionLevel,
pub(crate) trust: TrustConfig,
pub(crate) auth: AuthMethod,
pub(crate) readonly: bool,
pub(crate) packet_size: Option<u32>,
pub(crate) hostname_in_certificate: Option<String>,
pub(crate) client_name: Option<String>,
pub(crate) multi_subnet_failover: bool,
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
pub(crate) client_cert: Option<ClientCertificate>,
}
#[derive(Clone, Debug)]
pub(crate) enum TrustConfig {
#[allow(dead_code)]
CaCertificateLocation(PathBuf),
TrustAll,
Default,
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
#[derive(Clone, Debug)]
pub(crate) struct ClientCertificate {
pub(crate) source: ClientCertSource,
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
#[derive(Clone)]
pub(crate) enum ClientCertSource {
CertAndKey { cert: PathBuf, key: PathBuf },
#[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
Pkcs12 {
path: PathBuf,
password: zeroize::Zeroizing<String>,
},
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
impl std::fmt::Debug for ClientCertSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClientCertSource::CertAndKey { cert, key } => f
.debug_struct("CertAndKey")
.field("cert", cert)
.field("key", key)
.finish(),
#[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
ClientCertSource::Pkcs12 { path, .. } => f
.debug_struct("Pkcs12")
.field("path", path)
.field("password", &"<redacted>")
.finish(),
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
host: None,
port: None,
database: None,
instance_name: None,
application_name: None,
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
encryption: EncryptionLevel::Required,
#[cfg(not(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
)))]
encryption: EncryptionLevel::NotSupported,
trust: TrustConfig::Default,
auth: AuthMethod::None,
readonly: false,
packet_size: None,
hostname_in_certificate: None,
client_name: None,
multi_subnet_failover: false,
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
client_cert: None,
}
}
}
impl Config {
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> ConfigBuilder {
ConfigBuilder {
inner: Self::default(),
}
}
pub fn host(&mut self, host: impl ToString) {
self.host = Some(host.to_string());
}
pub fn port(&mut self, port: u16) {
self.port = Some(port);
}
pub fn database(&mut self, database: impl ToString) {
self.database = Some(database.to_string())
}
pub fn instance_name(&mut self, name: impl ToString) {
self.instance_name = Some(name.to_string());
}
pub fn application_name(&mut self, name: impl ToString) {
self.application_name = Some(name.to_string());
}
pub fn packet_size(&mut self, size: u32) {
self.packet_size = Some(size);
}
pub fn get_packet_size(&self) -> Option<u32> {
self.packet_size
}
pub fn encryption(&mut self, encryption: EncryptionLevel) {
self.encryption = encryption;
}
pub fn trust_cert(&mut self) {
if let TrustConfig::CaCertificateLocation(_) = &self.trust {
panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
}
self.trust = TrustConfig::TrustAll;
}
pub fn trust_cert_ca(&mut self, path: impl ToString) {
if let TrustConfig::TrustAll = &self.trust {
panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
} else {
self.trust = TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string()))
}
}
pub fn hostname_in_certificate(&mut self, hostname: impl ToString) {
self.hostname_in_certificate = Some(hostname.to_string());
}
pub fn client_name(&mut self, name: impl ToString) {
self.client_name = Some(name.to_string());
}
pub fn authentication(&mut self, auth: AuthMethod) {
self.auth = auth;
}
pub fn readonly(&mut self, readnoly: bool) {
self.readonly = readnoly;
}
pub fn multi_subnet_failover(&mut self, multi_subnet_failover: bool) {
self.multi_subnet_failover = multi_subnet_failover;
}
pub fn get_multi_subnet_failover(&self) -> bool {
self.multi_subnet_failover
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
)))
)]
pub fn client_certificate(&mut self, cert: impl Into<PathBuf>, key: impl Into<PathBuf>) {
self.client_cert = Some(ClientCertificate {
source: ClientCertSource::CertAndKey {
cert: cert.into(),
key: key.into(),
},
});
}
#[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "native-tls", feature = "vendored-openssl")))
)]
pub fn client_certificate_pkcs12(
&mut self,
path: impl Into<PathBuf>,
password: impl Into<String>,
) {
self.client_cert = Some(ClientCertificate {
source: ClientCertSource::Pkcs12 {
path: path.into(),
password: zeroize::Zeroizing::new(password.into()),
},
});
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
pub(crate) fn get_client_certificate(&self) -> Option<&ClientCertificate> {
self.client_cert.as_ref()
}
pub(crate) fn get_host(&self) -> &str {
self.host
.as_deref()
.filter(|v| v != &".")
.unwrap_or("localhost")
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
pub(crate) fn get_hostname_in_certificate(&self) -> &str {
self.hostname_in_certificate
.as_deref()
.unwrap_or_else(|| self.get_host())
}
pub(crate) fn get_port(&self) -> u16 {
match (self.port, self.instance_name.as_ref()) {
(Some(port), _) => port,
(None, Some(_)) => 1434,
(None, None) => 1433,
}
}
pub fn get_addr(&self) -> String {
format!("{}:{}", self.get_host(), self.get_port())
}
pub fn from_ado_string(s: &str) -> crate::Result<Self> {
let ado: AdoNetConfig = s.parse()?;
Self::from_config_string(ado)
}
pub fn from_jdbc_string(s: &str) -> crate::Result<Self> {
let jdbc: JdbcConfig = s.parse()?;
Self::from_config_string(jdbc)
}
fn from_config_string(s: impl ConfigString) -> crate::Result<Self> {
let mut builder = Self::new();
let server = s.server()?;
if let Some(host) = server.host {
builder.host(host);
}
if let Some(port) = server.port {
builder.port(port);
}
if let Some(instance) = server.instance {
builder.instance_name(instance);
}
builder.authentication(s.authentication()?);
if let Some(database) = s.database() {
builder.database(database);
}
if let Some(name) = s.application_name() {
builder.application_name(name);
}
if s.trust_cert()? {
builder.trust_cert();
}
if let Some(ca) = s.trust_cert_ca() {
builder.trust_cert_ca(ca);
}
if let Some(hostname_in_cert) = s.hostname_in_certificate() {
builder.hostname_in_certificate(hostname_in_cert);
}
builder.encryption(s.encrypt()?);
builder.readonly(s.readonly());
if let Some(client_name) = s.client_name() {
builder.client_name(client_name);
}
builder.multi_subnet_failover(s.multi_subnet_failover()?);
Ok(builder)
}
}
#[derive(Clone, Debug)]
pub struct ConfigBuilder {
inner: Config,
}
impl ConfigBuilder {
pub fn host(mut self, host: impl ToString) -> Self {
self.inner.host = Some(host.to_string());
self
}
pub fn port(mut self, port: u16) -> Self {
self.inner.port = Some(port);
self
}
pub fn database(mut self, database: impl ToString) -> Self {
self.inner.database = Some(database.to_string());
self
}
pub fn instance_name(mut self, name: impl ToString) -> Self {
self.inner.instance_name = Some(name.to_string());
self
}
pub fn application_name(mut self, name: impl ToString) -> Self {
self.inner.application_name = Some(name.to_string());
self
}
pub fn encryption(mut self, encryption: EncryptionLevel) -> Self {
self.inner.encryption = encryption;
self
}
pub fn trust_cert(mut self) -> Self {
if let TrustConfig::CaCertificateLocation(_) = &self.inner.trust {
panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
}
self.inner.trust = TrustConfig::TrustAll;
self
}
pub fn trust_cert_ca(mut self, path: impl ToString) -> Self {
if let TrustConfig::TrustAll = &self.inner.trust {
panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
} else {
self.inner.trust = TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string()))
}
self
}
pub fn authentication(mut self, auth: AuthMethod) -> Self {
self.inner.auth = auth;
self
}
pub fn readonly(mut self, readonly: bool) -> Self {
self.inner.readonly = readonly;
self
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
)))
)]
pub fn client_certificate(mut self, cert: impl Into<PathBuf>, key: impl Into<PathBuf>) -> Self {
self.inner.client_certificate(cert, key);
self
}
#[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "native-tls", feature = "vendored-openssl")))
)]
pub fn client_certificate_pkcs12(
mut self,
path: impl Into<PathBuf>,
password: impl Into<String>,
) -> Self {
self.inner.client_certificate_pkcs12(path, password);
self
}
pub fn build(self) -> Config {
self.inner
}
}
impl From<Config> for ConfigBuilder {
fn from(config: Config) -> Self {
ConfigBuilder { inner: config }
}
}
impl From<ConfigBuilder> for Config {
fn from(builder: ConfigBuilder) -> Self {
builder.inner
}
}
pub(crate) struct ServerDefinition {
host: Option<String>,
port: Option<u16>,
instance: Option<String>,
}
pub(crate) trait ConfigString {
fn dict(&self) -> &HashMap<String, String>;
fn server(&self) -> crate::Result<ServerDefinition>;
fn authentication(&self) -> crate::Result<AuthMethod> {
let user = self
.dict()
.get("uid")
.or_else(|| self.dict().get("username"))
.or_else(|| self.dict().get("user"))
.or_else(|| self.dict().get("user id"))
.map(|s| s.as_str());
let pw = self
.dict()
.get("password")
.or_else(|| self.dict().get("pwd"))
.map(|s| s.as_str());
match self
.dict()
.get("integratedsecurity")
.or_else(|| self.dict().get("integrated security"))
{
#[cfg(all(windows, feature = "winauth"))]
Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => match (user, pw)
{
(None, None) => Ok(AuthMethod::Integrated),
_ => Ok(AuthMethod::windows(user.unwrap_or(""), pw.unwrap_or(""))),
},
#[cfg(all(unix, feature = "sspi-rs"))]
Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => {
match (user, pw) {
(Some(user), Some(pw)) => Ok(AuthMethod::windows(user, pw)),
#[cfg(feature = "integrated-auth-gssapi")]
(None, None) => Ok(AuthMethod::Integrated),
_ => Ok(AuthMethod::windows(user.unwrap_or(""), pw.unwrap_or(""))),
}
}
#[cfg(all(
feature = "integrated-auth-gssapi",
not(all(unix, feature = "sspi-rs"))
))]
Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => {
Ok(AuthMethod::Integrated)
}
_ => Ok(AuthMethod::sql_server(user.unwrap_or(""), pw.unwrap_or(""))),
}
}
fn database(&self) -> Option<String> {
self.dict()
.get("database")
.or_else(|| self.dict().get("initial catalog"))
.or_else(|| self.dict().get("databasename"))
.map(|db| db.to_string())
}
fn application_name(&self) -> Option<String> {
self.dict()
.get("application name")
.or_else(|| self.dict().get("applicationname"))
.map(|name| name.to_string())
}
fn trust_cert(&self) -> crate::Result<bool> {
self.dict()
.get("trustservercertificate")
.map(Self::parse_bool)
.unwrap_or(Ok(false))
}
fn trust_cert_ca(&self) -> Option<String> {
self.dict()
.get("trustservercertificateca")
.map(|ca| ca.to_string())
}
fn hostname_in_certificate(&self) -> Option<String> {
self.dict()
.get("hostnameincertificate")
.or_else(|| self.dict().get("hostname in certificate"))
.map(|host| host.to_string())
}
fn client_name(&self) -> Option<String> {
self.dict()
.get("workstationid")
.or_else(|| self.dict().get("workstation id"))
.map(|name| name.to_string())
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
fn encrypt(&self) -> crate::Result<EncryptionLevel> {
self.dict()
.get("encrypt")
.map(|val| match Self::parse_bool(val) {
Ok(true) => Ok(EncryptionLevel::Required),
Ok(false) => Ok(EncryptionLevel::Off),
Err(_) if val == "DANGER_PLAINTEXT" => Ok(EncryptionLevel::NotSupported),
Err(_) if val.eq_ignore_ascii_case("strict") && cfg!(feature = "tds80") => {
Ok(EncryptionLevel::Strict)
}
Err(_) if val.eq_ignore_ascii_case("strict") => Err(crate::Error::Conversion(
"encrypt=strict requires the crate's `tds80` feature to be enabled".into(),
)),
Err(e) => Err(e),
})
.unwrap_or(Ok(EncryptionLevel::Required))
}
#[cfg(not(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
)))]
fn encrypt(&self) -> crate::Result<EncryptionLevel> {
Ok(EncryptionLevel::NotSupported)
}
fn parse_bool<T: AsRef<str>>(v: T) -> crate::Result<bool> {
match v.as_ref().trim().to_lowercase().as_str() {
"true" | "yes" => Ok(true),
"false" | "no" => Ok(false),
_ => Err(crate::Error::Conversion(
"Connection string: Not a valid boolean".into(),
)),
}
}
fn readonly(&self) -> bool {
self.dict()
.get("applicationintent")
.filter(|val| val.trim().eq_ignore_ascii_case("ReadOnly"))
.is_some()
}
fn multi_subnet_failover(&self) -> crate::Result<bool> {
self.dict()
.get("multisubnetfailover")
.map(Self::parse_bool)
.unwrap_or(Ok(false))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_builder_constructs_config() {
let config = Config::builder()
.host("db.example.com")
.port(4433)
.database("northwind")
.application_name("my-app")
.authentication(AuthMethod::sql_server("SA", "secret"))
.readonly(true)
.build();
assert_eq!("db.example.com", config.get_host());
assert_eq!(4433, config.get_port());
assert_eq!("db.example.com:4433", config.get_addr());
assert_eq!(Some("northwind"), config.database.as_deref());
assert_eq!(Some("my-app"), config.application_name.as_deref());
assert!(config.readonly);
assert!(matches!(config.auth, AuthMethod::SqlServer(_)));
assert!(matches!(config.trust, TrustConfig::Default));
}
#[test]
fn config_builder_roundtrips_via_from() {
let config = Config::builder().host("localhost").port(1433).build();
let builder: ConfigBuilder = config.into();
let config = builder.database("master").build();
assert_eq!("localhost:1433", config.get_addr());
assert_eq!(Some("master"), config.database.as_deref());
}
#[test]
fn config_from_builder_carries_builder_settings() {
let config: Config = Config::builder().host("db.internal").port(2020).into();
assert_eq!("db.internal", config.get_host());
assert_eq!(2020, config.get_port());
}
#[test]
fn get_packet_size_reflects_the_set_value() {
let mut config = Config::new();
assert_eq!(config.get_packet_size(), None);
config.packet_size(8192);
assert_eq!(config.get_packet_size(), Some(8192));
}
#[test]
fn from_jdbc_string_parses_host_and_port() {
let config =
Config::from_jdbc_string("jdbc:sqlserver://db.example.com:2345").expect("valid jdbc");
assert_eq!("db.example.com", config.get_host());
assert_eq!(2345, config.get_port());
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
#[test]
fn get_hostname_in_certificate_falls_back_to_host() {
let mut config = Config::new();
config.host("real.host");
assert_eq!(config.get_hostname_in_certificate(), "real.host");
config.hostname_in_certificate("cert.host");
assert_eq!(config.get_hostname_in_certificate(), "cert.host");
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
#[test]
fn client_certificate_sets_cert_and_key_source() {
let mut config = Config::new();
assert!(config.get_client_certificate().is_none());
config.client_certificate("/tmp/client.pem", "/tmp/client.key");
let cert = config
.get_client_certificate()
.expect("client certificate should be set");
match &cert.source {
ClientCertSource::CertAndKey { cert, key } => {
assert_eq!(cert, &PathBuf::from("/tmp/client.pem"));
assert_eq!(key, &PathBuf::from("/tmp/client.key"));
}
#[allow(unreachable_patterns)]
other => panic!("expected CertAndKey source, got {other:?}"),
}
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
#[test]
fn config_builder_sets_client_certificate() {
let config = Config::builder()
.host("localhost")
.client_certificate("cert.der", "key.der")
.build();
match &config
.get_client_certificate()
.expect("client certificate should be set")
.source
{
ClientCertSource::CertAndKey { cert, key } => {
assert_eq!(cert, &PathBuf::from("cert.der"));
assert_eq!(key, &PathBuf::from("key.der"));
}
#[allow(unreachable_patterns)]
other => panic!("expected CertAndKey source, got {other:?}"),
}
}
#[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
#[test]
fn client_certificate_pkcs12_sets_bundle_source() {
let mut config = Config::new();
config.client_certificate_pkcs12("/tmp/identity.pfx", "s3cr3t");
match &config
.get_client_certificate()
.expect("client certificate should be set")
.source
{
ClientCertSource::Pkcs12 { path, password } => {
assert_eq!(path, &PathBuf::from("/tmp/identity.pfx"));
assert_eq!(password.as_str(), "s3cr3t");
}
other => panic!("expected Pkcs12 source, got {other:?}"),
}
}
#[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
#[test]
fn client_certificate_debug_redacts_pkcs12_password() {
let mut config = Config::new();
config.client_certificate_pkcs12("/tmp/identity.pfx", "topsecret");
let dbg = format!("{:?}", config.get_client_certificate().unwrap());
assert!(dbg.contains("<redacted>"));
assert!(!dbg.contains("topsecret"));
}
#[cfg(all(unix, feature = "sspi-rs"))]
#[test]
fn ado_integrated_security_sspi_with_credentials_uses_windows_ntlm() {
let config = Config::from_ado_string(
"server=tcp:localhost,1433;IntegratedSecurity=SSPI;uid=DOMAIN\\user;pwd=secret",
)
.unwrap();
match config.auth {
AuthMethod::Windows(auth) => {
assert_eq!("user", auth.user);
assert_eq!(Some("DOMAIN"), auth.domain.as_deref());
}
other => panic!("expected Windows NTLM auth, got {other:?}"),
}
}
#[test]
fn config_direct_setters_populate_fields() {
let mut config = Config::new();
config.database("northwind");
config.instance_name("SQLEXPRESS");
config.client_name("workstation-7");
assert_eq!(Some("northwind"), config.database.as_deref());
assert_eq!(Some("SQLEXPRESS"), config.instance_name.as_deref());
assert_eq!(Some("workstation-7"), config.client_name.as_deref());
}
#[test]
fn get_port_defaults_without_port_or_instance() {
let config = Config::new();
assert_eq!(1433, config.get_port());
}
#[test]
fn get_port_uses_sql_browser_port_for_named_instance() {
let mut config = Config::new();
config.instance_name("SQLEXPRESS");
assert_eq!(1434, config.get_port());
}
#[test]
#[should_panic(expected = "mutual exclusive")]
fn trust_cert_after_trust_cert_ca_panics() {
let mut config = Config::new();
config.trust_cert_ca("/tmp/ca.crt");
config.trust_cert();
}
#[test]
#[should_panic(expected = "mutual exclusive")]
fn trust_cert_ca_after_trust_cert_panics() {
let mut config = Config::new();
config.trust_cert();
config.trust_cert_ca("/tmp/ca.crt");
}
#[test]
fn trust_cert_ca_sets_ca_location() {
let mut config = Config::new();
config.trust_cert_ca("/tmp/ca.crt");
assert!(matches!(
config.trust,
TrustConfig::CaCertificateLocation(_)
));
}
#[test]
fn config_builder_covers_all_setters() {
let config = Config::builder()
.host("localhost")
.instance_name("SQLEXPRESS")
.encryption(EncryptionLevel::Off)
.trust_cert_ca("/tmp/ca.crt")
.build();
assert_eq!(Some("SQLEXPRESS"), config.instance_name.as_deref());
assert!(matches!(config.encryption, EncryptionLevel::Off));
assert!(matches!(
config.trust,
TrustConfig::CaCertificateLocation(_)
));
}
#[test]
fn config_builder_trust_cert_sets_trust_all() {
let config = Config::builder().trust_cert().build();
assert!(matches!(config.trust, TrustConfig::TrustAll));
}
#[test]
#[should_panic(expected = "mutual exclusive")]
fn config_builder_trust_cert_after_ca_panics() {
Config::builder().trust_cert_ca("/tmp/ca.crt").trust_cert();
}
#[test]
#[should_panic(expected = "mutual exclusive")]
fn config_builder_trust_cert_ca_after_trust_cert_panics() {
Config::builder().trust_cert().trust_cert_ca("/tmp/ca.crt");
}
#[test]
fn from_ado_string_populates_optional_fields() {
let config = Config::from_ado_string(
"server=tcp:my-server.com\\SQLEXPRESS;database=northwind;\
HostNameInCertificate=cert.host;WorkstationID=ws-1",
)
.expect("valid ado string");
assert_eq!("my-server.com", config.get_host());
assert_eq!(Some("SQLEXPRESS"), config.instance_name.as_deref());
assert_eq!(Some("northwind"), config.database.as_deref());
assert_eq!(Some("cert.host"), config.hostname_in_certificate.as_deref());
assert_eq!(Some("ws-1"), config.client_name.as_deref());
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
#[test]
fn client_cert_source_debug_formats_cert_and_key() {
let mut config = Config::new();
config.client_certificate("/tmp/client.pem", "/tmp/client.key");
let dbg = format!("{:?}", config.get_client_certificate().unwrap().source);
assert!(dbg.contains("CertAndKey"));
assert!(dbg.contains("client.pem"));
assert!(dbg.contains("client.key"));
}
#[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
#[test]
fn config_builder_sets_pkcs12_client_certificate() {
let config = Config::builder()
.client_certificate_pkcs12("/tmp/identity.pfx", "s3cr3t")
.build();
match &config
.get_client_certificate()
.expect("client certificate should be set")
.source
{
ClientCertSource::Pkcs12 { path, password } => {
assert_eq!(path, &PathBuf::from("/tmp/identity.pfx"));
assert_eq!(password.as_str(), "s3cr3t");
}
other => panic!("expected Pkcs12 source, got {other:?}"),
}
}
#[cfg(all(unix, feature = "sspi-rs"))]
#[test]
fn ado_integrated_security_sspi_with_partial_credentials_uses_windows() {
let config = Config::from_ado_string(
"server=tcp:localhost,1433;IntegratedSecurity=SSPI;uid=onlyuser",
)
.unwrap();
match config.auth {
AuthMethod::Windows(auth) => {
assert_eq!("onlyuser", auth.user);
}
other => panic!("expected Windows auth, got {other:?}"),
}
}
}