Skip to content

Commit 21ed6ae

Browse files
RUST-2265: cherrypick #1453 for 3.2.5 (#1455)
Co-authored-by: Isabel Atkinson <isabel.atkinson@mongodb.com>
1 parent 359734a commit 21ed6ae

File tree

4 files changed

+140
-76
lines changed

4 files changed

+140
-76
lines changed

src/action/client_options.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ impl ClientOptions {
5151
/// * `socketTimeoutMS`: unsupported, does not map to any field
5252
/// * `ssl`: an alias of the `tls` option
5353
/// * `tls`: maps to the TLS variant of the `tls` field`.
54-
/// * `tlsInsecure`: relaxes the TLS constraints on connections being made; currently is just
55-
/// an alias of `tlsAllowInvalidCertificates`, but more behavior may be added to this option
56-
/// in the future
57-
/// * `tlsAllowInvalidCertificates`: maps to the `allow_invalidCertificates` field of the
54+
/// * `tlsInsecure`: relaxes the TLS constraints on connections being made; if set, the
55+
/// `allow_invalid_certificates` and `allow_invalid_hostnames` fields of the `tls` field are
56+
/// set to its value
57+
/// * `tlsAllowInvalidCertificates`: maps to the `allow_invalid_certificates` field of the
5858
/// `tls` field
5959
/// * `tlsCAFile`: maps to the `ca_file_path` field of the `tls` field
6060
/// * `tlsCertificateKeyFile`: maps to the `cert_key_file_path` field of the `tls` field

src/action/insert_one.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl<T: Serialize + Send + Sync> crate::sync::Collection<T> {
5959
}
6060
}
6161

62-
/// Inserts a document into a collection. Construct with ['Collection::insert_one`].
62+
/// Inserts a document into a collection. Construct with [`Collection::insert_one`].
6363
#[must_use]
6464
pub struct InsertOne<'a> {
6565
coll: CollRef<'a>,

src/client/options.rs

Lines changed: 82 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ pub(crate) use resolver_config::ResolverConfig;
5555

5656
pub(crate) const DEFAULT_PORT: u16 = 27017;
5757

58+
const TLS_INSECURE: &str = "tlsinsecure";
59+
const TLS_ALLOW_INVALID_CERTIFICATES: &str = "tlsallowinvalidcertificates";
60+
#[cfg(feature = "openssl-tls")]
61+
const TLS_ALLOW_INVALID_HOSTNAMES: &str = "tlsallowinvalidhostnames";
5862
const URI_OPTIONS: &[&str] = &[
5963
"appname",
6064
"authmechanism",
@@ -82,8 +86,8 @@ const URI_OPTIONS: &[&str] = &[
8286
"sockettimeoutms",
8387
"tls",
8488
"ssl",
85-
"tlsinsecure",
86-
"tlsallowinvalidcertificates",
89+
TLS_INSECURE,
90+
TLS_ALLOW_INVALID_CERTIFICATES,
8791
"tlscafile",
8892
"tlscertificatekeyfile",
8993
"uuidRepresentation",
@@ -1646,7 +1650,9 @@ impl ConnectionString {
16461650
}
16471651

16481652
/// Relax TLS constraints as much as possible (e.g. allowing invalid certificates or hostname
1649-
/// mismatches). Not supported by the Rust driver.
1653+
/// mismatches). This option can only be set in a URI. If it is set in a URI provided to
1654+
/// [`ConnectionString::parse`], [`TlsOptions::allow_invalid_certificates`] and
1655+
/// [`TlsOptions::allow_invalid_hostnames`] are set to its value.
16501656
pub fn tls_insecure(&self) -> Option<bool> {
16511657
self.tls_insecure
16521658
}
@@ -1661,42 +1667,47 @@ impl ConnectionString {
16611667
return Ok(parts);
16621668
}
16631669

1664-
let mut keys: Vec<&str> = Vec::new();
1670+
let mut keys = HashSet::new();
16651671

16661672
for option_pair in options.split('&') {
1667-
let (key, value) = match option_pair.find('=') {
1668-
Some(index) => option_pair.split_at(index),
1673+
let (key, value) = match option_pair.split_once('=') {
1674+
Some((key, value)) => (key.to_lowercase(), value),
16691675
None => {
1670-
return Err(ErrorKind::InvalidArgument {
1671-
message: format!(
1672-
"connection string options is not a `key=value` pair: {}",
1673-
option_pair,
1674-
),
1675-
}
1676-
.into())
1676+
return Err(Error::invalid_argument(format!(
1677+
"connection string option is not a 'key=value' pair: {option_pair}"
1678+
)))
16771679
}
16781680
};
16791681

1680-
if key.to_lowercase() != "readpreferencetags" && keys.contains(&key) {
1681-
return Err(ErrorKind::InvalidArgument {
1682-
message: "repeated options are not allowed in the connection string"
1683-
.to_string(),
1684-
}
1685-
.into());
1686-
} else {
1687-
keys.push(key);
1682+
if !keys.insert(key.clone()) && key != "readpreferencetags" {
1683+
return Err(Error::invalid_argument(
1684+
"repeated options are not allowed in the connection string",
1685+
));
16881686
}
16891687

1690-
// Skip leading '=' in value.
16911688
self.parse_option_pair(
16921689
&mut parts,
1693-
&key.to_lowercase(),
1694-
percent_encoding::percent_decode(&value.as_bytes()[1..])
1690+
&key,
1691+
percent_encoding::percent_decode(value.as_bytes())
16951692
.decode_utf8_lossy()
16961693
.as_ref(),
16971694
)?;
16981695
}
16991696

1697+
if keys.contains(TLS_INSECURE) {
1698+
#[cfg(feature = "openssl-tls")]
1699+
let disallowed = [TLS_ALLOW_INVALID_CERTIFICATES, TLS_ALLOW_INVALID_HOSTNAMES];
1700+
#[cfg(not(feature = "openssl-tls"))]
1701+
let disallowed = [TLS_ALLOW_INVALID_CERTIFICATES];
1702+
for option in disallowed {
1703+
if keys.contains(option) {
1704+
return Err(Error::invalid_argument(format!(
1705+
"cannot set both {TLS_INSECURE} and {option} in the connection string"
1706+
)));
1707+
}
1708+
}
1709+
}
1710+
17001711
if let Some(tags) = parts.read_preference_tags.take() {
17011712
self.read_preference = match self.read_preference.take() {
17021713
Some(read_pref) => Some(read_pref.with_tags(tags)?),
@@ -2042,63 +2053,63 @@ impl ConnectionString {
20422053
k @ "tls" | k @ "ssl" => {
20432054
let tls = get_bool!(value, k);
20442055

2045-
match (self.tls.as_ref(), tls) {
2046-
(Some(Tls::Disabled), true) | (Some(Tls::Enabled(..)), false) => {
2047-
return Err(ErrorKind::InvalidArgument {
2048-
message: "All instances of `tls` and `ssl` must have the same
2049-
value"
2050-
.to_string(),
2056+
match self.tls {
2057+
Some(Tls::Enabled(_)) if !tls => {
2058+
return Err(Error::invalid_argument(
2059+
"cannot set {key}={tls} if other TLS options are set",
2060+
))
2061+
}
2062+
Some(Tls::Disabled) if tls => {
2063+
return Err(Error::invalid_argument(
2064+
"cannot set {key}={tls} if TLS is disabled",
2065+
))
2066+
}
2067+
None => {
2068+
if tls {
2069+
self.tls = Some(Tls::Enabled(Default::default()))
2070+
} else {
2071+
self.tls = Some(Tls::Disabled)
20512072
}
2052-
.into());
20532073
}
20542074
_ => {}
2055-
};
2056-
2057-
if self.tls.is_none() {
2058-
let tls = if tls {
2059-
Tls::Enabled(Default::default())
2060-
} else {
2061-
Tls::Disabled
2062-
};
2063-
2064-
self.tls = Some(tls);
20652075
}
20662076
}
2067-
k @ "tlsinsecure" | k @ "tlsallowinvalidcertificates" => {
2068-
let val = get_bool!(value, k);
2069-
2070-
let allow_invalid_certificates = if k == "tlsinsecure" { !val } else { val };
2077+
TLS_INSECURE => {
2078+
let val = get_bool!(value, key);
2079+
self.tls_insecure = Some(val);
20712080

2072-
match self.tls {
2073-
Some(Tls::Disabled) => {
2074-
return Err(ErrorKind::InvalidArgument {
2075-
message: "'tlsInsecure' can't be set if tls=false".into(),
2081+
match self
2082+
.tls
2083+
.get_or_insert_with(|| Tls::Enabled(Default::default()))
2084+
{
2085+
Tls::Enabled(ref mut options) => {
2086+
options.allow_invalid_certificates = Some(val);
2087+
#[cfg(feature = "openssl-tls")]
2088+
{
2089+
options.allow_invalid_hostnames = Some(val);
20762090
}
2077-
.into())
20782091
}
2079-
Some(Tls::Enabled(ref options))
2080-
if options.allow_invalid_certificates.is_some()
2081-
&& options.allow_invalid_certificates
2082-
!= Some(allow_invalid_certificates) =>
2083-
{
2084-
return Err(ErrorKind::InvalidArgument {
2085-
message: "all instances of 'tlsInsecure' and \
2086-
'tlsAllowInvalidCertificates' must be consistent (e.g. \
2087-
'tlsInsecure' cannot be true when \
2088-
'tlsAllowInvalidCertificates' is false, or vice-versa)"
2089-
.into(),
2090-
}
2091-
.into());
2092+
Tls::Disabled => {
2093+
return Err(Error::invalid_argument(format!(
2094+
"cannot set {key} when TLS is disabled"
2095+
)));
20922096
}
2093-
Some(Tls::Enabled(ref mut options)) => {
2094-
options.allow_invalid_certificates = Some(allow_invalid_certificates);
2097+
}
2098+
}
2099+
TLS_ALLOW_INVALID_CERTIFICATES => {
2100+
let val = get_bool!(value, key);
2101+
2102+
match self
2103+
.tls
2104+
.get_or_insert_with(|| Tls::Enabled(Default::default()))
2105+
{
2106+
Tls::Enabled(ref mut options) => {
2107+
options.allow_invalid_certificates = Some(val);
20952108
}
2096-
None => {
2097-
self.tls = Some(Tls::Enabled(
2098-
TlsOptions::builder()
2099-
.allow_invalid_certificates(allow_invalid_certificates)
2100-
.build(),
2101-
))
2109+
Tls::Disabled => {
2110+
return Err(Error::invalid_argument(format!(
2111+
"cannot set {key} when TLS is disabled"
2112+
)))
21022113
}
21032114
}
21042115
}

src/client/options/test.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
bson_util::get_int,
1111
client::options::{ClientOptions, ConnectionString, ServerAddress},
1212
error::ErrorKind,
13+
options::Tls,
1314
test::spec::deserialize_spec_tests,
1415
Client,
1516
};
@@ -395,3 +396,55 @@ async fn tls_cert_key_password_connect() {
395396
.await
396397
.unwrap();
397398
}
399+
400+
#[tokio::test]
401+
async fn tls_insecure() {
402+
let uri = "mongodb://localhost:27017/?tls=true&tlsInsecure=true";
403+
let options = ClientOptions::parse(uri).await.unwrap();
404+
let Some(Tls::Enabled(tls_options)) = options.tls else {
405+
panic!("expected tls options to be set");
406+
};
407+
assert_eq!(tls_options.allow_invalid_certificates, Some(true));
408+
#[cfg(feature = "openssl-tls")]
409+
assert_eq!(tls_options.allow_invalid_hostnames, Some(true));
410+
411+
let uri = "mongodb://localhost:27017/?tls=true&tlsInsecure=false";
412+
let options = ClientOptions::parse(uri).await.unwrap();
413+
let Some(Tls::Enabled(tls_options)) = options.tls else {
414+
panic!("expected tls options to be set");
415+
};
416+
assert_eq!(tls_options.allow_invalid_certificates, Some(false));
417+
#[cfg(feature = "openssl-tls")]
418+
assert_eq!(tls_options.allow_invalid_hostnames, Some(false));
419+
420+
let uri = "mongodb://localhost:27017/?tls=false&tlsInsecure=true";
421+
let error = ClientOptions::parse(uri).await.unwrap_err();
422+
assert!(error.message().unwrap().contains("TLS is disabled"));
423+
424+
let uri = "mongodb://localhost:27017/?tlsInsecure=true&tls=false";
425+
let error = ClientOptions::parse(uri).await.unwrap_err();
426+
assert!(error
427+
.message()
428+
.unwrap()
429+
.contains("other TLS options are set"));
430+
431+
let uri = "mongodb://localhost:27017/?tlsInsecure=true&tlsAllowInvalidCertificates=true";
432+
let error = ClientOptions::parse(uri).await.unwrap_err();
433+
assert!(error.message().unwrap().contains("cannot set both"));
434+
435+
let uri = "mongodb://localhost:27017/?tlsInsecure=true&tlsAllowInvalidCertificates=false";
436+
let error = ClientOptions::parse(uri).await.unwrap_err();
437+
assert!(error.message().unwrap().contains("cannot set both"));
438+
439+
// TODO RUST-1896: uncomment these tests
440+
// #[cfg(feature = "openssl-tls")]
441+
// {
442+
// let uri = "mongodb://localhost:27017/?tlsInsecure=true&tlsAllowInvalidHostnames=true";
443+
// let error = ClientOptions::parse(uri).await.unwrap_err();
444+
// assert!(error.message().unwrap().contains("cannot set both"));
445+
446+
// let uri = "mongodb://localhost:27017/?tlsInsecure=true&tlsAllowInvalidHostnames=false";
447+
// let error = ClientOptions::parse(uri).await.unwrap_err();
448+
// assert!(error.message().unwrap().contains("cannot set both"));
449+
// }
450+
}

0 commit comments

Comments
 (0)