Merge branch 'v9/dev' into v9/contrib

# Conflicts:
#	src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs
This commit is contained in:
Sebastiaan Janssen
2022-04-12 13:41:34 +02:00
87 changed files with 2050 additions and 640 deletions

View File

@@ -1,7 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
using Umbraco.Extensions;

View File

@@ -18,9 +18,9 @@ using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Implement;
using Umbraco.Cms.Infrastructure.Packaging;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
using Umbraco.Cms.Infrastructure.Services.Implement;
using Umbraco.Cms.Infrastructure.Templates;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.DependencyInjection
@@ -92,6 +92,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique<ICreatedPackagesRepository, CreatedPackageSchemaRepository>();
builder.Services.AddSingleton<PackageDataInstallation>();
builder.Services.AddUnique<IPackageInstallation, PackageInstallation>();
builder.Services.AddUnique<IHtmlMacroParameterParser, HtmlMacroParameterParser>();
return builder;
}

View File

@@ -4,10 +4,9 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Cms.Core;
namespace Umbraco.Cms.Infrastructure.HostedServices
{
@@ -46,11 +45,8 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
// Scheduled for removal in V11
[Obsolete("Please use constructor that takes an ILogger instead")]
protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay)
{
_period = period;
_delay = delay;
_logger = StaticServiceProvider.Instance.GetRequiredService<ILoggerFactory>().CreateLogger(GetType());
}
: this(null, period, delay)
{ }
/// <inheritdoc/>
public Task StartAsync(CancellationToken cancellationToken)
@@ -82,7 +78,8 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception in recurring hosted service.");
ILogger logger = _logger ?? StaticApplicationLogging.CreateLogger(GetType());
logger.LogError(ex, "Unhandled exception in recurring hosted service.");
}
finally
{
@@ -108,7 +105,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
{
if (disposing)
{
_timer?.Dispose();
_timer?.Dispose();
}
_disposedValue = true;

View File

@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NPoco;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Infrastructure.Migrations.Notifications;
@@ -11,6 +15,7 @@ using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
using ColumnInfo = Umbraco.Cms.Infrastructure.Persistence.SqlSyntax.ColumnInfo;
@@ -88,15 +93,33 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
private readonly ILogger<DatabaseSchemaCreator> _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly IUmbracoVersion _umbracoVersion;
private readonly IOptionsMonitor<InstallDefaultDataSettings> _defaultDataCreationSettings;
public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger<DatabaseSchemaCreator> logger,
ILoggerFactory loggerFactory, IUmbracoVersion umbracoVersion, IEventAggregator eventAggregator)
[Obsolete("Please use constructor taking all parameters. Scheduled for removal in V11.")]
public DatabaseSchemaCreator(
IUmbracoDatabase database,
ILogger<DatabaseSchemaCreator> logger,
ILoggerFactory loggerFactory,
IUmbracoVersion umbracoVersion,
IEventAggregator eventAggregator)
: this (database, logger, loggerFactory, umbracoVersion, eventAggregator, StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<InstallDefaultDataSettings>>())
{
}
public DatabaseSchemaCreator(
IUmbracoDatabase database,
ILogger<DatabaseSchemaCreator> logger,
ILoggerFactory loggerFactory,
IUmbracoVersion umbracoVersion,
IEventAggregator eventAggregator,
IOptionsMonitor<InstallDefaultDataSettings> defaultDataCreationSettings)
{
_database = database ?? throw new ArgumentNullException(nameof(database));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
_umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion));
_eventAggregator = eventAggregator;
_defaultDataCreationSettings = defaultDataCreationSettings;
if (_database?.SqlContext?.SqlSyntax == null)
{
@@ -153,8 +176,11 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
if (creatingNotification.Cancel == false)
{
var dataCreation = new DatabaseDataCreator(_database,
_loggerFactory.CreateLogger<DatabaseDataCreator>(), _umbracoVersion);
var dataCreation = new DatabaseDataCreator(
_database,
_loggerFactory.CreateLogger<DatabaseDataCreator>(),
_umbracoVersion,
_defaultDataCreationSettings);
foreach (Type table in OrderedTables)
{
CreateTable(false, table, dataCreation);
@@ -419,9 +445,14 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
where T : new()
{
Type tableType = typeof(T);
CreateTable(overwrite, tableType,
new DatabaseDataCreator(_database, _loggerFactory.CreateLogger<DatabaseDataCreator>(),
_umbracoVersion));
CreateTable(
overwrite,
tableType,
new DatabaseDataCreator(
_database,
_loggerFactory.CreateLogger<DatabaseDataCreator>(),
_umbracoVersion,
_defaultDataCreationSettings));
}
/// <summary>

View File

@@ -1,7 +1,12 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Infrastructure.Migrations.Install
{
@@ -14,22 +19,35 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
private readonly ILoggerFactory _loggerFactory;
private readonly IUmbracoVersion _umbracoVersion;
private readonly IEventAggregator _eventAggregator;
private readonly IOptionsMonitor<InstallDefaultDataSettings> _installDefaultDataSettings;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in V11.")]
public DatabaseSchemaCreatorFactory(
ILogger<DatabaseSchemaCreator> logger,
ILoggerFactory loggerFactory,
IUmbracoVersion umbracoVersion,
IEventAggregator eventAggregator)
: this(logger, loggerFactory, umbracoVersion, eventAggregator, StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<InstallDefaultDataSettings>>())
{
}
public DatabaseSchemaCreatorFactory(
ILogger<DatabaseSchemaCreator> logger,
ILoggerFactory loggerFactory,
IUmbracoVersion umbracoVersion,
IEventAggregator eventAggregator,
IOptionsMonitor<InstallDefaultDataSettings> installDefaultDataSettings)
{
_logger = logger;
_loggerFactory = loggerFactory;
_umbracoVersion = umbracoVersion;
_eventAggregator = eventAggregator;
_installDefaultDataSettings = installDefaultDataSettings;
}
public DatabaseSchemaCreator Create(IUmbracoDatabase database)
{
return new DatabaseSchemaCreator(database, _logger, _loggerFactory, _umbracoVersion, _eventAggregator);
return new DatabaseSchemaCreator(database, _logger, _loggerFactory, _umbracoVersion, _eventAggregator, _installDefaultDataSettings);
}
}
}

View File

@@ -659,6 +659,12 @@ namespace Umbraco.Extensions
return sql;
}
public static Sql<ISqlContext> SelectDistinct(this Sql<ISqlContext> sql, params object[] columns)
{
sql.Append("SELECT DISTINCT " + string.Join(", ", columns));
return sql;
}
//this.Append("SELECT " + string.Join(", ", columns), new object[0]);
/// <summary>

View File

@@ -18,14 +18,16 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
internal class MacroRepository : EntityRepositoryBase<int, IMacro>, IMacroRepository
internal class MacroRepository : EntityRepositoryBase<int, IMacro>, IMacroWithAliasRepository
{
private readonly IShortStringHelper _shortStringHelper;
private readonly IRepositoryCachePolicy<IMacro, string> _macroByAliasCachePolicy;
public MacroRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<MacroRepository> logger, IShortStringHelper shortStringHelper)
: base(scopeAccessor, cache, logger)
{
_shortStringHelper = shortStringHelper;
_macroByAliasCachePolicy = new DefaultRepositoryCachePolicy<IMacro, string>(GlobalIsolatedCache, ScopeAccessor, DefaultOptions);
}
protected override IMacro PerformGet(int id)
@@ -68,6 +70,38 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
return Get(id) != null;
}
public IMacro GetByAlias(string alias)
{
return _macroByAliasCachePolicy.Get(alias, PerformGetByAlias, PerformGetAllByAlias);
}
public IEnumerable<IMacro> GetAllByAlias(string[] aliases)
{
if (aliases.Any() is false)
{
return base.GetMany();
}
return _macroByAliasCachePolicy.GetAll(aliases, PerformGetAllByAlias);
}
private IMacro PerformGetByAlias(string alias)
{
var query = Query<IMacro>().Where(x => x.Alias.Equals(alias));
return PerformGetByQuery(query).FirstOrDefault();
}
private IEnumerable<IMacro> PerformGetAllByAlias(params string[] aliases)
{
if (aliases.Any() is false)
{
return base.GetMany();
}
var query = Query<IMacro>().Where(x => aliases.Contains(x.Alias));
return PerformGetByQuery(query);
}
protected override IEnumerable<IMacro> PerformGetAll(params int[] ids)
{
return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds();

View File

@@ -60,14 +60,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
var urlHash = url.GenerateHash<SHA1>();
Sql<ISqlContext> sql = GetBaseQuery(false)
.Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash &&
(x.Culture == culture.ToLower() || x.Culture == string.Empty))
(x.Culture == culture.ToLower() || x.Culture == null || x.Culture == string.Empty))
.OrderByDescending<RedirectUrlDto>(x => x.CreateDateUtc);
List<RedirectUrlDto> dtos = Database.Fetch<RedirectUrlDto>(sql);
RedirectUrlDto dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower());
if (dto == null)
{
dto = dtos.FirstOrDefault(f => f.Culture == string.Empty);
dto = dtos.FirstOrDefault(f => string.IsNullOrWhiteSpace(f.Culture));
}
return dto == null ? null : Map(dto);

View File

@@ -198,22 +198,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
});
}
public IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildIds(int[] childIds, long pageIndex, int pageSize, out long totalRecords, int[] relationTypes, params Guid[] entityTypes)
{
return _entityRepository.GetPagedResultsByQuery(Query<IUmbracoEntity>(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
{
SqlJoinRelations(sql);
sql.WhereIn<RelationDto>(rel => rel.ChildId, childIds);
sql.WhereAny(s => s.WhereIn<RelationDto>(rel => rel.ParentId, childIds), s => s.WhereNotIn<NodeDto>(node => node.NodeId, childIds));
if (relationTypes != null && relationTypes.Any())
{
sql.WhereIn<RelationDto>(rel => rel.RelationType, relationTypes);
}
});
}
public IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
{
return GetPagedChildEntitiesByParentId(parentId, pageIndex, pageSize, out totalRecords, new int[0], entityTypes);
@@ -241,21 +225,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
});
}
public IEnumerable<IUmbracoEntity> GetPagedEntitiesForItemsInRelation(int[] itemIds, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
{
return _entityRepository.GetPagedResultsByQuery(Query<IUmbracoEntity>(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
{
SqlJoinRelations(sql);
sql.WhereIn<RelationDto>(rel => rel.ChildId, itemIds);
sql.Where<RelationDto, NodeDto>((rel, node) => rel.ChildId == node.NodeId);
sql.Where<RelationTypeDto>(type => type.IsDependency);
});
}
public void Save(IEnumerable<IRelation> relations)
{
foreach (var hasIdentityGroup in relations.GroupBy(r => r.HasIdentity))
@@ -475,8 +444,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
[Column(Name = "contentTypeName")]
public string ChildContentTypeName { get; set; }
[Column(Name = "relationTypeName")]
public string RelationTypeName { get; set; }

View File

@@ -1,9 +1,8 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
@@ -20,10 +19,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
_scopeAccessor = scopeAccessor;
}
public IEnumerable<RelationItem> GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize,
bool filterMustBeIsDependency, out long totalRecords)
/// <summary>
/// Gets a page of items used in any kind of relation from selected integer ids.
/// </summary>
public IEnumerable<RelationItem> GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords)
{
var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select(
var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct(
"[pn].[id] as nodeId",
"[pn].[uniqueId] as nodeKey",
"[pn].[text] as nodeName",
@@ -36,12 +37,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[umbracoRelationType].[isDependency] as relationTypeIsDependency",
"[umbracoRelationType].[dual] as relationTypeIsBidirectional")
.From<RelationDto>("r")
.InnerJoin<RelationTypeDto>("umbracoRelationType").On<RelationDto, RelationTypeDto>((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType")
.InnerJoin<NodeDto>("cn").On<RelationDto, NodeDto, RelationTypeDto>((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" )
.InnerJoin<NodeDto>("pn").On<RelationDto, NodeDto, NodeDto>((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" )
.LeftJoin<ContentDto>("c").On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId, aliasLeft:"pn", aliasRight:"c")
.LeftJoin<ContentTypeDto>("ct").On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct")
.LeftJoin<NodeDto>("ctn").On<ContentTypeDto, NodeDto>((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn");
.InnerJoin<RelationTypeDto>("umbracoRelationType").On<RelationDto, RelationTypeDto>((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType")
.InnerJoin<NodeDto>("cn").On<RelationDto, NodeDto, RelationTypeDto>((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType")
.InnerJoin<NodeDto>("pn").On<RelationDto, NodeDto, NodeDto>((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn")
.LeftJoin<ContentDto>("c").On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", aliasRight: "c")
.LeftJoin<ContentTypeDto>("ct").On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct")
.LeftJoin<NodeDto>("ctn").On<ContentTypeDto, NodeDto>((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn");
if (ids.Any())
{
@@ -57,13 +58,15 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
sql = sql.OrderBy<RelationTypeDto>(x => x.Alias);
var pagedResult = _scopeAccessor.AmbientScope.Database.Page<RelationItemDto>(pageIndex + 1, pageSize, sql);
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
totalRecords = pagedResult.TotalItems;
return pagedResult.Items.Select(MapDtoToEntity);
}
public IEnumerable<RelationItem> GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency,
out long totalRecords)
/// <summary>
/// Gets a page of the descending items that have any references, given a parent id.
/// </summary>
public IEnumerable<RelationItem> GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords)
{
var syntax = _scopeAccessor.AmbientScope.Database.SqlContext.SqlSyntax;
@@ -73,13 +76,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
.From<NodeDto>("node")
.Where<NodeDto>(x => x.NodeId == parentId, "node");
// Gets the descendants of the parent node
Sql<ISqlContext> subQuery;
if (_scopeAccessor.AmbientScope.Database.DatabaseType.IsSqlCe())
{
// SqlCE do not support nested selects that returns a scalar. So we need to do this in multiple queries
// SqlCE does not support nested selects that returns a scalar. So we need to do this in multiple queries
var pathForLike = _scopeAccessor.AmbientScope.Database.ExecuteScalar<string>(subsubQuery);
@@ -96,10 +98,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
.WhereLike<NodeDto>(x => x.Path, subsubQuery);
}
// Get all relations where parent is in the sub query
var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select(
var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct(
"[pn].[id] as nodeId",
"[pn].[uniqueId] as nodeKey",
"[pn].[text] as nodeName",
@@ -112,29 +112,35 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[umbracoRelationType].[isDependency] as relationTypeIsDependency",
"[umbracoRelationType].[dual] as relationTypeIsBidirectional")
.From<RelationDto>("r")
.InnerJoin<RelationTypeDto>("umbracoRelationType").On<RelationDto, RelationTypeDto>((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType")
.InnerJoin<NodeDto>("cn").On<RelationDto, NodeDto, RelationTypeDto>((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" )
.InnerJoin<NodeDto>("pn").On<RelationDto, NodeDto, NodeDto>((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" )
.LeftJoin<ContentDto>("c").On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId, aliasLeft:"pn", aliasRight:"c")
.LeftJoin<ContentTypeDto>("ct").On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct")
.LeftJoin<NodeDto>("ctn").On<ContentTypeDto, NodeDto>((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn")
.InnerJoin<RelationTypeDto>("umbracoRelationType").On<RelationDto, RelationTypeDto>((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType")
.InnerJoin<NodeDto>("cn").On<RelationDto, NodeDto, RelationTypeDto>((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType")
.InnerJoin<NodeDto>("pn").On<RelationDto, NodeDto, NodeDto>((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn")
.LeftJoin<ContentDto>("c").On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", aliasRight: "c")
.LeftJoin<ContentTypeDto>("ct").On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct")
.LeftJoin<NodeDto>("ctn").On<ContentTypeDto, NodeDto>((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn")
.WhereIn((System.Linq.Expressions.Expression<Func<NodeDto, object>>)(x => x.NodeId), subQuery, "pn");
if (filterMustBeIsDependency)
{
sql = sql.Where<RelationTypeDto>(rt => rt.IsDependency, "umbracoRelationType");
}
// Ordering is required for paging
sql = sql.OrderBy<RelationTypeDto>(x => x.Alias);
var pagedResult = _scopeAccessor.AmbientScope.Database.Page<RelationItemDto>(pageIndex + 1, pageSize, sql);
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
totalRecords = pagedResult.TotalItems;
return pagedResult.Items.Select(MapDtoToEntity);
}
public IEnumerable<RelationItem> GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords)
/// <summary>
/// Gets a page of items which are in relation with the current item.
/// Basically, shows the items which depend on the current item.
/// </summary>
public IEnumerable<RelationItem> GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords)
{
var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select(
var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct(
"[cn].[id] as nodeId",
"[cn].[uniqueId] as nodeKey",
"[cn].[text] as nodeName",
@@ -147,17 +153,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[umbracoRelationType].[isDependency] as relationTypeIsDependency",
"[umbracoRelationType].[dual] as relationTypeIsBidirectional")
.From<RelationDto>("r")
.InnerJoin<RelationTypeDto>("umbracoRelationType").On<RelationDto, RelationTypeDto>((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType")
.InnerJoin<NodeDto>("cn").On<RelationDto, NodeDto, RelationTypeDto>((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" )
.InnerJoin<NodeDto>("pn").On<RelationDto, NodeDto, NodeDto>((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" )
.LeftJoin<ContentDto>("c").On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId, aliasLeft:"cn", aliasRight:"c")
.LeftJoin<ContentTypeDto>("ct").On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct")
.LeftJoin<NodeDto>("ctn").On<ContentTypeDto, NodeDto>((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn");
if (ids.Any())
{
sql = sql.Where<NodeDto>(x => ids.Contains(x.NodeId), "pn");
}
.InnerJoin<RelationTypeDto>("umbracoRelationType").On<RelationDto, RelationTypeDto>((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType")
.InnerJoin<NodeDto>("cn").On<RelationDto, NodeDto, RelationTypeDto>((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType")
.InnerJoin<NodeDto>("pn").On<RelationDto, NodeDto, NodeDto>((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn")
.LeftJoin<ContentDto>("c").On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId, aliasLeft: "cn", aliasRight: "c")
.LeftJoin<ContentTypeDto>("ct").On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct")
.LeftJoin<NodeDto>("ctn").On<ContentTypeDto, NodeDto>((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn")
.Where<NodeDto>(x => x.NodeId == id, "pn")
.Where<RelationDto>(x => x.ChildId == id || x.ParentId == id, "r"); // This last Where is purely to help SqlServer make a smarter query plan. More info https://github.com/umbraco/Umbraco-CMS/issues/12190
if (filterMustBeIsDependency)
{
@@ -168,14 +171,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
sql = sql.OrderBy<RelationTypeDto>(x => x.Alias);
var pagedResult = _scopeAccessor.AmbientScope.Database.Page<RelationItemDto>(pageIndex + 1, pageSize, sql);
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
totalRecords = pagedResult.TotalItems;
return pagedResult.Items.Select(MapDtoToEntity);
}
private RelationItem MapDtoToEntity(RelationItemDto dto)
{
var type = ObjectTypes.GetUdiType(dto.ChildNodeObjectType);
return new RelationItem()
{
NodeId = dto.ChildNodeId,

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Media;
@@ -14,6 +15,8 @@ using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Core.Templates;
using Umbraco.Cms.Infrastructure.Templates;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors
@@ -37,6 +40,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
private readonly RichTextEditorPastedImages _pastedImages;
private readonly HtmlLocalLinkParser _localLinkParser;
private readonly IImageUrlGenerator _imageUrlGenerator;
private readonly IHtmlMacroParameterParser _macroParameterParser;
public GridPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
@@ -45,7 +49,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
RichTextEditorPastedImages pastedImages,
HtmlLocalLinkParser localLinkParser,
IIOHelper ioHelper,
IImageUrlGenerator imageUrlGenerator)
IImageUrlGenerator imageUrlGenerator,
IHtmlMacroParameterParser macroParameterParser)
: base(dataValueEditorFactory)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
@@ -54,6 +59,20 @@ namespace Umbraco.Cms.Core.PropertyEditors
_pastedImages = pastedImages;
_localLinkParser = localLinkParser;
_imageUrlGenerator = imageUrlGenerator;
_macroParameterParser = macroParameterParser;
}
[Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")]
public GridPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
HtmlImageSourceParser imageSourceParser,
RichTextEditorPastedImages pastedImages,
HtmlLocalLinkParser localLinkParser,
IIOHelper ioHelper,
IImageUrlGenerator imageUrlGenerator)
: this (dataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, pastedImages, localLinkParser, ioHelper, imageUrlGenerator, StaticServiceProvider.Instance.GetRequiredService<IHtmlMacroParameterParser>())
{
}
public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory();
@@ -74,6 +93,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
private readonly RichTextPropertyEditor.RichTextPropertyValueEditor _richTextPropertyValueEditor;
private readonly MediaPickerPropertyEditor.MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor;
private readonly IImageUrlGenerator _imageUrlGenerator;
private readonly IHtmlMacroParameterParser _macroParameterParser;
public GridPropertyValueEditor(
IDataValueEditorFactory dataValueEditorFactory,
@@ -85,7 +105,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
IShortStringHelper shortStringHelper,
IImageUrlGenerator imageUrlGenerator,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper)
IIOHelper ioHelper,
IHtmlMacroParameterParser macroParameterParser)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
@@ -96,6 +117,25 @@ namespace Umbraco.Cms.Core.PropertyEditors
_mediaPickerPropertyValueEditor =
dataValueEditorFactory.Create<MediaPickerPropertyEditor.MediaPickerPropertyValueEditor>(attribute);
_imageUrlGenerator = imageUrlGenerator;
_macroParameterParser = macroParameterParser;
}
[Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")]
public GridPropertyValueEditor(
IDataValueEditorFactory dataValueEditorFactory,
DataEditorAttribute attribute,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
ILocalizedTextService localizedTextService,
HtmlImageSourceParser imageSourceParser,
RichTextEditorPastedImages pastedImages,
IShortStringHelper shortStringHelper,
IImageUrlGenerator imageUrlGenerator,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper)
: this (dataValueEditorFactory, attribute, backOfficeSecurityAccessor, localizedTextService,
imageSourceParser, pastedImages, shortStringHelper, imageUrlGenerator, jsonSerializer, ioHelper,
StaticServiceProvider.Instance.GetRequiredService<IHtmlMacroParameterParser>())
{
}
/// <summary>
@@ -120,7 +160,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
var mediaParent = config?.MediaParentId;
var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid;
var grid = DeserializeGridValue(rawJson, out var rtes, out _);
var grid = DeserializeGridValue(rawJson, out var rtes, out _, out _);
var userId = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser?.Id ?? Constants.Security.SuperUserId;
@@ -154,7 +194,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
if (val.IsNullOrWhiteSpace())
return string.Empty;
var grid = DeserializeGridValue(val, out var rtes, out _);
var grid = DeserializeGridValue(val, out var rtes, out _, out _);
//process the rte values
foreach (var rte in rtes.ToList())
@@ -168,7 +208,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
return grid;
}
private GridValue DeserializeGridValue(string rawJson, out IEnumerable<GridValue.GridControl> richTextValues, out IEnumerable<GridValue.GridControl> mediaValues)
private GridValue DeserializeGridValue(string rawJson, out IEnumerable<GridValue.GridControl> richTextValues, out IEnumerable<GridValue.GridControl> mediaValues, out IEnumerable<GridValue.GridControl> macroValues)
{
var grid = JsonConvert.DeserializeObject<GridValue>(rawJson);
@@ -177,6 +217,9 @@ namespace Umbraco.Cms.Core.PropertyEditors
richTextValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "rte");
mediaValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "media");
// Find all the macros
macroValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "macro");
return grid;
}
@@ -192,7 +235,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
if (rawJson.IsNullOrWhiteSpace())
yield break;
DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues);
DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues, out var macroValues);
foreach (var umbracoEntityReference in richTextEditorValues.SelectMany(x =>
_richTextPropertyValueEditor.GetReferences(x.Value)))
@@ -201,6 +244,9 @@ namespace Umbraco.Cms.Core.PropertyEditors
foreach (var umbracoEntityReference in mediaValues.Where(x => x.Value.HasValues)
.SelectMany(x => _mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"])))
yield return umbracoEntityReference;
foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromGridControlMacros(macroValues))
yield return umbracoEntityReference;
}
}
}

View File

@@ -3,8 +3,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
@@ -16,6 +15,8 @@ using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Core.Templates;
using Umbraco.Cms.Infrastructure.Examine;
using Umbraco.Cms.Infrastructure.Macros;
using Umbraco.Cms.Infrastructure.Templates;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors
@@ -36,12 +37,13 @@ namespace Umbraco.Cms.Core.PropertyEditors
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly HtmlImageSourceParser _imageSourceParser;
private readonly HtmlLocalLinkParser _localLinkParser;
private readonly IHtmlMacroParameterParser _macroParameterParser;
private readonly RichTextEditorPastedImages _pastedImages;
private readonly IIOHelper _ioHelper;
private readonly IImageUrlGenerator _imageUrlGenerator;
/// <summary>
/// The constructor will setup the property editor based on the attribute if one is found
/// The constructor will setup the property editor based on the attribute if one is found.
/// </summary>
public RichTextPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
@@ -50,7 +52,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
HtmlLocalLinkParser localLinkParser,
RichTextEditorPastedImages pastedImages,
IIOHelper ioHelper,
IImageUrlGenerator imageUrlGenerator)
IImageUrlGenerator imageUrlGenerator,
IHtmlMacroParameterParser macroParameterParser)
: base(dataValueEditorFactory)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
@@ -59,6 +62,20 @@ namespace Umbraco.Cms.Core.PropertyEditors
_pastedImages = pastedImages;
_ioHelper = ioHelper;
_imageUrlGenerator = imageUrlGenerator;
_macroParameterParser = macroParameterParser;
}
[Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")]
public RichTextPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
HtmlImageSourceParser imageSourceParser,
HtmlLocalLinkParser localLinkParser,
RichTextEditorPastedImages pastedImages,
IIOHelper ioHelper,
IImageUrlGenerator imageUrlGenerator)
: this (dataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, localLinkParser, pastedImages, ioHelper, imageUrlGenerator, StaticServiceProvider.Instance.GetRequiredService<IHtmlMacroParameterParser>())
{
}
/// <summary>
@@ -79,6 +96,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly HtmlImageSourceParser _imageSourceParser;
private readonly HtmlLocalLinkParser _localLinkParser;
private readonly IHtmlMacroParameterParser _macroParameterParser;
private readonly RichTextEditorPastedImages _pastedImages;
private readonly IImageUrlGenerator _imageUrlGenerator;
private readonly IHtmlSanitizer _htmlSanitizer;
@@ -94,7 +112,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
IImageUrlGenerator imageUrlGenerator,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IHtmlSanitizer htmlSanitizer)
IHtmlSanitizer htmlSanitizer,
IHtmlMacroParameterParser macroParameterParser)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
@@ -103,6 +122,26 @@ namespace Umbraco.Cms.Core.PropertyEditors
_pastedImages = pastedImages;
_imageUrlGenerator = imageUrlGenerator;
_htmlSanitizer = htmlSanitizer;
_macroParameterParser = macroParameterParser;
}
[Obsolete("Use the constructor which takes an HtmlMacroParameterParser instead")]
public RichTextPropertyValueEditor(
DataEditorAttribute attribute,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
HtmlImageSourceParser imageSourceParser,
HtmlLocalLinkParser localLinkParser,
RichTextEditorPastedImages pastedImages,
IImageUrlGenerator imageUrlGenerator,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IHtmlSanitizer htmlSanitizer)
: this(attribute, backOfficeSecurityAccessor, localizedTextService, shortStringHelper, imageSourceParser,
localLinkParser, pastedImages, imageUrlGenerator, jsonSerializer, ioHelper, htmlSanitizer,
StaticServiceProvider.Instance.GetRequiredService<IHtmlMacroParameterParser>())
{
}
/// <inheritdoc />
@@ -182,6 +221,10 @@ namespace Umbraco.Cms.Core.PropertyEditors
yield return new UmbracoEntityReference(udi);
//TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs
//UPDATE: We are getting the Macros in 'FindUmbracoEntityReferencesFromEmbeddedMacros' - perhaps we just return the macro Udis here too or do they need their own relationAlias?
foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromEmbeddedMacros(asString))
yield return umbracoEntityReference;
}
}

View File

@@ -71,6 +71,7 @@ namespace Umbraco.Cms.Core.Security
return result;
}
}
// We need to check for clear text passwords from members as the first thing. This was possible in v8 :(
else if (IsSuccessfulLegacyPassword(hashedPassword, providedPassword))
{
@@ -138,7 +139,7 @@ namespace Umbraco.Cms.Core.Security
}
var result = LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName, providedPassword, hashedPassword);
return result || LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName, providedPassword, hashedPassword);
return result || LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword);
}
private static string DecryptLegacyPassword(string encryptedPassword, string algorithmName, string decryptionKey)

View File

@@ -1,3 +1,4 @@
using System;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Models.Membership;
@@ -10,7 +11,6 @@ namespace Umbraco.Cms.Core.Security
where TUser: UmbracoIdentityUser
{
private readonly IJsonSerializer _jsonSerializer;
private readonly PasswordHasher<TUser> _aspnetV2PasswordHasher = new PasswordHasher<TUser>(new V2PasswordHasherOptions());
public UmbracoPasswordHasher(LegacyPasswordSecurity legacyPasswordSecurity, IJsonSerializer jsonSerializer)
{
@@ -43,57 +43,64 @@ namespace Umbraco.Cms.Core.Security
{
if (user is null)
{
throw new System.ArgumentNullException(nameof(user));
throw new ArgumentNullException(nameof(user));
}
if (!user.PasswordConfig.IsNullOrWhiteSpace())
try
{
// check if the (legacy) password security supports this hash algorith and if so then use it
var deserialized = _jsonSerializer.Deserialize<PersistedPasswordSettings>(user.PasswordConfig);
if (LegacyPasswordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm))
// Best case and most likely scenario, a modern hash supported by ASP.Net identity.
PasswordVerificationResult upstreamResult = base.VerifyHashedPassword(user, hashedPassword, providedPassword);
if (upstreamResult != PasswordVerificationResult.Failed)
{
var result = LegacyPasswordSecurity.VerifyPassword(deserialized.HashAlgorithm, providedPassword, hashedPassword);
//We need to special handle this case, apparently v8 still saves the user algorithm as {"hashAlgorithm":"HMACSHA256"}, when using legacy encoding and hasinging.
if (result == false)
{
result = LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword);
}
return result
? PasswordVerificationResult.SuccessRehashNeeded
: PasswordVerificationResult.Failed;
}
// We will explicitly detect names here
// The default is PBKDF2.ASPNETCORE.V3:
// PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
// The underlying class only lets us change 2 things which is the version: options.CompatibilityMode and the iteration count
// The PBKDF2.ASPNETCORE.V2 settings are:
// PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
switch (deserialized.HashAlgorithm)
{
case Constants.Security.AspNetCoreV3PasswordHashAlgorithmName:
return base.VerifyHashedPassword(user, hashedPassword, providedPassword);
case Constants.Security.AspNetCoreV2PasswordHashAlgorithmName:
var legacyResult = _aspnetV2PasswordHasher.VerifyHashedPassword(user, hashedPassword, providedPassword);
if (legacyResult == PasswordVerificationResult.Success)
return PasswordVerificationResult.SuccessRehashNeeded;
return legacyResult;
return upstreamResult;
}
}
// else go the default (v3)
return base.VerifyHashedPassword(user, hashedPassword, providedPassword);
}
private class V2PasswordHasherOptions : IOptions<PasswordHasherOptions>
{
public PasswordHasherOptions Value => new PasswordHasherOptions
catch (FormatException)
{
CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2
};
// hash wasn't a valid base64 encoded string, MS concat the salt bytes and hash bytes and base 64 encode both together.
// We however historically base 64 encoded the salt bytes and hash bytes separately then concat the strings so we got 2 sets of padding.
// both salt bytes and hash bytes lengths were not evenly divisible by 3 hence 2 sets of padding.
// We could check upfront with TryFromBase64String, but not whilst we target netstandard 2.0
// so might as well just deal with the exception.
}
// At this point we either have a legacy password or a bad attempt.
// Check the supported worst case scenario, a "useLegacyEncoding" password - HMACSHA1 but with password used as key so not unique for users sharing same password
// This was the standard for v4.
// Do this first because with useLegacyEncoding the algorithm stored in the database is irrelevant.
if (LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword))
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
// For users we expect to know the historic algorithm.
// NOTE: MemberPasswordHasher subclasses this class to deal with the fact that PasswordConfig wasn't stored.
if (user.PasswordConfig.IsNullOrWhiteSpace())
{
return PasswordVerificationResult.Failed;
}
PersistedPasswordSettings deserialized;
try
{
deserialized = _jsonSerializer.Deserialize<PersistedPasswordSettings>(user.PasswordConfig);
}
catch
{
return PasswordVerificationResult.Failed;
}
if (!LegacyPasswordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm))
{
return PasswordVerificationResult.Failed;
}
// Last chance must be HMACSHA256 or SHA1
return LegacyPasswordSecurity.VerifyPassword(deserialized.HashAlgorithm, providedPassword, hashedPassword)
? PasswordVerificationResult.SuccessRehashNeeded
: PasswordVerificationResult.Failed;
}
}
}

View File

@@ -13,13 +13,12 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <summary>
/// Represents the Macro Service, which is an easy access to operations involving <see cref="IMacro"/>
/// </summary>
internal class MacroService : RepositoryService, IMacroService
internal class MacroService : RepositoryService, IMacroWithAliasService
{
private readonly IMacroRepository _macroRepository;
private readonly IAuditRepository _auditRepository;
public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
IMacroRepository macroRepository, IAuditRepository auditRepository)
public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMacroRepository macroRepository, IAuditRepository auditRepository)
: base(provider, loggerFactory, eventMessagesFactory)
{
_macroRepository = macroRepository;
@@ -33,10 +32,14 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <returns>An <see cref="IMacro"/> object</returns>
public IMacro GetByAlias(string alias)
{
if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository)
{
return GetAll().FirstOrDefault(x => x.Alias == alias);
}
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var q = Query<IMacro>().Where(x => x.Alias == alias);
return _macroRepository.Get(q).FirstOrDefault();
return macroWithAliasRepository.GetByAlias(alias);
}
}
@@ -61,6 +64,20 @@ namespace Umbraco.Cms.Core.Services.Implement
}
}
public IEnumerable<IMacro> GetAll(params string[] aliases)
{
if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository)
{
var hashset = new HashSet<string>(aliases);
return GetAll().Where(x => hashset.Contains(x.Alias));
}
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return macroWithAliasRepository.GetAllByAlias(aliases);
}
}
public IMacro GetById(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))

View File

@@ -1,4 +1,3 @@
using System.Linq;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
@@ -18,22 +17,32 @@ namespace Umbraco.Cms.Core.Services.Implement
_entityService = entityService;
}
public PagedResult<RelationItem> GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency)
/// <summary>
/// Gets a paged result of items which are in relation with the current item.
/// Basically, shows the items which depend on the current item.
/// </summary>
public PagedResult<RelationItem> GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency)
{
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
var items = _trackedReferencesRepository.GetPagedRelationsForItems(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems);
var items = _trackedReferencesRepository.GetPagedRelationsForItem(id, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems);
return new PagedResult<RelationItem>(totalItems, pageIndex+1, pageSize) { Items = items };
return new PagedResult<RelationItem>(totalItems, pageIndex + 1, pageSize) { Items = items };
}
/// <summary>
/// Gets a paged result of items used in any kind of relation from selected integer ids.
/// </summary>
public PagedResult<RelationItem> GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency)
{
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
var items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems);
var items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems);
return new PagedResult<RelationItem>(totalItems, pageIndex+1, pageSize) { Items = items };
return new PagedResult<RelationItem>(totalItems, pageIndex + 1, pageSize) { Items = items };
}
/// <summary>
/// Gets a paged result of the descending items that have any references, given a parent id.
/// </summary>
public PagedResult<RelationItem> GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency)
{
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
@@ -44,7 +53,7 @@ namespace Umbraco.Cms.Core.Services.Implement
pageSize,
filterMustBeIsDependency,
out var totalItems);
return new PagedResult<RelationItem>(totalItems, pageIndex+1, pageSize) { Items = items };
return new PagedResult<RelationItem>(totalItems, pageIndex + 1, pageSize) { Items = items };
}
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Editors;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Macros;
namespace Umbraco.Cms.Infrastructure.Templates
{
public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
{
private readonly IMacroService _macroService;
private readonly ILogger<HtmlMacroParameterParser> _logger;
private readonly ParameterEditorCollection _parameterEditors;
public HtmlMacroParameterParser(IMacroService macroService, ILogger<HtmlMacroParameterParser> logger, ParameterEditorCollection parameterEditors)
{
_macroService = macroService;
_logger = logger;
_parameterEditors = parameterEditors;
}
/// <summary>
/// Parses out media UDIs from an HTML string based on embedded macro parameter values.
/// </summary>
/// <param name="text">HTML string</param>
/// <returns></returns>
public IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromEmbeddedMacros(string text)
{
// There may be more than one macro with the same alias on the page so using a tuple
var foundMacros = new List<Tuple<string, Dictionary<string, string>>>();
// This legacy ParseMacros() already finds the macros within a Rich Text Editor using regexes
// It seems to lowercase the macro parameter alias - so making the dictionary case insensitive
MacroTagParser.ParseMacros(text, textblock => { }, (macroAlias, macroAttributes) => foundMacros.Add(new Tuple<string, Dictionary<string, string>>(macroAlias, new Dictionary<string, string>(macroAttributes, StringComparer.OrdinalIgnoreCase))));
foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros))
{
yield return umbracoEntityReference;
}
}
/// <summary>
/// Parses out media UDIs from Macro Grid Control parameters.
/// </summary>
/// <param name="macroGridControls"></param>
/// <returns></returns>
public IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable<GridValue.GridControl> macroGridControls)
{
var foundMacros = new List<Tuple<string, Dictionary<string, string>>>();
foreach (var macroGridControl in macroGridControls)
{
// Deserialise JSON of Macro Grid Control to a class
var gridMacro = macroGridControl.Value.ToObject<GridMacro>();
// Collect any macro parameters that contain the media udi format
if (gridMacro is not null && gridMacro.MacroParameters is not null && gridMacro.MacroParameters.Any())
{
foundMacros.Add(new Tuple<string, Dictionary<string, string>>(gridMacro.MacroAlias, gridMacro.MacroParameters));
}
}
foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros))
{
yield return umbracoEntityReference;
}
}
private IEnumerable<UmbracoEntityReference> GetUmbracoEntityReferencesFromMacros(List<Tuple<string, Dictionary<string, string>>> macros)
{
if (_macroService is not IMacroWithAliasService macroWithAliasService)
{
yield break;
}
var uniqueMacroAliases = macros.Select(f => f.Item1).Distinct();
// TODO: Tracking Macro references
// Here we are finding the used macros' Udis (there should be a Related Macro relation type - but Relations don't accept 'Macro' as an option)
var foundMacroUmbracoEntityReferences = new List<UmbracoEntityReference>();
// Get all the macro configs in one hit for these unique macro aliases - this is now cached with a custom cache policy
var macroConfigs = macroWithAliasService.GetAll(uniqueMacroAliases.ToArray());
foreach (var macro in macros)
{
var macroConfig = macroConfigs.FirstOrDefault(f => f.Alias == macro.Item1);
if (macroConfig is null)
{
continue;
}
foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key)));
// Only do this if the macros actually have parameters
if (macroConfig.Properties is not null && macroConfig.Properties.Keys.Any(f => f != "macroAlias"))
{
foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(macro.Item2, macroConfig, _parameterEditors))
{
yield return umbracoEntityReference;
}
}
}
}
/// <summary>
/// Finds media UDIs in Macro Parameter Values by calling the GetReference method for all the Macro Parameter Editors for a particular macro.
/// </summary>
/// <param name="macroParameters">The parameters for the macro a dictionary of key/value strings</param>
/// <param name="macroConfig">The macro configuration for this particular macro - contains the types of editors used for each parameter</param>
/// <param name="parameterEditors">A list of all the registered parameter editors used in the Umbraco implmentation - to look up the corresponding property editor for a macro parameter</param>
/// <returns></returns>
private IEnumerable<UmbracoEntityReference> GetUmbracoEntityReferencesFromMacroParameters(Dictionary<string, string> macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors)
{
var foundUmbracoEntityReferences = new List<UmbracoEntityReference>();
foreach (var parameter in macroConfig.Properties)
{
if (macroParameters.TryGetValue(parameter.Alias, out string parameterValue))
{
var parameterEditorAlias = parameter.EditorAlias;
// Lookup propertyEditor from the registered ParameterEditors with the implmementation to avoid looking up for each parameter
var parameterEditor = parameterEditors.FirstOrDefault(f => string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase));
if (parameterEditor is not null)
{
// Get the ParameterValueEditor for this PropertyEditor (where the GetReferences method is implemented) - cast as IDataValueReference to determine if 'it is' implemented for the editor
if (parameterEditor.GetValueEditor() is IDataValueReference parameterValueEditor)
{
foreach (var entityReference in parameterValueEditor.GetReferences(parameterValue))
{
foundUmbracoEntityReferences.Add(entityReference);
}
}
else
{
_logger.LogInformation("{0} doesn't have a ValueEditor that implements IDataValueReference", parameterEditor.Alias);
}
}
}
}
return foundUmbracoEntityReferences;
}
// Poco class to deserialise the Json for a Macro Control
private class GridMacro
{
[JsonProperty("macroAlias")]
public string MacroAlias { get; set; }
[JsonProperty("macroParamsDictionary")]
public Dictionary<string, string> MacroParameters { get; set; }
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Editors;
namespace Umbraco.Cms.Infrastructure.Templates
{
/// <summary>
/// Provides methods to parse referenced entities as Macro parameters.
/// </summary>
public interface IHtmlMacroParameterParser
{
/// <summary>
/// Parses out media UDIs from an HTML string based on embedded macro parameter values.
/// </summary>
/// <param name="text">HTML string</param>
/// <returns></returns>
IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromEmbeddedMacros(string text);
/// <summary>
/// Parses out media UDIs from Macro Grid Control parameters.
/// </summary>
/// <param name="macroGridControls"></param>
/// <returns></returns>
IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable<GridValue.GridControl> macroGridControls);
}
}