diff --git a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs index 1e5eab5..7ca65ec 100644 --- a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs +++ b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs @@ -11,8 +11,10 @@ public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Bui public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(DbUp.Engine.Transactions.IConnectionManager connectionManager, string schema) { } public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString, string schema) { } public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString, string schema, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } + public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString, string schema, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } + public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger) { } } namespace DbUp.Postgresql @@ -22,8 +24,16 @@ public class PostgresqlConnectionManager : DbUp.Engine.Transactions.DatabaseConn public PostgresqlConnectionManager(string connectionString) { } public PostgresqlConnectionManager(Npgsql.NpgsqlDataSource datasource) { } public PostgresqlConnectionManager(string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } + public PostgresqlConnectionManager(string connectionString, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } public override System.Collections.Generic.IEnumerable SplitScriptIntoCommands(string scriptContents) { } } + public class PostgresqlConnectionOptions + { + public PostgresqlConnectionOptions() { } + public System.Security.Cryptography.X509Certificates.X509Certificate2 ClientCertificate { get; set; } + public string MasterDatabaseName { get; set; } + public System.Net.Security.RemoteCertificateValidationCallback UserCertificateValidationCallback { get; set; } + } public class PostgresqlObjectParser : DbUp.Support.SqlObjectParser, DbUp.Engine.ISqlObjectParser { public PostgresqlObjectParser() { } diff --git a/src/dbup-postgresql/PostgresqlConnectionManager.cs b/src/dbup-postgresql/PostgresqlConnectionManager.cs index 6610bab..b78742b 100644 --- a/src/dbup-postgresql/PostgresqlConnectionManager.cs +++ b/src/dbup-postgresql/PostgresqlConnectionManager.cs @@ -27,11 +27,23 @@ public PostgresqlConnectionManager(string connectionString) /// The PostgreSQL connection string. /// Certificate for securing connection. public PostgresqlConnectionManager(string connectionString, X509Certificate2 certificate) + : this(connectionString, new PostgresqlConnectionOptions + { + ClientCertificate = certificate + }) + { + } + + /// + /// Create a new PostgreSQL database connection + /// + /// The PostgreSQL connection string. + /// Custom options to apply on the created connection + public PostgresqlConnectionManager(string connectionString, PostgresqlConnectionOptions connectionOptions) : base(new DelegateConnectionFactory(l => { NpgsqlConnection databaseConnection = new NpgsqlConnection(connectionString); - databaseConnection.ProvideClientCertificatesCallback += - certs => certs.Add(certificate); + databaseConnection.ApplyConnectionOptions(connectionOptions); return databaseConnection; } diff --git a/src/dbup-postgresql/PostgresqlConnectionOptions.cs b/src/dbup-postgresql/PostgresqlConnectionOptions.cs new file mode 100644 index 0000000..7eb088b --- /dev/null +++ b/src/dbup-postgresql/PostgresqlConnectionOptions.cs @@ -0,0 +1,27 @@ +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace DbUp.Postgresql +{ + /// + /// Options that will be applied on the created connection + /// + public class PostgresqlConnectionOptions + { + /// + /// Certificate for securing connection. + /// + public X509Certificate2 ClientCertificate { get; set; } + + /// + /// Custom handler to verify the remote SSL certificate. + /// Ignored if Npgsql.NpgsqlConnectionStringBuilder.TrustServerCertificate is set. + /// + public RemoteCertificateValidationCallback UserCertificateValidationCallback { get; set; } + + /// + /// The database to connect to initially. Default is 'postgres'. + /// + public string MasterDatabaseName { get; set; } = "postgres"; + } +} diff --git a/src/dbup-postgresql/PostgresqlExtensions.cs b/src/dbup-postgresql/PostgresqlExtensions.cs index fd885df..00bbe66 100644 --- a/src/dbup-postgresql/PostgresqlExtensions.cs +++ b/src/dbup-postgresql/PostgresqlExtensions.cs @@ -51,6 +51,19 @@ public static UpgradeEngineBuilder PostgresqlDatabase(this SupportedDatabases su public static UpgradeEngineBuilder PostgresqlDatabase(this SupportedDatabases supported, string connectionString, string schema, X509Certificate2 certificate) => PostgresqlDatabase(new PostgresqlConnectionManager(connectionString, certificate), schema); + /// + /// Creates an upgrader for PostgreSQL databases that use SSL. + /// + /// Fluent helper type. + /// PostgreSQL database connection string. + /// The schema in which to check for changes + /// Connection options to set SSL parameters + /// + /// A builder for a database upgrader designed for PostgreSQL databases. + /// + public static UpgradeEngineBuilder PostgresqlDatabase(this SupportedDatabases supported, string connectionString, string schema, PostgresqlConnectionOptions connectionOptions) + => PostgresqlDatabase(new PostgresqlConnectionManager(connectionString, connectionOptions), schema); + /// /// Creates an upgrader for PostgreSQL databases. /// @@ -113,6 +126,18 @@ public static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase s PostgresqlDatabase(supported, connectionString, new ConsoleUpgradeLog(), certificate); } + /// + /// Ensures that the database specified in the connection string exists using SSL for the connection. + /// + /// Fluent helper type. + /// The connection string. + /// Connection SSL to customize SSL behaviour + /// + public static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase supported, string connectionString, PostgresqlConnectionOptions connectionOptions) + { + PostgresqlDatabase(supported, connectionString, new ConsoleUpgradeLog(), connectionOptions); + } + /// /// Ensures that the database specified in the connection string exists. /// @@ -122,10 +147,24 @@ public static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase s /// public static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase supported, string connectionString, IUpgradeLog logger) { - PostgresqlDatabase(supported, connectionString, logger, null); + PostgresqlDatabase(supported, connectionString, logger, (PostgresqlConnectionOptions)null); } - + private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase supported, string connectionString, IUpgradeLog logger, X509Certificate2 certificate) + { + var options = new PostgresqlConnectionOptions + { + ClientCertificate = certificate + }; + PostgresqlDatabase(supported, connectionString, logger, options); + } + + private static void PostgresqlDatabase( + this SupportedDatabasesForEnsureDatabase supported, + string connectionString, + IUpgradeLog logger, + PostgresqlConnectionOptions connectionOptions + ) { if (supported == null) throw new ArgumentNullException("supported"); @@ -137,7 +176,7 @@ private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase if (logger == null) throw new ArgumentNullException("logger"); var masterConnectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString); - + var databaseName = masterConnectionStringBuilder.Database; if (string.IsNullOrEmpty(databaseName) || databaseName.Trim() == string.Empty) @@ -145,7 +184,7 @@ private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase throw new InvalidOperationException("The connection string does not specify a database name."); } - masterConnectionStringBuilder.Database = "postgres"; + masterConnectionStringBuilder.Database = connectionOptions.MasterDatabaseName; var logMasterConnectionStringBuilder = new NpgsqlConnectionStringBuilder(masterConnectionStringBuilder.ConnectionString); if (!string.IsNullOrEmpty(logMasterConnectionStringBuilder.Password)) @@ -157,11 +196,7 @@ private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase using (var connection = new NpgsqlConnection(masterConnectionStringBuilder.ConnectionString)) { - if (certificate != null) - { - connection.ProvideClientCertificatesCallback += - certs => certs.Add(certificate); - } + connection.ApplyConnectionOptions(connectionOptions); connection.Open(); var sqlCommandText = @@ -209,4 +244,17 @@ public static UpgradeEngineBuilder JournalToPostgresqlTable(this UpgradeEngineBu builder.Configure(c => c.Journal = new PostgresqlTableJournal(() => c.ConnectionManager, () => c.Log, schema, table)); return builder; } + + internal static void ApplyConnectionOptions(this NpgsqlConnection connection, PostgresqlConnectionOptions connectionOptions) + { + if (connectionOptions?.ClientCertificate != null) + { + connection.ProvideClientCertificatesCallback += + certs => certs.Add(connectionOptions.ClientCertificate); + } + if (connectionOptions?.UserCertificateValidationCallback != null) + { + connection.UserCertificateValidationCallback = connectionOptions.UserCertificateValidationCallback; + } + } }