Skip to content

Commit fe52cc8

Browse files
authored
Fix pgpass and certificate environment logic (npgsql#3642)
Fixes npgsql#3640 Fixes npgsql#3641
1 parent e013ec8 commit fe52cc8

6 files changed

Lines changed: 110 additions & 32 deletions

File tree

Npgsql.sln.DotSettings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
<s:Boolean x:Key="/Default/UserDictionary/Words/=NOEXPORT/@EntryIndexedValue">True</s:Boolean>
9292
<s:Boolean x:Key="/Default/UserDictionary/Words/=Npgsql/@EntryIndexedValue">True</s:Boolean>
9393
<s:Boolean x:Key="/Default/UserDictionary/Words/=Npgsql_0027s/@EntryIndexedValue">True</s:Boolean>
94+
<s:Boolean x:Key="/Default/UserDictionary/Words/=pgpass/@EntryIndexedValue">True</s:Boolean>
9495
<s:Boolean x:Key="/Default/UserDictionary/Words/=PGTZ/@EntryIndexedValue">True</s:Boolean>
9596
<s:Boolean x:Key="/Default/UserDictionary/Words/=Postgis/@EntryIndexedValue">True</s:Boolean>
9697
<s:Boolean x:Key="/Default/UserDictionary/Words/=Postgre/@EntryIndexedValue">True</s:Boolean>

src/Npgsql/NpgsqlConnector.Auth.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -460,12 +460,7 @@ class AuthenticationCompleteException : Exception { }
460460
if (password != null)
461461
return password;
462462

463-
var passFile = Settings.Passfile ?? PostgresEnvironment.PassFile;
464-
if (passFile is null && PostgresEnvironment.PassFileDefault is string passFileDefault)
465-
{
466-
passFile = passFileDefault;
467-
}
468-
463+
var passFile = Settings.Passfile ?? PostgresEnvironment.PassFile ?? PostgresEnvironment.PassFileDefault;
469464
if (passFile != null)
470465
{
471466
var matchingEntry = new PgPassFile(passFile!)

src/Npgsql/NpgsqlConnector.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ string GetUsername()
644644
if (username?.Length > 0)
645645
return username;
646646

647-
if (!PGUtil.IsWindows)
647+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
648648
{
649649
username = KerberosUsernameProvider.GetUsername(Settings.IncludeRealm);
650650
if (username?.Length > 0)
@@ -716,7 +716,7 @@ async Task RawOpen(NpgsqlTimeout timeout, bool async, CancellationToken cancella
716716
{
717717
#if NET5_0
718718
// It's PEM time
719-
var keyPath = Settings.SslKey ?? PostgresEnvironment.SslKey;
719+
var keyPath = Settings.SslKey ?? PostgresEnvironment.SslKey ?? PostgresEnvironment.SslKeyDefault;
720720
cert = string.IsNullOrEmpty(password)
721721
? X509Certificate2.CreateFromPemFile(certPath, keyPath)
722722
: X509Certificate2.CreateFromEncryptedPemFile(certPath, password, keyPath);

src/Npgsql/PostgresEnvironment.cs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4+
using System.Runtime.InteropServices;
45
using Npgsql.Util;
56

67
namespace Npgsql
@@ -13,17 +14,28 @@ static class PostgresEnvironment
1314

1415
internal static string? PassFile => Environment.GetEnvironmentVariable("PGPASSFILE");
1516

16-
internal static string? PassFileDefault => GetDefaultFilePath(PGUtil.IsWindows ? "pgpass.conf" : ".pgpass");
17+
internal static string? PassFileDefault
18+
=> (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
19+
? Path.Combine(GetHomePostgresDir(), "pgpass.conf")
20+
: Path.Combine(GetHomeDir(), ".pgpass")) is var path &&
21+
File.Exists(path)
22+
? path
23+
: null;
1724

1825
internal static string? SslCert => Environment.GetEnvironmentVariable("PGSSLCERT");
1926

20-
internal static string? SslCertDefault => GetDefaultFilePath("postgresql.crt");
27+
internal static string? SslCertDefault
28+
=> Path.Combine(GetHomePostgresDir(), "postgresql.crt") is var path && File.Exists(path) ? path : null;
2129

22-
internal static string? SslCertRoot => Environment.GetEnvironmentVariable("PGSSLROOTCERT");
30+
internal static string? SslKey => Environment.GetEnvironmentVariable("PGSSLKEY");
2331

24-
internal static string? SslCertRootDefault => GetDefaultFilePath("root.crt");
32+
internal static string? SslKeyDefault
33+
=> Path.Combine(GetHomePostgresDir(), "postgresql.key") is var path && File.Exists(path) ? path : null;
2534

26-
internal static string? SslKey => Environment.GetEnvironmentVariable("PGSSLKEY");
35+
internal static string? SslCertRoot => Environment.GetEnvironmentVariable("PGSSLROOTCERT");
36+
37+
internal static string? SslCertRootDefault
38+
=> Path.Combine(GetHomePostgresDir(), "root.crt") is var path && File.Exists(path) ? path : null;
2739

2840
internal static string? ClientEncoding => Environment.GetEnvironmentVariable("PGCLIENTENCODING");
2941

@@ -33,11 +45,15 @@ static class PostgresEnvironment
3345

3446
internal static string? TargetSessionAttributes => Environment.GetEnvironmentVariable("PGTARGETSESSIONATTRS");
3547

36-
static string? GetDefaultFilePath(string fileName) =>
37-
Environment.GetEnvironmentVariable(PGUtil.IsWindows ? "APPDATA" : "HOME") is string appData &&
38-
Path.Combine(appData, "postgresql", fileName) is string filePath &&
39-
File.Exists(filePath)
40-
? filePath
41-
: null;
48+
static string GetHomeDir()
49+
{
50+
var envVar = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "APPDATA" : "HOME";
51+
return Environment.GetEnvironmentVariable(envVar) is string homedir
52+
? homedir
53+
: throw new InvalidOperationException($"Environment variable {envVar} not defined");
54+
}
55+
56+
static string GetHomePostgresDir()
57+
=> Path.Combine(GetHomeDir(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "postgresql" : ".postgresql");
4258
}
4359
}

src/Npgsql/Util/PGUtil.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,6 @@ internal static int RotateShift(int val, int shift)
119119
internal static readonly Task<bool> FalseTask = Task.FromResult(false);
120120

121121
internal static StringComparer InvariantCaseIgnoringStringComparer => StringComparer.InvariantCultureIgnoreCase;
122-
123-
internal static bool IsWindows =>
124-
System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows);
125122
}
126123

127124
enum FormatCode : short

test/Npgsql.Tests/ConnectionTests.cs

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,30 +1401,99 @@ public async Task NoResetOnClose(bool noResetOnClose)
14011401
}
14021402

14031403
[Test]
1404-
[NonParallelizable]
1405-
public async Task UsePgPassFile()
1404+
public async Task Use_pgpass_from_connection_string()
14061405
{
14071406
using var resetPassword = SetEnvironmentVariable("PGPASSWORD", null);
14081407
var builder = new NpgsqlConnectionStringBuilder(ConnectionString);
14091408

14101409
var password = builder.Password;
1411-
var passFile = Path.GetTempFileName();
1410+
builder.Password = null;
14121411

1413-
builder.Password = password;
1412+
var passFile = Path.GetTempFileName();
1413+
File.WriteAllText(passFile, $"*:*:*:{builder.Username}:{password}");
14141414
builder.Passfile = passFile;
14151415

1416-
using var deletePassFile = Defer(() => File.Delete(passFile));
1416+
try
1417+
{
1418+
using var pool = CreateTempPool(builder.ConnectionString, out var connectionString);
1419+
using var conn = await OpenConnectionAsync(connectionString);
1420+
}
1421+
finally
1422+
{
1423+
File.Delete(passFile);
1424+
}
1425+
}
14171426

1418-
File.WriteAllText(passFile, $"*:*:*:{builder.Username}:{password}");
1427+
[Test]
1428+
[NonParallelizable]
1429+
public async Task Use_pgpass_from_environment_variable()
1430+
{
1431+
using var resetPassword = SetEnvironmentVariable("PGPASSWORD", null);
1432+
var builder = new NpgsqlConnectionStringBuilder(ConnectionString);
14191433

1434+
var password = builder.Password;
1435+
builder.Password = null;
1436+
1437+
var passFile = Path.GetTempFileName();
1438+
File.WriteAllText(passFile, $"*:*:*:{builder.Username}:{password}");
14201439
using var passFileVariable = SetEnvironmentVariable("PGPASSFILE", passFile);
1421-
using var pool = CreateTempPool(builder.ConnectionString, out var connectionString);
1422-
using var conn = await OpenConnectionAsync(connectionString);
1440+
1441+
try
1442+
{
1443+
using var pool = CreateTempPool(builder.ConnectionString, out var connectionString);
1444+
using var conn = await OpenConnectionAsync(connectionString);
1445+
}
1446+
finally
1447+
{
1448+
File.Delete(passFile);
1449+
}
1450+
}
1451+
1452+
[Test]
1453+
[NonParallelizable]
1454+
public async Task Use_pgpass_from_homedir()
1455+
{
1456+
using var resetPassword = SetEnvironmentVariable("PGPASSWORD", null);
1457+
var builder = new NpgsqlConnectionStringBuilder(ConnectionString);
1458+
1459+
var password = builder.Password;
1460+
builder.Password = null;
1461+
1462+
string? dirToDelete = null;
1463+
string passFile;
1464+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1465+
{
1466+
var dir = Path.Combine(Environment.GetEnvironmentVariable("APPDATA")!, "postgresql");
1467+
if (!Directory.Exists(dir))
1468+
{
1469+
Directory.CreateDirectory(dir);
1470+
dirToDelete = dir;
1471+
1472+
}
1473+
passFile = Path.Combine(dir, "pgpass.conf");
1474+
}
1475+
else
1476+
{
1477+
passFile = Path.Combine(Environment.GetEnvironmentVariable("HOME")!, ".pgpass");
1478+
}
1479+
1480+
try
1481+
{
1482+
File.WriteAllText(passFile, $"*:*:*:{builder.Username}:{password}");
1483+
using var pool = CreateTempPool(builder.ConnectionString, out var connectionString);
1484+
using var conn = await OpenConnectionAsync(connectionString);
1485+
}
1486+
finally
1487+
{
1488+
File.Delete(passFile);
1489+
if (dirToDelete is not null)
1490+
Directory.Delete(dirToDelete);
1491+
}
14231492
}
14241493

14251494
[Test]
14261495
[NonParallelizable]
1427-
public void PasswordSourcePrecendence()
1496+
public void PasswordSourcePrecedence()
14281497
{
14291498
using var resetPassword = SetEnvironmentVariable("PGPASSWORD", null);
14301499
var builder = new NpgsqlConnectionStringBuilder(ConnectionString);
@@ -1464,7 +1533,7 @@ Func<ValueTask> OpenConnection(string? password, string? passFile) => async () =
14641533
builder.Password = password;
14651534
builder.Passfile = passFile;
14661535
builder.IntegratedSecurity = false;
1467-
builder.ApplicationName = $"{nameof(PasswordSourcePrecendence)}:{Guid.NewGuid()}";
1536+
builder.ApplicationName = $"{nameof(PasswordSourcePrecedence)}:{Guid.NewGuid()}";
14681537

14691538
using var pool = CreateTempPool(builder.ConnectionString, out var connectionString);
14701539
using var connection = await OpenConnectionAsync(connectionString);

0 commit comments

Comments
 (0)