Merge remote-tracking branch 'origin/v8/dev' into netcore/dev

# Conflicts:
#	src/Umbraco.Core/Cache/MemberCacheRefresher.cs
#	src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs
#	src/Umbraco.Infrastructure/Search/ExamineComponent.cs
#	src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs
#	src/Umbraco.Web/Security/MembershipHelper.cs
#	src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs
This commit is contained in:
Bjarke Berg
2020-09-23 12:54:13 +02:00
29 changed files with 1018 additions and 248 deletions

View File

@@ -26,6 +26,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ITagRepository _tagRepository;
private readonly IPasswordHasher _passwordHasher;
private readonly IMemberGroupRepository _memberGroupRepository;
private readonly IRepositoryCachePolicy<IMember, string> _memberByUsernameCachePolicy;
public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
@@ -39,6 +40,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
_passwordHasher = passwordHasher;
_memberGroupRepository = memberGroupRepository;
_memberByUsernameCachePolicy = new DefaultRepositoryCachePolicy<IMember, string>(GlobalIsolatedCache, ScopeAccessor, DefaultOptions);
}
protected override MemberRepository This => this;
@@ -383,12 +386,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (changedCols.Count > 0)
Database.Update(dto, changedCols);
// replace the property data
var deletePropertyDataSql = SqlContext.Sql().Delete<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == member.VersionId);
Database.Execute(deletePropertyDataSql);
// Replace the property data
// Lookup the data to update with a UPDLOCK (using ForUpdate()) this is because we have another method that doesn't take an explicit WriteLock
// in SetLastLogin which is called very often and we want to avoid the lock timeout for the explicit lock table but we still need to ensure atomic
// operations between that method and this one.
var propDataSql = SqlContext.Sql().Select("*").From<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == member.VersionId).ForUpdate();
var existingPropData = Database.Fetch<PropertyDataDto>(propDataSql).ToDictionary(x => x.PropertyTypeId);
var propertyDataDtos = PropertyFactory.BuildDtos(member.ContentType.Variations, member.VersionId, 0, entity.Properties, LanguageRepository, out _, out _);
foreach (var propertyDataDto in propertyDataDtos)
Database.Insert(propertyDataDto);
{
// Check if this already exists and update, else insert a new one
if (existingPropData.TryGetValue(propertyDataDto.PropertyTypeId, out var propData))
{
propertyDataDto.Id = propData.Id;
Database.Update(propertyDataDto);
}
else
{
Database.Insert(propertyDataDto);
}
}
SetEntityTags(entity, _tagRepository);
@@ -506,6 +524,56 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return Database.ExecuteScalar<int>(fullSql);
}
/// <inheritdoc />
public void SetLastLogin(string username, DateTime date)
{
// Important - these queries are designed to execute without an exclusive WriteLock taken in our distributed lock
// table. However due to the data that we are updating which relies on version data we cannot update this data
// without taking some locks, otherwise we'll end up with strange situations because when a member is updated, that operation
// deletes and re-inserts all property data. So if there are concurrent transactions, one deleting and re-inserting and another trying
// to update there can be problems. This is only an issue for cmsPropertyData, not umbracoContentVersion because that table just
// maintains a single row and it isn't deleted/re-inserted.
// So the important part here is the ForUpdate() call on the select to fetch the property data to update.
// Update the cms property value for the member
var sqlSelectTemplateProperty = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin1", s => s
.Select<PropertyDataDto>(x => x.Id)
.From<PropertyDataDto>()
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>((l, r) => l.Id == r.PropertyTypeId)
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, PropertyDataDto>((l, r) => l.Id == r.VersionId)
.InnerJoin<NodeDto>().On<NodeDto, ContentVersionDto>((l, r) => l.NodeId == r.NodeId)
.InnerJoin<MemberDto>().On<MemberDto, NodeDto>((l, r) => l.NodeId == r.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType"))
.Where<PropertyTypeDto>(x => x.Alias == SqlTemplate.Arg<string>("propertyTypeAlias"))
.Where<MemberDto>(x => x.LoginName == SqlTemplate.Arg<string>("username"))
.ForUpdate());
var sqlSelectProperty = sqlSelectTemplateProperty.Sql(Constants.ObjectTypes.Member, Constants.Conventions.Member.LastLoginDate, username);
var update = Sql()
.Update<PropertyDataDto>(u => u
.Set(x => x.DateValue, date))
.WhereIn<PropertyDataDto>(x => x.Id, sqlSelectProperty);
Database.Execute(update);
// Update the umbracoContentVersion value for the member
var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s
.Select<ContentVersionDto>(x => x.Id)
.From<ContentVersionDto>()
.InnerJoin<NodeDto>().On<NodeDto, ContentVersionDto>((l, r) => l.NodeId == r.NodeId)
.InnerJoin<MemberDto>().On<MemberDto, NodeDto>((l, r) => l.NodeId == r.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType"))
.Where<MemberDto>(x => x.LoginName == SqlTemplate.Arg<string>("username")));
var sqlSelectVersion = sqlSelectTemplateVersion.Sql(Constants.ObjectTypes.Member, username);
Database.Execute(Sql()
.Update<ContentVersionDto>(u => u
.Set(x => x.VersionDate, date))
.WhereIn<ContentVersionDto>(x => x.Id, sqlSelectVersion));
}
/// <summary>
/// Gets paged member results.
/// </summary>
@@ -529,20 +597,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ordering);
}
private string _pagedResultsByQueryWhere;
private string GetPagedResultsByQueryWhere()
{
if (_pagedResultsByQueryWhere == null)
_pagedResultsByQueryWhere = " AND ("
+ $"({SqlSyntax.GetQuotedTableName("umbracoNode")}.{SqlSyntax.GetQuotedColumnName("text")} LIKE @0)"
+ " OR "
+ $"({SqlSyntax.GetQuotedTableName("cmsMember")}.{SqlSyntax.GetQuotedColumnName("LoginName")} LIKE @0)"
+ ")";
return _pagedResultsByQueryWhere;
}
protected override string ApplySystemOrdering(ref Sql<ISqlContext> sql, Ordering ordering)
{
if (ordering.OrderBy.InvariantEquals("email"))
@@ -632,5 +686,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
member.ResetDirtyProperties(false);
return member;
}
public IMember GetByUsername(string username)
{
return _memberByUsernameCachePolicy.Get(username, PerformGetByUsername, PerformGetAllByUsername);
}
private IMember PerformGetByUsername(string username)
{
var query = Query<IMember>().Where(x => x.Username.Equals(username));
return PerformGetByQuery(query).FirstOrDefault();
}
private IEnumerable<IMember> PerformGetAllByUsername(params string[] usernames)
{
var query = Query<IMember>().WhereIn(x => x.Username, usernames);
return PerformGetByQuery(query);
}
}
}