Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| efd6bd82bd | |||
| 17527c99eb | |||
| 361557dc39 | |||
| 342b2087e9 | |||
| dc371c1979 | |||
| 2faa4defd8 | |||
| bb8356d7db | |||
| 686f6752f3 | |||
| 66ff4d191a | |||
| 21218b0332 | |||
| 23ccdeb2cf | |||
| 6148ce4e52 | |||
| 06850ed25c | |||
| 0320a56cb5 | |||
| b9e44f381b | |||
| cd27bb210f | |||
| 67f680a675 | |||
| 47c3e3a79f | |||
| 02f49a0039 | |||
| b07df63422 | |||
| 487e45b45b | |||
| 929c84822a | |||
| 993f11cd2c | |||
| 6544b5c272 | |||
| 817a458334 | |||
| f3bfc1ffb2 | |||
| 3603090421 | |||
| 79159b684d | |||
| 26d4d056be | |||
| 6ad28f1103 | |||
| 89cd655df2 | |||
| 14c4c4815d | |||
| bfb69a34ef | |||
| b675f6252c | |||
| d52420183e | |||
| 0cbe0b4796 | |||
| 0feb053e51 | |||
| 09c8ae4e7e | |||
| 2aaca865e7 | |||
| 77dda5a70f | |||
| f6a8d487cc | |||
| 59bf24c461 | |||
| 2748c3635d | |||
| 2252db0d55 | |||
| df896af476 | |||
| 4b605b2580 | |||
| 80d7f1b2c9 | |||
| 5ada85df29 |
+2
-2
@@ -18,5 +18,5 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("8.0.0")]
|
||||
|
||||
// these are FYI and changed automatically
|
||||
[assembly: AssemblyFileVersion("8.1.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.1.0")]
|
||||
[assembly: AssemblyFileVersion("8.1.1")]
|
||||
[assembly: AssemblyInformationalVersion("8.1.1")]
|
||||
|
||||
@@ -67,31 +67,34 @@ namespace Umbraco.Core
|
||||
: new NamedSemaphoreReleaser(_semaphore2);
|
||||
}
|
||||
|
||||
public Task<IDisposable> LockAsync()
|
||||
{
|
||||
var wait = _semaphore != null
|
||||
? _semaphore.WaitAsync()
|
||||
: _semaphore2.WaitOneAsync();
|
||||
//NOTE: We don't use the "Async" part of this lock at all
|
||||
//TODO: Remove this and rename this class something like SystemWideLock, then we can re-instate this logic if we ever need an Async lock again
|
||||
|
||||
return wait.IsCompleted
|
||||
? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
: wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()),
|
||||
this, CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
}
|
||||
//public Task<IDisposable> LockAsync()
|
||||
//{
|
||||
// var wait = _semaphore != null
|
||||
// ? _semaphore.WaitAsync()
|
||||
// : _semaphore2.WaitOneAsync();
|
||||
|
||||
public Task<IDisposable> LockAsync(int millisecondsTimeout)
|
||||
{
|
||||
var wait = _semaphore != null
|
||||
? _semaphore.WaitAsync(millisecondsTimeout)
|
||||
: _semaphore2.WaitOneAsync(millisecondsTimeout);
|
||||
// return wait.IsCompleted
|
||||
// ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
// : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()),
|
||||
// this, CancellationToken.None,
|
||||
// TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
//}
|
||||
|
||||
return wait.IsCompleted
|
||||
? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
: wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
|
||||
this, CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
}
|
||||
//public Task<IDisposable> LockAsync(int millisecondsTimeout)
|
||||
//{
|
||||
// var wait = _semaphore != null
|
||||
// ? _semaphore.WaitAsync(millisecondsTimeout)
|
||||
// : _semaphore2.WaitOneAsync(millisecondsTimeout);
|
||||
|
||||
// return wait.IsCompleted
|
||||
// ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
// : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
|
||||
// this, CancellationToken.None,
|
||||
// TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
//}
|
||||
|
||||
public IDisposable Lock()
|
||||
{
|
||||
|
||||
@@ -14,6 +14,23 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public const string InternalGenericPropertiesPrefix = "_umb_";
|
||||
|
||||
public static class Legacy
|
||||
{
|
||||
public static class Aliases
|
||||
{
|
||||
public const string Textbox = "Umbraco.Textbox";
|
||||
public const string Date = "Umbraco.Date";
|
||||
public const string ContentPicker2 = "Umbraco.ContentPicker2";
|
||||
public const string MediaPicker2 = "Umbraco.MediaPicker2";
|
||||
public const string MemberPicker2 = "Umbraco.MemberPicker2";
|
||||
public const string MultiNodeTreePicker2 = "Umbraco.MultiNodeTreePicker2";
|
||||
public const string TextboxMultiple = "Umbraco.TextboxMultiple";
|
||||
public const string RelatedLinks2 = "Umbraco.RelatedLinks2";
|
||||
public const string RelatedLinks = "Umbraco.RelatedLinks";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines Umbraco built-in property editor aliases.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Web.Hosting;
|
||||
@@ -113,7 +114,7 @@ namespace Umbraco.Core
|
||||
|
||||
lock (_locko)
|
||||
{
|
||||
_logger.Debug<MainDom>("Signaled {Signaled} ({SignalSource})", _signaled ? "(again)" : string.Empty, source);
|
||||
_logger.Debug<MainDom>("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source);
|
||||
if (_signaled) return;
|
||||
if (_isMainDom == false) return; // probably not needed
|
||||
_signaled = true;
|
||||
@@ -171,6 +172,7 @@ namespace Umbraco.Core
|
||||
// if more than 1 instance reach that point, one will get the lock
|
||||
// and the other one will timeout, which is accepted
|
||||
|
||||
//TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset?
|
||||
_asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds);
|
||||
_isMainDom = true;
|
||||
|
||||
@@ -181,6 +183,9 @@ namespace Umbraco.Core
|
||||
// which is accepted
|
||||
|
||||
_signal.Reset();
|
||||
|
||||
//WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread
|
||||
|
||||
_signal.WaitOneAsync()
|
||||
.ContinueWith(_ => OnSignal("signal"));
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
var sqlDataTypes = Sql()
|
||||
.Select<DataTypeDto>()
|
||||
.From<DataTypeDto>()
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == "Umbraco.RelatedLinks"
|
||||
|| x.EditorAlias == "Umbraco.RelatedLinks2");
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks
|
||||
|| x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2);
|
||||
|
||||
var dataTypes = Database.Fetch<DataTypeDto>(sqlDataTypes);
|
||||
var dataTypeIds = dataTypes.Select(x => x.NodeId).ToList();
|
||||
@@ -50,10 +50,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
var properties = Database.Fetch<PropertyDataDto>(sqlPropertyData);
|
||||
|
||||
// Create a Multi URL Picker datatype for the converted RelatedLinks data
|
||||
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var value = property.Value.ToString();
|
||||
var value = property.Value?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
continue;
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
Name = relatedLink.Caption,
|
||||
Target = relatedLink.NewWindow ? "_blank" : null,
|
||||
Udi = udi,
|
||||
// Should only have a URL if it's an external link otherwise it wil be a UDI
|
||||
// Should only have a URL if it's an external link otherwise it wil be a UDI
|
||||
Url = relatedLink.IsInternal == false ? relatedLink.Link : null
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
Database.Update(property);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -18,6 +19,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private static readonly ISet<string> LegacyAliases = new HashSet<string>()
|
||||
{
|
||||
Constants.PropertyEditors.Legacy.Aliases.Date,
|
||||
Constants.PropertyEditors.Legacy.Aliases.Textbox,
|
||||
Constants.PropertyEditors.Legacy.Aliases.ContentPicker2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.MediaPicker2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.MemberPicker2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple,
|
||||
Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2,
|
||||
};
|
||||
|
||||
public DataTypeMigration(IMigrationContext context, PreValueMigratorCollection preValueMigrators, PropertyEditorCollection propertyEditors, ILogger logger)
|
||||
: base(context)
|
||||
{
|
||||
@@ -70,16 +83,23 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
var newAlias = migrator.GetNewAlias(dataType.EditorAlias);
|
||||
if (newAlias == null)
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>("Skipping validation of configuration for data type {NodeId} : {EditorAlias}."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, dataType.EditorAlias);
|
||||
if (!LegacyAliases.Contains(dataType.EditorAlias))
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>(
|
||||
"Skipping validation of configuration for data type {NodeId} : {EditorAlias}."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, dataType.EditorAlias);
|
||||
}
|
||||
}
|
||||
else if (!_propertyEditors.TryGet(newAlias, out var propertyEditor))
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})"
|
||||
+ " because no property editor with that alias was found."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, newAlias, dataType.EditorAlias);
|
||||
if (!LegacyAliases.Contains(newAlias))
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})"
|
||||
+ " because no property editor with that alias was found."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, newAlias, dataType.EditorAlias);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
class ContentPickerPreValueMigrator : DefaultPreValueMigrator
|
||||
{
|
||||
public override bool CanMigrate(string editorAlias)
|
||||
=> editorAlias == "Umbraco.ContentPicker2";
|
||||
=> editorAlias == Constants.PropertyEditors.Legacy.Aliases.ContentPicker2;
|
||||
|
||||
public override string GetNewAlias(string editorAlias)
|
||||
=> null;
|
||||
|
||||
+3
-3
@@ -6,15 +6,15 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
{
|
||||
private readonly string[] _editors =
|
||||
{
|
||||
"Umbraco.MediaPicker2",
|
||||
"Umbraco.MediaPicker"
|
||||
Constants.PropertyEditors.Legacy.Aliases.MediaPicker2,
|
||||
Constants.PropertyEditors.Aliases.MediaPicker
|
||||
};
|
||||
|
||||
public override bool CanMigrate(string editorAlias)
|
||||
=> _editors.Contains(editorAlias);
|
||||
|
||||
public override string GetNewAlias(string editorAlias)
|
||||
=> "Umbraco.MediaPicker";
|
||||
=> Constants.PropertyEditors.Aliases.MediaPicker;
|
||||
|
||||
// you wish - but MediaPickerConfiguration lives in Umbraco.Web
|
||||
/*
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
private readonly string[] _editors =
|
||||
{
|
||||
"Umbraco.RadioButtonList",
|
||||
"Umbraco.CheckBoxList",
|
||||
"Umbraco.DropDown",
|
||||
"Umbraco.DropdownlistPublishingKeys",
|
||||
"Umbraco.DropDownMultiple",
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
var dataTypes = GetDataTypes("Umbraco.Date");
|
||||
var dataTypes = GetDataTypes(Constants.PropertyEditors.Legacy.Aliases.Date);
|
||||
|
||||
foreach (var dataType in dataTypes)
|
||||
{
|
||||
@@ -25,6 +25,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
config = (DateTimeConfiguration) new CustomDateTimeConfigurationEditor().FromDatabase(
|
||||
dataType.Configuration);
|
||||
|
||||
// If the Umbraco.Date type is the default from V7 and it has never been updated, then the
|
||||
// configuration is empty, and the format stuff is handled by in JS by moment.js. - We can't do that
|
||||
// after the migration, so we force the format to the default from V7.
|
||||
if (string.IsNullOrEmpty(dataType.Configuration))
|
||||
{
|
||||
config.Format = "YYYY-MM-DD";
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -12,12 +12,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.ContentPicker + "2", Constants.PropertyEditors.Aliases.ContentPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.MediaPicker + "2", Constants.PropertyEditors.Aliases.MediaPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.MemberPicker + "2", Constants.PropertyEditors.Aliases.MemberPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.MultiNodeTreePicker + "2", Constants.PropertyEditors.Aliases.MultiNodeTreePicker);
|
||||
RenameDataType("Umbraco.TextboxMultiple", Constants.PropertyEditors.Aliases.TextArea, false);
|
||||
RenameDataType("Umbraco.Textbox", Constants.PropertyEditors.Aliases.TextBox, false);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.ContentPicker2, Constants.PropertyEditors.Aliases.ContentPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, Constants.PropertyEditors.Aliases.MediaPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MemberPicker2, Constants.PropertyEditors.Aliases.MemberPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2, Constants.PropertyEditors.Aliases.MultiNodeTreePicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple, Constants.PropertyEditors.Aliases.TextArea, false);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.Textbox, Constants.PropertyEditors.Aliases.TextBox, false);
|
||||
}
|
||||
|
||||
private void RenameDataType(string fromAlias, string toAlias, bool checkCollision = true)
|
||||
@@ -43,7 +43,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
$"Property Editors. Before upgrading to v8, any Data Types using property editors that are named with the prefix '(Obsolete)' must be migrated " +
|
||||
$"to the non-obsolete v7 property editors of the same type.");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Database.Execute(Sql()
|
||||
|
||||
@@ -14,11 +14,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
public override void Migrate()
|
||||
{
|
||||
//Get anything currently scheduled
|
||||
var scheduleSql = new Sql()
|
||||
.Select("nodeId", "releaseDate", "expireDate")
|
||||
var releaseSql = new Sql()
|
||||
.Select("nodeId", "releaseDate")
|
||||
.From("umbracoDocument")
|
||||
.Where("releaseDate IS NOT NULL OR expireDate IS NOT NULL");
|
||||
var schedules = Database.Dictionary<int, (DateTime? releaseDate, DateTime? expireDate)> (scheduleSql);
|
||||
.Where("releaseDate IS NOT NULL");
|
||||
var releases = Database.Dictionary<int, DateTime> (releaseSql);
|
||||
|
||||
var expireSql = new Sql()
|
||||
.Select("nodeId", "expireDate")
|
||||
.From("umbracoDocument")
|
||||
.Where("expireDate IS NOT NULL");
|
||||
var expires = Database.Dictionary<int, DateTime>(expireSql);
|
||||
|
||||
|
||||
//drop old cols
|
||||
Delete.Column("releaseDate").FromTable("umbracoDocument").Do();
|
||||
@@ -27,20 +34,25 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
Create.Table<ContentScheduleDto>(true).Do();
|
||||
|
||||
//migrate the schedule
|
||||
foreach(var s in schedules)
|
||||
foreach(var s in releases)
|
||||
{
|
||||
var date = s.Value.releaseDate;
|
||||
var date = s.Value;
|
||||
var action = ContentScheduleAction.Release.ToString();
|
||||
if (!date.HasValue)
|
||||
{
|
||||
date = s.Value.expireDate;
|
||||
action = ContentScheduleAction.Expire.ToString();
|
||||
}
|
||||
|
||||
Insert.IntoTable(ContentScheduleDto.TableName)
|
||||
.Row(new { nodeId = s.Key, date = date.Value, action = action })
|
||||
.Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action })
|
||||
.Do();
|
||||
}
|
||||
|
||||
foreach (var s in expires)
|
||||
{
|
||||
var date = s.Value;
|
||||
var action = ContentScheduleAction.Expire.ToString();
|
||||
|
||||
Insert.IntoTable(ContentScheduleDto.TableName)
|
||||
.Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action })
|
||||
.Do();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-8
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
@@ -36,6 +37,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
|
||||
var properties = Database.Fetch<PropertyDataDto>(sqlPropertyData);
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var value = property.TextValue;
|
||||
@@ -43,19 +45,34 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
|
||||
if (property.PropertyTypeDto.DataTypeDto.EditorAlias == Constants.PropertyEditors.Aliases.Grid)
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject<JObject>(value);
|
||||
var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
|
||||
|
||||
foreach (var control in allControls.SelectMany(c => c))
|
||||
try
|
||||
{
|
||||
var controlValue = control["value"];
|
||||
if (controlValue.Type == JTokenType.String)
|
||||
var obj = JsonConvert.DeserializeObject<JObject>(value);
|
||||
var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
|
||||
|
||||
foreach (var control in allControls.SelectMany(c => c))
|
||||
{
|
||||
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
|
||||
var controlValue = control["value"];
|
||||
if (controlValue.Type == JTokenType.String)
|
||||
{
|
||||
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
|
||||
}
|
||||
}
|
||||
|
||||
property.TextValue = JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
exceptions.Add(new InvalidOperationException(
|
||||
"Cannot deserialize the value as json. This can be because the property editor " +
|
||||
"type is changed from another type into a grid. Old versions of the value in this " +
|
||||
"property can have the structure from the old property editor type. This needs to be " +
|
||||
"changed manually before updating the database.\n" +
|
||||
$"Property info: Id = {property.Id}, LanguageId = {property.LanguageId}, VersionId = {property.VersionId}, Value = {property.Value}"
|
||||
, e));
|
||||
continue;
|
||||
}
|
||||
|
||||
property.TextValue = JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -65,6 +82,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
Database.Update(property);
|
||||
}
|
||||
|
||||
|
||||
if (exceptions.Any())
|
||||
{
|
||||
throw new AggregateException("One or more errors related to unexpected data in grid values occurred.", exceptions);
|
||||
}
|
||||
|
||||
Context.AddPostMigration<RebuildPublishedSnapshot>();
|
||||
}
|
||||
|
||||
|
||||
@@ -103,18 +103,26 @@ namespace Umbraco.Core
|
||||
/// Gets the children of the content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="culture">The specific culture to get the url children for. If null is used the current culture is used (Default is null).</param>
|
||||
/// <param name="culture">
|
||||
/// The specific culture to get the url children for. Default is null which will use the current culture in <see cref="VariationContext"/>
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// <para>Gets children that are available for the specified culture.</para>
|
||||
/// <para>Children are sorted by their sortOrder.</para>
|
||||
/// <para>The '*' culture and supported and returns everything.</para>
|
||||
/// <para>
|
||||
/// For culture,
|
||||
/// if null is used the current culture is used.
|
||||
/// If an empty string is used only invariant children are returned.
|
||||
/// If "*" is used all children are returned.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If a variant culture is specified or there is a current culture in the <see cref="VariationContext"/> then the Children returned
|
||||
/// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture.
|
||||
/// However, if an empty string is specified only invariant children are returned.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, string culture = null)
|
||||
{
|
||||
// invariant has invariant value (whatever the requested culture)
|
||||
if (!content.ContentType.VariesByCulture() && culture != "*")
|
||||
culture = "";
|
||||
|
||||
// handle context culture for variant
|
||||
if (culture == null)
|
||||
culture = VariationContextAccessor?.VariationContext?.Culture ?? "";
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Umbraco.Core.Runtime
|
||||
// there should be none, really - this is here "just in case"
|
||||
Compose(composition);
|
||||
|
||||
// acquire the main domain
|
||||
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
|
||||
AcquireMainDom(mainDom);
|
||||
|
||||
// determine our runtime level
|
||||
@@ -218,13 +218,13 @@ namespace Umbraco.Core.Runtime
|
||||
IOHelper.SetRootDirectory(path);
|
||||
}
|
||||
|
||||
private void AcquireMainDom(MainDom mainDom)
|
||||
private bool AcquireMainDom(MainDom mainDom)
|
||||
{
|
||||
using (var timer = ProfilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired."))
|
||||
{
|
||||
try
|
||||
{
|
||||
mainDom.Acquire();
|
||||
return mainDom.Acquire();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -97,6 +97,11 @@ namespace Umbraco.Core
|
||||
/// <param name="request"></param>
|
||||
internal void EnsureApplicationUrl(HttpRequestBase request = null)
|
||||
{
|
||||
//Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that
|
||||
// it changes the URL to `localhost:80` which actually doesn't work for pinging itself, it only works internally in Azure. The ironic part
|
||||
// about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626
|
||||
|
||||
|
||||
// see U4-10626 - in some cases we want to reset the application url
|
||||
// (this is a simplified version of what was in 7.x)
|
||||
// note: should this be optional? is it expensive?
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace Umbraco.Core
|
||||
handle,
|
||||
(state, timedOut) =>
|
||||
{
|
||||
//TODO: We aren't checking if this is timed out
|
||||
|
||||
tcs.SetResult(null);
|
||||
|
||||
// we take a lock here to make sure the outer method has completed setting the local variable callbackHandle.
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
_source = new TestDataSource(kits);
|
||||
|
||||
// at last, create the complete NuCache snapshot service!
|
||||
var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
_snapshotService = new PublishedSnapshotService(options,
|
||||
null,
|
||||
runtime,
|
||||
@@ -151,117 +151,139 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Mock.Get(factory).Setup(x => x.GetInstance(typeof(IVariationContextAccessor))).Returns(_variationAccesor);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> GetNestedVariantKits()
|
||||
{
|
||||
var paths = new Dictionary<int, string> { { -1, "-1" } };
|
||||
|
||||
//1x variant (root)
|
||||
yield return CreateVariantKit(1, -1, 1, paths);
|
||||
|
||||
//1x invariant under root
|
||||
yield return CreateInvariantKit(4, 1, 1, paths);
|
||||
|
||||
//1x variant under root
|
||||
yield return CreateVariantKit(7, 1, 4, paths);
|
||||
|
||||
//2x mixed under invariant
|
||||
yield return CreateVariantKit(10, 4, 1, paths);
|
||||
yield return CreateInvariantKit(11, 4, 2, paths);
|
||||
|
||||
//2x mixed under variant
|
||||
yield return CreateVariantKit(12, 7, 1, paths);
|
||||
yield return CreateInvariantKit(13, 7, 2, paths);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> GetInvariantKits()
|
||||
{
|
||||
var paths = new Dictionary<int, string> { { -1, "-1" } };
|
||||
|
||||
ContentNodeKit CreateKit(int id, int parentId, int sortOrder)
|
||||
yield return CreateInvariantKit(1, -1, 1, paths);
|
||||
yield return CreateInvariantKit(2, -1, 2, paths);
|
||||
yield return CreateInvariantKit(3, -1, 3, paths);
|
||||
|
||||
yield return CreateInvariantKit(4, 1, 1, paths);
|
||||
yield return CreateInvariantKit(5, 1, 2, paths);
|
||||
yield return CreateInvariantKit(6, 1, 3, paths);
|
||||
|
||||
yield return CreateInvariantKit(7, 2, 3, paths);
|
||||
yield return CreateInvariantKit(8, 2, 2, paths);
|
||||
yield return CreateInvariantKit(9, 2, 1, paths);
|
||||
|
||||
yield return CreateInvariantKit(10, 3, 1, paths);
|
||||
|
||||
yield return CreateInvariantKit(11, 4, 1, paths);
|
||||
yield return CreateInvariantKit(12, 4, 2, paths);
|
||||
}
|
||||
|
||||
private ContentNodeKit CreateInvariantKit(int id, int parentId, int sortOrder, Dictionary<int, string> paths)
|
||||
{
|
||||
if (!paths.TryGetValue(parentId, out var parentPath))
|
||||
throw new Exception("Unknown parent.");
|
||||
|
||||
var path = paths[id] = parentPath + "," + id;
|
||||
var level = path.Count(x => x == ',');
|
||||
var now = DateTime.Now;
|
||||
|
||||
return new ContentNodeKit
|
||||
{
|
||||
if (!paths.TryGetValue(parentId, out var parentPath))
|
||||
throw new Exception("Unknown parent.");
|
||||
|
||||
var path = paths[id] = parentPath + "," + id;
|
||||
var level = path.Count(x => x == ',');
|
||||
var now = DateTime.Now;
|
||||
|
||||
return new ContentNodeKit
|
||||
ContentTypeId = _contentTypeInvariant.Id,
|
||||
Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
|
||||
DraftData = null,
|
||||
PublishedData = new ContentData
|
||||
{
|
||||
ContentTypeId = _contentTypeInvariant.Id,
|
||||
Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
|
||||
DraftData = null,
|
||||
PublishedData = new ContentData
|
||||
{
|
||||
Name = "N" + id,
|
||||
Published = true,
|
||||
TemplateId = 0,
|
||||
VersionId = 1,
|
||||
VersionDate = now,
|
||||
WriterId = 0,
|
||||
Properties = new Dictionary<string, PropertyData[]>(),
|
||||
CultureInfos = new Dictionary<string, CultureVariation>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
yield return CreateKit(1, -1, 1);
|
||||
yield return CreateKit(2, -1, 2);
|
||||
yield return CreateKit(3, -1, 3);
|
||||
|
||||
yield return CreateKit(4, 1, 1);
|
||||
yield return CreateKit(5, 1, 2);
|
||||
yield return CreateKit(6, 1, 3);
|
||||
|
||||
yield return CreateKit(7, 2, 3);
|
||||
yield return CreateKit(8, 2, 2);
|
||||
yield return CreateKit(9, 2, 1);
|
||||
|
||||
yield return CreateKit(10, 3, 1);
|
||||
|
||||
yield return CreateKit(11, 4, 1);
|
||||
yield return CreateKit(12, 4, 2);
|
||||
Name = "N" + id,
|
||||
Published = true,
|
||||
TemplateId = 0,
|
||||
VersionId = 1,
|
||||
VersionDate = now,
|
||||
WriterId = 0,
|
||||
Properties = new Dictionary<string, PropertyData[]>(),
|
||||
CultureInfos = new Dictionary<string, CultureVariation>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> GetVariantKits()
|
||||
{
|
||||
var paths = new Dictionary<int, string> { { -1, "-1" } };
|
||||
|
||||
Dictionary<string, CultureVariation> GetCultureInfos(int id, DateTime now)
|
||||
yield return CreateVariantKit(1, -1, 1, paths);
|
||||
yield return CreateVariantKit(2, -1, 2, paths);
|
||||
yield return CreateVariantKit(3, -1, 3, paths);
|
||||
|
||||
yield return CreateVariantKit(4, 1, 1, paths);
|
||||
yield return CreateVariantKit(5, 1, 2, paths);
|
||||
yield return CreateVariantKit(6, 1, 3, paths);
|
||||
|
||||
yield return CreateVariantKit(7, 2, 3, paths);
|
||||
yield return CreateVariantKit(8, 2, 2, paths);
|
||||
yield return CreateVariantKit(9, 2, 1, paths);
|
||||
|
||||
yield return CreateVariantKit(10, 3, 1, paths);
|
||||
|
||||
yield return CreateVariantKit(11, 4, 1, paths);
|
||||
yield return CreateVariantKit(12, 4, 2, paths);
|
||||
}
|
||||
|
||||
private static Dictionary<string, CultureVariation> GetCultureInfos(int id, DateTime now)
|
||||
{
|
||||
var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
|
||||
var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 };
|
||||
|
||||
var infos = new Dictionary<string, CultureVariation>();
|
||||
if (en.Contains(id))
|
||||
infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false };
|
||||
if (fr.Contains(id))
|
||||
infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false };
|
||||
return infos;
|
||||
}
|
||||
|
||||
private ContentNodeKit CreateVariantKit(int id, int parentId, int sortOrder, Dictionary<int, string> paths)
|
||||
{
|
||||
if (!paths.TryGetValue(parentId, out var parentPath))
|
||||
throw new Exception("Unknown parent.");
|
||||
|
||||
var path = paths[id] = parentPath + "," + id;
|
||||
var level = path.Count(x => x == ',');
|
||||
var now = DateTime.Now;
|
||||
|
||||
return new ContentNodeKit
|
||||
{
|
||||
var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
|
||||
var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 };
|
||||
|
||||
var infos = new Dictionary<string, CultureVariation>();
|
||||
if (en.Contains(id))
|
||||
infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false };
|
||||
if (fr.Contains(id))
|
||||
infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false };
|
||||
return infos;
|
||||
}
|
||||
|
||||
ContentNodeKit CreateKit(int id, int parentId, int sortOrder)
|
||||
{
|
||||
if (!paths.TryGetValue(parentId, out var parentPath))
|
||||
throw new Exception("Unknown parent.");
|
||||
|
||||
var path = paths[id] = parentPath + "," + id;
|
||||
var level = path.Count(x => x == ',');
|
||||
var now = DateTime.Now;
|
||||
|
||||
return new ContentNodeKit
|
||||
ContentTypeId = _contentTypeVariant.Id,
|
||||
Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
|
||||
DraftData = null,
|
||||
PublishedData = new ContentData
|
||||
{
|
||||
ContentTypeId = _contentTypeVariant.Id,
|
||||
Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
|
||||
DraftData = null,
|
||||
PublishedData = new ContentData
|
||||
{
|
||||
Name = "N" + id,
|
||||
Published = true,
|
||||
TemplateId = 0,
|
||||
VersionId = 1,
|
||||
VersionDate = now,
|
||||
WriterId = 0,
|
||||
Properties = new Dictionary<string, PropertyData[]>(),
|
||||
CultureInfos = GetCultureInfos(id, now)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
yield return CreateKit(1, -1, 1);
|
||||
yield return CreateKit(2, -1, 2);
|
||||
yield return CreateKit(3, -1, 3);
|
||||
|
||||
yield return CreateKit(4, 1, 1);
|
||||
yield return CreateKit(5, 1, 2);
|
||||
yield return CreateKit(6, 1, 3);
|
||||
|
||||
yield return CreateKit(7, 2, 3);
|
||||
yield return CreateKit(8, 2, 2);
|
||||
yield return CreateKit(9, 2, 1);
|
||||
|
||||
yield return CreateKit(10, 3, 1);
|
||||
|
||||
yield return CreateKit(11, 4, 1);
|
||||
yield return CreateKit(12, 4, 2);
|
||||
Name = "N" + id,
|
||||
Published = true,
|
||||
TemplateId = 0,
|
||||
VersionId = 1,
|
||||
VersionDate = now,
|
||||
WriterId = 0,
|
||||
Properties = new Dictionary<string, PropertyData[]>(),
|
||||
CultureInfos = GetCultureInfos(id, now)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> GetVariantWithDraftKits()
|
||||
@@ -660,6 +682,96 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NestedVariationChildrenTest()
|
||||
{
|
||||
var mixedKits = GetNestedVariantKits();
|
||||
Init(mixedKits);
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
|
||||
//TEST with en-us variation context
|
||||
|
||||
_variationAccesor.VariationContext = new VariationContext("en-US");
|
||||
|
||||
var documents = snapshot.Content.GetAtRoot().ToArray();
|
||||
AssertDocuments(documents, "N1-en-US");
|
||||
|
||||
documents = snapshot.Content.GetById(1).Children().ToArray();
|
||||
AssertDocuments(documents, "N4", "N7-en-US");
|
||||
|
||||
//Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants
|
||||
documents = snapshot.Content.GetById(4).Children().ToArray();
|
||||
AssertDocuments(documents, "N10-en-US", "N11");
|
||||
|
||||
//Get the variant and list children, there's a variation context so it should return invariant AND en-us variants
|
||||
documents = snapshot.Content.GetById(7).Children().ToArray();
|
||||
AssertDocuments(documents, "N12-en-US", "N13");
|
||||
|
||||
//TEST with fr-fr variation context
|
||||
|
||||
_variationAccesor.VariationContext = new VariationContext("fr-FR");
|
||||
|
||||
documents = snapshot.Content.GetAtRoot().ToArray();
|
||||
AssertDocuments(documents, "N1-fr-FR");
|
||||
|
||||
documents = snapshot.Content.GetById(1).Children().ToArray();
|
||||
AssertDocuments(documents, "N4", "N7-fr-FR");
|
||||
|
||||
//Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants
|
||||
documents = snapshot.Content.GetById(4).Children().ToArray();
|
||||
AssertDocuments(documents, "N10-fr-FR", "N11");
|
||||
|
||||
//Get the variant and list children, there's a variation context so it should return invariant AND en-us variants
|
||||
documents = snapshot.Content.GetById(7).Children().ToArray();
|
||||
AssertDocuments(documents, "N12-fr-FR", "N13");
|
||||
|
||||
//TEST specific cultures
|
||||
|
||||
documents = snapshot.Content.GetAtRoot("fr-FR").ToArray();
|
||||
AssertDocuments(documents, "N1-fr-FR");
|
||||
|
||||
documents = snapshot.Content.GetById(1).Children("fr-FR").ToArray();
|
||||
AssertDocuments(documents, "N4", "N7-fr-FR"); //NOTE: Returns invariant, this is expected
|
||||
documents = snapshot.Content.GetById(1).Children("").ToArray();
|
||||
AssertDocuments(documents, "N4"); //Only returns invariant since that is what was requested
|
||||
|
||||
documents = snapshot.Content.GetById(4).Children("fr-FR").ToArray();
|
||||
AssertDocuments(documents, "N10-fr-FR", "N11"); //NOTE: Returns invariant, this is expected
|
||||
documents = snapshot.Content.GetById(4).Children("").ToArray();
|
||||
AssertDocuments(documents, "N11"); //Only returns invariant since that is what was requested
|
||||
|
||||
documents = snapshot.Content.GetById(7).Children("fr-FR").ToArray();
|
||||
AssertDocuments(documents, "N12-fr-FR", "N13"); //NOTE: Returns invariant, this is expected
|
||||
documents = snapshot.Content.GetById(7).Children("").ToArray();
|
||||
AssertDocuments(documents, "N13"); //Only returns invariant since that is what was requested
|
||||
|
||||
//TEST without variation context
|
||||
// This will actually convert the culture to "" which will be invariant since that's all it will know how to do
|
||||
// This will return a NULL name for culture specific entities because there is no variation context
|
||||
|
||||
_variationAccesor.VariationContext = null;
|
||||
|
||||
documents = snapshot.Content.GetAtRoot().ToArray();
|
||||
//will return nothing because there's only variant at root
|
||||
Assert.AreEqual(0, documents.Length);
|
||||
//so we'll continue to getting the known variant, do not fully assert this because the Name will NULL
|
||||
documents = snapshot.Content.GetAtRoot("fr-FR").ToArray();
|
||||
Assert.AreEqual(1, documents.Length);
|
||||
|
||||
documents = snapshot.Content.GetById(1).Children().ToArray();
|
||||
AssertDocuments(documents, "N4");
|
||||
|
||||
//Get the invariant and list children
|
||||
documents = snapshot.Content.GetById(4).Children().ToArray();
|
||||
AssertDocuments(documents, "N11");
|
||||
|
||||
//Get the variant and list children
|
||||
documents = snapshot.Content.GetById(7).Children().ToArray();
|
||||
AssertDocuments(documents, "N13");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void VariantChildrenTest()
|
||||
{
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
_variationAccesor = new TestVariationContextAccessor();
|
||||
|
||||
// at last, create the complete NuCache snapshot service!
|
||||
var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
_snapshotService = new PublishedSnapshotService(options,
|
||||
null,
|
||||
runtime,
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Umbraco.Tests.Scoping
|
||||
|
||||
protected override IPublishedSnapshotService CreatePublishedSnapshotService()
|
||||
{
|
||||
var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
|
||||
var runtimeStateMock = new Mock<IRuntimeState>();
|
||||
runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run);
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Web;
|
||||
using System.Web.Helpers;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Tests.Security
|
||||
{
|
||||
[TestFixture]
|
||||
public class UmbracoAntiForgeryAdditionalDataProviderTests
|
||||
{
|
||||
[Test]
|
||||
public void Test_Wrapped_Non_BeginUmbracoForm()
|
||||
{
|
||||
var wrapped = Mock.Of<IAntiForgeryAdditionalDataProvider>(x => x.GetAdditionalData(It.IsAny<HttpContextBase>()) == "custom");
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var data = provider.GetAdditionalData(httpContextFactory.HttpContext);
|
||||
|
||||
Assert.IsTrue(data.DetectIsJson());
|
||||
var json = JsonConvert.DeserializeObject<UmbracoAntiForgeryAdditionalDataProvider.AdditionalData>(data);
|
||||
Assert.AreEqual(null, json.Ufprt);
|
||||
Assert.IsTrue(json.Stamp != default);
|
||||
Assert.AreEqual("custom", json.WrappedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Null_Wrapped_Non_BeginUmbracoForm()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var data = provider.GetAdditionalData(httpContextFactory.HttpContext);
|
||||
|
||||
Assert.IsTrue(data.DetectIsJson());
|
||||
var json = JsonConvert.DeserializeObject<UmbracoAntiForgeryAdditionalDataProvider.AdditionalData>(data);
|
||||
Assert.AreEqual(null, json.Ufprt);
|
||||
Assert.IsTrue(json.Stamp != default);
|
||||
Assert.AreEqual("default", json.WrappedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_Non_Json()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "hello");
|
||||
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_Invalid_Json()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '0'}");
|
||||
Assert.IsFalse(isValid);
|
||||
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': ''}");
|
||||
Assert.IsFalse(isValid);
|
||||
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'hello': 'world'}");
|
||||
Assert.IsFalse(isValid);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_No_Request_Ufprt()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
//there is a ufprt in the additional data, but not in the request
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': 'ASBVDFDFDFDF'}");
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_No_AdditionalData_Ufprt()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var requestMock = Mock.Get(httpContextFactory.HttpContext.Request);
|
||||
requestMock.SetupGet(x => x["ufprt"]).Returns("ABCDEFG");
|
||||
|
||||
//there is a ufprt in the additional data, but not in the request
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}");
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_No_AdditionalData_Or_Request_Ufprt()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
|
||||
//there is a ufprt in the additional data, but not in the request
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}");
|
||||
Assert.IsTrue(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_Request_And_AdditionalData_Ufprt()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var requestMock = Mock.Get(httpContextFactory.HttpContext.Request);
|
||||
requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey());
|
||||
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsTrue(isValid);
|
||||
|
||||
routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_Wrapped_Request_And_AdditionalData_Ufprt()
|
||||
{
|
||||
var wrapped = Mock.Of<IAntiForgeryAdditionalDataProvider>(x => x.ValidateAdditionalData(It.IsAny<HttpContextBase>(), "custom") == true);
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped);
|
||||
|
||||
var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var requestMock = Mock.Get(httpContextFactory.HttpContext.Request);
|
||||
requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey());
|
||||
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsFalse(isValid);
|
||||
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'custom', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsTrue(isValid);
|
||||
|
||||
routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
protected override IPublishedSnapshotService CreatePublishedSnapshotService()
|
||||
{
|
||||
var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
|
||||
var runtimeStateMock = new Mock<IRuntimeState>();
|
||||
runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run);
|
||||
|
||||
@@ -147,7 +147,6 @@
|
||||
<Compile Include="Routing\MediaUrlProviderTests.cs" />
|
||||
<Compile Include="Runtimes\StandaloneTests.cs" />
|
||||
<Compile Include="Routing\GetContentUrlsTests.cs" />
|
||||
<Compile Include="Security\UmbracoAntiForgeryAdditionalDataProviderTests.cs" />
|
||||
<Compile Include="Services\AmbiguousEventTests.cs" />
|
||||
<Compile Include="Services\ContentServiceEventTests.cs" />
|
||||
<Compile Include="Services\ContentServicePublishBranchTests.cs" />
|
||||
@@ -268,6 +267,7 @@
|
||||
<Compile Include="StringNewlineExtensions.cs" />
|
||||
<Compile Include="Strings\StylesheetHelperTests.cs" />
|
||||
<Compile Include="Strings\StringValidationTests.cs" />
|
||||
<Compile Include="Web\Mvc\ValidateUmbracoFormRouteStringAttributeTests.cs" />
|
||||
<Compile Include="Web\UmbracoHelperTests.cs" />
|
||||
<Compile Include="Membership\MembershipProviderBaseTests.cs" />
|
||||
<Compile Include="Membership\UmbracoServiceMembershipProviderTests.cs" />
|
||||
|
||||
@@ -4,6 +4,7 @@ using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.Web.Mvc
|
||||
{
|
||||
|
||||
[TestFixture]
|
||||
public class HtmlHelperExtensionMethodsTests
|
||||
{
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Tests.Web.Mvc
|
||||
{
|
||||
[TestFixture]
|
||||
public class ValidateUmbracoFormRouteStringAttributeTests
|
||||
{
|
||||
[Test]
|
||||
public void Validate_Route_String()
|
||||
{
|
||||
var attribute = new ValidateUmbracoFormRouteStringAttribute();
|
||||
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(null, null, null, null));
|
||||
|
||||
const string ControllerName = "Test";
|
||||
const string ControllerAction = "Index";
|
||||
const string Area = "MyArea";
|
||||
var validUfprt = UrlHelperRenderExtensions.CreateEncryptedRouteString(ControllerName, ControllerAction, Area);
|
||||
|
||||
var invalidUfprt = validUfprt + "z";
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(invalidUfprt, null, null, null));
|
||||
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, "doesntMatch"));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, null));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, ControllerName, "doesntMatch", Area));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, ControllerName, null, Area));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, "doesntMatch", ControllerAction, Area));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, null, ControllerAction, Area));
|
||||
|
||||
Assert.DoesNotThrow(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, Area));
|
||||
Assert.DoesNotThrow(() => attribute.ValidateRouteString(validUfprt, ControllerName.ToLowerInvariant(), ControllerAction.ToLowerInvariant(), Area.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,10 @@
|
||||
content.apps[0].active = true;
|
||||
$scope.appChanged(content.apps[0]);
|
||||
}
|
||||
// otherwise make sure the save options are up to date with the current content state
|
||||
else {
|
||||
createButtons($scope.content);
|
||||
}
|
||||
|
||||
editorState.set(content);
|
||||
|
||||
|
||||
+6
-12
@@ -101,14 +101,14 @@ angular.module('umbraco.directives')
|
||||
var eventBindings = [];
|
||||
|
||||
function oneTimeClick(event) {
|
||||
// ignore clicks on button groups toggles (i.e. the save and publish button)
|
||||
var parents = $(event.target).closest("[data-element='button-group-toggle']");
|
||||
if (parents.length > 0) {
|
||||
return;
|
||||
}
|
||||
var el = event.target.nodeName;
|
||||
|
||||
//ignore link and button clicks
|
||||
var els = ["INPUT", "A", "BUTTON"];
|
||||
if (els.indexOf(el) >= 0) { return; }
|
||||
|
||||
// ignore clicks on new overlay
|
||||
parents = $(event.target).parents(".umb-overlay,.umb-tour");
|
||||
var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour");
|
||||
if (parents.length > 0) {
|
||||
return;
|
||||
}
|
||||
@@ -131,12 +131,6 @@ angular.module('umbraco.directives')
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore clicks on dialog actions
|
||||
var actions = $(event.target).parents(".umb-action");
|
||||
if (actions.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
//ignore clicks inside this element
|
||||
if ($(element).has($(event.target)).length > 0) {
|
||||
return;
|
||||
|
||||
@@ -404,7 +404,8 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
pageNumber: 100,
|
||||
filter: '',
|
||||
orderDirection: "Ascending",
|
||||
orderBy: "SortOrder"
|
||||
orderBy: "SortOrder",
|
||||
dataTypeKey: null
|
||||
};
|
||||
if (options === undefined) {
|
||||
options = {};
|
||||
@@ -421,6 +422,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
options.orderDirection = "Descending";
|
||||
}
|
||||
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
|
||||
+8
-3
@@ -51,13 +51,17 @@ angular.module("umbraco")
|
||||
$scope.acceptedMediatypes = types;
|
||||
});
|
||||
|
||||
var dataTypeKey = null;
|
||||
if($scope.model && $scope.model.dataTypeKey) {
|
||||
dataTypeKey = $scope.model.dataTypeKey;
|
||||
}
|
||||
$scope.searchOptions = {
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
filter: '',
|
||||
dataTypeKey: $scope.model.dataTypeKey
|
||||
dataTypeKey: dataTypeKey
|
||||
};
|
||||
|
||||
//preload selected item
|
||||
@@ -160,7 +164,7 @@ angular.module("umbraco")
|
||||
}
|
||||
|
||||
if (folder.id > 0) {
|
||||
entityResource.getAncestors(folder.id, "media", null, { dataTypeKey: $scope.model.dataTypeKey })
|
||||
entityResource.getAncestors(folder.id, "media", null, { dataTypeKey: dataTypeKey })
|
||||
.then(function (anc) {
|
||||
$scope.path = _.filter(anc,
|
||||
function (f) {
|
||||
@@ -311,6 +315,7 @@ angular.module("umbraco")
|
||||
if ($scope.searchOptions.filter) {
|
||||
searchMedia();
|
||||
} else {
|
||||
|
||||
// reset pagination
|
||||
$scope.searchOptions = {
|
||||
pageNumber: 1,
|
||||
@@ -318,7 +323,7 @@ angular.module("umbraco")
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
filter: '',
|
||||
dataTypeKey: $scope.model.dataTypeKey
|
||||
dataTypeKey: dataTypeKey
|
||||
};
|
||||
getChildren($scope.currentFolder.id);
|
||||
}
|
||||
|
||||
+2
@@ -19,6 +19,7 @@
|
||||
$scope.hasError = false;
|
||||
|
||||
$scope.internal = function ($event) {
|
||||
|
||||
$scope.currentEditLink = null;
|
||||
|
||||
var contentPicker = {
|
||||
@@ -41,6 +42,7 @@
|
||||
};
|
||||
|
||||
$scope.selectInternal = function ($event, link) {
|
||||
|
||||
$scope.currentEditLink = link;
|
||||
|
||||
var contentPicker = {
|
||||
|
||||
@@ -345,9 +345,9 @@
|
||||
<WebProjectProperties>
|
||||
<UseIIS>False</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>8100</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>8110</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:8100</IISUrl>
|
||||
<IISUrl>http://localhost:8110</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>False</UseCustomServer>
|
||||
<CustomServerUrl>
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Umbraco.Web.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[ValidateUmbracoFormRouteString]
|
||||
public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model)
|
||||
{
|
||||
if (ModelState.IsValid == false)
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Umbraco.Web.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[ValidateUmbracoFormRouteString]
|
||||
public ActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model)
|
||||
{
|
||||
if (ModelState.IsValid == false)
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Umbraco.Web.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[ValidateUmbracoFormRouteString]
|
||||
public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model)
|
||||
{
|
||||
var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Umbraco.Web.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[ValidateUmbracoFormRouteString]
|
||||
public ActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model)
|
||||
{
|
||||
if (ModelState.IsValid == false)
|
||||
|
||||
@@ -224,13 +224,6 @@ namespace Umbraco.Web
|
||||
_controllerName = controllerName;
|
||||
_encryptedString = UrlHelperRenderExtensions.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals);
|
||||
|
||||
//For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used.
|
||||
//In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against
|
||||
//the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms,
|
||||
//that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique
|
||||
//per user.
|
||||
_viewContext.HttpContext.Items["ufprt"] = _encryptedString;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -106,8 +106,10 @@ namespace Umbraco.Web.JavaScript
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fullPath = currentHttpContext.Server.MapPath(XmlFileMapper.FileMapDefaultFolder);
|
||||
{
|
||||
var fullPath = XmlFileMapper.FileMapDefaultFolder.StartsWith("~/")
|
||||
? currentHttpContext.Server.MapPath(XmlFileMapper.FileMapDefaultFolder)
|
||||
: XmlFileMapper.FileMapDefaultFolder;
|
||||
if (fullPath != null)
|
||||
{
|
||||
cdfTempDirectories.Add(fullPath);
|
||||
@@ -122,13 +124,12 @@ namespace Umbraco.Web.JavaScript
|
||||
var success = true;
|
||||
foreach (var directory in cdfTempDirectories)
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(directory);
|
||||
if (directoryInfo.Exists == false)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
directoryInfo.Delete(true);
|
||||
if (!Directory.Exists(directory))
|
||||
continue;
|
||||
|
||||
Directory.Delete(directory, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Web;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception that occurs when an Umbraco form route string is invalid
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Web.HttpException" />
|
||||
[Serializable]
|
||||
public sealed class HttpUmbracoFormRouteStringException : HttpException
|
||||
{
|
||||
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
|
||||
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that holds the contextual information about the source or destination.</param>
|
||||
private HttpUmbracoFormRouteStringException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message displayed to the client when the exception is thrown.</param>
|
||||
public HttpUmbracoFormRouteStringException(string message)
|
||||
: base(message)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message displayed to the client when the exception is thrown.</param>
|
||||
/// <param name="innerException">The <see cref="P:System.Exception.InnerException" />, if any, that threw the current exception.</param>
|
||||
public HttpUmbracoFormRouteStringException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{ }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Mvc;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute used to check that the request contains a valid Umbraco form request string.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Web.Mvc.FilterAttribute" />
|
||||
/// <seealso cref="System.Web.Mvc.IAuthorizationFilter" />
|
||||
/// <remarks>
|
||||
/// Applying this attribute/filter to a <see cref="SurfaceController"/> or SurfaceController Action will ensure that the Action can only be executed
|
||||
/// when it is routed to from within Umbraco, typically when rendering a form with BegingUmbracoForm. It will mean that the natural MVC route for this Action
|
||||
/// will fail with a <see cref="HttpUmbracoFormRouteStringException"/>.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class ValidateUmbracoFormRouteStringAttribute : FilterAttribute, IAuthorizationFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when authorization is required.
|
||||
/// </summary>
|
||||
/// <param name="filterContext">The filter context.</param>
|
||||
/// <exception cref="ArgumentNullException">filterContext</exception>
|
||||
/// <exception cref="Umbraco.Web.Mvc.HttpUmbracoFormRouteStringException">The required request field \"ufprt\" is not present.
|
||||
/// or
|
||||
/// The Umbraco form request route string could not be decrypted.
|
||||
/// or
|
||||
/// The provided Umbraco form request route string was meant for a different controller and action.</exception>
|
||||
public void OnAuthorization(AuthorizationContext filterContext)
|
||||
{
|
||||
if (filterContext == null)
|
||||
throw new ArgumentNullException(nameof(filterContext));
|
||||
|
||||
var ufprt = filterContext.HttpContext.Request["ufprt"];
|
||||
ValidateRouteString(ufprt, filterContext.ActionDescriptor?.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor?.ActionName, filterContext.RouteData?.DataTokens["area"]?.ToString());
|
||||
}
|
||||
|
||||
public void ValidateRouteString(string ufprt, string currentController, string currentAction, string currentArea)
|
||||
{
|
||||
if (ufprt.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new HttpUmbracoFormRouteStringException("The required request field \"ufprt\" is not present.");
|
||||
}
|
||||
|
||||
if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprt, out var additionalDataParts))
|
||||
{
|
||||
throw new HttpUmbracoFormRouteStringException("The Umbraco form request route string could not be decrypted.");
|
||||
}
|
||||
|
||||
if (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller].InvariantEquals(currentController) ||
|
||||
!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action].InvariantEquals(currentAction) ||
|
||||
(!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].IsNullOrWhiteSpace() && !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].InvariantEquals(currentArea)))
|
||||
{
|
||||
throw new HttpUmbracoFormRouteStringException("The provided Umbraco form request route string was meant for a different controller and action.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -38,9 +39,24 @@ namespace Umbraco.Web.PropertyEditors
|
||||
/// <inheritdoc />
|
||||
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
|
||||
{
|
||||
return editorValue.Value is JArray json
|
||||
? json.Select(x => x.Value<string>())
|
||||
: null;
|
||||
var value = editorValue?.Value?.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (editorValue.Value is JArray json)
|
||||
{
|
||||
return json.Select(x => x.Value<string>());
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value) == false)
|
||||
{
|
||||
return value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
// register the NuCache published snapshot service
|
||||
// must register default options, required in the service ctor
|
||||
composition.Register(factory => new PublishedSnapshotService.Options());
|
||||
composition.Register(factory => new PublishedSnapshotServiceOptions());
|
||||
composition.SetPublishedSnapshotService<PublishedSnapshotService>();
|
||||
|
||||
// add the NuCache health check (hidden from type finder)
|
||||
|
||||
@@ -30,7 +30,8 @@ using File = System.IO.File;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
class PublishedSnapshotService : PublishedSnapshotServiceBase
|
||||
|
||||
internal class PublishedSnapshotService : PublishedSnapshotServiceBase
|
||||
{
|
||||
private readonly ServiceContext _serviceContext;
|
||||
private readonly IPublishedContentTypeFactory _publishedContentTypeFactory;
|
||||
@@ -56,7 +57,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
private BPlusTree<int, ContentNodeKit> _localContentDb;
|
||||
private BPlusTree<int, ContentNodeKit> _localMediaDb;
|
||||
private readonly bool _localDbExists;
|
||||
private bool _localDbExists;
|
||||
|
||||
// define constant - determines whether to use cache when previewing
|
||||
// to store eg routes, property converted values, anything - caching
|
||||
@@ -68,7 +69,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
//private static int _singletonCheck;
|
||||
|
||||
public PublishedSnapshotService(Options options, IMainDom mainDom, IRuntimeState runtime,
|
||||
public PublishedSnapshotService(PublishedSnapshotServiceOptions options, IMainDom mainDom, IRuntimeState runtime,
|
||||
ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IdkMap idkMap,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, ILogger logger, IScopeProvider scopeProvider,
|
||||
IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository,
|
||||
@@ -115,30 +116,36 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
if (options.IgnoreLocalDb == false)
|
||||
{
|
||||
var registered = mainDom.Register(
|
||||
null,
|
||||
() =>
|
||||
{
|
||||
//"install" phase of MainDom
|
||||
//this is inside of a lock in MainDom so this is guaranteed to run if MainDom was acquired and guaranteed
|
||||
//to not run if MainDom wasn't acquired.
|
||||
//If MainDom was not acquired, then _localContentDb and _localMediaDb will remain null which means this appdomain
|
||||
//will load in published content via the DB and in that case this appdomain will probably not exist long enough to
|
||||
//serve more than a page of content.
|
||||
|
||||
var path = GetLocalFilesPath();
|
||||
var localContentDbPath = Path.Combine(path, "NuCache.Content.db");
|
||||
var localMediaDbPath = Path.Combine(path, "NuCache.Media.db");
|
||||
_localDbExists = File.Exists(localContentDbPath) && File.Exists(localMediaDbPath);
|
||||
// if both local databases exist then GetTree will open them, else new databases will be created
|
||||
_localContentDb = BTree.GetTree(localContentDbPath, _localDbExists);
|
||||
_localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
//"release" phase of MainDom
|
||||
|
||||
lock (_storesLock)
|
||||
{
|
||||
_contentStore.ReleaseLocalDb();
|
||||
_contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned
|
||||
_localContentDb = null;
|
||||
_mediaStore.ReleaseLocalDb();
|
||||
_mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned
|
||||
_localMediaDb = null;
|
||||
}
|
||||
});
|
||||
|
||||
if (registered)
|
||||
{
|
||||
var path = GetLocalFilesPath();
|
||||
var localContentDbPath = Path.Combine(path, "NuCache.Content.db");
|
||||
var localMediaDbPath = Path.Combine(path, "NuCache.Media.db");
|
||||
_localDbExists = System.IO.File.Exists(localContentDbPath) && System.IO.File.Exists(localMediaDbPath);
|
||||
|
||||
// if both local databases exist then GetTree will open them, else new databases will be created
|
||||
_localContentDb = BTree.GetTree(localContentDbPath, _localDbExists);
|
||||
_localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists);
|
||||
}
|
||||
|
||||
// stores are created with a db so they can write to it, but they do not read from it,
|
||||
// stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to
|
||||
// figure out whether it can read the databases or it should populate them from sql
|
||||
@@ -251,19 +258,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
public class Options
|
||||
{
|
||||
// disabled: prevents the published snapshot from updating and exposing changes
|
||||
// or even creating a new published snapshot to see changes, uses old cache = bad
|
||||
//
|
||||
//// indicates that the snapshot cache should reuse the application request cache
|
||||
//// otherwise a new cache object would be created for the snapshot specifically,
|
||||
//// which is the default - web boot manager uses this to optimize facades
|
||||
//public bool PublishedSnapshotCacheIsApplicationRequestCache;
|
||||
|
||||
public bool IgnoreLocalDb;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local files
|
||||
@@ -1056,8 +1050,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
: _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray();
|
||||
|
||||
_mediaStore.UpdateContentTypes(removedIds, typesA, kits);
|
||||
_mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
|
||||
_mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray());
|
||||
if (!otherIds.IsCollectionEmpty())
|
||||
_mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
|
||||
if (!newIds.IsCollectionEmpty())
|
||||
_mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray());
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Options class for configuring the <see cref="IPublishedSnapshotService"/>
|
||||
/// </summary>
|
||||
public class PublishedSnapshotServiceOptions
|
||||
{
|
||||
// disabled: prevents the published snapshot from updating and exposing changes
|
||||
// or even creating a new published snapshot to see changes, uses old cache = bad
|
||||
//
|
||||
//// indicates that the snapshot cache should reuse the application request cache
|
||||
//// otherwise a new cache object would be created for the snapshot specifically,
|
||||
//// which is the default - web boot manager uses this to optimize facades
|
||||
//public bool PublishedSnapshotCacheIsApplicationRequestCache;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If true this disables the persisted local cache files for content and media
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default this is false which means umbraco will use locally persisted cache files for reading in all published content and media on application startup.
|
||||
/// The reason for this is to improve startup times because the alternative to populating the published content and media on application startup is to read
|
||||
/// these values from the database. In scenarios where sites are relatively small (below a few thousand nodes) reading the content/media from the database to populate
|
||||
/// the in memory cache isn't that slow and is only marginally slower than reading from the locally persisted cache files.
|
||||
/// </remarks>
|
||||
public bool IgnoreLocalDb { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,6 @@ namespace Umbraco.Web.Runtime
|
||||
|
||||
// ensure WebAPI is initialized, after everything
|
||||
GlobalConfiguration.Configuration.EnsureInitialized();
|
||||
|
||||
AntiForgeryConfig.AdditionalDataProvider = new UmbracoAntiForgeryAdditionalDataProvider(AntiForgeryConfig.AdditionalDataProvider);
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Umbraco.Web.Scheduling
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error<KeepAlive>(ex, "Failed (at '{UmbracoAppUrl}').", umbracoAppUrl);
|
||||
_logger.Error<KeepAlive>(ex, "Keep alive failed (at '{UmbracoAppUrl}').", umbracoAppUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,12 @@ using Umbraco.Core;
|
||||
using System.Web.Helpers;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Web.Composing;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom <see cref="IAntiForgeryAdditionalDataProvider"/> to create a unique antiforgery token/validator per form created with BeginUmbracoForm
|
||||
/// </summary>
|
||||
[Obsolete("This is no longer used and will be removed from the codebase in future versions")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class UmbracoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider
|
||||
{
|
||||
private readonly IAntiForgeryAdditionalDataProvider _defaultProvider;
|
||||
|
||||
@@ -219,9 +219,12 @@
|
||||
<Compile Include="Models\Mapping\MapperContextExtensions.cs" />
|
||||
<Compile Include="Models\PublishedContent\HybridVariationContextAccessor.cs" />
|
||||
<Compile Include="Models\TemplateQuery\QueryConditionExtensions.cs" />
|
||||
<Compile Include="Mvc\HttpUmbracoFormRouteStringException.cs" />
|
||||
<Compile Include="Mvc\ModelBindingExceptionFilter.cs" />
|
||||
<Compile Include="Mvc\SurfaceControllerTypeCollectionBuilder.cs" />
|
||||
<Compile Include="Mvc\ValidateUmbracoFormRouteStringAttribute.cs" />
|
||||
<Compile Include="Profiling\WebProfilingController.cs" />
|
||||
<Compile Include="PublishedCache\NuCache\PublishedSnapshotServiceOptions.cs" />
|
||||
<Compile Include="PublishedCache\NuCache\Snap\GenObj.cs" />
|
||||
<Compile Include="PublishedCache\NuCache\Snap\GenRef.cs" />
|
||||
<Compile Include="PublishedCache\NuCache\Snap\LinkedNode.cs" />
|
||||
@@ -230,7 +233,6 @@
|
||||
<Compile Include="Routing\IPublishedRouter.cs" />
|
||||
<Compile Include="Routing\MediaUrlProviderCollection.cs" />
|
||||
<Compile Include="Routing\MediaUrlProviderCollectionBuilder.cs" />
|
||||
<Compile Include="Security\UmbracoAntiForgeryAdditionalDataProvider.cs" />
|
||||
<Compile Include="Services\DashboardService.cs" />
|
||||
<Compile Include="Services\IDashboardService.cs" />
|
||||
<Compile Include="Models\Link.cs" />
|
||||
@@ -340,6 +342,7 @@
|
||||
<Compile Include="Editors\TourController.cs" />
|
||||
<Compile Include="HealthCheck\Checks\Security\XssProtectionCheck.cs" />
|
||||
<Compile Include="HealthCheck\Checks\Security\HstsCheck.cs" />
|
||||
<Compile Include="Security\UmbracoAntiForgeryAdditionalDataProvider.cs" />
|
||||
<Compile Include="Editors\UserEditorAuthorizationHelper.cs" />
|
||||
<Compile Include="Editors\Filters\UserGroupAuthorizationAttribute.cs" />
|
||||
<Compile Include="Editors\Filters\UserGroupEditorAuthorizationHelper.cs" />
|
||||
|
||||
@@ -820,7 +820,7 @@ namespace Umbraco.Web
|
||||
{
|
||||
decryptedString = ufprt.DecryptWithMachineKey();
|
||||
}
|
||||
catch (FormatException)
|
||||
catch (Exception ex) when (ex is FormatException || ex is ArgumentException)
|
||||
{
|
||||
Current.Logger.Warn(typeof(UmbracoHelper), "A value was detected in the ufprt parameter but Umbraco could not decrypt the string");
|
||||
parts = null;
|
||||
|
||||
Reference in New Issue
Block a user