fix(postgres): Match ~/.pgpass password after URL parsing and fix user and database ordering (#1566)

* fix(postgres): Fixes pgpass so it applies after parsing and matches usernames & databases correctly

* chore: Updated unit test

* refactor: Previous semantics of PgConnectOptions::default()

* refactor: formatting
This commit is contained in:
Rob Gilson 2021-12-29 16:00:49 -05:00 committed by GitHub
parent fca866d0bc
commit f79d321b78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 13 deletions

View File

@ -89,7 +89,7 @@ pub struct PgConnectOptions {
impl Default for PgConnectOptions {
fn default() -> Self {
Self::new()
Self::new_without_pgpass().apply_pgpass()
}
}
@ -115,6 +115,10 @@ impl PgConnectOptions {
/// let options = PgConnectOptions::new();
/// ```
pub fn new() -> Self {
Self::new_without_pgpass().apply_pgpass()
}
pub fn new_without_pgpass() -> Self {
let port = var("PGPORT")
.ok()
.and_then(|v| v.parse().ok())
@ -126,16 +130,12 @@ impl PgConnectOptions {
let database = var("PGDATABASE").ok();
let password = var("PGPASSWORD")
.ok()
.or_else(|| pgpass::load_password(&host, port, &username, database.as_deref()));
PgConnectOptions {
port,
host,
socket: None,
username,
password,
password: var("PGPASSWORD").ok(),
database,
ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from),
ssl_mode: var("PGSSLMODE")
@ -148,6 +148,19 @@ impl PgConnectOptions {
}
}
pub(crate) fn apply_pgpass(mut self) -> Self {
if self.password.is_none() {
self.password = pgpass::load_password(
&self.host,
self.port,
&self.username,
self.database.as_deref(),
);
}
self
}
/// Sets the name of the host to connect to.
///
/// If a host name begins with a slash, it specifies

View File

@ -11,7 +11,7 @@ impl FromStr for PgConnectOptions {
fn from_str(s: &str) -> Result<Self, Error> {
let url: Url = s.parse().map_err(Error::config)?;
let mut options = Self::default();
let mut options = Self::new_without_pgpass();
if let Some(host) = url.host_str() {
let host_decoded = percent_decode_str(host);
@ -89,6 +89,8 @@ impl FromStr for PgConnectOptions {
}
}
let options = options.apply_pgpass();
Ok(options)
}
}

View File

@ -90,13 +90,15 @@ fn load_password_from_line(
) -> Option<String> {
let whole_line = line;
// Pgpass line ordering: hostname, port, database, username, password
// See: https://www.postgresql.org/docs/9.3/libpq-pgpass.html
match line.trim_start().chars().next() {
None | Some('#') => None,
_ => {
matches_next_field(whole_line, &mut line, host)?;
matches_next_field(whole_line, &mut line, &port.to_string())?;
matches_next_field(whole_line, &mut line, username)?;
matches_next_field(whole_line, &mut line, database.unwrap_or_default())?;
matches_next_field(whole_line, &mut line, username)?;
Some(line.to_owned())
}
}
@ -219,7 +221,7 @@ mod tests {
// normal
assert_eq!(
load_password_from_line(
"localhost:5432:foo:bar:baz",
"localhost:5432:bar:foo:baz",
"localhost",
5432,
"foo",
@ -229,19 +231,19 @@ mod tests {
);
// wildcard
assert_eq!(
load_password_from_line("*:5432:foo:bar:baz", "localhost", 5432, "foo", Some("bar")),
load_password_from_line("*:5432:bar:foo:baz", "localhost", 5432, "foo", Some("bar")),
Some("baz".to_owned())
);
// accept wildcard with missing db
assert_eq!(
load_password_from_line("localhost:5432:foo:*:baz", "localhost", 5432, "foo", None),
load_password_from_line("localhost:5432:*:foo:baz", "localhost", 5432, "foo", None),
Some("baz".to_owned())
);
// doesn't match
assert_eq!(
load_password_from_line(
"thishost:5432:foo:bar:baz",
"thishost:5432:bar:foo:baz",
"thathost",
5432,
"foo",
@ -252,7 +254,7 @@ mod tests {
// malformed entry
assert_eq!(
load_password_from_line(
"localhost:5432:foo:bar",
"localhost:5432:bar:foo",
"localhost",
5432,
"foo",