Merge branch 'release/10.5' into v10/dev

This commit is contained in:
Nikolaj
2023-04-13 08:47:41 +02:00
17 changed files with 284 additions and 9 deletions

View File

@@ -1,12 +1,12 @@
namespace Umbraco.Cms.Persistence.SqlServer;
/// <summary>
/// Constants related to SQLite.
/// Constants related to SQL Server.
/// </summary>
public static class Constants
{
/// <summary>
/// SQLite provider name.
/// SQL Server provider name.
/// </summary>
public const string ProviderName = "Microsoft.Data.SqlClient";
}

View File

@@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using Microsoft.Data.SqlClient;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Extensions;
@@ -50,6 +51,25 @@ public class SqlAzureDatabaseProviderMetadata : IDatabaseProviderMetadata
/// <inheritdoc />
public bool ForceCreateDatabase => false;
/// <inheritdoc />
public bool CanRecognizeConnectionString(string? connectionString)
{
if (connectionString is null)
{
return false;
}
try
{
var builder = new SqlConnectionStringBuilder(connectionString);
return string.IsNullOrEmpty(builder.AttachDBFilename) && builder.DataSource.Contains("database.windows.net");
}
catch (ArgumentException)
{
return false;
}
}
/// <inheritdoc />
public string GenerateConnectionString(DatabaseModel databaseModel)
{

View File

@@ -51,6 +51,27 @@ public class SqlLocalDbDatabaseProviderMetadata : IDatabaseProviderMetadata
/// <inheritdoc />
public bool ForceCreateDatabase => true;
/// <inheritdoc />
public bool CanRecognizeConnectionString(string? connectionString)
{
if (connectionString is null)
{
return false;
}
try
{
var builder = new SqlConnectionStringBuilder(connectionString);
return !string.IsNullOrEmpty(builder.AttachDBFilename);
}
catch (ArgumentException)
{
return false;
}
}
/// <inheritdoc />
public string GenerateConnectionString(DatabaseModel databaseModel)
{

View File

@@ -1,6 +1,9 @@
using System.Data.Common;
using System.Runtime.Serialization;
using Microsoft.Data.SqlClient;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Extensions;
namespace Umbraco.Cms.Persistence.SqlServer.Services;
@@ -49,6 +52,26 @@ public class SqlServerDatabaseProviderMetadata : IDatabaseProviderMetadata
/// <inheritdoc />
public bool ForceCreateDatabase => false;
/// <inheritdoc />
public bool CanRecognizeConnectionString(string? connectionString)
{
if (connectionString is null)
{
return false;
}
try
{
var builder = new SqlConnectionStringBuilder(connectionString);
return string.IsNullOrEmpty(builder.AttachDBFilename);
}
catch (ArgumentException)
{
return false;
}
}
/// <inheritdoc />
public string GenerateConnectionString(DatabaseModel databaseModel) =>
databaseModel.IntegratedAuth

View File

@@ -56,6 +56,26 @@ public class SqliteDatabaseProviderMetadata : IDatabaseProviderMetadata
/// </remarks>
public bool ForceCreateDatabase => true;
/// <inheritdoc />
public bool CanRecognizeConnectionString(string? connectionString)
{
if (connectionString is null)
{
return false;
}
try
{
var builder = new SqliteConnectionStringBuilder(connectionString);
return !string.IsNullOrEmpty(builder.DataSource);
}
catch (ArgumentException)
{
return false;
}
}
/// <inheritdoc />
public string GenerateConnectionString(DatabaseModel databaseModel)
{

View File

@@ -164,7 +164,7 @@ namespace Umbraco.Cms.Infrastructure.Install
private bool IsBrandNewInstall =>
_connectionStrings.CurrentValue.IsConnectionStringConfigured() == false ||
_databaseBuilder.IsDatabaseConfigured == false ||
(_databaseBuilder.CanConnectToDatabase == false && _databaseProviderMetadata.CanForceCreateDatabase(_umbracoDatabaseFactory.SqlContext.SqlSyntax.DbProvider)) ||
(_databaseBuilder.CanConnectToDatabase == false && _databaseProviderMetadata.CanForceCreateDatabase(_umbracoDatabaseFactory)) ||
_databaseBuilder.IsUmbracoInstalled() == false;
}
}

View File

@@ -1,4 +1,5 @@
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
namespace Umbraco.Cms.Infrastructure.Persistence;
@@ -26,8 +27,32 @@ public static class DatabaseProviderMetadataExtensions
/// <returns>
/// <c>true</c> if a database can be created for the specified provider name; otherwise, <c>false</c>.
/// </returns>
[Obsolete("Use CanForceCreateDatabase that takes an IUmbracoDatabaseFactory. Scheduled for removal in Umbraco 13.")]
public static bool CanForceCreateDatabase(this IEnumerable<IDatabaseProviderMetadata> databaseProviderMetadata, string? providerName)
=> databaseProviderMetadata.FirstOrDefault(x => string.Equals(x.ProviderName, providerName, StringComparison.InvariantCultureIgnoreCase))?.ForceCreateDatabase == true;
{
return databaseProviderMetadata
.FirstOrDefault(x =>
string.Equals(x.ProviderName, providerName, StringComparison.InvariantCultureIgnoreCase))
?.ForceCreateDatabase == true;
}
/// <summary>
/// Determines whether a database can be created for the specified provider name while ignoring the value of <see cref="GlobalSettings.InstallMissingDatabase" />.
/// </summary>
/// <param name="databaseProviderMetadata">The database provider metadata.</param>
/// <param name="umbracoDatabaseFactory">The database factory.</param>
/// <returns>
/// <c>true</c> if a database can be created for the specified database; otherwise, <c>false</c>.
/// </returns>
public static bool CanForceCreateDatabase(this IEnumerable<IDatabaseProviderMetadata> databaseProviderMetadata, IUmbracoDatabaseFactory umbracoDatabaseFactory)
{
// In case more metadata providers can recognize the connection string, we need to check if any can force create.
// E.g. Both SqlServer and SqlAzure will recognize an azure connection string, but luckily none of those can force create.
return databaseProviderMetadata
.Where(x =>
string.Equals(x.ProviderName, umbracoDatabaseFactory.SqlContext.SqlSyntax.ProviderName, StringComparison.InvariantCultureIgnoreCase)
&& x.CanRecognizeConnectionString(umbracoDatabaseFactory.ConnectionString) && x.IsAvailable).Any(x => x.ForceCreateDatabase == true);
}
/// <summary>
/// Generates the connection string.

View File

@@ -82,6 +82,12 @@ public interface IDatabaseProviderMetadata
/// </summary>
public bool ForceCreateDatabase { get; }
/// <summary>
/// Gets a value indicating whether this connections could have been build using <see cref="GenerateConnectionString"/>.
/// </summary>
public bool CanRecognizeConnectionString(string? connectionString) => false;
/// <summary>
/// Creates a connection string for this provider.
/// </summary>

View File

@@ -29,6 +29,7 @@ public sealed class RichTextEditorPastedImages
private readonly IPublishedUrlProvider _publishedUrlProvider;
private readonly IShortStringHelper _shortStringHelper;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly string _tempFolderAbsolutePath;
public RichTextEditorPastedImages(
IUmbracoContextAccessor umbracoContextAccessor,
@@ -52,6 +53,9 @@ public sealed class RichTextEditorPastedImages
_mediaUrlGenerators = mediaUrlGenerators;
_shortStringHelper = shortStringHelper;
_publishedUrlProvider = publishedUrlProvider;
_tempFolderAbsolutePath = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempImageUploads);
}
/// <summary>
@@ -85,12 +89,14 @@ public sealed class RichTextEditorPastedImages
continue;
}
if (IsValidPath(tmpImgPath) == false)
var absoluteTempImagePath = Path.GetFullPath(_hostingEnvironment.MapPathContentRoot(tmpImgPath));
if (IsValidPath(absoluteTempImagePath) == false)
{
continue;
}
var absoluteTempImagePath = _hostingEnvironment.MapPathContentRoot(tmpImgPath);
var fileName = Path.GetFileName(absoluteTempImagePath);
var safeFileName = fileName.ToSafeFileName(_shortStringHelper);
@@ -191,5 +197,8 @@ public sealed class RichTextEditorPastedImages
return htmlDoc.DocumentNode.OuterHtml;
}
private bool IsValidPath(string imagePath) => imagePath.StartsWith(Constants.SystemDirectories.TempImageUploads);
private bool IsValidPath(string imagePath)
{
return imagePath.StartsWith(_tempFolderAbsolutePath);
}
}

View File

@@ -210,7 +210,7 @@ public class RuntimeState : IRuntimeState
// cannot connect to configured database, this is bad, fail
_logger.LogDebug("Could not connect to database.");
if (_globalSettings.Value.InstallMissingDatabase || _databaseProviderMetadata.CanForceCreateDatabase(_databaseFactory.ProviderName))
if (_globalSettings.Value.InstallMissingDatabase || _databaseProviderMetadata.CanForceCreateDatabase(_databaseFactory))
{
// ok to install on a configured but missing database
Level = RuntimeLevel.Install;

View File

@@ -0,0 +1,37 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Persistence.SqlServer.Services;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Persistence.SqlServer;
[TestFixture]
public class SqlAzureDatabaseProviderMetadataTests
{
[Test]
[TestCase("myServer", "myDatabase", "myLogin", "myPassword", true /*ignored*/, ExpectedResult = "Server=tcp:myServer.database.windows.net,1433;Database=myDatabase;User ID=myLogin@myServer;Password=myPassword")]
[TestCase("myServer", "myDatabase", "myLogin", "myPassword", false, ExpectedResult = "Server=tcp:myServer.database.windows.net,1433;Database=myDatabase;User ID=myLogin@myServer;Password=myPassword")]
public string GenerateConnectionString(string server, string databaseName, string login, string password, bool integratedAuth)
{
var sut = new SqlAzureDatabaseProviderMetadata();
return sut.GenerateConnectionString(new DatabaseModel()
{
DatabaseName = databaseName,
Login = login,
Password = password,
Server = server,
IntegratedAuth = integratedAuth
});
}
[Test]
[TestCase("Server=myServer;Database=myDatabase;Integrated Security=true", ExpectedResult = false)] // SqlServer
[TestCase("Server=myServer;Database=myDatabase;User Id=myLogin;Password=myPassword", ExpectedResult = false)] // SqlServer
[TestCase("Server=tcp:cmstest27032000.database.windows.net,1433;Database=test_27032000;User ID=asdasdas@cmstest27032000;Password=123456879", ExpectedResult = true)] // Azure
[TestCase("Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True", ExpectedResult = false)] // Sqlite
[TestCase("Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=aspnet-MvcMovie;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\\umbraco.mdf", ExpectedResult = false)] // localDB
public bool CanRecognizeConnectionString(string connectionString)
{
var sut = new SqlAzureDatabaseProviderMetadata();
return sut.CanRecognizeConnectionString(connectionString);
}
}

View File

@@ -0,0 +1,37 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Persistence.SqlServer.Services;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Persistence.SqlServer;
[TestFixture]
public class SqlLocalDbDatabaseProviderMetadataTests
{
[Test]
[TestCase("ignored", "myDatabase", "ignored", "ignored", true, ExpectedResult = "Data Source=(localdb)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\myDatabase.mdf;Integrated Security=True")]
[TestCase("ignored", "myDatabase2", "ignored", "ignored", false /*ignored*/, ExpectedResult = "Data Source=(localdb)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\myDatabase2.mdf;Integrated Security=True")]
public string GenerateConnectionString(string server, string databaseName, string login, string password, bool integratedAuth)
{
var sut = new SqlLocalDbDatabaseProviderMetadata();
return sut.GenerateConnectionString(new DatabaseModel()
{
DatabaseName = databaseName,
Login = login,
Password = password,
Server = server,
IntegratedAuth = integratedAuth,
});
}
[Test]
[TestCase("Server=myServer;Database=myDatabase;Integrated Security=true", ExpectedResult = false)] // SqlServer
[TestCase("Server=myServer;Database=myDatabase;User Id=myLogin;Password=myPassword", ExpectedResult = false)] // SqlServer
[TestCase("Server=tcp:cmstest27032000.database.windows.net,1433;Database=test_27032000;User ID=asdasdas@cmstest27032000;Password=123456879", ExpectedResult = false)] // Azure
[TestCase("Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True", ExpectedResult = false)] // Sqlite
[TestCase("Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=aspnet-MvcMovie;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\\umbraco.mdf", ExpectedResult = true)] // localDB
public bool CanRecognizeConnectionString(string connectionString)
{
var sut = new SqlLocalDbDatabaseProviderMetadata();
return sut.CanRecognizeConnectionString(connectionString);
}
}

View File

@@ -0,0 +1,37 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Persistence.SqlServer.Services;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Persistence.SqlServer;
[TestFixture]
public class SqlServerDatabaseProviderMetadataTests
{
[Test]
[TestCase("myServer", "myDatabase", "myLogin", "myPassword", true, ExpectedResult = "Server=myServer;Database=myDatabase;Integrated Security=true")]
[TestCase("myServer", "myDatabase", "myLogin", "myPassword", false, ExpectedResult = "Server=myServer;Database=myDatabase;User Id=myLogin;Password=myPassword")]
public string GenerateConnectionString(string server, string databaseName, string login, string password, bool integratedAuth)
{
var sut = new SqlServerDatabaseProviderMetadata();
return sut.GenerateConnectionString(new DatabaseModel()
{
DatabaseName = databaseName,
Login = login,
Password = password,
Server = server,
IntegratedAuth = integratedAuth
});
}
[Test]
[TestCase("Server=myServer;Database=myDatabase;Integrated Security=true", ExpectedResult = true)] // SqlServer
[TestCase("Server=myServer;Database=myDatabase;User Id=myLogin;Password=myPassword", ExpectedResult = true)] // SqlServer
[TestCase("Server=tcp:cmstest27032000.database.windows.net,1433;Database=test_27032000;User ID=asdasdas@cmstest27032000;Password=123456879", ExpectedResult = true)] // Azure
[TestCase("Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True", ExpectedResult = false)] // Sqlite
[TestCase("Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=aspnet-MvcMovie;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\\umbraco.mdf", ExpectedResult = false)] // localDB
public bool CanRecognizeConnectionString(string connectionString)
{
var sut = new SqlServerDatabaseProviderMetadata();
return sut.CanRecognizeConnectionString(connectionString);
}
}

View File

@@ -0,0 +1,37 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Persistence.Sqlite.Services;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Persistence.Sqlite;
[TestFixture]
public class SqliteDatabaseProviderMetadataTests
{
[Test]
[TestCase("ignored", "myDatabase", "ignored", "ignored", true /*ignored*/, ExpectedResult = "Data Source=|DataDirectory|/myDatabase.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True")]
[TestCase("ignored", "myDatabase2", "ignored", "ignored", false /*ignored*/, ExpectedResult = "Data Source=|DataDirectory|/myDatabase2.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True")]
public string GenerateConnectionString(string server, string databaseName, string login, string password, bool integratedAuth)
{
var sut = new SqliteDatabaseProviderMetadata();
return sut.GenerateConnectionString(new DatabaseModel()
{
DatabaseName = databaseName,
Login = login,
Password = password,
Server = server,
IntegratedAuth = integratedAuth
});
}
[Test]
[TestCase("Server=myServer;Database=myDatabase;Integrated Security=true", ExpectedResult = false)] // SqlServer
[TestCase("Server=myServer;Database=myDatabase;User Id=myLogin;Password=myPassword", ExpectedResult = false)] // SqlServer
[TestCase("Server=tcp:cmstest27032000.database.windows.net,1433;Database=test_27032000;User ID=asdasdas@cmstest27032000;Password=123456879", ExpectedResult = false)] // Azure
[TestCase("Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True", ExpectedResult = true)] // Sqlite
[TestCase("Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=aspnet-MvcMovie;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\\umbraco.mdf", ExpectedResult = false)] // localDB
public bool CanRecognizeConnectionString(string connectionString)
{
var sut = new SqliteDatabaseProviderMetadata();
return sut.CanRecognizeConnectionString(connectionString);
}
}

View File

@@ -177,6 +177,8 @@ public class DatabaseSettingsFactoryTests
public Func<DatabaseModel, string> GenerateConnectionStringDelegate { get; set; } =
_ => "ConnectionString";
public bool CanRecognizeConnectionString(string? connectionString) => false;
public string? GenerateConnectionString(DatabaseModel databaseModel) => GenerateConnectionStringDelegate(databaseModel);
}
}

View File

@@ -16,6 +16,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Umbraco.Cms.Persistence.Sqlite\Umbraco.Cms.Persistence.Sqlite.csproj" />
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj" />
<ProjectReference Include="..\..\src\Umbraco.Cms.ManagementApi\Umbraco.Cms.ManagementApi.csproj" />
<ProjectReference Include="..\..\src\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj" />

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "10.5.0-rc",
"version": "10.5.0",
"assemblyVersion": {
"precision": "build"
},