diff --git a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs
index 2666d81310..e0e5c1ab6a 100644
--- a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs
+++ b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs
@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Core.Semver;
using Umbraco.Cms.Core.Services;
-
+using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Install.InstallSteps
{
///
@@ -39,7 +39,7 @@ namespace Umbraco.Cms.Core.Install.InstallSteps
var currentState = FormatGuidState(_runtimeState.CurrentMigrationState);
var newState = FormatGuidState(_runtimeState.FinalMigrationState);
- var newVersion = _umbracoVersion.SemanticVersion.ToString();
+ var newVersion = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild();
var oldVersion = new SemVersion(_umbracoVersion.SemanticVersion.Major, 0, 0).ToString(); //TODO can we find the old version somehow? e.g. from current state
var reportUrl = $"https://our.umbraco.com/contribute/releases/compare?from={oldVersion}&to={newVersion}¬es=1";
diff --git a/src/Umbraco.Core/Notifications/SendEmailNotification.cs b/src/Umbraco.Core/Notifications/SendEmailNotification.cs
index 3c9caabb0e..4ca6dc80c0 100644
--- a/src/Umbraco.Core/Notifications/SendEmailNotification.cs
+++ b/src/Umbraco.Core/Notifications/SendEmailNotification.cs
@@ -7,5 +7,15 @@ namespace Umbraco.Cms.Core.Notifications
public SendEmailNotification(NotificationEmailModel message) => Message = message;
public NotificationEmailModel Message { get; }
+
+ ///
+ /// Call to tell Umbraco that the email sending is handled.
+ ///
+ public void HandleEmail() => IsHandled = true;
+
+ ///
+ /// Returns true if the email sending is handled.
+ ///
+ public bool IsHandled { get; private set; }
}
}
diff --git a/src/Umbraco.Core/Packaging/PackageMigrationResource.cs b/src/Umbraco.Core/Packaging/PackageMigrationResource.cs
index 2f407df88f..b9c0e99552 100644
--- a/src/Umbraco.Core/Packaging/PackageMigrationResource.cs
+++ b/src/Umbraco.Core/Packaging/PackageMigrationResource.cs
@@ -12,17 +12,52 @@ namespace Umbraco.Cms.Core.Packaging
{
public static class PackageMigrationResource
{
- private static Stream GetEmbeddedPackageStream(Type planType)
+ private static Stream GetEmbeddedPackageZipStream(Type planType)
{
// lookup the embedded resource by convention
Assembly currentAssembly = planType.Assembly;
var fileName = $"{planType.Namespace}.package.zip";
Stream stream = currentAssembly.GetManifestResourceStream(fileName);
+
+ return stream;
+ }
+
+ public static XDocument GetEmbeddedPackageDataManifest(Type planType, out ZipArchive zipArchive)
+ {
+ XDocument packageXml;
+ var zipStream = GetEmbeddedPackageZipStream(planType);
+ if (zipStream is not null)
+ {
+ zipArchive = GetPackageDataManifest(zipStream, out packageXml);
+ return packageXml;
+ }
+
+ zipArchive = null;
+ packageXml = GetEmbeddedPackageXmlDoc(planType);
+ return packageXml;
+ }
+
+ public static XDocument GetEmbeddedPackageDataManifest(Type planType)
+ {
+ return GetEmbeddedPackageDataManifest(planType, out _);
+ }
+
+ private static XDocument GetEmbeddedPackageXmlDoc(Type planType)
+ {
+ // lookup the embedded resource by convention
+ Assembly currentAssembly = planType.Assembly;
+ var fileName = $"{planType.Namespace}.package.xml";
+ Stream stream = currentAssembly.GetManifestResourceStream(fileName);
if (stream == null)
{
- throw new FileNotFoundException("Cannot find the embedded file.", fileName);
+ return null;
}
- return stream;
+ XDocument xml;
+ using (stream)
+ {
+ xml = XDocument.Load(stream);
+ }
+ return xml;
}
public static string GetEmbeddedPackageDataManifestHash(Type planType)
@@ -30,17 +65,46 @@ namespace Umbraco.Cms.Core.Packaging
// SEE: HashFromStreams in the benchmarks project for how fast this is. It will run
// on every startup for every embedded package.zip. The bigger the zip, the more time it takes.
// But it is still very fast ~303ms for a 100MB file. This will only be an issue if there are
- // several very large package.zips.
+ // several very large package.zips.
- using Stream stream = GetEmbeddedPackageStream(planType);
- return stream.GetStreamHash();
+ using Stream stream = GetEmbeddedPackageZipStream(planType);
+
+ if (stream is not null)
+ {
+ return stream.GetStreamHash();
+ }
+
+ var xml = GetEmbeddedPackageXmlDoc(planType);
+
+ if (xml is not null)
+ {
+ return xml.ToString();
+ }
+
+ throw new IOException("Missing embedded files for planType: " + planType);
}
- public static ZipArchive GetEmbeddedPackageDataManifest(Type planType, out XDocument packageXml)
- => GetPackageDataManifest(GetEmbeddedPackageStream(planType), out packageXml);
+ public static bool TryGetEmbeddedPackageDataManifest(Type planType, out XDocument packageXml, out ZipArchive zipArchive)
+ {
+ var zipStream = GetEmbeddedPackageZipStream(planType);
+ if (zipStream is not null)
+ {
+ zipArchive = GetPackageDataManifest(zipStream, out packageXml);
+ return true;
+ }
+
+ zipArchive = null;
+ packageXml = GetEmbeddedPackageXmlDoc(planType);
+ return packageXml is not null;
+ }
public static ZipArchive GetPackageDataManifest(Stream packageZipStream, out XDocument packageXml)
{
+ if (packageZipStream == null)
+ {
+ throw new ArgumentNullException(nameof(packageZipStream));
+ }
+
var zip = new ZipArchive(packageZipStream, ZipArchiveMode.Read);
ZipArchiveEntry packageXmlEntry = zip.GetEntry("package.xml");
if (packageXmlEntry == null)
diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs
index ffc67663cc..a24890d5f2 100644
--- a/src/Umbraco.Core/Packaging/PackagesRepository.cs
+++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs
@@ -209,29 +209,46 @@ namespace Umbraco.Cms.Core.Packaging
PackageDataTypes(definition, root);
Dictionary mediaFiles = PackageMedia(definition, root);
- var tempPackagePath = temporaryPath + "/package.zip";
-
- using (FileStream fileStream = File.OpenWrite(tempPackagePath))
- using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true))
+ string fileName;
+ string tempPackagePath;
+ if (mediaFiles.Count > 0)
{
- ZipArchiveEntry packageXmlEntry = archive.CreateEntry("package.xml");
- using (Stream entryStream = packageXmlEntry.Open())
+ fileName = "package.zip";
+ tempPackagePath = Path.Combine(temporaryPath, fileName);
+ using (FileStream fileStream = File.OpenWrite(tempPackagePath))
+ using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true))
{
- compiledPackageXml.Save(entryStream);
- }
-
- foreach (KeyValuePair mediaFile in mediaFiles)
- {
- var entryPath = $"media{mediaFile.Key.EnsureStartsWith('/')}";
- ZipArchiveEntry mediaEntry = archive.CreateEntry(entryPath);
- using (Stream entryStream = mediaEntry.Open())
- using (mediaFile.Value)
+ ZipArchiveEntry packageXmlEntry = archive.CreateEntry("package.xml");
+ using (Stream entryStream = packageXmlEntry.Open())
{
- mediaFile.Value.Seek(0, SeekOrigin.Begin);
- mediaFile.Value.CopyTo(entryStream);
+ compiledPackageXml.Save(entryStream);
+ }
+
+ foreach (KeyValuePair mediaFile in mediaFiles)
+ {
+ var entryPath = $"media{mediaFile.Key.EnsureStartsWith('/')}";
+ ZipArchiveEntry mediaEntry = archive.CreateEntry(entryPath);
+ using (Stream entryStream = mediaEntry.Open())
+ using (mediaFile.Value)
+ {
+ mediaFile.Value.Seek(0, SeekOrigin.Begin);
+ mediaFile.Value.CopyTo(entryStream);
+ }
}
}
}
+ else
+ {
+ fileName = "package.xml";
+ tempPackagePath = Path.Combine(temporaryPath, fileName);
+
+ using (FileStream fileStream = File.OpenWrite(tempPackagePath))
+ {
+ compiledPackageXml.Save(fileStream);
+ }
+ }
+
+
var directoryName =
_hostingEnvironment.MapPathWebRoot(Path.Combine(_mediaFolderPath, definition.Name.Replace(' ', '_')));
@@ -241,7 +258,7 @@ namespace Umbraco.Cms.Core.Packaging
Directory.CreateDirectory(directoryName);
}
- var finalPackagePath = Path.Combine(directoryName, "package.zip");
+ var finalPackagePath = Path.Combine(directoryName, fileName);
if (File.Exists(finalPackagePath))
{
@@ -347,7 +364,7 @@ namespace Umbraco.Cms.Core.Packaging
}
else if (items.ContainsKey(dictionaryItem.ParentId.Value))
{
- // we know the parent exists in the dictionary but
+ // we know the parent exists in the dictionary but
// we haven't processed it yet so we'll leave it for the next loop
continue;
}
diff --git a/src/Umbraco.Infrastructure/EmailSender.cs b/src/Umbraco.Infrastructure/EmailSender.cs
index 72daad7de1..20c48a3b04 100644
--- a/src/Umbraco.Infrastructure/EmailSender.cs
+++ b/src/Umbraco.Infrastructure/EmailSender.cs
@@ -3,6 +3,7 @@
using System.Net.Mail;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
@@ -23,12 +24,14 @@ namespace Umbraco.Cms.Infrastructure
private readonly IEventAggregator _eventAggregator;
private readonly GlobalSettings _globalSettings;
private readonly bool _notificationHandlerRegistered;
+ private readonly ILogger _logger;
- public EmailSender(IOptions globalSettings, IEventAggregator eventAggregator)
+ public EmailSender(
+ ILogger logger,
+ IOptions globalSettings,
+ IEventAggregator eventAggregator)
: this(globalSettings, eventAggregator, null)
- {
-
- }
+ => _logger = logger;
public EmailSender(IOptions globalSettings, IEventAggregator eventAggregator, INotificationHandler handler)
{
@@ -49,39 +52,47 @@ namespace Umbraco.Cms.Infrastructure
private async Task SendAsyncInternal(EmailMessage message, bool enableNotification)
{
+ if (enableNotification)
+ {
+ var notification = new SendEmailNotification(message.ToNotificationEmail(_globalSettings.Smtp?.From));
+ await _eventAggregator.PublishAsync(notification);
+
+ // if a handler handled sending the email then don't continue.
+ if (notification.IsHandled)
+ {
+ _logger.LogDebug("The email sending for {Subject} was handled by a notification handler", notification.Message.Subject);
+ return;
+ }
+ }
+
if (_globalSettings.IsSmtpServerConfigured == false)
{
- if (enableNotification)
- {
- await _eventAggregator.PublishAsync(
- new SendEmailNotification(message.ToNotificationEmail(_globalSettings.Smtp?.From)));
- }
+ _logger.LogDebug("Could not send email for {Subject}. It was not handled by a notification handler and there is no SMTP configured.", message.Subject);
return;
}
- using (var client = new SmtpClient())
+ using var client = new SmtpClient();
+
+ await client.ConnectAsync(_globalSettings.Smtp.Host,
+ _globalSettings.Smtp.Port,
+ (MailKit.Security.SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions);
+
+ if (!(_globalSettings.Smtp.Username is null && _globalSettings.Smtp.Password is null))
{
- await client.ConnectAsync(_globalSettings.Smtp.Host,
- _globalSettings.Smtp.Port,
- (MailKit.Security.SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions);
-
- if (!(_globalSettings.Smtp.Username is null && _globalSettings.Smtp.Password is null))
- {
- await client.AuthenticateAsync(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password);
- }
-
- var mailMessage = message.ToMimeMessage(_globalSettings.Smtp.From);
- if (_globalSettings.Smtp.DeliveryMethod == SmtpDeliveryMethod.Network)
- {
- await client.SendAsync(mailMessage);
- }
- else
- {
- client.Send(mailMessage);
- }
-
- await client.DisconnectAsync(true);
+ await client.AuthenticateAsync(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password);
}
+
+ var mailMessage = message.ToMimeMessage(_globalSettings.Smtp.From);
+ if (_globalSettings.Smtp.DeliveryMethod == SmtpDeliveryMethod.Network)
+ {
+ await client.SendAsync(mailMessage);
+ }
+ else
+ {
+ client.Send(mailMessage);
+ }
+
+ await client.DisconnectAsync(true);
}
///
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs
index 3cc19b364f..2e1a2bcd4d 100644
--- a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs
@@ -1,5 +1,4 @@
-using System;
-using System.IO;
+using System;
using Microsoft.Extensions.Configuration;
using Serilog;
using Serilog.Events;
@@ -15,17 +14,6 @@ namespace Umbraco.Cms.Core.Logging.Serilog
{
public global::Serilog.ILogger SerilogLog { get; }
- ///
- /// Initialize a new instance of the class with a configuration file.
- ///
- ///
- public SerilogLogger(FileInfo logConfigFile)
- {
- SerilogLog = new LoggerConfiguration()
- .ReadFrom.AppSettings(filePath: logConfigFile.FullName)
- .CreateLogger();
- }
-
public SerilogLogger(LoggerConfiguration logConfig)
{
//Configure Serilog static global logger with config passed in
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
index f8d480bc8c..4eb9c5ae38 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
@@ -212,7 +212,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
.To("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}")
.To("{4695D0C9-0729-4976-985B-048D503665D8}")
.To("{5C424554-A32D-4852-8ED1-A13508187901}")
- // to 9.0.0
+ // to 9.0.0 RC
.With()
.To("{22D801BA-A1FF-4539-BFCC-2139B55594F8}")
.To("{50A43237-A6F4-49E2-A7A6-5DAD65C84669}")
@@ -223,12 +223,16 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
//FINAL
.As("{5060F3D2-88BE-4D30-8755-CF51F28EAD12}");
+ // TO 9.0.0
+
// This should be safe to execute again. We need it with a new name to ensure updates from all the following has executed this step.
// - 8.15 RC - Current state: {4695D0C9-0729-4976-985B-048D503665D8}
// - 8.15 Final - Current state: {5C424554-A32D-4852-8ED1-A13508187901}
// - 9.0 RC1 - Current state: {5060F3D2-88BE-4D30-8755-CF51F28EAD12}
To("{622E5172-42E1-4662-AD80-9504AF5A4E53}");
+
+ To("{10F7BB61-C550-426B-830B-7F954F689CDF}");
}
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexes.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexes.cs
index ef29207093..f350ed633c 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexes.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexes.cs
@@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.Linq;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
{
+
public class ExternalLoginTableIndexes : MigrationBase
{
public ExternalLoginTableIndexes(IMigrationContext context)
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexesFixup.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexesFixup.cs
new file mode 100644
index 0000000000..5efb914eb7
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/ExternalLoginTableIndexesFixup.cs
@@ -0,0 +1,59 @@
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0
+{
+ ///
+ /// Fixes up the original for post RC release to ensure that
+ /// the correct indexes are applied.
+ ///
+ public class ExternalLoginTableIndexesFixup : MigrationBase
+ {
+ public ExternalLoginTableIndexesFixup(IMigrationContext context) : base(context)
+ {
+ }
+
+ protected override void Migrate()
+ {
+ var indexName1 = "IX_" + ExternalLoginDto.TableName + "_LoginProvider";
+ var indexName2 = "IX_" + ExternalLoginDto.TableName + "_ProviderKey";
+
+ if (IndexExists(indexName1))
+ {
+ // drop it since the previous migration index was wrong, and we
+ // need to modify a column that belons to it
+ Delete.Index(indexName1).OnTable(ExternalLoginDto.TableName).Do();
+ }
+
+ if (IndexExists(indexName2))
+ {
+ // drop since it's using a column we're about to modify
+ Delete.Index(indexName2).OnTable(ExternalLoginDto.TableName).Do();
+ }
+
+ // then fixup the length of the loginProvider column
+ AlterColumn(ExternalLoginDto.TableName, "loginProvider");
+
+ // create it with the correct definition
+ Create
+ .Index(indexName1)
+ .OnTable(ExternalLoginDto.TableName)
+ .OnColumn("loginProvider").Ascending()
+ .OnColumn("userId").Ascending()
+ .WithOptions()
+ .Unique()
+ .WithOptions()
+ .NonClustered()
+ .Do();
+
+ // re-create the original
+ Create
+ .Index(indexName2)
+ .OnTable(ExternalLoginDto.TableName)
+ .OnColumn("loginProvider").Ascending()
+ .OnColumn("providerKey").Ascending()
+ .WithOptions()
+ .NonClustered()
+ .Do();
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilderExpression.cs b/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilderExpression.cs
index b16326ea56..8eda0f0b45 100644
--- a/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilderExpression.cs
+++ b/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilderExpression.cs
@@ -19,12 +19,12 @@ namespace Umbraco.Cms.Infrastructure.Packaging
{
internal class ImportPackageBuilderExpression : MigrationExpressionBase
{
- private readonly IPackagingService _packagingService;
- private readonly IMediaService _mediaService;
- private readonly MediaFileManager _mediaFileManager;
- private readonly MediaUrlGeneratorCollection _mediaUrlGenerators;
- private readonly IShortStringHelper _shortStringHelper;
private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
+ private readonly MediaFileManager _mediaFileManager;
+ private readonly IMediaService _mediaService;
+ private readonly MediaUrlGeneratorCollection _mediaUrlGenerators;
+ private readonly IPackagingService _packagingService;
+ private readonly IShortStringHelper _shortStringHelper;
private bool _executed;
public ImportPackageBuilderExpression(
@@ -45,7 +45,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
}
///
- /// The type of the migration which dictates the namespace of the embedded resource
+ /// The type of the migration which dictates the namespace of the embedded resource
///
public Type EmbeddedResourceMigrationType { get; set; }
@@ -63,68 +63,77 @@ namespace Umbraco.Cms.Infrastructure.Packaging
if (EmbeddedResourceMigrationType == null && PackageDataManifest == null)
{
- throw new InvalidOperationException($"Nothing to execute, neither {nameof(EmbeddedResourceMigrationType)} or {nameof(PackageDataManifest)} has been set.");
+ throw new InvalidOperationException(
+ $"Nothing to execute, neither {nameof(EmbeddedResourceMigrationType)} or {nameof(PackageDataManifest)} has been set.");
}
InstallationSummary installationSummary;
if (EmbeddedResourceMigrationType != null)
{
- // get the embedded resource
- using (ZipArchive zipPackage = PackageMigrationResource.GetEmbeddedPackageDataManifest(
+ if (PackageMigrationResource.TryGetEmbeddedPackageDataManifest(
EmbeddedResourceMigrationType,
- out XDocument xml))
+ out XDocument xml, out ZipArchive zipPackage))
{
// first install the package
installationSummary = _packagingService.InstallCompiledPackageData(xml);
- // then we need to save each file to the saved media items
- var mediaWithFiles = xml.XPathSelectElements(
- "./umbPackage/MediaItems/MediaSet//*[@id][@mediaFilePath]")
- .ToDictionary(
- x => x.AttributeValue("key"),
- x => x.AttributeValue("mediaFilePath"));
-
- // Any existing media by GUID will not be installed by the package service, it will just be skipped
- // so you cannot 'update' media (or content) using a package since those are not schema type items.
- // This means you cannot 'update' the media file either. The installationSummary.MediaInstalled
- // will be empty for any existing media which means that the files will also not be updated.
- foreach (IMedia media in installationSummary.MediaInstalled)
+ if (zipPackage is not null)
{
- if (mediaWithFiles.TryGetValue(media.Key, out var mediaFilePath))
+ // get the embedded resource
+ using (zipPackage)
{
- // this is a media item that has a file, so find that file in the zip
- var entryPath = $"media{mediaFilePath.EnsureStartsWith('/')}";
- ZipArchiveEntry mediaEntry = zipPackage.GetEntry(entryPath);
- if (mediaEntry == null)
- {
- throw new InvalidOperationException("No media file found in package zip for path " + entryPath);
- }
+ // then we need to save each file to the saved media items
+ var mediaWithFiles = xml.XPathSelectElements(
+ "./umbPackage/MediaItems/MediaSet//*[@id][@mediaFilePath]")
+ .ToDictionary(
+ x => x.AttributeValue("key"),
+ x => x.AttributeValue("mediaFilePath"));
- // read the media file and save it to the media item
- // using the current file system provider.
- using (Stream mediaStream = mediaEntry.Open())
+ // Any existing media by GUID will not be installed by the package service, it will just be skipped
+ // so you cannot 'update' media (or content) using a package since those are not schema type items.
+ // This means you cannot 'update' the media file either. The installationSummary.MediaInstalled
+ // will be empty for any existing media which means that the files will also not be updated.
+ foreach (IMedia media in installationSummary.MediaInstalled)
{
- media.SetValue(
- _mediaFileManager,
- _mediaUrlGenerators,
- _shortStringHelper,
- _contentTypeBaseServiceProvider,
- Constants.Conventions.Media.File,
- Path.GetFileName(mediaFilePath),
- mediaStream);
- }
+ if (mediaWithFiles.TryGetValue(media.Key, out var mediaFilePath))
+ {
+ // this is a media item that has a file, so find that file in the zip
+ var entryPath = $"media{mediaFilePath.EnsureStartsWith('/')}";
+ ZipArchiveEntry mediaEntry = zipPackage.GetEntry(entryPath);
+ if (mediaEntry == null)
+ {
+ throw new InvalidOperationException(
+ "No media file found in package zip for path " +
+ entryPath);
+ }
- _mediaService.Save(media);
+ // read the media file and save it to the media item
+ // using the current file system provider.
+ using (Stream mediaStream = mediaEntry.Open())
+ {
+ media.SetValue(
+ _mediaFileManager,
+ _mediaUrlGenerators,
+ _shortStringHelper,
+ _contentTypeBaseServiceProvider,
+ Constants.Conventions.Media.File,
+ Path.GetFileName(mediaFilePath),
+ mediaStream);
+ }
+
+ _mediaService.Save(media);
+ }
+ }
}
}
}
- }
- else
- {
- installationSummary = _packagingService.InstallCompiledPackageData(PackageDataManifest);
- }
+ else
+ {
+ installationSummary = _packagingService.InstallCompiledPackageData(PackageDataManifest);
+ }
- Logger.LogInformation($"Package migration executed. Summary: {installationSummary}");
+ Logger.LogInformation($"Package migration executed. Summary: {installationSummary}");
+ }
}
}
}
diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
index fcf4ada7c0..2511aab600 100644
--- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
+++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
@@ -222,14 +222,12 @@ namespace Umbraco.Cms.Infrastructure.Packaging
importedContentTypes.Add(contentTypeAlias, contentType);
}
- TContentBase content = CreateContentFromXml(root, importedContentTypes[contentTypeAlias], default, parentId, service);
- if (content == null)
+ if (TryCreateContentFromXml(root, importedContentTypes[contentTypeAlias], default, parentId, service,
+ out TContentBase content))
{
- continue;
+ contents.Add(content);
}
- contents.Add(content);
-
var children = root.Elements().Where(doc => (string)doc.Attribute("isDoc") == string.Empty)
.ToList();
@@ -262,8 +260,10 @@ namespace Umbraco.Cms.Infrastructure.Packaging
}
//Create and add the child to the list
- var content = CreateContentFromXml(child, importedContentTypes[contentTypeAlias], parent, default, service);
- list.Add(content);
+ if (TryCreateContentFromXml(child, importedContentTypes[contentTypeAlias], parent, default, service, out var content))
+ {
+ list.Add(content);
+ }
//Recursive call
var child1 = child;
@@ -278,21 +278,24 @@ namespace Umbraco.Cms.Infrastructure.Packaging
return list;
}
- private T CreateContentFromXml(
+ private bool TryCreateContentFromXml(
XElement element,
S contentType,
T parent,
int parentId,
- IContentServiceBase service)
+ IContentServiceBase service,
+ out T output)
where T : class, IContentBase
where S : IContentTypeComposition
{
Guid key = element.RequiredAttributeValue("key");
// we need to check if the content already exists and if so we ignore the installation for this item
- if (service.GetById(key) != null)
+ var value = service.GetById(key);
+ if (value != null)
{
- return null;
+ output = value;
+ return false;
}
var level = element.Attribute("level").Value;
@@ -383,7 +386,8 @@ namespace Umbraco.Cms.Infrastructure.Packaging
}
}
- return content;
+ output = content;
+ return true;
}
private T CreateContent(string name, T parent, int parentId, S contentType, Guid key, int level, int sortOrder, int? templateId)
@@ -498,7 +502,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
//Iterate the sorted document types and create them as IContentType objects
foreach (XElement documentType in documentTypes)
{
- var alias = documentType.Element("Info").Element("Alias").Value;
+ var alias = documentType.Element("Info").Element("Alias").Value;
if (importedContentTypes.ContainsKey(alias) == false)
{
@@ -1142,7 +1146,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
IDictionaryItem dictionaryItem;
var itemName = dictionaryItemElement.Attribute("Name").Value;
Guid key = dictionaryItemElement.RequiredAttributeValue("Key");
-
+
dictionaryItem = _localizationService.GetDictionaryItemById(key);
if (dictionaryItem != null)
{
@@ -1277,7 +1281,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
throw new InvalidOperationException("No path attribute found");
}
var contents = element.Value ?? string.Empty;
-
+
var physicalPath = _hostingEnvironment.MapPathContentRoot(path);
// TODO: Do we overwrite? IMO I don't think so since these will be views a user will change.
if (!System.IO.File.Exists(physicalPath))
@@ -1419,7 +1423,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
if (partialView == null)
{
var content = partialViewXml.Value ?? string.Empty;
-
+
partialView = new PartialView(PartialViewType.PartialView, path) { Content = content };
_fileService.SavePartialView(partialView, userId);
result.Add(partialView);
diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbType.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbType.cs
new file mode 100644
index 0000000000..00be5c51ab
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbType.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+
+namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations
+{
+ ///
+ /// Allows for specifying custom DB types that are not natively mapped.
+ ///
+ public struct SpecialDbType : IEquatable
+ {
+ private readonly string _dbType;
+
+ public SpecialDbType(string dbType)
+ {
+ if (string.IsNullOrWhiteSpace(dbType))
+ {
+ throw new ArgumentException($"'{nameof(dbType)}' cannot be null or whitespace.", nameof(dbType));
+ }
+
+ _dbType = dbType;
+ }
+
+ public SpecialDbType(SpecialDbTypes specialDbTypes)
+ => _dbType = specialDbTypes.ToString();
+
+ public static SpecialDbType NTEXT { get; } = new SpecialDbType(SpecialDbTypes.NTEXT);
+ public static SpecialDbType NCHAR { get; } = new SpecialDbType(SpecialDbTypes.NCHAR);
+ public static SpecialDbType NVARCHARMAX { get; } = new SpecialDbType(SpecialDbTypes.NVARCHARMAX);
+
+ public override bool Equals(object obj) => obj is SpecialDbType types && Equals(types);
+ public bool Equals(SpecialDbType other) => _dbType == other._dbType;
+ public override int GetHashCode() => 1038481724 + EqualityComparer.Default.GetHashCode(_dbType);
+
+ public override string ToString() => _dbType.ToString();
+
+ // Make this directly castable to string
+ public static implicit operator string(SpecialDbType dbType) => dbType.ToString();
+
+ // direct equality operators with SpecialDbTypes enum
+ public static bool operator ==(SpecialDbTypes x, SpecialDbType y) => x.ToString() == y;
+ public static bool operator !=(SpecialDbTypes x, SpecialDbType y) => x.ToString() != y;
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs
index 158a7ccb9b..d7fd2ff34f 100644
--- a/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs
+++ b/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations
{
@@ -12,13 +12,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations
public class SpecialDbTypeAttribute : Attribute
{
public SpecialDbTypeAttribute(SpecialDbTypes databaseType)
- {
- DatabaseType = databaseType;
- }
+ => DatabaseType = new SpecialDbType(databaseType);
+
+ public SpecialDbTypeAttribute(string databaseType)
+ => DatabaseType = new SpecialDbType(databaseType);
///
- /// Gets or sets the for this column
+ /// Gets or sets the for this column
///
- public SpecialDbTypes DatabaseType { get; private set; }
+ public SpecialDbType DatabaseType { get; private set; }
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbTypes.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbTypes.cs
index 9d07395743..d867d6f682 100644
--- a/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbTypes.cs
+++ b/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/SpecialDbTypes.cs
@@ -1,13 +1,12 @@
namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations
{
///
- /// Enum with the two special types that has to be supported because
- /// of the current umbraco db schema.
+ /// Known special DB types required for Umbraco.
///
public enum SpecialDbTypes
{
NTEXT,
NCHAR,
- NVARCHARMAX
+ NVARCHARMAX,
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs
index 2c22863ae5..dee560a40d 100644
--- a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs
+++ b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Data;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
@@ -12,9 +12,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions
//When DbType isn't set explicitly the Type will be used to find the right DbType in the SqlSyntaxProvider.
//This type is typically used as part of an initial table creation
public Type PropertyType { get; set; }
- //Only used for special cases as part of an initial table creation
- public bool HasSpecialDbType { get; set; }
- public SpecialDbTypes DbType { get; set; }
+
+ ///
+ /// Used for column types that cannot be natively mapped.
+ ///
+ public SpecialDbType? CustomDbType { get; set; }
+
public virtual int Seeding { get; set; }
public virtual int Size { get; set; }
public virtual int Precision { get; set; }
diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs
index 407672c995..34ad767b04 100644
--- a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using System.Reflection;
using NPoco;
@@ -75,8 +75,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions
var databaseTypeAttribute = propertyInfo.FirstAttribute();
if (databaseTypeAttribute != null)
{
- definition.HasSpecialDbType = true;
- definition.DbType = databaseTypeAttribute.DatabaseType;
+ definition.CustomDbType = databaseTypeAttribute.DatabaseType;
}
else
{
diff --git a/src/Umbraco.Infrastructure/Persistence/DbProviderFactoryCreator.cs b/src/Umbraco.Infrastructure/Persistence/DbProviderFactoryCreator.cs
index 797400b7cc..942368f5cb 100644
--- a/src/Umbraco.Infrastructure/Persistence/DbProviderFactoryCreator.cs
+++ b/src/Umbraco.Infrastructure/Persistence/DbProviderFactoryCreator.cs
@@ -24,15 +24,16 @@ namespace Umbraco.Cms.Infrastructure.Persistence
IEnumerable providerSpecificMapperFactories)
{
_getFactory = getFactory;
- _embeddedDatabaseCreators = embeddedDatabaseCreators.ToDictionary(x=>x.ProviderName);
- _syntaxProviders = syntaxProviders.ToDictionary(x=>x.ProviderName);
- _bulkSqlInsertProviders = bulkSqlInsertProviders.ToDictionary(x=>x.ProviderName);
- _providerSpecificMapperFactories = providerSpecificMapperFactories.ToDictionary(x=>x.ProviderName);
+ _embeddedDatabaseCreators = embeddedDatabaseCreators.ToDictionary(x => x.ProviderName);
+ _syntaxProviders = syntaxProviders.ToDictionary(x => x.ProviderName);
+ _bulkSqlInsertProviders = bulkSqlInsertProviders.ToDictionary(x => x.ProviderName);
+ _providerSpecificMapperFactories = providerSpecificMapperFactories.ToDictionary(x => x.ProviderName);
}
public DbProviderFactory CreateFactory(string providerName)
{
- if (string.IsNullOrEmpty(providerName)) return null;
+ if (string.IsNullOrEmpty(providerName))
+ return null;
return _getFactory(providerName);
}
@@ -40,7 +41,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public ISqlSyntaxProvider GetSqlSyntaxProvider(string providerName)
{
- if(!_syntaxProviders.TryGetValue(providerName, out var result))
+ if (!_syntaxProviders.TryGetValue(providerName, out var result))
{
throw new InvalidOperationException($"Unknown provider name \"{providerName}\"");
}
@@ -51,7 +52,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public IBulkSqlInsertProvider CreateBulkSqlInsertProvider(string providerName)
{
- if(!_bulkSqlInsertProviders.TryGetValue(providerName, out var result))
+ if (!_bulkSqlInsertProviders.TryGetValue(providerName, out var result))
{
return new BasicBulkSqlInsertProvider();
}
@@ -61,7 +62,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public void CreateDatabase(string providerName)
{
- if(_embeddedDatabaseCreators.TryGetValue(providerName, out var creator))
+ if (_embeddedDatabaseCreators.TryGetValue(providerName, out var creator))
{
creator.Create();
}
@@ -69,7 +70,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public NPocoMapperCollection ProviderSpecificMappers(string providerName)
{
- if(_providerSpecificMapperFactories.TryGetValue(providerName, out var mapperFactory))
+ if (_providerSpecificMapperFactories.TryGetValue(providerName, out var mapperFactory))
{
return mapperFactory.Mappers;
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs
index 5c56f642c8..69bf1b837e 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs
@@ -28,9 +28,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
/// Used to store the name of the provider (i.e. Facebook, Google)
///
[Column("loginProvider")]
- [Length(4000)] // TODO: This value seems WAY too high, this is just a name
+ [Length(400)]
[NullSetting(NullSetting = NullSettings.NotNull)]
- [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_LoginProvider")]
+ [Index(IndexTypes.UniqueNonClustered, ForColumns = "loginProvider,userId", Name = "IX_" + TableName + "_LoginProvider")]
public string LoginProvider { get; set; }
///
diff --git a/src/Umbraco.Infrastructure/Persistence/PocoDataDataReader.cs b/src/Umbraco.Infrastructure/Persistence/PocoDataDataReader.cs
index 38e6c23e70..c3875d3770 100644
--- a/src/Umbraco.Infrastructure/Persistence/PocoDataDataReader.cs
+++ b/src/Umbraco.Infrastructure/Persistence/PocoDataDataReader.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
@@ -6,6 +6,7 @@ using NPoco;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
+using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Persistence
{
@@ -69,22 +70,22 @@ namespace Umbraco.Cms.Infrastructure.Persistence
foreach (var col in _columnDefinitions)
{
SqlDbType sqlDbType;
- if (col.HasSpecialDbType)
+ if (col.CustomDbType.HasValue)
{
//get the SqlDbType from the 'special type'
- switch (col.DbType)
+ switch (col.CustomDbType)
{
- case SpecialDbTypes.NTEXT:
+ case var x when x == SpecialDbType.NTEXT:
sqlDbType = SqlDbType.NText;
break;
- case SpecialDbTypes.NCHAR:
+ case var x when x == SpecialDbType.NCHAR:
sqlDbType = SqlDbType.NChar;
break;
- case SpecialDbTypes.NVARCHARMAX:
+ case var x when x == SpecialDbType.NVARCHARMAX:
sqlDbType = SqlDbType.NVarChar;
break;
default:
- throw new ArgumentOutOfRangeException();
+ throw new ArgumentOutOfRangeException("The custom DB type " + col.CustomDbType + " is not supported for bulk import statements.");
}
}
else if (col.Type.HasValue)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
index 0dd6e2d43c..71bc5b8d33 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
@@ -432,11 +432,12 @@ ORDER BY colName";
{
var list = new List
{
- "DELETE FROM umbracoUser2UserGroup WHERE userId = @id",
- "DELETE FROM umbracoUser2NodeNotify WHERE userId = @id",
- "DELETE FROM umbracoUserStartNode WHERE userId = @id",
- "DELETE FROM umbracoUser WHERE id = @id",
- "DELETE FROM umbracoExternalLogin WHERE id = @id"
+ $"DELETE FROM {Constants.DatabaseSchema.Tables.UserLogin} WHERE userId = @id",
+ $"DELETE FROM {Constants.DatabaseSchema.Tables.User2UserGroup} WHERE userId = @id",
+ $"DELETE FROM {Constants.DatabaseSchema.Tables.User2NodeNotify} WHERE userId = @id",
+ $"DELETE FROM {Constants.DatabaseSchema.Tables.UserStartNode} WHERE userId = @id",
+ $"DELETE FROM {Constants.DatabaseSchema.Tables.User} WHERE id = @id",
+ $"DELETE FROM {Constants.DatabaseSchema.Tables.ExternalLogin} WHERE id = @id"
};
return list;
}
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/DbTypes.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/DbTypes.cs
index 004c4f11f4..18e4791d0b 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/DbTypes.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/DbTypes.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Data;
@@ -6,24 +6,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
{
public class DbTypes
{
- public DbType DbType;
- public string TextDefinition;
- public bool ShouldQuoteValue;
- public Dictionary ColumnTypeMap = new Dictionary();
- public Dictionary ColumnDbTypeMap = new Dictionary();
-
- public void Set(DbType dbType, string fieldDefinition)
+ public DbTypes(IReadOnlyDictionary columnTypeMap, IReadOnlyDictionary columnDbTypeMap)
{
- DbType = dbType;
- TextDefinition = fieldDefinition;
- ShouldQuoteValue = fieldDefinition != "INTEGER"
- && fieldDefinition != "BIGINT"
- && fieldDefinition != "DOUBLE"
- && fieldDefinition != "DECIMAL"
- && fieldDefinition != "BOOL";
-
- ColumnTypeMap[typeof(T)] = fieldDefinition;
- ColumnDbTypeMap[typeof(T)] = dbType;
+ ColumnTypeMap = columnTypeMap;
+ ColumnDbTypeMap = columnDbTypeMap;
}
+
+ public IReadOnlyDictionary ColumnTypeMap { get; }
+ public IReadOnlyDictionary ColumnDbTypeMap { get; }
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/DbTypesFactory.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/DbTypesFactory.cs
new file mode 100644
index 0000000000..bf1e0989f5
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/DbTypesFactory.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+
+namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
+{
+ internal class DbTypesFactory
+ {
+ private readonly Dictionary _columnTypeMap = new Dictionary();
+ private readonly Dictionary _columnDbTypeMap = new Dictionary();
+
+ public void Set(DbType dbType, string fieldDefinition)
+ {
+ _columnTypeMap[typeof(T)] = fieldDefinition;
+ _columnDbTypeMap[typeof(T)] = dbType;
+ }
+
+ public DbTypes Create() => new DbTypes(_columnTypeMap, _columnDbTypeMap);
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
index 6c551648b7..75d348df1a 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Data;
using System.Text.RegularExpressions;
@@ -28,7 +28,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
string GetQuotedName(string name);
bool DoesTableExist(IDatabase db, string tableName);
string GetIndexType(IndexTypes indexTypes);
- string GetSpecialDbType(SpecialDbTypes dbTypes);
+ string GetSpecialDbType(SpecialDbType dbType);
string CreateTable { get; }
string DropTable { get; }
string AddColumn { get; }
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs
index 4c75128926..0093ee14ce 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs
@@ -22,8 +22,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
DecimalColumnDefinition = "DECIMAL(38,6)";
TimeColumnDefinition = "TIME"; //SQLSERVER 2008+
BlobColumnDefinition = "VARBINARY(MAX)";
-
- InitColumnTypeMap();
}
public override string RenameTable => "sp_rename '{0}', '{1}'";
@@ -78,7 +76,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
///
public virtual SqlDbType GetSqlDbType(Type clrType)
{
- var dbType = DbTypeMap.ColumnDbTypeMap.First(x => x.Key == clrType).Value;
+ var dbType = DbTypeMap.ColumnDbTypeMap[clrType];
return GetSqlDbType(dbType);
}
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
index 24548fd36b..753a372e82 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
@@ -24,6 +24,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
public abstract class SqlSyntaxProviderBase : ISqlSyntaxProvider
where TSyntax : ISqlSyntaxProvider
{
+ private readonly Lazy _dbTypes;
+
protected SqlSyntaxProviderBase()
{
ClauseOrder = new List>
@@ -42,94 +44,96 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
StringColumnDefinition = string.Format(StringLengthColumnDefinitionFormat, DefaultStringLength);
DecimalColumnDefinition = string.Format(DecimalColumnDefinitionFormat, DefaultDecimalPrecision, DefaultDecimalScale);
- InitColumnTypeMap();
-
// ReSharper disable VirtualMemberCallInConstructor
// ok to call virtual GetQuotedXxxName here - they don't depend on any state
var col = Regex.Escape(GetQuotedColumnName("column")).Replace("column", @"\w+");
var fld = Regex.Escape(GetQuotedTableName("table") + ".").Replace("table", @"\w+") + col;
// ReSharper restore VirtualMemberCallInConstructor
AliasRegex = new Regex("(" + fld + @")\s+AS\s+(" + col + ")", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled);
+
+ _dbTypes = new Lazy(InitColumnTypeMap);
}
public Regex AliasRegex { get; }
- public string GetWildcardPlaceholder()
- {
- return "%";
- }
+ public string GetWildcardPlaceholder() => "%";
- public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})";
- public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})";
- public string DecimalColumnDefinitionFormat = "DECIMAL({0},{1})";
+ public string StringLengthNonUnicodeColumnDefinitionFormat { get; } = "VARCHAR({0})";
+ public string StringLengthUnicodeColumnDefinitionFormat { get; } = "NVARCHAR({0})";
+ public string DecimalColumnDefinitionFormat { get; } = "DECIMAL({0},{1})";
- public string DefaultValueFormat = "DEFAULT ({0})";
- public int DefaultStringLength = 255;
- public int DefaultDecimalPrecision = 20;
- public int DefaultDecimalScale = 9;
+ public string DefaultValueFormat { get; } = "DEFAULT ({0})";
+ public int DefaultStringLength { get; } = 255;
+ public int DefaultDecimalPrecision { get; } = 20;
+ public int DefaultDecimalScale { get; } = 9;
//Set by Constructor
- public string StringColumnDefinition;
- public string StringLengthColumnDefinitionFormat;
+ public string StringColumnDefinition { get; }
+ public string StringLengthColumnDefinitionFormat { get; }
- public string AutoIncrementDefinition = "AUTOINCREMENT";
- public string IntColumnDefinition = "INTEGER";
- public string LongColumnDefinition = "BIGINT";
- public string GuidColumnDefinition = "GUID";
- public string BoolColumnDefinition = "BOOL";
- public string RealColumnDefinition = "DOUBLE";
- public string DecimalColumnDefinition;
- public string BlobColumnDefinition = "BLOB";
- public string DateTimeColumnDefinition = "DATETIME";
- public string TimeColumnDefinition = "DATETIME";
+ public string AutoIncrementDefinition { get; protected set; } = "AUTOINCREMENT";
+ public string IntColumnDefinition { get; protected set; } = "INTEGER";
+ public string LongColumnDefinition { get; protected set; } = "BIGINT";
+ public string GuidColumnDefinition { get; protected set; } = "GUID";
+ public string BoolColumnDefinition { get; protected set; } = "BOOL";
+ public string RealColumnDefinition { get; protected set; } = "DOUBLE";
+ public string DecimalColumnDefinition { get; protected set; }
+ public string BlobColumnDefinition { get; protected set; } = "BLOB";
+ public string DateTimeColumnDefinition { get; protected set; } = "DATETIME";
+ public string DateTimeOffsetColumnDefinition { get; protected set; } = "DATETIMEOFFSET(7)";
+ public string TimeColumnDefinition { get; protected set; } = "DATETIME";
protected IList> ClauseOrder { get; }
- protected DbTypes DbTypeMap = new DbTypes();
- protected void InitColumnTypeMap()
+ protected DbTypes DbTypeMap => _dbTypes.Value;
+
+ private DbTypes InitColumnTypeMap()
{
- DbTypeMap.Set(DbType.String, StringColumnDefinition);
- DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition);
- DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition);
- DbTypeMap.Set(DbType.String, StringColumnDefinition);
- DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition);
- DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition);
- DbTypeMap.Set(DbType.Guid, GuidColumnDefinition);
- DbTypeMap.Set(DbType.Guid, GuidColumnDefinition);
- DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition);
- DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition);
- DbTypeMap.Set(DbType.Time, TimeColumnDefinition);
- DbTypeMap.Set(DbType.Time, TimeColumnDefinition);
- DbTypeMap.Set(DbType.Time, TimeColumnDefinition);
- DbTypeMap.Set(DbType.Time, TimeColumnDefinition);
+ var dbTypeMap = new DbTypesFactory();
+ dbTypeMap.Set(DbType.String, StringColumnDefinition);
+ dbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition);
+ dbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition);
+ dbTypeMap.Set(DbType.String, StringColumnDefinition);
+ dbTypeMap.Set(DbType.Boolean, BoolColumnDefinition);
+ dbTypeMap.Set(DbType.Boolean, BoolColumnDefinition);
+ dbTypeMap.Set(DbType.Guid, GuidColumnDefinition);
+ dbTypeMap.Set(DbType.Guid, GuidColumnDefinition);
+ dbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition);
+ dbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition);
+ dbTypeMap.Set(DbType.Time, TimeColumnDefinition);
+ dbTypeMap.Set(DbType.Time, TimeColumnDefinition);
+ dbTypeMap.Set(DbType.DateTimeOffset, DateTimeOffsetColumnDefinition);
+ dbTypeMap.Set(DbType.DateTimeOffset, DateTimeOffsetColumnDefinition);
- DbTypeMap.Set(DbType.Byte, IntColumnDefinition);
- DbTypeMap.Set(DbType.Byte, IntColumnDefinition);
- DbTypeMap.Set(DbType.SByte, IntColumnDefinition);
- DbTypeMap.Set(DbType.SByte, IntColumnDefinition);
- DbTypeMap.Set(DbType.Int16, IntColumnDefinition);
- DbTypeMap.Set(DbType.Int16, IntColumnDefinition);
- DbTypeMap.Set(DbType.UInt16, IntColumnDefinition);
- DbTypeMap.Set(DbType.UInt16, IntColumnDefinition);
- DbTypeMap.Set(DbType.Int32, IntColumnDefinition);
- DbTypeMap.Set(DbType.Int32, IntColumnDefinition);
- DbTypeMap.Set(DbType.UInt32, IntColumnDefinition);
- DbTypeMap.Set(DbType.UInt32, IntColumnDefinition);
+ dbTypeMap.Set(DbType.Byte, IntColumnDefinition);
+ dbTypeMap.Set(DbType.Byte, IntColumnDefinition);
+ dbTypeMap.Set(DbType.SByte, IntColumnDefinition);
+ dbTypeMap.Set(DbType.SByte, IntColumnDefinition);
+ dbTypeMap.Set(DbType.Int16, IntColumnDefinition);
+ dbTypeMap.Set(DbType.Int16, IntColumnDefinition);
+ dbTypeMap.Set(DbType.UInt16, IntColumnDefinition);
+ dbTypeMap.Set(DbType.UInt16, IntColumnDefinition);
+ dbTypeMap.Set(DbType.Int32, IntColumnDefinition);
+ dbTypeMap.Set(DbType.Int32, IntColumnDefinition);
+ dbTypeMap.Set(DbType.UInt32, IntColumnDefinition);
+ dbTypeMap.Set(DbType.UInt32, IntColumnDefinition);
- DbTypeMap.Set(DbType.Int64, LongColumnDefinition);
- DbTypeMap.Set(DbType.Int64, LongColumnDefinition);
- DbTypeMap.Set(DbType.UInt64, LongColumnDefinition);
- DbTypeMap.Set(DbType.UInt64, LongColumnDefinition);
+ dbTypeMap.Set(DbType.Int64, LongColumnDefinition);
+ dbTypeMap.Set(DbType.Int64, LongColumnDefinition);
+ dbTypeMap.Set(DbType.UInt64, LongColumnDefinition);
+ dbTypeMap.Set(DbType.UInt64, LongColumnDefinition);
- DbTypeMap.Set(DbType.Single, RealColumnDefinition);
- DbTypeMap.Set(DbType.Single, RealColumnDefinition);
- DbTypeMap.Set(DbType.Double, RealColumnDefinition);
- DbTypeMap.Set(DbType.Double, RealColumnDefinition);
+ dbTypeMap.Set(DbType.Single, RealColumnDefinition);
+ dbTypeMap.Set(DbType.Single, RealColumnDefinition);
+ dbTypeMap.Set(DbType.Double, RealColumnDefinition);
+ dbTypeMap.Set(DbType.Double, RealColumnDefinition);
- DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition);
- DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition);
+ dbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition);
+ dbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition);
- DbTypeMap.Set(DbType.Binary, BlobColumnDefinition);
+ dbTypeMap.Set(DbType.Binary, BlobColumnDefinition);
+
+ return dbTypeMap.Create();
}
public abstract string ProviderName { get; }
@@ -193,17 +197,17 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
return indexType;
}
- public virtual string GetSpecialDbType(SpecialDbTypes dbTypes)
+ public virtual string GetSpecialDbType(SpecialDbType dbType)
{
- if (dbTypes == SpecialDbTypes.NCHAR)
+ if (dbType == SpecialDbType.NCHAR)
{
- return "NCHAR";
+ return SpecialDbType.NCHAR;
}
- else if (dbTypes == SpecialDbTypes.NTEXT)
+ else if (dbType == SpecialDbType.NTEXT)
{
- return "NTEXT";
+ return SpecialDbType.NTEXT;
}
- else if (dbTypes == SpecialDbTypes.NVARCHARMAX)
+ else if (dbType == SpecialDbType.NVARCHARMAX)
{
return "NVARCHAR(MAX)";
}
@@ -470,14 +474,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
if (column.Type.HasValue == false && string.IsNullOrEmpty(column.CustomType) == false)
return column.CustomType;
- if (column.HasSpecialDbType)
+ if (column.CustomDbType.HasValue)
{
- if (column.Size != default(int))
+ if (column.Size != default)
{
- return $"{GetSpecialDbType(column.DbType)}({column.Size})";
+ return $"{GetSpecialDbType(column.CustomDbType.Value)}({column.Size})";
}
- return GetSpecialDbType(column.DbType);
+ return GetSpecialDbType(column.CustomDbType.Value);
}
var type = column.Type.HasValue
@@ -486,19 +490,19 @@ namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax
if (type == typeof(string))
{
- var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength;
+ var valueOrDefault = column.Size != default ? column.Size : DefaultStringLength;
return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault);
}
if (type == typeof(decimal))
{
- var precision = column.Size != default(int) ? column.Size : DefaultDecimalPrecision;
- var scale = column.Precision != default(int) ? column.Precision : DefaultDecimalScale;
+ var precision = column.Size != default ? column.Size : DefaultDecimalPrecision;
+ var scale = column.Precision != default ? column.Precision : DefaultDecimalScale;
return string.Format(DecimalColumnDefinitionFormat, precision, scale);
}
- var definition = DbTypeMap.ColumnTypeMap.First(x => x.Key == type).Value;
- var dbTypeDefinition = column.Size != default(int)
+ var definition = DbTypeMap.ColumnTypeMap[type];
+ var dbTypeDefinition = column.Size != default
? $"{definition}({column.Size})"
: definition;
//NOTE Precision is left out
diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs
index 9ddb67d611..e3ddc69e6f 100644
--- a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs
+++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs
@@ -18,6 +18,7 @@ namespace Umbraco.Cms.Core.Security
private string[] _allowedSections;
private int[] _startMediaIds;
private int[] _startContentIds;
+ private DateTime? _inviteDateUtc;
private static readonly DelegateEqualityComparer s_startIdsComparer = new DelegateEqualityComparer(
(groups, enumerable) => groups.UnsortedSequenceEqual(enumerable),
@@ -75,6 +76,15 @@ namespace Umbraco.Cms.Core.Security
public int[] CalculatedMediaStartNodeIds { get; set; }
public int[] CalculatedContentStartNodeIds { get; set; }
+ ///
+ /// Gets or sets invite date
+ ///
+ public DateTime? InviteDateUtc
+ {
+ get => _inviteDateUtc;
+ set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _inviteDateUtc, nameof(InviteDateUtc));
+ }
+
///
/// Gets or sets content start nodes assigned to the User (not ones assigned to the user's groups)
///
diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
index e6a58efa88..deb85ff496 100644
--- a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
+++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
@@ -438,6 +438,13 @@ namespace Umbraco.Cms.Core.Security
user.LastLoginDate = dt;
}
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.InviteDateUtc))
+ || (user.InvitedDate?.ToUniversalTime() != identityUser.InviteDateUtc))
+ {
+ anythingChanged = true;
+ user.InvitedDate = identityUser.InviteDateUtc?.ToLocalTime();
+ }
+
if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastPasswordChangeDateUtc))
|| (user.LastPasswordChangeDate != default && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|| (identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value))
diff --git a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
index 0d0b9fc156..5addc73a3f 100644
--- a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
+++ b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
@@ -74,6 +74,7 @@ namespace Umbraco.Cms.Core.Security
target.UserName = source.Username;
target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime();
target.LastLoginDateUtc = source.LastLoginDate.ToUniversalTime();
+ target.InviteDateUtc = source.InvitedDate?.ToUniversalTime();
target.EmailConfirmed = source.EmailConfirmedDate.HasValue;
target.Name = source.Name;
target.AccessFailedCount = source.FailedPasswordAttempts;
@@ -87,7 +88,7 @@ namespace Umbraco.Cms.Core.Security
target.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null;
}
- // TODO: We need to validate this mapping is OK, we need to get Umbraco.Code working
+ // Umbraco.Code.MapAll -Id -LockoutEnabled -PhoneNumber -PhoneNumberConfirmed -TwoFactorEnabled -ConcurrencyStamp -NormalizedEmail -NormalizedUserName -Roles
private void Map(IMember source, MemberIdentityUser target)
{
target.Email = source.Email;
diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
index 7a7bc628b8..2f709d5d27 100644
--- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
+++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
@@ -41,12 +41,12 @@
-
+
diff --git a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs
index 00cbdce532..c7e6df8cb2 100644
--- a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs
+++ b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs
@@ -55,15 +55,17 @@ namespace Umbraco.Cms.Infrastructure.WebAssets
_runtimeMinifier.CreateCssBundle(UmbracoInitCssBundleName,
BundlingOptions.NotOptimizedAndComposite,
- FormatPaths("lib/bootstrap-social/bootstrap-social.css",
- "assets/css/umbraco.min.css",
- "lib/font-awesome/css/font-awesome.min.css"));
+ FormatPaths(
+ "assets/css/umbraco.min.css",
+ "lib/bootstrap-social/bootstrap-social.css",
+ "lib/font-awesome/css/font-awesome.min.css"));
_runtimeMinifier.CreateCssBundle(UmbracoUpgradeCssBundleName,
BundlingOptions.NotOptimizedAndComposite,
- FormatPaths("assets/css/umbraco.min.css",
- "lib/bootstrap-social/bootstrap-social.css",
- "lib/font-awesome/css/font-awesome.min.css"));
+ FormatPaths(
+ "assets/css/umbraco.min.css",
+ "lib/bootstrap-social/bootstrap-social.css",
+ "lib/font-awesome/css/font-awesome.min.css"));
_runtimeMinifier.CreateCssBundle(UmbracoPreviewCssBundleName,
BundlingOptions.NotOptimizedAndComposite,
diff --git a/src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs b/src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs
index e81b6135da..62aa933a04 100644
--- a/src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs
+++ b/src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
@@ -25,10 +25,10 @@ namespace Umbraco.Cms.Persistence.SqlCe
{
_globalSettings = globalSettings;
BlobColumnDefinition = "IMAGE";
- // This is silly to have to do this but the way these inherited classes are structured it's the easiest
- // way without an overhaul in type map initialization
- DbTypeMap.Set(DbType.Binary, BlobColumnDefinition);
-
+ // NOTE: if this column type is used in sqlce, it will prob result in errors since
+ // SQLCE cannot support this type correctly without 2x columns and a lot of work arounds.
+ // We don't use this natively within Umbraco but 3rd parties might with SQL server.
+ DateTimeOffsetColumnDefinition = "DATETIME";
}
public override string ProviderName => Constants.DatabaseProviders.SqlCe;
@@ -300,10 +300,14 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
GetQuotedTableName(index.TableName), columns);
}
- public override string GetSpecialDbType(SpecialDbTypes dbTypes)
+ public override string GetSpecialDbType(SpecialDbType dbTypes)
{
- if (dbTypes == SpecialDbTypes.NVARCHARMAX) // SqlCE does not have nvarchar(max) for now
+ // SqlCE does not have nvarchar(max) for now
+ if (dbTypes == SpecialDbType.NVARCHARMAX)
+ {
return "NTEXT";
+ }
+
return base.GetSpecialDbType(dbTypes);
}
public override SqlDbType GetSqlDbType(DbType dbType)
diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
index 59b970ebbc..71b9ac3ef9 100644
--- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
+++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
@@ -181,8 +181,8 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
app.UseUmbraco()
.WithMiddleware(u =>
{
- u.WithBackOffice();
- u.WithWebsite();
+ u.UseBackOffice();
+ u.UseWebsite();
})
.WithEndpoints(u =>
{
diff --git a/src/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs
index 3ee9b1375c..74d364256f 100644
--- a/src/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs
+++ b/src/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs
@@ -8,6 +8,7 @@ using System.IO.Compression;
using System.Linq;
using System.Xml.Linq;
using NUnit.Framework;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
@@ -36,7 +37,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Packaging
[TearDown]
public void DeleteTestFolder() =>
Directory.Delete(HostingEnvironment.MapPathContentRoot("~/" + _testBaseFolder), true);
-
+
private IContentService ContentService => GetRequiredService();
private IContentTypeService ContentTypeService => GetRequiredService();
@@ -164,7 +165,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Packaging
{
var parent = new DictionaryItem("Parent")
{
- Key = Guid.NewGuid()
+ Key = Guid.NewGuid()
};
LocalizationService.Save(parent);
var child1 = new DictionaryItem(parent.Key, "Child1")
@@ -204,12 +205,12 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Packaging
};
PackageBuilder.SavePackage(def);
-
+
string packageXmlPath = PackageBuilder.ExportPackage(def);
- using (var packageZipStream = File.OpenRead(packageXmlPath))
- using (ZipArchive zipArchive = PackageMigrationResource.GetPackageDataManifest(packageZipStream, out XDocument packageXml))
+ using (var packageXmlStream = File.OpenRead(packageXmlPath))
{
+ var packageXml = XDocument.Load(packageXmlStream);
var dictionaryItems = packageXml.Root.Element("DictionaryItems");
Assert.IsNotNull(dictionaryItems);
var rootItems = dictionaryItems.Elements("DictionaryItem").ToList();
@@ -226,7 +227,53 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Packaging
}
[Test]
- public void Export()
+ public void Export_Zip()
+ {
+ var mt = MediaTypeBuilder.CreateImageMediaType("testImage");
+ MediaTypeService.Save(mt);
+ var m1 = MediaBuilder.CreateMediaFile(mt, -1);
+ MediaService.Save(m1);
+
+ //Ensure a file exist
+ var fullPath = HostingEnvironment.MapPathWebRoot(m1.Properties[Constants.Conventions.Media.File].GetValue().ToString());
+ using (StreamWriter file1 = File.CreateText(fullPath))
+ {
+ file1.WriteLine("hello");
+ }
+
+ var def = new PackageDefinition
+ {
+ Name = "test",
+ MediaUdis = new List(){m1.GetUdi()}
+ };
+
+ bool result = PackageBuilder.SavePackage(def);
+ Assert.IsTrue(result);
+ Assert.IsTrue(def.PackagePath.IsNullOrWhiteSpace());
+
+ string packageXmlPath = PackageBuilder.ExportPackage(def);
+
+ def = PackageBuilder.GetById(def.Id); // re-get
+ Assert.IsNotNull(def.PackagePath);
+
+ using (FileStream packageZipStream = File.OpenRead(packageXmlPath))
+ using (ZipArchive zipArchive = PackageMigrationResource.GetPackageDataManifest(packageZipStream, out XDocument packageXml))
+ {
+ Assert.AreEqual("umbPackage", packageXml.Root.Name.ToString());
+ Assert.IsNotNull(zipArchive.GetEntry("media/media/test-file.txt"));
+
+ Assert.AreEqual(
+ $"",
+ packageXml.Element("umbPackage").Element("MediaItems").ToString(SaveOptions.DisableFormatting));
+
+ // TODO: There's a whole lot more assertions to be done
+
+ }
+ }
+
+
+ [Test]
+ public void Export_Xml()
{
var template = TemplateBuilder.CreateTextPageTemplate();
@@ -242,19 +289,17 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Packaging
Assert.IsTrue(result);
Assert.IsTrue(def.PackagePath.IsNullOrWhiteSpace());
- string packageXmlPath = PackageBuilder.ExportPackage(def);
+ string packageXmlPath = PackageBuilder.ExportPackage(def); // Get
def = PackageBuilder.GetById(def.Id); // re-get
Assert.IsNotNull(def.PackagePath);
- using (FileStream packageZipStream = File.OpenRead(packageXmlPath))
- using (ZipArchive zipArchive = PackageMigrationResource.GetPackageDataManifest(packageZipStream, out XDocument packageXml))
+ using (var packageXmlStream = File.OpenRead(packageXmlPath))
{
- Assert.AreEqual("umbPackage", packageXml.Root.Name.ToString());
+ var xml = XDocument.Load(packageXmlStream);
+ Assert.AreEqual("umbPackage", xml.Root.Name.ToString());
- Assert.AreEqual(
- $"Text pagetextPage",
- packageXml.Element("umbPackage").Element("Templates").ToString(SaveOptions.DisableFormatting));
+ Assert.AreEqual($"Text pagetextPage", xml.Element("umbPackage").Element("Templates").ToString(SaveOptions.DisableFormatting));
// TODO: There's a whole lot more assertions to be done
diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
index f432787dc4..48e83b9dcf 100644
--- a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
+++ b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
@@ -8,9 +8,11 @@ using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
+using Castle.Core.Logging;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
@@ -137,7 +139,7 @@ namespace Umbraco.Cms.Tests.UnitTests.TestHelpers
public static UriUtility UriUtility => s_testHelperInternal.UriUtility;
- public static IEmailSender EmailSender { get; } = new EmailSender(Options.Create(new GlobalSettings()), Mock.Of());
+ public static IEmailSender EmailSender { get; } = new EmailSender(new NullLogger(), Options.Create(new GlobalSettings()), Mock.Of());
///
/// Some test files are copied to the /bin (/bin/debug) on build, this is a utility to return their physical path based on a virtual path name
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj
index 3cddeb435e..b5d7373dc3 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj
@@ -13,7 +13,6 @@
-
diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
index ae7776dfaf..cf7a7dd729 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
@@ -181,14 +181,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
else
{
- var opt = _externalAuthenticationOptions.Get(authType.Name);
+ BackOfficeExternaLoginProviderScheme opt = await _externalAuthenticationOptions.GetAsync(authType.Name);
if (opt == null)
{
return BadRequest($"Could not find external authentication options registered for provider {unlinkLoginModel.LoginProvider}");
}
else
{
- if (!opt.Options.AutoLinkOptions.AllowManualLinking)
+ if (!opt.ExternalLoginProvider.Options.AutoLinkOptions.AllowManualLinking)
{
// If AllowManualLinking is disabled for this provider we cannot unlink
return BadRequest();
diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
index ec41ddcfaa..a52e018f58 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
@@ -33,6 +33,7 @@ using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Filters;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
+using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
@@ -49,6 +50,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
// this controller itself doesn't require authz but it's more clear what the intention is.
private readonly IBackOfficeUserManager _userManager;
+ private readonly IRuntimeState _runtimeState;
private readonly IRuntimeMinifier _runtimeMinifier;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
@@ -68,6 +70,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
public BackOfficeController(
IBackOfficeUserManager userManager,
+ IRuntimeState runtimeState,
IRuntimeMinifier runtimeMinifier,
IOptions globalSettings,
IHostingEnvironment hostingEnvironment,
@@ -86,6 +89,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
ServerVariablesParser serverVariables)
{
_userManager = userManager;
+ _runtimeState = runtimeState;
_runtimeMinifier = runtimeMinifier;
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
@@ -108,6 +112,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[AllowAnonymous]
public async Task Default()
{
+ // TODO: It seems that if you login during an authorize upgrade and the upgrade fails, you can still
+ // access the back office. This should redirect to the installer in that case?
+
// force authentication to occur since this is not an authorized endpoint
var result = await this.AuthenticateBackOfficeAsync();
@@ -414,7 +421,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (ViewData.FromBase64CookieData(_httpContextAccessor.HttpContext, ViewDataExtensions.TokenExternalSignInError, _jsonSerializer) ||
ViewData.FromTempData(TempData, ViewDataExtensions.TokenExternalSignInError) ||
ViewData.FromTempData(TempData, ViewDataExtensions.TokenPasswordResetCode))
+ {
return defaultResponse();
+ }
//First check if there's external login info, if there's not proceed as normal
var loginInfo = await _signInManager.GetExternalLoginInfoAsync();
@@ -444,16 +453,23 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (response == null) throw new ArgumentNullException(nameof(response));
// Sign in the user with this external login provider (which auto links, etc...)
- var result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false);
+ SignInResult result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false);
var errors = new List();
- if (result == Microsoft.AspNetCore.Identity.SignInResult.Success)
+ if (result == SignInResult.Success)
{
// Update any authentication tokens if succeeded
await _signInManager.UpdateExternalAuthenticationTokensAsync(loginInfo);
+
+ // Check if we are in an upgrade state, if so we need to redirect
+ if (_runtimeState.Level == Core.RuntimeLevel.Upgrade)
+ {
+ // redirect to the the installer
+ return Redirect("/");
+ }
}
- else if (result == Microsoft.AspNetCore.Identity.SignInResult.TwoFactorRequired)
+ else if (result == SignInResult.TwoFactorRequired)
{
var attemptedUser = await _userManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
@@ -481,17 +497,17 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return verifyResponse;
}
- else if (result == Microsoft.AspNetCore.Identity.SignInResult.LockedOut)
+ else if (result == SignInResult.LockedOut)
{
errors.Add($"The local user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out.");
}
- else if (result == Microsoft.AspNetCore.Identity.SignInResult.NotAllowed)
+ else if (result == SignInResult.NotAllowed)
{
// This occurs when SignInManager.CanSignInAsync fails which is when RequireConfirmedEmail , RequireConfirmedPhoneNumber or RequireConfirmedAccount fails
// however since we don't enforce those rules (yet) this shouldn't happen.
errors.Add($"The user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not confirmed their details and cannot sign in.");
}
- else if (result == Microsoft.AspNetCore.Identity.SignInResult.Failed)
+ else if (result == SignInResult.Failed)
{
// Failed only occurs when the user does not exist
errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office.");
@@ -508,6 +524,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
errors.AddRange(autoLinkSignInResult.Errors);
}
+ else if (!result.Succeeded)
+ {
+ // this shouldn't occur, the above should catch the correct error but we'll be safe just in case
+ errors.Add($"An unknown error with the requested provider ({loginInfo.LoginProvider}) occurred.");
+ }
if (errors.Count > 0)
{
diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
index c3fb203ec1..61c1660dd0 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
@@ -144,7 +144,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// Returns the server variables for authenticated users
///
///
- internal Task> GetServerVariablesAsync()
+ internal async Task> GetServerVariablesAsync()
{
var globalSettings = _globalSettings;
var backOfficeControllerName = ControllerExtensions.GetControllerName();
@@ -432,12 +432,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
// TODO: It would be nicer to not have to manually translate these properties
// but then needs to be changed in quite a few places in angular
- "providers", _externalLogins.GetBackOfficeProviders()
+ "providers", (await _externalLogins.GetBackOfficeProvidersAsync())
.Select(p => new
{
- authType = p.AuthenticationType,
- caption = p.Name,
- properties = p.Options
+ authType = p.ExternalLoginProvider.AuthenticationType,
+ caption = p.AuthenticationScheme.DisplayName,
+ properties = p.ExternalLoginProvider.Options
})
.ToArray()
}
@@ -456,7 +456,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
}
};
- return Task.FromResult(defaultVals);
+
+ return defaultVals;
}
[DataContract]
diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
index 3d08d2dab1..00e486fad5 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
@@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using MimeKit;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
@@ -551,7 +552,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings),
new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail });
- var mailMessage = new EmailMessage(fromEmail, to.Email, emailSubject, emailBody, true);
+ // This needs to be in the correct mailto format including the name, else
+ // the name cannot be captured in the email sending notification.
+ // i.e. "Some Person"
+ var toMailBoxAddress = new MailboxAddress(to.Name, to.Email);
+
+ var mailMessage = new EmailMessage(fromEmail, toMailBoxAddress.ToString(), emailSubject, emailBody, true);
await _emailSender.SendAsync(mailMessage, true);
}
diff --git a/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs
index 3340988714..fee75f6eae 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -54,18 +54,18 @@ namespace Umbraco.Extensions
///
///
///
- public static Task AngularValueExternalLoginInfoScriptAsync(this IHtmlHelper html,
+ public static async Task AngularValueExternalLoginInfoScriptAsync(this IHtmlHelper html,
IBackOfficeExternalLoginProviders externalLogins,
BackOfficeExternalLoginProviderErrors externalLoginErrors)
{
- var providers = externalLogins.GetBackOfficeProviders();
+ var providers = await externalLogins.GetBackOfficeProvidersAsync();
var loginProviders = providers
.Select(p => new
{
- authType = p.AuthenticationType,
- caption = p.Name,
- properties = p.Options
+ authType = p.ExternalLoginProvider.AuthenticationType,
+ caption = p.AuthenticationScheme.DisplayName,
+ properties = p.ExternalLoginProvider.Options
})
.ToArray();
@@ -89,7 +89,7 @@ namespace Umbraco.Extensions
sb.AppendLine(JsonConvert.SerializeObject(loginProviders));
sb.AppendLine(@"});");
- return Task.FromResult(html.Raw(sb.ToString()));
+ return html.Raw(sb.ToString());
}
///
diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.BackOffice.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.BackOffice.cs
index 29216ba980..83c1b4b5f2 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.BackOffice.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.BackOffice.cs
@@ -13,7 +13,7 @@ using Umbraco.Cms.Web.Common.Middleware;
namespace Umbraco.Extensions
{
///
- /// extensions for Umbraco
+ /// extensions for Umbraco
///
public static partial class UmbracoApplicationBuilderExtensions
{
@@ -22,7 +22,7 @@ namespace Umbraco.Extensions
///
///
///
- public static IUmbracoMiddlewareBuilder WithBackOffice(this IUmbracoMiddlewareBuilder builder)
+ public static IUmbracoApplicationBuilderContext UseBackOffice(this IUmbracoApplicationBuilderContext builder)
{
KeepAliveSettings keepAliveSettings = builder.ApplicationServices.GetRequiredService>().Value;
IHostingEnvironment hostingEnvironment = builder.ApplicationServices.GetRequiredService();
@@ -34,7 +34,7 @@ namespace Umbraco.Extensions
return builder;
}
- public static IUmbracoEndpointBuilder UseBackOfficeEndpoints(this IUmbracoEndpointBuilder app)
+ public static IUmbracoEndpointBuilderContext UseBackOfficeEndpoints(this IUmbracoEndpointBuilderContext app)
{
// NOTE: This method will have been called after UseRouting, UseAuthentication, UseAuthorization
if (app == null)
diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Installer.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Installer.cs
index 2be8c6bb28..d61b955efc 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Installer.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Installer.cs
@@ -13,7 +13,7 @@ namespace Umbraco.Extensions
///
/// Enables the Umbraco installer
///
- public static IUmbracoEndpointBuilder UseInstallerEndpoints(this IUmbracoEndpointBuilder app)
+ public static IUmbracoEndpointBuilderContext UseInstallerEndpoints(this IUmbracoEndpointBuilderContext app)
{
if (!app.RuntimeState.UmbracoCanBoot())
{
diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Preview.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Preview.cs
index 014f81fe8c..012205575a 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Preview.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Preview.cs
@@ -6,11 +6,11 @@ using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace Umbraco.Extensions
{
///
- /// extensions for Umbraco
+ /// extensions for Umbraco
///
public static partial class UmbracoApplicationBuilderExtensions
{
- public static IUmbracoEndpointBuilder UseUmbracoPreviewEndpoints(this IUmbracoEndpointBuilder app)
+ public static IUmbracoEndpointBuilderContext UseUmbracoPreviewEndpoints(this IUmbracoEndpointBuilderContext app)
{
PreviewRoutes previewRoutes = app.ApplicationServices.GetRequiredService();
previewRoutes.CreateRoutes(app.EndpointRouteBuilder);
diff --git a/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs b/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs
index 3da2553d04..3901e96fbd 100644
--- a/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs
+++ b/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs
@@ -9,12 +9,12 @@ namespace Umbraco.Cms.Web.BackOffice.Security
///
public class AutoLinkSignInResult : SignInResult
{
- public static AutoLinkSignInResult FailedNotLinked => new AutoLinkSignInResult()
+ public static AutoLinkSignInResult FailedNotLinked { get; } = new AutoLinkSignInResult()
{
Succeeded = false
};
- public static AutoLinkSignInResult FailedNoEmail => new AutoLinkSignInResult()
+ public static AutoLinkSignInResult FailedNoEmail { get; } = new AutoLinkSignInResult()
{
Succeeded = false
};
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs
index 5ccd1c0aa1..bc9f64129f 100644
--- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -12,18 +13,16 @@ namespace Umbraco.Cms.Web.BackOffice.Security
///
public class BackOfficeAuthenticationBuilder : AuthenticationBuilder
{
- private readonly BackOfficeExternalLoginProviderOptions _loginProviderOptions;
+ private readonly Action _loginProviderOptions;
- public BackOfficeAuthenticationBuilder(IServiceCollection services, BackOfficeExternalLoginProviderOptions loginProviderOptions)
+ public BackOfficeAuthenticationBuilder(
+ IServiceCollection services,
+ Action loginProviderOptions = null)
: base(services)
- {
- _loginProviderOptions = loginProviderOptions;
- }
+ => _loginProviderOptions = loginProviderOptions ?? (x => { });
public string SchemeForBackOffice(string scheme)
- {
- return Constants.Security.BackOfficeExternalAuthenticationTypePrefix + scheme;
- }
+ => Constants.Security.BackOfficeExternalAuthenticationTypePrefix + scheme;
///
/// Overridden to track the final authenticationScheme being registered for the external login
@@ -43,7 +42,13 @@ namespace Umbraco.Cms.Web.BackOffice.Security
}
// add our login provider to the container along with a custom options configuration
- Services.AddSingleton(x => new BackOfficeExternalLoginProvider(displayName, authenticationScheme, _loginProviderOptions));
+ Services.Configure(authenticationScheme, _loginProviderOptions);
+ base.Services.AddSingleton(services =>
+ {
+ return new BackOfficeExternalLoginProvider(
+ authenticationScheme,
+ services.GetRequiredService>());
+ });
Services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureBackOfficeScheme>());
return base.AddRemoteScheme(authenticationScheme, displayName, configureOptions);
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternaLoginProviderScheme.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternaLoginProviderScheme.cs
new file mode 100644
index 0000000000..2732338426
--- /dev/null
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternaLoginProviderScheme.cs
@@ -0,0 +1,20 @@
+using System;
+using Microsoft.AspNetCore.Authentication;
+
+namespace Umbraco.Cms.Web.BackOffice.Security
+{
+ public class BackOfficeExternaLoginProviderScheme
+ {
+ public BackOfficeExternaLoginProviderScheme(
+ BackOfficeExternalLoginProvider externalLoginProvider,
+ AuthenticationScheme authenticationScheme)
+ {
+ ExternalLoginProvider = externalLoginProvider ?? throw new ArgumentNullException(nameof(externalLoginProvider));
+ AuthenticationScheme = authenticationScheme ?? throw new ArgumentNullException(nameof(authenticationScheme));
+ }
+
+ public BackOfficeExternalLoginProvider ExternalLoginProvider { get; }
+ public AuthenticationScheme AuthenticationScheme { get; }
+ }
+
+}
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs
index ff2a64f155..9e78917087 100644
--- a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs
@@ -1,4 +1,5 @@
-using System;
+using System;
+using Microsoft.Extensions.Options;
namespace Umbraco.Cms.Web.BackOffice.Security
{
@@ -7,33 +8,29 @@ namespace Umbraco.Cms.Web.BackOffice.Security
///
public class BackOfficeExternalLoginProvider : IEquatable
{
- public BackOfficeExternalLoginProvider(string name, string authenticationType, BackOfficeExternalLoginProviderOptions properties)
+ public BackOfficeExternalLoginProvider(
+ string authenticationType,
+ IOptionsMonitor properties)
{
- Name = name ?? throw new ArgumentNullException(nameof(name));
+ if (properties is null)
+ {
+ throw new ArgumentNullException(nameof(properties));
+ }
+
AuthenticationType = authenticationType ?? throw new ArgumentNullException(nameof(authenticationType));
- Options = properties ?? throw new ArgumentNullException(nameof(properties));
+ Options = properties.Get(authenticationType);
}
- public string Name { get; }
+ ///
+ /// The authentication "Scheme"
+ ///
public string AuthenticationType { get; }
+
public BackOfficeExternalLoginProviderOptions Options { get; }
- public override bool Equals(object obj)
- {
- return Equals(obj as BackOfficeExternalLoginProvider);
- }
-
- public bool Equals(BackOfficeExternalLoginProvider other)
- {
- return other != null &&
- Name == other.Name &&
- AuthenticationType == other.AuthenticationType;
- }
-
- public override int GetHashCode()
- {
- return HashCode.Combine(Name, AuthenticationType);
- }
+ public override bool Equals(object obj) => Equals(obj as BackOfficeExternalLoginProvider);
+ public bool Equals(BackOfficeExternalLoginProvider other) => other != null && AuthenticationType == other.AuthenticationType;
+ public override int GetHashCode() => HashCode.Combine(AuthenticationType);
}
}
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs
index fa1c1fe487..d58f1cea17 100644
--- a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs
@@ -1,14 +1,13 @@
-namespace Umbraco.Cms.Web.BackOffice.Security
+namespace Umbraco.Cms.Web.BackOffice.Security
{
-
-
///
/// Options used to configure back office external login providers
///
public class BackOfficeExternalLoginProviderOptions
{
public BackOfficeExternalLoginProviderOptions(
- string buttonStyle, string icon,
+ string buttonStyle,
+ string icon,
ExternalSignInAutoLinkOptions autoLinkOptions = null,
bool denyLocalLogin = false,
bool autoRedirectLoginToExternalProvider = false,
@@ -22,18 +21,23 @@
CustomBackOfficeView = customBackOfficeView;
}
- public string ButtonStyle { get; }
- public string Icon { get; }
+ public BackOfficeExternalLoginProviderOptions()
+ {
+ }
+
+ public string ButtonStyle { get; set; } = "btn-openid";
+
+ public string Icon { get; set; } = "fa fa-user";
///
/// Options used to control how users can be auto-linked/created/updated based on the external login provider
///
- public ExternalSignInAutoLinkOptions AutoLinkOptions { get; }
+ public ExternalSignInAutoLinkOptions AutoLinkOptions { get; set; } = new ExternalSignInAutoLinkOptions();
///
/// When set to true will disable all local user login functionality
///
- public bool DenyLocalLogin { get; }
+ public bool DenyLocalLogin { get; set; }
///
/// When specified this will automatically redirect to the OAuth login provider instead of prompting the user to click on the OAuth button first.
@@ -42,7 +46,7 @@
/// This is generally used in conjunction with . If more than one OAuth provider specifies this, the last registered
/// provider's redirect settings will win.
///
- public bool AutoRedirectLoginToExternalProvider { get; }
+ public bool AutoRedirectLoginToExternalProvider { get; set; }
///
/// A virtual path to a custom angular view that is used to replace the entire UI that renders the external login button that the user interacts with
@@ -51,6 +55,6 @@
/// If this view is specified it is 100% up to the user to render the html responsible for rendering the link/un-link buttons along with showing any errors
/// that occur. This overrides what Umbraco normally does by default.
///
- public string CustomBackOfficeView { get; }
+ public string CustomBackOfficeView { get; set; }
}
}
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs
index 7ecb4e2829..4c9799b9a4 100644
--- a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs
@@ -1,41 +1,71 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
namespace Umbraco.Cms.Web.BackOffice.Security
{
+
///
public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders
{
- public BackOfficeExternalLoginProviders(IEnumerable externalLogins)
+ private readonly Dictionary _externalLogins;
+ private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
+
+ public BackOfficeExternalLoginProviders(
+ IEnumerable externalLogins,
+ IAuthenticationSchemeProvider authenticationSchemeProvider)
{
- _externalLogins = externalLogins;
+ _externalLogins = externalLogins.ToDictionary(x => x.AuthenticationType);
+ _authenticationSchemeProvider = authenticationSchemeProvider;
}
- private readonly IEnumerable _externalLogins;
-
///
- public BackOfficeExternalLoginProvider Get(string authenticationType)
+ public async Task GetAsync(string authenticationType)
{
- return _externalLogins.FirstOrDefault(x => x.AuthenticationType == authenticationType);
+ if (!_externalLogins.TryGetValue(authenticationType, out BackOfficeExternalLoginProvider provider))
+ {
+ return null;
+ }
+
+ // get the associated scheme
+ AuthenticationScheme associatedScheme = await _authenticationSchemeProvider.GetSchemeAsync(provider.AuthenticationType);
+
+ if (associatedScheme == null)
+ {
+ throw new InvalidOperationException("No authentication scheme registered for " + provider.AuthenticationType);
+ }
+
+ return new BackOfficeExternaLoginProviderScheme(provider, associatedScheme);
}
///
public string GetAutoLoginProvider()
{
- var found = _externalLogins.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList();
+ var found = _externalLogins.Values.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList();
return found.Count > 0 ? found[0].AuthenticationType : null;
}
///
- public IEnumerable GetBackOfficeProviders()
+ public async Task> GetBackOfficeProvidersAsync()
{
- return _externalLogins;
+ var providersWithSchemes = new List();
+ foreach (BackOfficeExternalLoginProvider login in _externalLogins.Values)
+ {
+ // get the associated scheme
+ AuthenticationScheme associatedScheme = await _authenticationSchemeProvider.GetSchemeAsync(login.AuthenticationType);
+
+ providersWithSchemes.Add(new BackOfficeExternaLoginProviderScheme(login, associatedScheme));
+ }
+
+ return providersWithSchemes;
}
///
public bool HasDenyLocalLogin()
{
- var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList();
+ var found = _externalLogins.Values.Where(x => x.Options.DenyLocalLogin).ToList();
return found.Count > 0;
}
}
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs
index daea904a49..cab3ea10d1 100644
--- a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Microsoft.Extensions.DependencyInjection;
namespace Umbraco.Cms.Web.BackOffice.Security
@@ -21,9 +21,9 @@ namespace Umbraco.Cms.Web.BackOffice.Security
///
///
///
- public BackOfficeExternalLoginsBuilder AddBackOfficeLogin(
- BackOfficeExternalLoginProviderOptions loginProviderOptions,
- Action build)
+ public BackOfficeExternalLoginsBuilder AddBackOfficeLogin(
+ Action build,
+ Action loginProviderOptions = null)
{
build(new BackOfficeAuthenticationBuilder(_services, loginProviderOptions));
return this;
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs
index 3e921ba0f9..4b970e4b72 100644
--- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs
@@ -64,7 +64,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// to be able to deal with auto-linking and reduce duplicate lookups
- var autoLinkOptions = _externalLogins.Get(loginInfo.LoginProvider)?.Options?.AutoLinkOptions;
+ var autoLinkOptions = (await _externalLogins.GetAsync(loginInfo.LoginProvider))?.ExternalLoginProvider?.Options?.AutoLinkOptions;
var user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
if (user == null)
{
diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs
index d47873f3cd..2426cfcf4d 100644
--- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs
+++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
+using System.Threading.Tasks;
namespace Umbraco.Cms.Web.BackOffice.Security
{
@@ -13,13 +14,13 @@ namespace Umbraco.Cms.Web.BackOffice.Security
///
///
///
- BackOfficeExternalLoginProvider Get(string authenticationType);
+ Task GetAsync(string authenticationType);
///
/// Get all registered
///
///
- IEnumerable GetBackOfficeProviders();
+ Task> GetBackOfficeProvidersAsync();
///
/// Returns the authentication type for the last registered external login (oauth) provider that specifies an auto-login redirect option
diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs
index 090ef52790..2c817d9eb8 100644
--- a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs
+++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs
@@ -1,22 +1,21 @@
using System;
-using Microsoft.AspNetCore.Builder;
-using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
public interface IUmbracoApplicationBuilder
{
///
- /// Called to include umbraco middleware
+ /// EXPERT call to replace the middlewares that Umbraco installs by default with a completely custom pipeline.
///
- ///
+ ///
///
- IUmbracoApplicationBuilder WithMiddleware(Action configureUmbraco);
+ IUmbracoEndpointBuilder WithCustomMiddleware(Action configureUmbracoMiddleware);
///
- /// Final call during app building to configure endpoints
+ /// Called to include default middleware to run umbraco.
///
- ///
- void WithEndpoints(Action configureUmbraco);
+ ///
+ ///
+ IUmbracoEndpointBuilder WithMiddleware(Action configureUmbracoMiddleware);
}
}
diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderContext.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderContext.cs
new file mode 100644
index 0000000000..ecf62af32e
--- /dev/null
+++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderContext.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace Umbraco.Cms.Web.Common.ApplicationBuilder
+{
+ ///
+ /// The context object used during
+ ///
+ public interface IUmbracoApplicationBuilderContext : IUmbracoApplicationBuilderServices
+ {
+ ///
+ /// Called to include the core umbraco middleware.
+ ///
+ void UseUmbracoCoreMiddleware();
+
+ ///
+ /// Manually runs the pre pipeline filters
+ ///
+ void RunPrePipeline();
+
+ ///
+ /// Manually runs the post pipeline filters
+ ///
+ void RunPostPipeline();
+
+ ///
+ /// Called to include all of the default umbraco required middleware.
+ ///
+ ///
+ /// If using this method, there is no need to use
+ ///
+ void RegisterDefaultRequiredMiddleware();
+ }
+}
diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoMiddlewareBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderServices.cs
similarity index 63%
rename from src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoMiddlewareBuilder.cs
rename to src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderServices.cs
index 78d7f28ab9..5310018969 100644
--- a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoMiddlewareBuilder.cs
+++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderServices.cs
@@ -1,13 +1,16 @@
-using System;
+using System;
using Microsoft.AspNetCore.Builder;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
- public interface IUmbracoMiddlewareBuilder
+ ///
+ /// Services used during the Umbraco building phase.
+ ///
+ public interface IUmbracoApplicationBuilderServices
{
- IRuntimeState RuntimeState { get; }
- IServiceProvider ApplicationServices { get; }
IApplicationBuilder AppBuilder { get; }
+ IServiceProvider ApplicationServices { get; }
+ IRuntimeState RuntimeState { get; }
}
}
diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilder.cs
index 31507477ae..58e0b8fec2 100644
--- a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilder.cs
+++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilder.cs
@@ -1,13 +1,13 @@
-using Microsoft.AspNetCore.Routing;
+using System;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
-
- ///
- /// A builder to allow encapsulating the enabled routing features in Umbraco
- ///
- public interface IUmbracoEndpointBuilder : IUmbracoMiddlewareBuilder
- {
- IEndpointRouteBuilder EndpointRouteBuilder { get; }
+ public interface IUmbracoEndpointBuilder
+ {
+ ///
+ /// Final call during app building to configure endpoints
+ ///
+ ///
+ void WithEndpoints(Action configureUmbraco);
}
}
diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilderContext.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilderContext.cs
new file mode 100644
index 0000000000..6122c9ef2e
--- /dev/null
+++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilderContext.cs
@@ -0,0 +1,13 @@
+using Microsoft.AspNetCore.Routing;
+
+namespace Umbraco.Cms.Web.Common.ApplicationBuilder
+{
+
+ ///
+ /// A builder to allow encapsulating the enabled routing features in Umbraco
+ ///
+ public interface IUmbracoEndpointBuilderContext : IUmbracoApplicationBuilderServices
+ {
+ IEndpointRouteBuilder EndpointRouteBuilder { get; }
+ }
+}
diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs
index b7acc45d22..9d30551071 100644
--- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs
+++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs
@@ -2,61 +2,144 @@ using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using SixLabors.ImageSharp.Web.DependencyInjection;
using Umbraco.Cms.Core.Services;
+using Umbraco.Extensions;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
///
- /// A builder to allow encapsulating the enabled endpoints in Umbraco
+ /// A builder used to enable middleware and endpoints required for Umbraco to operate.
///
- internal class UmbracoApplicationBuilder : IUmbracoApplicationBuilder, IUmbracoMiddlewareBuilder
+ ///
+ /// This helps to ensure that everything is registered in the correct order.
+ ///
+ public class UmbracoApplicationBuilder : IUmbracoApplicationBuilder, IUmbracoEndpointBuilder, IUmbracoApplicationBuilderContext
{
- public UmbracoApplicationBuilder(IServiceProvider services, IRuntimeState runtimeState, IApplicationBuilder appBuilder)
+ private readonly IOptions _umbracoPipelineStartupOptions;
+
+ public UmbracoApplicationBuilder(IApplicationBuilder appBuilder)
{
- ApplicationServices = services;
- RuntimeState = runtimeState;
- AppBuilder = appBuilder;
+ AppBuilder = appBuilder ?? throw new ArgumentNullException(nameof(appBuilder));
+ ApplicationServices = appBuilder.ApplicationServices;
+ RuntimeState = appBuilder.ApplicationServices.GetRequiredService();
+ _umbracoPipelineStartupOptions = ApplicationServices.GetRequiredService>();
}
public IServiceProvider ApplicationServices { get; }
public IRuntimeState RuntimeState { get; }
public IApplicationBuilder AppBuilder { get; }
- public IUmbracoApplicationBuilder WithMiddleware(Action configureUmbraco)
+ ///
+ public IUmbracoEndpointBuilder WithCustomMiddleware(Action configureUmbracoMiddleware)
{
- IOptions startupOptions = ApplicationServices.GetRequiredService>();
- RunPostPipeline(startupOptions.Value);
+ if (configureUmbracoMiddleware is null)
+ {
+ throw new ArgumentNullException(nameof(configureUmbracoMiddleware));
+ }
- configureUmbraco(this);
+ configureUmbracoMiddleware(this);
return this;
}
- public void WithEndpoints(Action configureUmbraco)
+ ///
+ public IUmbracoEndpointBuilder WithMiddleware(Action configureUmbracoMiddleware)
+ {
+ if (configureUmbracoMiddleware is null)
+ {
+ throw new ArgumentNullException(nameof(configureUmbracoMiddleware));
+ }
+
+ RunPrePipeline();
+
+ RegisterDefaultRequiredMiddleware();
+
+ RunPostPipeline();
+
+ configureUmbracoMiddleware(this);
+
+ return this;
+ }
+
+ ///
+ public void WithEndpoints(Action configureUmbraco)
{
IOptions startupOptions = ApplicationServices.GetRequiredService>();
- RunPreEndpointsPipeline(startupOptions.Value);
+ RunPreEndpointsPipeline();
AppBuilder.UseEndpoints(endpoints =>
{
- var umbAppBuilder = (IUmbracoEndpointBuilder)ActivatorUtilities.CreateInstance(
+ var umbAppBuilder = (IUmbracoEndpointBuilderContext)ActivatorUtilities.CreateInstance(
ApplicationServices,
new object[] { AppBuilder, endpoints });
configureUmbraco(umbAppBuilder);
});
}
- private void RunPostPipeline(UmbracoPipelineOptions startupOptions)
+ ///
+ /// Registers the default required middleware to run Umbraco
+ ///
+ ///
+ public void RegisterDefaultRequiredMiddleware()
{
- foreach (IUmbracoPipelineFilter filter in startupOptions.PipelineFilters)
+ UseUmbracoCoreMiddleware();
+
+ AppBuilder.UseStatusCodePages();
+
+ // Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
+ AppBuilder.UseImageSharp();
+ AppBuilder.UseStaticFiles();
+ AppBuilder.UseUmbracoPluginsStaticFiles();
+
+ // UseRouting adds endpoint routing middleware, this means that middlewares registered after this one
+ // will execute after endpoint routing. The ordering of everything is quite important here, see
+ // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-5.0
+ // where we need to have UseAuthentication and UseAuthorization proceeding this call but before
+ // endpoints are defined.
+ AppBuilder.UseRouting();
+ AppBuilder.UseAuthentication();
+ AppBuilder.UseAuthorization();
+
+ // This must come after auth because the culture is based on the auth'd user
+ AppBuilder.UseRequestLocalization();
+
+ // Must be called after UseRouting and before UseEndpoints
+ AppBuilder.UseSession();
+
+ // DO NOT PUT ANY UseEndpoints declarations here!! Those must all come very last in the pipeline,
+ // endpoints are terminating middleware. All of our endpoints are declared in ext of IUmbracoApplicationBuilder
+ }
+
+ public void UseUmbracoCoreMiddleware()
+ {
+ AppBuilder.UseUmbracoCore();
+ AppBuilder.UseUmbracoRequestLogging();
+
+ // We need to add this before UseRouting so that the UmbracoContext and other middlewares are executed
+ // before endpoint routing middleware.
+ AppBuilder.UseUmbracoRouting();
+ }
+
+ public void RunPrePipeline()
+ {
+ foreach (IUmbracoPipelineFilter filter in _umbracoPipelineStartupOptions.Value.PipelineFilters)
+ {
+ filter.OnPrePipeline(AppBuilder);
+ }
+ }
+
+ public void RunPostPipeline()
+ {
+ foreach (IUmbracoPipelineFilter filter in _umbracoPipelineStartupOptions.Value.PipelineFilters)
{
filter.OnPostPipeline(AppBuilder);
}
}
- private void RunPreEndpointsPipeline(UmbracoPipelineOptions startupOptions)
+ private void RunPreEndpointsPipeline()
{
- foreach (IUmbracoPipelineFilter filter in startupOptions.PipelineFilters)
+ foreach (IUmbracoPipelineFilter filter in _umbracoPipelineStartupOptions.Value.PipelineFilters)
{
filter.OnEndpoints(AppBuilder);
}
diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoEndpointBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoEndpointBuilder.cs
index 56d856a22a..86e8f3e957 100644
--- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoEndpointBuilder.cs
+++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoEndpointBuilder.cs
@@ -8,7 +8,7 @@ namespace Umbraco.Cms.Web.Common.ApplicationBuilder
///
/// A builder to allow encapsulating the enabled endpoints in Umbraco
///
- internal class UmbracoEndpointBuilder : IUmbracoEndpointBuilder
+ internal class UmbracoEndpointBuilder : IUmbracoEndpointBuilderContext
{
public UmbracoEndpointBuilder(IServiceProvider services, IRuntimeState runtimeState, IApplicationBuilder appBuilder, IEndpointRouteBuilder endpointRouteBuilder)
{
diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
index 64839d2dd3..cc035d116b 100644
--- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
@@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Serilog.Context;
-using SixLabors.ImageSharp.Web.DependencyInjection;
using StackExchange.Profiling;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
@@ -26,63 +25,7 @@ namespace Umbraco.Extensions
/// Configures and use services required for using Umbraco
///
public static IUmbracoApplicationBuilder UseUmbraco(this IApplicationBuilder app)
- {
- // TODO: Should we do some checks like this to verify that the corresponding "Add" methods have been called for the
- // corresponding "Use" methods?
- // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Mvc/Mvc.Core/src/Builder/MvcApplicationBuilderExtensions.cs#L132
- if (app == null)
- {
- throw new ArgumentNullException(nameof(app));
- }
-
- IOptions startupOptions = app.ApplicationServices.GetRequiredService>();
- app.RunPrePipeline(startupOptions.Value);
-
- app.UseUmbracoCore();
- app.UseUmbracoRequestLogging();
-
- // We need to add this before UseRouting so that the UmbracoContext and other middlewares are executed
- // before endpoint routing middleware.
- app.UseUmbracoRouting();
-
- app.UseStatusCodePages();
-
- // Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
- // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too?
- app.UseImageSharp();
- app.UseStaticFiles();
- app.UseUmbracoPlugins();
-
- // UseRouting adds endpoint routing middleware, this means that middlewares registered after this one
- // will execute after endpoint routing. The ordering of everything is quite important here, see
- // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-5.0
- // where we need to have UseAuthentication and UseAuthorization proceeding this call but before
- // endpoints are defined.
- app.UseRouting();
- app.UseAuthentication();
- app.UseAuthorization();
-
- // This must come after auth because the culture is based on the auth'd user
- app.UseRequestLocalization();
-
- // Must be called after UseRouting and before UseEndpoints
- app.UseSession();
-
- // DO NOT PUT ANY UseEndpoints declarations here!! Those must all come very last in the pipeline,
- // endpoints are terminating middleware. All of our endpoints are declared in ext of IUmbracoApplicationBuilder
-
- return ActivatorUtilities.CreateInstance(
- app.ApplicationServices,
- new object[] { app });
- }
-
- private static void RunPrePipeline(this IApplicationBuilder app, UmbracoPipelineOptions startupOptions)
- {
- foreach (IUmbracoPipelineFilter filter in startupOptions.PipelineFilters)
- {
- filter.OnPrePipeline(app);
- }
- }
+ => new UmbracoApplicationBuilder(app);
///
/// Returns true if Umbraco is greater than
@@ -158,7 +101,12 @@ namespace Umbraco.Extensions
return app;
}
- public static IApplicationBuilder UseUmbracoPlugins(this IApplicationBuilder app)
+ ///
+ /// Allow static file access for App_Plugins folders
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseUmbracoPluginsStaticFiles(this IApplicationBuilder app)
{
var hostingEnvironment = app.ApplicationServices.GetRequiredService();
var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>();
diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.RuntimeMinification.cs b/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.RuntimeMinification.cs
index 0d8c7df72b..74b67c36a6 100644
--- a/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.RuntimeMinification.cs
+++ b/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.RuntimeMinification.cs
@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Web.Common.Extensions
///
/// Enables runtime minification for Umbraco
///
- public static IUmbracoEndpointBuilder UseUmbracoRuntimeMinificationEndpoints(this IUmbracoEndpointBuilder app)
+ public static IUmbracoEndpointBuilderContext UseUmbracoRuntimeMinificationEndpoints(this IUmbracoEndpointBuilderContext app)
{
if (app == null)
{
diff --git a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs
index dd52b397d3..80729412a3 100644
--- a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs
+++ b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs
@@ -198,6 +198,9 @@ namespace Umbraco.Cms.Web.Common.Security
// code taken from aspnetcore: https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// we also override to set the current HttpContext principal since this isn't done by default
+ // we also need to call our handle login to ensure all date/events are set
+ await HandleSignIn(user, user.UserName, SignInResult.Success);
+
var userPrincipal = await CreateUserPrincipalAsync(user);
foreach (var claim in additionalClaims)
{
@@ -363,7 +366,7 @@ namespace Umbraco.Cms.Web.Common.Security
await Context.SignOutAsync(ExternalAuthenticationType);
}
if (loginProvider == null)
- {
+ {
await SignInWithClaimsAsync(user, isPersistent, new Claim[] { new Claim("amr", "pwd") });
}
else
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js
index b44f79dd65..10092aaf38 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js
@@ -5,63 +5,66 @@
**/
function externalLoginInfoService(externalLoginInfo, umbRequestHelper) {
- function getLoginProvider(provider) {
- if (provider) {
- var found = _.find(externalLoginInfo.providers, x => x.authType == provider);
- return found;
- }
- return null;
+ function getLoginProvider(provider) {
+ if (provider) {
+ var found = _.find(externalLoginInfo.providers, x => x.authType == provider);
+ return found;
}
+ return null;
+ }
- function getLoginProviderView(provider) {
- if (provider && provider.properties && provider.properties.CustomBackOfficeView) {
- return umbRequestHelper.convertVirtualToAbsolutePath(provider.properties.CustomBackOfficeView);
- }
- return null;
+ function getLoginProviderView(provider) {
+ if (provider && provider.properties && provider.properties.CustomBackOfficeView) {
+ return umbRequestHelper.convertVirtualToAbsolutePath(provider.properties.CustomBackOfficeView);
}
+ return null;
+ }
- /**
- * Returns true if any provider denies local login if `provider` is null, else whether the passed
- * @param {any} provider
- */
- function hasDenyLocalLogin(provider) {
- if (!provider) {
- return _.some(externalLoginInfo.providers, x => x.properties && (x.properties.DenyLocalLogin === true));
- }
- else {
- return provider && provider.properties && (provider.properties.DenyLocalLogin === true);
- }
+ /**
+ * Returns true if any provider denies local login if `provider` is null, else whether the passed
+ * @param {any} provider
+ */
+ function hasDenyLocalLogin(provider) {
+ if (!provider) {
+ return _.some(externalLoginInfo.providers, x => x.properties && (x.properties.DenyLocalLogin === true));
}
-
- /**
- * Returns all login providers
- */
- function getLoginProviders() {
- return externalLoginInfo.providers;
+ else {
+ return provider && provider.properties && (provider.properties.DenyLocalLogin === true);
}
+ }
- /** Returns all logins providers that have options that the user can interact with */
- function getLoginProvidersWithOptions() {
- // only include providers that allow manual linking or ones that provide a custom view
- var providers = _.filter(externalLoginInfo.providers, x => {
- // transform the data and also include the custom view as a nicer property
- x.customView = getLoginProviderView(x);
- if (x.customView) {
- return true;
- }
- else {
- return x.properties.AutoLinkOptions.AllowManualLinking;
- }
- });
- return providers;
- }
+ /**
+ * Returns all login providers
+ */
+ function getLoginProviders() {
+ return externalLoginInfo.providers;
+ }
- return {
- hasDenyLocalLogin: hasDenyLocalLogin,
- getLoginProvider: getLoginProvider,
- getLoginProviders: getLoginProviders,
- getLoginProvidersWithOptions: getLoginProvidersWithOptions,
- getLoginProviderView: getLoginProviderView
- };
+ /** Returns all logins providers that have options that the user can interact with */
+ function getLoginProvidersWithOptions() {
+ // only include providers that allow manual linking or ones that provide a custom view
+ var providers = _.filter(externalLoginInfo.providers, x => {
+ // transform the data and also include the custom view as a nicer property
+ x.customView = getLoginProviderView(x);
+ if (x.customView) {
+ return true;
+ }
+ else if (x.properties.AutoLinkOptions) {
+ return x.properties.AutoLinkOptions.AllowManualLinking;
+ }
+ else {
+ return false;
+ }
+ });
+ return providers;
+ }
+
+ return {
+ hasDenyLocalLogin: hasDenyLocalLogin,
+ getLoginProvider: getLoginProvider,
+ getLoginProviders: getLoginProviders,
+ getLoginProvidersWithOptions: getLoginProvidersWithOptions,
+ getLoginProviderView: getLoginProviderView
+ };
}
angular.module('umbraco.services').factory('externalLoginInfoService', externalLoginInfoService);
diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs
index 0419a8c0e4..73eeef864f 100644
--- a/src/Umbraco.Web.UI.NetCore/Startup.cs
+++ b/src/Umbraco.Web.UI.NetCore/Startup.cs
@@ -62,8 +62,8 @@ namespace Umbraco.Cms.Web.UI.NetCore
app.UseUmbraco()
.WithMiddleware(u =>
{
- u.WithBackOffice();
- u.WithWebsite();
+ u.UseBackOffice();
+ u.UseWebsite();
})
.WithEndpoints(u =>
{
diff --git a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs
index 2b4f0ac491..5ba0ffc3fe 100644
--- a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs
+++ b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Net;
using System.Text;
using System.Text.Encodings.Web;
+using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.DataProtection;
@@ -18,6 +19,7 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Logging;
+using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Core.Web.Mvc;
using Umbraco.Cms.Web.Common.Controllers;
@@ -85,7 +87,7 @@ namespace Umbraco.Extensions
}
- public static IHtmlContent CachedPartial(
+ public static async Task CachedPartialAsync(
this IHtmlHelper htmlHelper,
string partialViewName,
object model,
@@ -115,11 +117,9 @@ namespace Umbraco.Extensions
if (cacheByMember)
{
- // TODO reintroduce when members are migrated
- throw new NotImplementedException("Reintroduce when members are migrated");
- // var helper = Current.MembershipHelper;
- // var currentMember = helper.GetCurrentMember();
- // cacheKey.AppendFormat("m{0}-", currentMember?.Id ?? 0);
+ var memberManager = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService();
+ var currentMember = await memberManager.GetCurrentMemberAsync();
+ cacheKey.AppendFormat("m{0}-", currentMember?.Id ?? "0");
}
if (contextualKeyBuilder != null)
diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs b/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs
index 843d3030ec..c549609397 100644
--- a/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs
+++ b/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs
@@ -18,7 +18,7 @@ namespace Umbraco.Extensions
///
///
///
- public static IUmbracoMiddlewareBuilder WithWebsite(this IUmbracoMiddlewareBuilder builder)
+ public static IUmbracoApplicationBuilderContext UseWebsite(this IUmbracoApplicationBuilderContext builder)
{
builder.AppBuilder.UseMiddleware();
builder.AppBuilder.UseMiddleware();
@@ -28,7 +28,7 @@ namespace Umbraco.Extensions
///
/// Sets up routes for the front-end umbraco website
///
- public static IUmbracoEndpointBuilder UseWebsiteEndpoints(this IUmbracoEndpointBuilder builder)
+ public static IUmbracoEndpointBuilderContext UseWebsiteEndpoints(this IUmbracoEndpointBuilderContext builder)
{
if (builder == null)
{