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:
@@ -34,6 +34,7 @@
|
||||
if (-not (test-path $nuget))
|
||||
{
|
||||
Write-Host "Download NuGet..."
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
Invoke-WebRequest $source -OutFile $nuget
|
||||
if (-not $?) { throw "Failed to download NuGet." }
|
||||
}
|
||||
|
||||
@@ -1,21 +1,37 @@
|
||||
using System;
|
||||
//using Newtonsoft.Json;
|
||||
using System;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.Cache
|
||||
{
|
||||
public sealed class MemberCacheRefresher : TypedCacheRefresherBase<MemberCacheRefresher, IMember>
|
||||
public sealed class MemberCacheRefresher : PayloadCacheRefresherBase<MemberCacheRefresher, MemberCacheRefresher.JsonPayload>
|
||||
{
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
private readonly LegacyMemberCacheRefresher _legacyMemberRefresher;
|
||||
|
||||
public MemberCacheRefresher(AppCaches appCaches, IIdKeyMap idKeyMap)
|
||||
: base(appCaches)
|
||||
public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IIdKeyMap idKeyMap)
|
||||
: base(appCaches, serializer)
|
||||
{
|
||||
_idKeyMap = idKeyMap;
|
||||
_legacyMemberRefresher = new LegacyMemberCacheRefresher(this, appCaches);
|
||||
}
|
||||
|
||||
public class JsonPayload
|
||||
{
|
||||
//[JsonConstructor]
|
||||
public JsonPayload(int id, string username)
|
||||
{
|
||||
Id = id;
|
||||
Username = username;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
public string Username { get; }
|
||||
}
|
||||
|
||||
#region Define
|
||||
@@ -32,38 +48,45 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
#region Refresher
|
||||
|
||||
public override void Refresh(JsonPayload[] payloads)
|
||||
{
|
||||
ClearCache(payloads);
|
||||
base.Refresh(payloads);
|
||||
}
|
||||
|
||||
public override void Refresh(int id)
|
||||
{
|
||||
ClearCache(id);
|
||||
ClearCache(new JsonPayload(id, null));
|
||||
base.Refresh(id);
|
||||
}
|
||||
|
||||
public override void Remove(int id)
|
||||
{
|
||||
ClearCache(id);
|
||||
ClearCache(new JsonPayload(id, null));
|
||||
base.Remove(id);
|
||||
}
|
||||
|
||||
public override void Refresh(IMember instance)
|
||||
{
|
||||
ClearCache(instance.Id);
|
||||
base.Refresh(instance);
|
||||
}
|
||||
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
|
||||
public void Refresh(IMember instance) => _legacyMemberRefresher.Refresh(instance);
|
||||
|
||||
public override void Remove(IMember instance)
|
||||
{
|
||||
ClearCache(instance.Id);
|
||||
base.Remove(instance);
|
||||
}
|
||||
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
|
||||
public void Remove(IMember instance) => _legacyMemberRefresher.Remove(instance);
|
||||
|
||||
private void ClearCache(int id)
|
||||
private void ClearCache(params JsonPayload[] payloads)
|
||||
{
|
||||
_idKeyMap.ClearCache(id);
|
||||
AppCaches.ClearPartialViewCache();
|
||||
|
||||
var memberCache = AppCaches.IsolatedCaches.Get<IMember>();
|
||||
if (memberCache)
|
||||
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember>(id));
|
||||
|
||||
foreach (var p in payloads)
|
||||
{
|
||||
_idKeyMap.ClearCache(p.Id);
|
||||
if (memberCache)
|
||||
{
|
||||
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember>(p.Id));
|
||||
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember>(p.Username));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -76,5 +99,38 @@ namespace Umbraco.Web.Cache
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Backwards Compat
|
||||
|
||||
// TODO: this is here purely for backwards compat but should be removed in netcore
|
||||
private class LegacyMemberCacheRefresher : TypedCacheRefresherBase<MemberCacheRefresher, IMember>
|
||||
{
|
||||
private readonly MemberCacheRefresher _parent;
|
||||
|
||||
public LegacyMemberCacheRefresher(MemberCacheRefresher parent, AppCaches appCaches) : base(appCaches)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public override Guid RefresherUniqueId => _parent.RefresherUniqueId;
|
||||
|
||||
public override string Name => _parent.Name;
|
||||
|
||||
protected override MemberCacheRefresher This => _parent;
|
||||
|
||||
public override void Refresh(IMember instance)
|
||||
{
|
||||
_parent.ClearCache(new JsonPayload(instance.Id, instance.Username));
|
||||
base.Refresh(instance.Id);
|
||||
}
|
||||
|
||||
public override void Remove(IMember instance)
|
||||
{
|
||||
_parent.ClearCache(new JsonPayload(instance.Id, instance.Username));
|
||||
base.Remove(instance);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
@@ -107,6 +108,18 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="membershipUser"><see cref="IMember"/> or <see cref="IUser"/> to Delete</param>
|
||||
void Delete(T membershipUser);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the last login date for the member if they are found by username
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="date"></param>
|
||||
/// <remarks>
|
||||
/// This is a specialized method because whenever a member logs in, the membership provider requires us to set the 'online' which requires
|
||||
/// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only executing a single query
|
||||
/// for this data since there won't be any other data contention issues.
|
||||
/// </remarks>
|
||||
void SetLastLogin(string username, DateTime date);
|
||||
|
||||
/// <summary>
|
||||
/// Saves an <see cref="IMembershipUser"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Umbraco.Examine
|
||||
/// <param name="onComplete"></param>
|
||||
protected override void PerformIndexItems(IEnumerable<ValueSet> values, Action<IndexOperationEventArgs> onComplete)
|
||||
{
|
||||
//We don't want to re-enumerate this list, but we need to split it into 2x enumerables: invalid and valid items.
|
||||
// We don't want to re-enumerate this list, but we need to split it into 2x enumerables: invalid and valid items.
|
||||
// The Invalid items will be deleted, these are items that have invalid paths (i.e. moved to the recycle bin, etc...)
|
||||
// Then we'll index the Value group all together.
|
||||
// We return 0 or 1 here so we can order the results and do the invalid first and then the valid.
|
||||
@@ -86,7 +86,7 @@ namespace Umbraco.Examine
|
||||
|| !validator.ValidateProtectedContent(path, v.Category))
|
||||
? 0
|
||||
: 1;
|
||||
});
|
||||
}).ToList();
|
||||
|
||||
var hasDeletes = false;
|
||||
var hasUpdates = false;
|
||||
@@ -105,7 +105,7 @@ namespace Umbraco.Examine
|
||||
{
|
||||
hasUpdates = true;
|
||||
//these are the valid ones, so just index them all at once
|
||||
base.PerformIndexItems(group, onComplete);
|
||||
base.PerformIndexItems(group.ToList(), onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,15 +128,16 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
public static void RefreshMemberCache(this DistributedCache dc, params IMember[] members)
|
||||
{
|
||||
dc.Refresh(MemberCacheRefresher.UniqueId, x => x.Id, members);
|
||||
if (members.Length == 0) return;
|
||||
dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username)));
|
||||
}
|
||||
|
||||
public static void RemoveMemberCache(this DistributedCache dc, params IMember[] members)
|
||||
{
|
||||
dc.Remove(MemberCacheRefresher.UniqueId, x => x.Id, members);
|
||||
if (members.Length == 0) return;
|
||||
dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username)));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region MemberGroupCache
|
||||
|
||||
@@ -5,11 +5,9 @@ using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
@@ -33,42 +31,78 @@ namespace Umbraco.Web.Compose
|
||||
public void Initialize()
|
||||
{
|
||||
//Send notifications for the send to publish action
|
||||
ContentService.SentToPublish += (sender, args) => _notifier.Notify(_actions.GetAction<ActionToPublish>(), args.Entity);
|
||||
|
||||
ContentService.SentToPublish += ContentService_SentToPublish;
|
||||
//Send notifications for the published action
|
||||
ContentService.Published += (sender, args) => _notifier.Notify(_actions.GetAction<ActionPublish>(), args.PublishedEntities.ToArray());
|
||||
|
||||
ContentService.Published += ContentService_Published;
|
||||
//Send notifications for the saved action
|
||||
ContentService.Sorted += (sender, args) => ContentServiceSorted(_notifier, sender, args, _actions);
|
||||
|
||||
ContentService.Sorted += ContentService_Sorted;
|
||||
//Send notifications for the update and created actions
|
||||
ContentService.Saved += (sender, args) => ContentServiceSaved(_notifier, sender, args, _actions);
|
||||
|
||||
ContentService.Saved += ContentService_Saved;
|
||||
//Send notifications for the unpublish action
|
||||
ContentService.Unpublished += (sender, args) => _notifier.Notify(_actions.GetAction<ActionUnpublish>(), args.PublishedEntities.ToArray());
|
||||
|
||||
ContentService.Unpublished += ContentService_Unpublished;
|
||||
//Send notifications for the move/move to recycle bin and restore actions
|
||||
ContentService.Moved += (sender, args) => ContentServiceMoved(_notifier, sender, args, _actions);
|
||||
|
||||
ContentService.Moved += ContentService_Moved;
|
||||
//Send notifications for the delete action when content is moved to the recycle bin
|
||||
ContentService.Trashed += (sender, args) => _notifier.Notify(_actions.GetAction<ActionDelete>(), args.MoveInfoCollection.Select(m => m.Entity).ToArray());
|
||||
|
||||
ContentService.Trashed += ContentService_Trashed;
|
||||
//Send notifications for the copy action
|
||||
ContentService.Copied += (sender, args) => _notifier.Notify(_actions.GetAction<ActionCopy>(), args.Original);
|
||||
|
||||
ContentService.Copied += ContentService_Copied;
|
||||
//Send notifications for the rollback action
|
||||
ContentService.RolledBack += (sender, args) => _notifier.Notify(_actions.GetAction<ActionRollback>(), args.Entity);
|
||||
|
||||
ContentService.RolledBack += ContentService_RolledBack;
|
||||
//Send notifications for the public access changed action
|
||||
PublicAccessService.Saved += (sender, args) => PublicAccessServiceSaved(_notifier, sender, args, _contentService, _actions);
|
||||
PublicAccessService.Saved += PublicAccessService_Saved;
|
||||
|
||||
UserService.UserGroupPermissionsAssigned += (sender, args) => UserServiceUserGroupPermissionsAssigned(_notifier, sender, args, _contentService, _actions);
|
||||
UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned;
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
ContentService.SentToPublish -= ContentService_SentToPublish;
|
||||
ContentService.Published -= ContentService_Published;
|
||||
ContentService.Sorted -= ContentService_Sorted;
|
||||
ContentService.Saved -= ContentService_Saved;
|
||||
ContentService.Unpublished -= ContentService_Unpublished;
|
||||
ContentService.Moved -= ContentService_Moved;
|
||||
ContentService.Trashed -= ContentService_Trashed;
|
||||
ContentService.Copied -= ContentService_Copied;
|
||||
ContentService.RolledBack -= ContentService_RolledBack;
|
||||
PublicAccessService.Saved -= PublicAccessService_Saved;
|
||||
UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned;
|
||||
}
|
||||
|
||||
private void ContentServiceSorted(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs<IContent> args, ActionCollection actions)
|
||||
private void UserService_UserGroupPermissionsAssigned(IUserService sender, Core.Events.SaveEventArgs<EntityPermission> args)
|
||||
=> UserServiceUserGroupPermissionsAssigned(args, _contentService);
|
||||
|
||||
private void PublicAccessService_Saved(IPublicAccessService sender, Core.Events.SaveEventArgs<PublicAccessEntry> args)
|
||||
=> PublicAccessServiceSaved(args, _contentService);
|
||||
|
||||
private void ContentService_RolledBack(IContentService sender, Core.Events.RollbackEventArgs<IContent> args)
|
||||
=> _notifier.Notify(_actions.GetAction<ActionRollback>(), args.Entity);
|
||||
|
||||
private void ContentService_Copied(IContentService sender, Core.Events.CopyEventArgs<IContent> args)
|
||||
=> _notifier.Notify(_actions.GetAction<ActionCopy>(), args.Original);
|
||||
|
||||
private void ContentService_Trashed(IContentService sender, Core.Events.MoveEventArgs<IContent> args)
|
||||
=> _notifier.Notify(_actions.GetAction<ActionDelete>(), args.MoveInfoCollection.Select(m => m.Entity).ToArray());
|
||||
|
||||
private void ContentService_Moved(IContentService sender, Core.Events.MoveEventArgs<IContent> args)
|
||||
=> ContentServiceMoved(args);
|
||||
|
||||
private void ContentService_Unpublished(IContentService sender, Core.Events.PublishEventArgs<IContent> args)
|
||||
=> _notifier.Notify(_actions.GetAction<ActionUnpublish>(), args.PublishedEntities.ToArray());
|
||||
|
||||
private void ContentService_Saved(IContentService sender, Core.Events.ContentSavedEventArgs args)
|
||||
=> ContentServiceSaved(args);
|
||||
|
||||
private void ContentService_Sorted(IContentService sender, Core.Events.SaveEventArgs<IContent> args)
|
||||
=> ContentServiceSorted(sender, args);
|
||||
|
||||
private void ContentService_Published(IContentService sender, Core.Events.ContentPublishedEventArgs args)
|
||||
=> _notifier.Notify(_actions.GetAction<ActionPublish>(), args.PublishedEntities.ToArray());
|
||||
|
||||
private void ContentService_SentToPublish(IContentService sender, Core.Events.SendToPublishEventArgs<IContent> args)
|
||||
=> _notifier.Notify(_actions.GetAction<ActionToPublish>(), args.Entity);
|
||||
|
||||
private void ContentServiceSorted(IContentService sender, Core.Events.SaveEventArgs<IContent> args)
|
||||
{
|
||||
var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList();
|
||||
if (parentId.Count != 1) return; // this shouldn't happen, for sorting all entities will have the same parent id
|
||||
@@ -80,10 +114,10 @@ namespace Umbraco.Web.Compose
|
||||
var parent = sender.GetById(parentId[0]);
|
||||
if (parent == null) return; // this shouldn't happen
|
||||
|
||||
notifier.Notify(actions.GetAction<ActionSort>(), new[] { parent });
|
||||
_notifier.Notify(_actions.GetAction<ActionSort>(), new[] { parent });
|
||||
}
|
||||
|
||||
private void ContentServiceSaved(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs<IContent> args, ActionCollection actions)
|
||||
private void ContentServiceSaved(Core.Events.SaveEventArgs<IContent> args)
|
||||
{
|
||||
var newEntities = new List<IContent>();
|
||||
var updatedEntities = new List<IContent>();
|
||||
@@ -103,21 +137,21 @@ namespace Umbraco.Web.Compose
|
||||
updatedEntities.Add(entity);
|
||||
}
|
||||
}
|
||||
notifier.Notify(actions.GetAction<ActionNew>(), newEntities.ToArray());
|
||||
notifier.Notify(actions.GetAction<ActionUpdate>(), updatedEntities.ToArray());
|
||||
_notifier.Notify(_actions.GetAction<ActionNew>(), newEntities.ToArray());
|
||||
_notifier.Notify(_actions.GetAction<ActionUpdate>(), updatedEntities.ToArray());
|
||||
}
|
||||
|
||||
private void UserServiceUserGroupPermissionsAssigned(Notifier notifier, IUserService sender, Core.Events.SaveEventArgs<EntityPermission> args, IContentService contentService, ActionCollection actions)
|
||||
private void UserServiceUserGroupPermissionsAssigned(Core.Events.SaveEventArgs<EntityPermission> args, IContentService contentService)
|
||||
{
|
||||
var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray();
|
||||
if(entities.Any() == false)
|
||||
if (entities.Any() == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
notifier.Notify(actions.GetAction<ActionRights>(), entities);
|
||||
_notifier.Notify(_actions.GetAction<ActionRights>(), entities);
|
||||
}
|
||||
|
||||
private void ContentServiceMoved(Notifier notifier, IContentService sender, Core.Events.MoveEventArgs<IContent> args, ActionCollection actions)
|
||||
private void ContentServiceMoved(Core.Events.MoveEventArgs<IContent> args)
|
||||
{
|
||||
// notify about the move for all moved items
|
||||
_notifier.Notify(_actions.GetAction<ActionMove>(), args.MoveInfoCollection.Select(m => m.Entity).ToArray());
|
||||
@@ -127,20 +161,20 @@ namespace Umbraco.Web.Compose
|
||||
.Where(m => m.OriginalPath.Contains(Constants.System.RecycleBinContentString))
|
||||
.Select(m => m.Entity)
|
||||
.ToArray();
|
||||
if(restoredEntities.Any())
|
||||
if (restoredEntities.Any())
|
||||
{
|
||||
_notifier.Notify(_actions.GetAction<ActionRestore>(), restoredEntities);
|
||||
}
|
||||
}
|
||||
|
||||
private void PublicAccessServiceSaved(Notifier notifier, IPublicAccessService sender, Core.Events.SaveEventArgs<PublicAccessEntry> args, IContentService contentService, ActionCollection actions)
|
||||
private void PublicAccessServiceSaved(Core.Events.SaveEventArgs<PublicAccessEntry> args, IContentService contentService)
|
||||
{
|
||||
var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray();
|
||||
if(entities.Any() == false)
|
||||
if (entities.Any() == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
notifier.Notify(actions.GetAction<ActionProtect>(), entities);
|
||||
_notifier.Notify(_actions.GetAction<ActionProtect>(), entities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,13 +16,15 @@ namespace Umbraco.Web.Compose
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
MemberGroupService.Saved += (s, e) => MemberGroupService_Saved(s, e, _publicAccessService);
|
||||
MemberGroupService.Saved += MemberGroupService_Saved;
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
MemberGroupService.Saved -= MemberGroupService_Saved;
|
||||
}
|
||||
|
||||
static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs<Core.Models.IMemberGroup> e, IPublicAccessService publicAccessService)
|
||||
void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs<Core.Models.IMemberGroup> e)
|
||||
{
|
||||
foreach (var grp in e.SavedEntities)
|
||||
{
|
||||
@@ -32,7 +34,7 @@ namespace Umbraco.Web.Compose
|
||||
&& grp.AdditionalData["previousName"].ToString().IsNullOrWhiteSpace() == false
|
||||
&& grp.AdditionalData["previousName"].ToString() != grp.Name)
|
||||
{
|
||||
publicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"].ToString(), grp.Name);
|
||||
_publicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"].ToString(), grp.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ namespace Umbraco.Core.Compose
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
ContentService.Copied -= ContentServiceCopied;
|
||||
}
|
||||
|
||||
private void ContentServiceCopied(IContentService sender, Events.CopyEventArgs<IContent> e)
|
||||
{
|
||||
|
||||
@@ -24,47 +24,52 @@ namespace Umbraco.Core.Compose
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
ContentService.Moved += (sender, args) => ContentService_Moved(sender, args, _relationService);
|
||||
ContentService.Trashed += (sender, args) => ContentService_Trashed(sender, args, _relationService, _entityService, _textService, _auditService);
|
||||
MediaService.Moved += (sender, args) => MediaService_Moved(sender, args, _relationService);
|
||||
MediaService.Trashed += (sender, args) => MediaService_Trashed(sender, args, _relationService, _entityService, _textService, _auditService);
|
||||
ContentService.Moved += ContentService_Moved;
|
||||
ContentService.Trashed += ContentService_Trashed;
|
||||
MediaService.Moved += MediaService_Moved;
|
||||
MediaService.Trashed += MediaService_Trashed;
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
ContentService.Moved -= ContentService_Moved;
|
||||
ContentService.Trashed -= ContentService_Trashed;
|
||||
MediaService.Moved -= MediaService_Moved;
|
||||
MediaService.Trashed -= MediaService_Trashed;
|
||||
}
|
||||
|
||||
private static void ContentService_Moved(IContentService sender, MoveEventArgs<IContent> e, IRelationService relationService)
|
||||
private void ContentService_Moved(IContentService sender, MoveEventArgs<IContent> e)
|
||||
{
|
||||
foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContentString)))
|
||||
{
|
||||
|
||||
const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias;
|
||||
var relations = relationService.GetByChildId(item.Entity.Id);
|
||||
var relations = _relationService.GetByChildId(item.Entity.Id);
|
||||
|
||||
foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias)))
|
||||
{
|
||||
relationService.Delete(relation);
|
||||
_relationService.Delete(relation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MediaService_Moved(IMediaService sender, MoveEventArgs<IMedia> e, IRelationService relationService)
|
||||
private void MediaService_Moved(IMediaService sender, MoveEventArgs<IMedia> e)
|
||||
{
|
||||
foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinMediaString)))
|
||||
{
|
||||
const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias;
|
||||
var relations = relationService.GetByChildId(item.Entity.Id);
|
||||
var relations = _relationService.GetByChildId(item.Entity.Id);
|
||||
foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias)))
|
||||
{
|
||||
relationService.Delete(relation);
|
||||
_relationService.Delete(relation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ContentService_Trashed(IContentService sender, MoveEventArgs<IContent> e, IRelationService relationService, IEntityService entityService, ILocalizedTextService textService, IAuditService auditService)
|
||||
private void ContentService_Trashed(IContentService sender, MoveEventArgs<IContent> e)
|
||||
{
|
||||
const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias;
|
||||
var relationType = relationService.GetRelationTypeByAlias(relationTypeAlias);
|
||||
var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias);
|
||||
|
||||
// check that the relation-type exists, if not, then recreate it
|
||||
if (relationType == null)
|
||||
@@ -73,7 +78,7 @@ namespace Umbraco.Core.Compose
|
||||
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName;
|
||||
|
||||
relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
|
||||
relationService.Save(relationType);
|
||||
_relationService.Save(relationType);
|
||||
}
|
||||
|
||||
foreach (var item in e.MoveInfoCollection)
|
||||
@@ -86,34 +91,34 @@ namespace Umbraco.Core.Compose
|
||||
//before we can create this relation, we need to ensure that the original parent still exists which
|
||||
//may not be the case if the encompassing transaction also deleted it when this item was moved to the bin
|
||||
|
||||
if (entityService.Exists(originalParentId))
|
||||
if (_entityService.Exists(originalParentId))
|
||||
{
|
||||
// Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later
|
||||
var relation = new Relation(originalParentId, item.Entity.Id, relationType);
|
||||
relationService.Save(relation);
|
||||
_relationService.Save(relation);
|
||||
|
||||
auditService.Add(AuditType.Delete,
|
||||
_auditService.Add(AuditType.Delete,
|
||||
item.Entity.WriterId,
|
||||
item.Entity.Id,
|
||||
ObjectTypes.GetName(UmbracoObjectTypes.Document),
|
||||
string.Format(textService.Localize(
|
||||
string.Format(_textService.Localize(
|
||||
"recycleBin/contentTrashed"),
|
||||
item.Entity.Id, originalParentId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MediaService_Trashed(IMediaService sender, MoveEventArgs<IMedia> e, IRelationService relationService, IEntityService entityService, ILocalizedTextService textService, IAuditService auditService)
|
||||
private void MediaService_Trashed(IMediaService sender, MoveEventArgs<IMedia> e)
|
||||
{
|
||||
const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias;
|
||||
var relationType = relationService.GetRelationTypeByAlias(relationTypeAlias);
|
||||
var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias);
|
||||
// check that the relation-type exists, if not, then recreate it
|
||||
if (relationType == null)
|
||||
{
|
||||
var documentObjectType = Constants.ObjectTypes.Document;
|
||||
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName;
|
||||
relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
|
||||
relationService.Save(relationType);
|
||||
_relationService.Save(relationType);
|
||||
}
|
||||
foreach (var item in e.MoveInfoCollection)
|
||||
{
|
||||
@@ -123,16 +128,16 @@ namespace Umbraco.Core.Compose
|
||||
: Constants.System.Root;
|
||||
//before we can create this relation, we need to ensure that the original parent still exists which
|
||||
//may not be the case if the encompassing transaction also deleted it when this item was moved to the bin
|
||||
if (entityService.Exists(originalParentId))
|
||||
if (_entityService.Exists(originalParentId))
|
||||
{
|
||||
// Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later
|
||||
var relation = new Relation(originalParentId, item.Entity.Id, relationType);
|
||||
relationService.Save(relation);
|
||||
auditService.Add(AuditType.Delete,
|
||||
_relationService.Save(relation);
|
||||
_auditService.Add(AuditType.Delete,
|
||||
item.Entity.CreatorId,
|
||||
item.Entity.Id,
|
||||
ObjectTypes.GetName(UmbracoObjectTypes.Media),
|
||||
string.Format(textService.Localize(
|
||||
string.Format(_textService.Localize(
|
||||
"recycleBin/mediaTrashed"),
|
||||
item.Entity.Id, originalParentId));
|
||||
}
|
||||
|
||||
@@ -53,6 +53,11 @@ namespace Umbraco.Examine
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
return GetValueSetsEnumerable(content, creatorIds, writerIds);
|
||||
}
|
||||
|
||||
private IEnumerable<ValueSet> GetValueSetsEnumerable(IContent[] content, Dictionary<int, IProfile> creatorIds, Dictionary<int, IProfile> writerIds)
|
||||
{
|
||||
// TODO: There is a lot of boxing going on here and ultimately all values will be boxed by Lucene anyways
|
||||
// but I wonder if there's a way to reduce the boxing that we have to do or if it will matter in the end since
|
||||
// Lucene will do it no matter what? One idea was to create a `FieldValue` struct which would contain `object`, `object[]`, `ValueType` and `ValueType[]`
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
|
||||
@@ -6,6 +7,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IMemberRepository : IContentRepository<int, IMember>
|
||||
{
|
||||
IMember GetByUsername(string username);
|
||||
|
||||
/// <summary>
|
||||
/// Finds members in a given role
|
||||
/// </summary>
|
||||
@@ -35,5 +38,17 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <param name="query"></param>
|
||||
/// <returns></returns>
|
||||
int GetCountByQuery(IQuery<IMember> query);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a members last login date based on their username
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="date"></param>
|
||||
/// <remarks>
|
||||
/// This is a specialized method because whenever a member logs in, the membership provider requires us to set the 'online' which requires
|
||||
/// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only executing a single query
|
||||
/// for this data since there won't be any other data contention issues.
|
||||
/// </remarks>
|
||||
void SetLastLogin(string username, DateTime date);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
// TODO: Obsolete this, change all implementations of this like in Dictionary to just use custom Cache policies like in the member repository.
|
||||
|
||||
/// <summary>
|
||||
/// Simple abstract ReadOnly repository used to simply have PerformGet and PeformGetAll with an underlying cache
|
||||
/// </summary>
|
||||
|
||||
@@ -153,10 +153,7 @@ ORDER BY colName";
|
||||
}
|
||||
|
||||
public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true)
|
||||
{
|
||||
// TODO: I know this doesn't follow the normal repository conventions which would require us to create a UserSessionRepository
|
||||
//and also business logic models for these objects but that's just so overkill for what we are doing
|
||||
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var dto = new UserLoginDto
|
||||
{
|
||||
@@ -186,13 +183,14 @@ ORDER BY colName";
|
||||
// that query is going to run a *lot*, make it a template
|
||||
var t = SqlContext.Templates.Get("Umbraco.Core.UserRepository.ValidateLoginSession", s => s
|
||||
.Select<UserLoginDto>()
|
||||
.SelectTop(1)
|
||||
.From<UserLoginDto>()
|
||||
.Where<UserLoginDto>(x => x.SessionId == SqlTemplate.Arg<Guid>("sessionId"))
|
||||
.ForUpdate());
|
||||
|
||||
var sql = t.Sql(sessionId);
|
||||
|
||||
var found = Database.Query<UserLoginDto>(sql).FirstOrDefault();
|
||||
var found = Database.FirstOrDefault<UserLoginDto>(sql);
|
||||
if (found == null || found.UserId != userId || found.LoggedOutUtc.HasValue)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
@@ -9,6 +13,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
public sealed class PropertyEditorsComponent : IComponent
|
||||
{
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly List<Action> _terminate = new List<Action>();
|
||||
|
||||
public PropertyEditorsComponent(PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
@@ -27,32 +32,48 @@ namespace Umbraco.Web.PropertyEditors
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
|
||||
private static void Initialize(FileUploadPropertyEditor fileUpload)
|
||||
{
|
||||
MediaService.Saving += fileUpload.MediaServiceSaving;
|
||||
ContentService.Copied += fileUpload.ContentServiceCopied;
|
||||
|
||||
MediaService.Deleted += (sender, args)
|
||||
=> args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
ContentService.Deleted += (sender, args)
|
||||
=> args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
MemberService.Deleted += (sender, args)
|
||||
=> args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
foreach (var t in _terminate) t();
|
||||
}
|
||||
|
||||
private static void Initialize(ImageCropperPropertyEditor imageCropper)
|
||||
private void Initialize(FileUploadPropertyEditor fileUpload)
|
||||
{
|
||||
MediaService.Saving += fileUpload.MediaServiceSaving;
|
||||
_terminate.Add(() => MediaService.Saving -= fileUpload.MediaServiceSaving);
|
||||
ContentService.Copied += fileUpload.ContentServiceCopied;
|
||||
_terminate.Add(() => ContentService.Copied -= fileUpload.ContentServiceCopied);
|
||||
|
||||
void mediaServiceDeleted(IMediaService sender, DeleteEventArgs<IMedia> args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
MediaService.Deleted += mediaServiceDeleted;
|
||||
_terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted);
|
||||
|
||||
void contentServiceDeleted(IContentService sender, DeleteEventArgs<IContent> args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
ContentService.Deleted += contentServiceDeleted;
|
||||
_terminate.Add(() => ContentService.Deleted -= contentServiceDeleted);
|
||||
|
||||
void memberServiceDeleted(IMemberService sender, DeleteEventArgs<IMember> args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
MemberService.Deleted += memberServiceDeleted;
|
||||
_terminate.Add(() => MemberService.Deleted -= memberServiceDeleted);
|
||||
}
|
||||
|
||||
private void Initialize(ImageCropperPropertyEditor imageCropper)
|
||||
{
|
||||
MediaService.Saving += imageCropper.MediaServiceSaving;
|
||||
_terminate.Add(() => MediaService.Saving -= imageCropper.MediaServiceSaving);
|
||||
ContentService.Copied += imageCropper.ContentServiceCopied;
|
||||
_terminate.Add(() => ContentService.Copied -= imageCropper.ContentServiceCopied);
|
||||
|
||||
MediaService.Deleted += (sender, args)
|
||||
=> args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
ContentService.Deleted += (sender, args)
|
||||
=> args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
MemberService.Deleted += (sender, args)
|
||||
=> args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
void mediaServiceDeleted(IMediaService sender, DeleteEventArgs<IMedia> args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
MediaService.Deleted += mediaServiceDeleted;
|
||||
_terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted);
|
||||
|
||||
void contentServiceDeleted(IContentService sender, DeleteEventArgs<IContent> args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
ContentService.Deleted += contentServiceDeleted;
|
||||
_terminate.Add(() => ContentService.Deleted -= contentServiceDeleted);
|
||||
|
||||
void memberServiceDeleted(IMemberService sender, DeleteEventArgs<IMember> args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast<ContentBase>()));
|
||||
MemberService.Deleted += memberServiceDeleted;
|
||||
_terminate.Add(() => MemberService.Deleted -= memberServiceDeleted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,12 @@ namespace Umbraco.Web.Routing
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
ContentService.Publishing -= ContentService_Publishing;
|
||||
ContentService.Published -= ContentService_Published;
|
||||
ContentService.Moving -= ContentService_Moving;
|
||||
ContentService.Moved -= ContentService_Moved;
|
||||
}
|
||||
|
||||
private void ContentService_Publishing(IContentService sender, PublishEventArgs<IContent> args)
|
||||
{
|
||||
|
||||
32
src/Umbraco.Infrastructure/Scheduling/SimpleTask.cs
Normal file
32
src/Umbraco.Infrastructure/Scheduling/SimpleTask.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple task that executes a delegate synchronously
|
||||
/// </summary>
|
||||
internal class SimpleTask : IBackgroundTask
|
||||
{
|
||||
private readonly Action _action;
|
||||
|
||||
public SimpleTask(Action action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public bool IsAsync => false;
|
||||
|
||||
public void Run() => _action();
|
||||
|
||||
public Task RunAsync(CancellationToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using Examine;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Scoping;
|
||||
@@ -13,9 +14,12 @@ using Umbraco.Core.Services.Changes;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Web.Cache;
|
||||
using Umbraco.Examine;
|
||||
using Umbraco.Web.Scheduling;
|
||||
|
||||
namespace Umbraco.Web.Search
|
||||
{
|
||||
|
||||
|
||||
public sealed class ExamineComponent : Umbraco.Core.Composing.IComponent
|
||||
{
|
||||
private readonly IExamineManager _examineManager;
|
||||
@@ -30,7 +34,7 @@ namespace Umbraco.Web.Search
|
||||
private readonly IMainDom _mainDom;
|
||||
private readonly IProfilingLogger _logger;
|
||||
private readonly IUmbracoIndexesCreator _indexCreator;
|
||||
|
||||
private readonly BackgroundTaskRunner<IBackgroundTask> _indexItemTaskRunner;
|
||||
|
||||
// the default enlist priority is 100
|
||||
// enlist with a lower priority to ensure that anything "default" runs after us
|
||||
@@ -45,7 +49,8 @@ namespace Umbraco.Web.Search
|
||||
IPublishedContentValueSetBuilder publishedContentValueSetBuilder,
|
||||
IValueSetBuilder<IMedia> mediaValueSetBuilder,
|
||||
IValueSetBuilder<IMember> memberValueSetBuilder,
|
||||
BackgroundIndexRebuilder backgroundIndexRebuilder)
|
||||
BackgroundIndexRebuilder backgroundIndexRebuilder,
|
||||
IApplicationShutdownRegistry applicationShutdownRegistry)
|
||||
{
|
||||
_services = services;
|
||||
_scopeProvider = scopeProvider;
|
||||
@@ -58,6 +63,7 @@ namespace Umbraco.Web.Search
|
||||
_mainDom = mainDom;
|
||||
_logger = profilingLogger;
|
||||
_indexCreator = indexCreator;
|
||||
_indexItemTaskRunner = new BackgroundTaskRunner<IBackgroundTask>(_logger, applicationShutdownRegistry);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
@@ -104,7 +110,13 @@ namespace Umbraco.Web.Search
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
ContentCacheRefresher.CacheUpdated -= ContentCacheRefresherUpdated;
|
||||
ContentTypeCacheRefresher.CacheUpdated -= ContentTypeCacheRefresherUpdated;
|
||||
MediaCacheRefresher.CacheUpdated -= MediaCacheRefresherUpdated;
|
||||
MemberCacheRefresher.CacheUpdated -= MemberCacheRefresherUpdated;
|
||||
LanguageCacheRefresher.CacheUpdated -= LanguageCacheRefresherUpdated;
|
||||
}
|
||||
|
||||
#region Cache refresher updated event handlers
|
||||
|
||||
@@ -557,12 +569,18 @@ namespace Umbraco.Web.Search
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An action that will execute at the end of the Scope being completed
|
||||
/// </summary>
|
||||
private abstract class DeferedAction
|
||||
{
|
||||
public virtual void Execute()
|
||||
{ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-indexes an <see cref="IContent"/> item on a background thread
|
||||
/// </summary>
|
||||
private class DeferedReIndexForContent : DeferedAction
|
||||
{
|
||||
private readonly ExamineComponent _examineComponent;
|
||||
@@ -583,21 +601,32 @@ namespace Umbraco.Web.Search
|
||||
|
||||
public static void Execute(ExamineComponent examineComponent, IContent content, bool isPublished)
|
||||
{
|
||||
foreach (var index in examineComponent._examineManager.Indexes.OfType<IUmbracoIndex>()
|
||||
//filter the indexers
|
||||
.Where(x => isPublished || !x.PublishedValuesOnly)
|
||||
.Where(x => x.EnableDefaultEventHandler))
|
||||
// perform the ValueSet lookup on a background thread
|
||||
examineComponent._indexItemTaskRunner.Add(new SimpleTask(() =>
|
||||
{
|
||||
//for content we have a different builder for published vs unpublished
|
||||
var builder = index.PublishedValuesOnly
|
||||
? examineComponent._publishedContentValueSetBuilder
|
||||
: (IValueSetBuilder<IContent>)examineComponent._contentValueSetBuilder;
|
||||
// for content we have a different builder for published vs unpublished
|
||||
// we don't want to build more value sets than is needed so we'll lazily build 2 one for published one for non-published
|
||||
var builders = new Dictionary<bool, Lazy<List<ValueSet>>>
|
||||
{
|
||||
[true] = new Lazy<List<ValueSet>>(() => examineComponent._publishedContentValueSetBuilder.GetValueSets(content).ToList()),
|
||||
[false] = new Lazy<List<ValueSet>>(() => examineComponent._contentValueSetBuilder.GetValueSets(content).ToList())
|
||||
};
|
||||
|
||||
index.IndexItems(builder.GetValueSets(content));
|
||||
}
|
||||
foreach (var index in examineComponent._examineManager.Indexes.OfType<IUmbracoIndex>()
|
||||
//filter the indexers
|
||||
.Where(x => isPublished || !x.PublishedValuesOnly)
|
||||
.Where(x => x.EnableDefaultEventHandler))
|
||||
{
|
||||
var valueSet = builders[index.PublishedValuesOnly].Value;
|
||||
index.IndexItems(valueSet);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-indexes an <see cref="IMedia"/> item on a background thread
|
||||
/// </summary>
|
||||
private class DeferedReIndexForMedia : DeferedAction
|
||||
{
|
||||
private readonly ExamineComponent _examineComponent;
|
||||
@@ -618,18 +647,25 @@ namespace Umbraco.Web.Search
|
||||
|
||||
public static void Execute(ExamineComponent examineComponent, IMedia media, bool isPublished)
|
||||
{
|
||||
var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList();
|
||||
|
||||
foreach (var index in examineComponent._examineManager.Indexes.OfType<IUmbracoIndex>()
|
||||
//filter the indexers
|
||||
.Where(x => isPublished || !x.PublishedValuesOnly)
|
||||
.Where(x => x.EnableDefaultEventHandler))
|
||||
// perform the ValueSet lookup on a background thread
|
||||
examineComponent._indexItemTaskRunner.Add(new SimpleTask(() =>
|
||||
{
|
||||
index.IndexItems(valueSet);
|
||||
}
|
||||
var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList();
|
||||
|
||||
foreach (var index in examineComponent._examineManager.Indexes.OfType<IUmbracoIndex>()
|
||||
//filter the indexers
|
||||
.Where(x => isPublished || !x.PublishedValuesOnly)
|
||||
.Where(x => x.EnableDefaultEventHandler))
|
||||
{
|
||||
index.IndexItems(valueSet);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-indexes an <see cref="IMember"/> item on a background thread
|
||||
/// </summary>
|
||||
private class DeferedReIndexForMember : DeferedAction
|
||||
{
|
||||
private readonly ExamineComponent _examineComponent;
|
||||
@@ -648,13 +684,17 @@ namespace Umbraco.Web.Search
|
||||
|
||||
public static void Execute(ExamineComponent examineComponent, IMember member)
|
||||
{
|
||||
var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList();
|
||||
foreach (var index in examineComponent._examineManager.Indexes.OfType<IUmbracoIndex>()
|
||||
//filter the indexers
|
||||
.Where(x => x.EnableDefaultEventHandler))
|
||||
// perform the ValueSet lookup on a background thread
|
||||
examineComponent._indexItemTaskRunner.Add(new SimpleTask(() =>
|
||||
{
|
||||
index.IndexItems(valueSet);
|
||||
}
|
||||
var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList();
|
||||
foreach (var index in examineComponent._examineManager.Indexes.OfType<IUmbracoIndex>()
|
||||
//filter the indexers
|
||||
.Where(x => x.EnableDefaultEventHandler))
|
||||
{
|
||||
index.IndexItems(valueSet);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -435,15 +435,10 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns><see cref="IMember"/></returns>
|
||||
public IMember GetByUsername(string username)
|
||||
{
|
||||
// TODO: Somewhere in here, whether at this level or the repository level, we need to add
|
||||
// a caching mechanism since this method is used by all the membership providers and could be
|
||||
// called quite a bit when dealing with members.
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
var query = Query<IMember>().Where(x => x.Username.Equals(username));
|
||||
return _memberRepository.Get(query).FirstOrDefault();
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
return _memberRepository.GetByUsername(username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,12 +788,17 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
#region Save
|
||||
|
||||
/// <summary>
|
||||
/// Saves an <see cref="IMember"/>
|
||||
/// </summary>
|
||||
/// <param name="member"><see cref="IMember"/> to Save</param>
|
||||
/// <param name="raiseEvents">Optional parameter to raise events.
|
||||
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
|
||||
/// <inheritdoc />
|
||||
public void SetLastLogin(string username, DateTime date)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_memberRepository.SetLastLogin(username, date);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save(IMember member, bool raiseEvents = true)
|
||||
{
|
||||
//trimming username and email to make sure we have no trailing space
|
||||
@@ -834,12 +834,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a list of <see cref="IMember"/> objects
|
||||
/// </summary>
|
||||
/// <param name="members"><see cref="IEnumerable{IMember}"/> to save</param>
|
||||
/// <param name="raiseEvents">Optional parameter to raise events.
|
||||
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
|
||||
/// <inheritdoc />
|
||||
public void Save(IEnumerable<IMember> members, bool raiseEvents = true)
|
||||
{
|
||||
var membersA = members.ToArray();
|
||||
|
||||
@@ -252,6 +252,13 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
// explicit implementation because we don't need it now but due to the way that the members membership provider is put together
|
||||
// this method must exist in this service as an implementation (legacy)
|
||||
void IMembershipMemberService<IUser>.SetLastLogin(string username, DateTime date)
|
||||
{
|
||||
throw new NotSupportedException("This method is not implemented or supported for users");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves an <see cref="IUser"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -53,32 +53,38 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
ServerVariablesParser.Parsing -= ServerVariablesParser_Parsing;
|
||||
ContentModelBinder.ModelBindingException -= ContentModelBinder_ModelBindingException;
|
||||
FileService.SavingTemplate -= FileService_SavingTemplate;
|
||||
}
|
||||
|
||||
private void InstallServerVars()
|
||||
{
|
||||
// register our url - for the backoffice api
|
||||
ServerVariablesParser.Parsing += (sender, serverVars) =>
|
||||
{
|
||||
if (!serverVars.ContainsKey("umbracoUrls"))
|
||||
throw new ArgumentException("Missing umbracoUrls.");
|
||||
var umbracoUrlsObject = serverVars["umbracoUrls"];
|
||||
if (umbracoUrlsObject == null)
|
||||
throw new ArgumentException("Null umbracoUrls");
|
||||
if (!(umbracoUrlsObject is Dictionary<string, object> umbracoUrls))
|
||||
throw new ArgumentException("Invalid umbracoUrls");
|
||||
ServerVariablesParser.Parsing += ServerVariablesParser_Parsing;
|
||||
}
|
||||
|
||||
if (!serverVars.ContainsKey("umbracoPlugins"))
|
||||
throw new ArgumentException("Missing umbracoPlugins.");
|
||||
if (!(serverVars["umbracoPlugins"] is Dictionary<string, object> umbracoPlugins))
|
||||
throw new ArgumentException("Invalid umbracoPlugins");
|
||||
private void ServerVariablesParser_Parsing(object sender, Dictionary<string, object> serverVars)
|
||||
{
|
||||
if (!serverVars.ContainsKey("umbracoUrls"))
|
||||
throw new ArgumentException("Missing umbracoUrls.");
|
||||
var umbracoUrlsObject = serverVars["umbracoUrls"];
|
||||
if (umbracoUrlsObject == null)
|
||||
throw new ArgumentException("Null umbracoUrls");
|
||||
if (!(umbracoUrlsObject is Dictionary<string, object> umbracoUrls))
|
||||
throw new ArgumentException("Invalid umbracoUrls");
|
||||
|
||||
if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null");
|
||||
var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()));
|
||||
if (!serverVars.ContainsKey("umbracoPlugins"))
|
||||
throw new ArgumentException("Missing umbracoPlugins.");
|
||||
if (!(serverVars["umbracoPlugins"] is Dictionary<string, object> umbracoPlugins))
|
||||
throw new ArgumentException("Invalid umbracoPlugins");
|
||||
|
||||
umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl<ModelsBuilderDashboardController>(controller => controller.BuildModels());
|
||||
umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings();
|
||||
};
|
||||
if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null");
|
||||
var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()));
|
||||
|
||||
umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl<ModelsBuilderDashboardController>(controller => controller.BuildModels());
|
||||
umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings();
|
||||
}
|
||||
|
||||
private Dictionary<string, object> GetModelsBuilderSettings()
|
||||
|
||||
@@ -14,6 +14,11 @@ using Umbraco.Tests.Testing;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using System;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Tests.Common.Builders;
|
||||
using Umbraco.Tests.Common.Builders.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
@@ -77,6 +82,104 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
return new UserGroupRepository(accessor, AppCaches.Disabled, Logger, ShortStringHelper);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_Login_Session()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
var user = MockedUser.CreateUser();
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
repository.Save(user);
|
||||
}
|
||||
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
var sessionId = repository.CreateLoginSession(user.Id, "1.2.3.4");
|
||||
|
||||
// manually update this record to be in the past
|
||||
scope.Database.Execute(SqlContext.Sql()
|
||||
.Update<UserLoginDto>(u => u.Set(x => x.LoggedOutUtc, DateTime.UtcNow.AddDays(-100)))
|
||||
.Where<UserLoginDto>(x => x.SessionId == sessionId));
|
||||
|
||||
var isValid = repository.ValidateLoginSession(user.Id, sessionId);
|
||||
Assert.IsFalse(isValid);
|
||||
|
||||
// create a new one
|
||||
sessionId = repository.CreateLoginSession(user.Id, "1.2.3.4");
|
||||
isValid = repository.ValidateLoginSession(user.Id, sessionId);
|
||||
Assert.IsTrue(isValid);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Add_On_UserRepository()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var user = MockedUser.CreateUser();
|
||||
|
||||
// Act
|
||||
repository.Save(user);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.That(user.HasIdentity, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Multiple_Adds_On_UserRepository()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var user1 = MockedUser.CreateUser("1");
|
||||
var use2 = MockedUser.CreateUser("2");
|
||||
|
||||
// Act
|
||||
repository.Save(user1);
|
||||
|
||||
repository.Save(use2);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.That(user1.HasIdentity, Is.True);
|
||||
Assert.That(use2.HasIdentity, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Verify_Fresh_Entity_Is_Not_Dirty()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var user = MockedUser.CreateUser();
|
||||
repository.Save(user);
|
||||
|
||||
|
||||
// Act
|
||||
var resolved = repository.Get((int)user.Id);
|
||||
bool dirty = ((User)resolved).IsDirty();
|
||||
|
||||
// Assert
|
||||
Assert.That(dirty, Is.False);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Update_On_UserRepository()
|
||||
{
|
||||
@@ -85,7 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var userRepository = CreateRepository(provider);
|
||||
var contentRepository = CreateContentRepository(provider, out var contentTypeRepo);
|
||||
@@ -139,6 +242,268 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Delete_On_UserRepository()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var user = MockedUser.CreateUser();
|
||||
|
||||
// Act
|
||||
repository.Save(user);
|
||||
|
||||
var id = user.Id;
|
||||
|
||||
var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of<IMapperCollection>(),TestObjects.GetGlobalSettings(), Mock.Of<IUserPasswordConfiguration>());
|
||||
|
||||
repository2.Delete(user);
|
||||
|
||||
|
||||
var resolved = repository2.Get((int) id);
|
||||
|
||||
// Assert
|
||||
Assert.That(resolved, Is.Null);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Get_On_UserRepository()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
var userGroupRepository = CreateUserGroupRepository(provider);
|
||||
|
||||
var user = CreateAndCommitUserWithGroup(repository, userGroupRepository);
|
||||
|
||||
// Act
|
||||
var updatedItem = repository.Get(user.Id);
|
||||
|
||||
// FIXME: this test cannot work, user has 2 sections but the way it's created,
|
||||
// they don't show, so the comparison with updatedItem fails - fix!
|
||||
|
||||
// Assert
|
||||
AssertPropertyValues(updatedItem, user);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_GetByQuery_On_UserRepository()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
CreateAndCommitMultipleUsers(repository);
|
||||
|
||||
// Act
|
||||
var query = scope.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1");
|
||||
var result = repository.Get(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Count(), Is.GreaterThanOrEqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_GetAll_By_Param_Ids_On_UserRepository()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var users = CreateAndCommitMultipleUsers(repository);
|
||||
|
||||
// Act
|
||||
var result = repository.GetMany((int) users[0].Id, (int) users[1].Id);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.Any(), Is.True);
|
||||
Assert.That(result.Count(), Is.EqualTo(2));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_GetAll_On_UserRepository()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
CreateAndCommitMultipleUsers(repository);
|
||||
|
||||
// Act
|
||||
var result = repository.GetMany();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.Any(), Is.True);
|
||||
Assert.That(result.Count(), Is.GreaterThanOrEqualTo(3));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Exists_On_UserRepository()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var users = CreateAndCommitMultipleUsers(repository);
|
||||
|
||||
// Act
|
||||
var exists = repository.Exists(users[0].Id);
|
||||
|
||||
// Assert
|
||||
Assert.That(exists, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Count_On_UserRepository()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var users = CreateAndCommitMultipleUsers(repository);
|
||||
|
||||
// Act
|
||||
var query = scope.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2");
|
||||
var result = repository.Count(query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, result);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Paged_Results_By_Query_And_Filter_And_Groups()
|
||||
{
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var users = CreateAndCommitMultipleUsers(repository);
|
||||
var query = provider.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2");
|
||||
|
||||
try
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
|
||||
|
||||
// Act
|
||||
var result = repository.GetPagedResultsByQuery(query, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
|
||||
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
|
||||
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id > -1));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, totalRecs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Paged_Results_With_Filter_And_Groups()
|
||||
{
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var users = CreateAndCommitMultipleUsers(repository);
|
||||
|
||||
try
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
|
||||
|
||||
// Act
|
||||
var result = repository.GetPagedResultsByQuery(null, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
|
||||
includeUserGroups: new[] { Constants.Security.AdminGroupAlias, Constants.Security.SensitiveDataGroupAlias },
|
||||
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
|
||||
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id == -1));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, totalRecs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Invalidate_SecurityStamp_On_Username_Change()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
var userGroupRepository = CreateUserGroupRepository(provider);
|
||||
|
||||
var user = CreateAndCommitUserWithGroup(repository, userGroupRepository);
|
||||
var originalSecurityStamp = user.SecurityStamp;
|
||||
|
||||
// Ensure when user generated a security stamp is present
|
||||
Assert.That(user.SecurityStamp, Is.Not.Null);
|
||||
Assert.That(user.SecurityStamp, Is.Not.Empty);
|
||||
|
||||
// Update username
|
||||
user.Username = user.Username + "UPDATED";
|
||||
repository.Save(user);
|
||||
|
||||
// Get the user
|
||||
var updatedUser = repository.Get(user.Id);
|
||||
|
||||
// Ensure the Security Stamp is invalidated & no longer the same
|
||||
Assert.AreNotEqual(originalSecurityStamp, updatedUser.SecurityStamp);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertPropertyValues(IUser updatedItem, IUser originalUser)
|
||||
{
|
||||
Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id));
|
||||
Assert.That(updatedItem.Name, Is.EqualTo(originalUser.Name));
|
||||
Assert.That(updatedItem.Language, Is.EqualTo(originalUser.Language));
|
||||
Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved));
|
||||
Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue));
|
||||
Assert.That(updatedItem.IsLockedOut, Is.EqualTo(originalUser.IsLockedOut));
|
||||
Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(originalUser.StartContentIds));
|
||||
Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(originalUser.StartMediaIds));
|
||||
Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email));
|
||||
Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username));
|
||||
Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(originalUser.AllowedSections.Count()));
|
||||
foreach (var allowedSection in originalUser.AllowedSections)
|
||||
Assert.IsTrue(updatedItem.AllowedSections.Contains(allowedSection));
|
||||
}
|
||||
|
||||
private static User CreateAndCommitUserWithGroup(IUserRepository repository, IUserGroupRepository userGroupRepository)
|
||||
{
|
||||
@@ -153,6 +518,16 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
return user;
|
||||
}
|
||||
private IUser[] CreateAndCommitMultipleUsers(IUserRepository repository)
|
||||
{
|
||||
var user1 = new UserBuilder().WithoutIdentity().WithSuffix("1").Build();
|
||||
var user2 = new UserBuilder().WithoutIdentity().WithSuffix("2").Build();
|
||||
var user3 = new UserBuilder().WithoutIdentity().WithSuffix("3").Build();
|
||||
repository.Save(user1);
|
||||
repository.Save(user2);
|
||||
repository.Save(user3);
|
||||
return new IUser[] { user1, user2, user3 };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,60 @@ namespace Umbraco.Tests.Services
|
||||
base.SetUp();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Update_Member_Property_Value()
|
||||
{
|
||||
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
|
||||
ServiceContext.MemberTypeService.Save(memberType);
|
||||
IMember member = MockedMember.CreateSimpleMember(memberType, "hello", "helloworld@test123.com", "hello", "hello");
|
||||
member.SetValue("title", "title of mine");
|
||||
ServiceContext.MemberService.Save(member);
|
||||
|
||||
// re-get
|
||||
member = ServiceContext.MemberService.GetById(member.Id);
|
||||
member.SetValue("title", "another title of mine");
|
||||
ServiceContext.MemberService.Save(member);
|
||||
|
||||
// re-get
|
||||
member = ServiceContext.MemberService.GetById(member.Id);
|
||||
Assert.AreEqual("another title of mine", member.GetValue("title"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_By_Username()
|
||||
{
|
||||
var memberType = ServiceContext.MemberTypeService.Get("member");
|
||||
IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true);
|
||||
ServiceContext.MemberService.Save(member);
|
||||
|
||||
var member2 = ServiceContext.MemberService.GetByUsername(member.Username);
|
||||
|
||||
Assert.IsNotNull(member2);
|
||||
Assert.AreEqual(member.Email, member2.Email);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Set_Last_Login_Date()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var memberType = ServiceContext.MemberTypeService.Get("member");
|
||||
IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true)
|
||||
{
|
||||
LastLoginDate = now,
|
||||
UpdateDate = now
|
||||
};
|
||||
ServiceContext.MemberService.Save(member);
|
||||
|
||||
var newDate = now.AddDays(10);
|
||||
ServiceContext.MemberService.SetLastLogin(member.Username, newDate);
|
||||
|
||||
//re-get
|
||||
member = ServiceContext.MemberService.GetById(member.Id);
|
||||
|
||||
Assert.That(member.LastLoginDate, Is.EqualTo(newDate).Within(1).Seconds);
|
||||
Assert.That(member.UpdateDate, Is.EqualTo(newDate).Within(1).Seconds);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Create_Member_With_Properties()
|
||||
{
|
||||
|
||||
@@ -48,7 +48,19 @@ namespace Umbraco.Core.Compose
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
UserService.SavedUserGroup -= OnSavedUserGroupWithUsers;
|
||||
|
||||
UserService.SavedUser -= OnSavedUser;
|
||||
UserService.DeletedUser -= OnDeletedUser;
|
||||
UserService.UserGroupPermissionsAssigned -= UserGroupPermissionAssigned;
|
||||
|
||||
MemberService.Saved -= OnSavedMember;
|
||||
MemberService.Deleted -= OnDeletedMember;
|
||||
MemberService.AssignedRoles -= OnAssignedRoles;
|
||||
MemberService.RemovedRoles -= OnRemovedRoles;
|
||||
MemberService.Exported -= OnMemberExported;
|
||||
}
|
||||
|
||||
public static IUser UnknownUser(IGlobalSettings globalSettings) => new User(globalSettings) { Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" };
|
||||
|
||||
|
||||
@@ -38,7 +38,19 @@ namespace Umbraco.Web.Compose
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
//BackOfficeUserManager.AccountLocked -= ;
|
||||
//BackOfficeUserManager.AccountUnlocked -= ;
|
||||
BackOfficeUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest;
|
||||
BackOfficeUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange;
|
||||
BackOfficeUserManager.LoginFailed -= OnLoginFailed;
|
||||
//BackOfficeUserManager.LoginRequiresVerification -= ;
|
||||
BackOfficeUserManager.LoginSuccess -= OnLoginSuccess;
|
||||
BackOfficeUserManager.LogoutSuccess -= OnLogoutSuccess;
|
||||
BackOfficeUserManager.PasswordChanged -= OnPasswordChanged;
|
||||
BackOfficeUserManager.PasswordReset -= OnPasswordReset;
|
||||
//BackOfficeUserManager.ResetAccessFailedCount -= ;
|
||||
}
|
||||
|
||||
private IUser GetPerformingUser(int userId)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -9,6 +10,7 @@ namespace Umbraco.Web.Logging
|
||||
{
|
||||
private readonly WebProfiler _profiler;
|
||||
private readonly bool _profile;
|
||||
private readonly List<Action> _terminate = new List<Action>();
|
||||
|
||||
public WebProfilerComponent(IProfiler profiler, ILogger logger)
|
||||
{
|
||||
@@ -35,15 +37,23 @@ namespace Umbraco.Web.Logging
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
{
|
||||
UmbracoApplicationBase.ApplicationInit -= InitializeApplication;
|
||||
foreach (var t in _terminate) t();
|
||||
}
|
||||
|
||||
private void InitializeApplication(object sender, EventArgs args)
|
||||
{
|
||||
if (!(sender is HttpApplication app)) return;
|
||||
|
||||
// for *each* application (this will run more than once)
|
||||
app.BeginRequest += (s, a) => _profiler.UmbracoApplicationBeginRequest(s, a);
|
||||
app.EndRequest += (s, a) => _profiler.UmbracoApplicationEndRequest(s, a);
|
||||
void beginRequest(object s, EventArgs a) => _profiler.UmbracoApplicationBeginRequest(s, a);
|
||||
app.BeginRequest += beginRequest;
|
||||
_terminate.Add(() => app.BeginRequest -= beginRequest);
|
||||
|
||||
void endRequest(object s, EventArgs a) => _profiler.UmbracoApplicationEndRequest(s, a);
|
||||
app.EndRequest += endRequest;
|
||||
_terminate.Add(() => app.EndRequest -= endRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -300,8 +299,9 @@ namespace Umbraco.Web.Security
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//Set member online
|
||||
var member = provider.GetUser(username, true);
|
||||
// Get the member, do not set to online - this is done implicitly as part of ValidateUser which is consistent with
|
||||
// how the .NET framework SqlMembershipProvider works. Passing in true will just cause more unnecessary SQL queries/locks.
|
||||
var member = provider.GetUser(username, false);
|
||||
if (member == null)
|
||||
{
|
||||
//this should not happen
|
||||
@@ -770,29 +770,12 @@ namespace Umbraco.Web.Security
|
||||
/// <returns></returns>
|
||||
private IMember GetCurrentPersistedMember()
|
||||
{
|
||||
return _appCaches.RequestCache.GetCacheItem<IMember>(
|
||||
GetCacheKey("GetCurrentPersistedMember"), () =>
|
||||
{
|
||||
var provider = _membershipProvider;
|
||||
var provider = _membershipProvider;
|
||||
|
||||
var username = provider.GetCurrentUserName();
|
||||
var member = _memberService.GetByUsername(username);
|
||||
return member;
|
||||
});
|
||||
}
|
||||
|
||||
private static string GetCacheKey(string key, params object[] additional)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(typeof(MembershipHelper).Name);
|
||||
sb.Append("-");
|
||||
sb.Append(key);
|
||||
foreach (var s in additional)
|
||||
{
|
||||
sb.Append("-");
|
||||
sb.Append(s);
|
||||
}
|
||||
return sb.ToString();
|
||||
// The result of this is cached by the MemberRepository
|
||||
var member = _memberService.GetByUsername(username);
|
||||
return member;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,7 +12,6 @@ using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
|
||||
@@ -321,15 +320,16 @@ namespace Umbraco.Web.Security.Providers
|
||||
|
||||
if (userIsOnline)
|
||||
{
|
||||
member.LastLoginDate = DateTime.Now;
|
||||
member.UpdateDate = DateTime.Now;
|
||||
//don't raise events for this! It just sets the member dates, if we do raise events this will
|
||||
// cause all distributed cache to execute - which will clear out some caches we don't want.
|
||||
// http://issues.umbraco.org/issue/U4-3451
|
||||
|
||||
// when upgrading from 7.2 to 7.3 trying to save will throw
|
||||
if (_umbracoVersion.Current >= new Version(7, 3, 0, 0))
|
||||
MemberService.Save(member, false);
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
// update the database data directly instead of a full member save which requires DB locks
|
||||
MemberService.SetLastLogin(username, now);
|
||||
member.LastLoginDate = now;
|
||||
member.UpdateDate = now;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ConvertToMembershipUser(member);
|
||||
@@ -521,6 +521,8 @@ namespace Umbraco.Web.Security.Providers
|
||||
|
||||
var authenticated = PasswordSecurity.VerifyPassword(password, member.RawPasswordValue);
|
||||
|
||||
var requiresFullSave = false;
|
||||
|
||||
if (authenticated == false)
|
||||
{
|
||||
// TODO: Increment login attempts - lock if too many.
|
||||
@@ -540,6 +542,8 @@ namespace Umbraco.Web.Security.Providers
|
||||
{
|
||||
Current.Logger.Info<UmbracoMembershipProviderBase>("Login attempt failed for username {Username} from IP address {IpAddress}", username, _ipResolver.GetCurrentRequestIpAddress());
|
||||
}
|
||||
|
||||
requiresFullSave = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -547,6 +551,7 @@ namespace Umbraco.Web.Security.Providers
|
||||
{
|
||||
//we have successfully logged in, reset the AccessFailedCount
|
||||
member.FailedPasswordAttempts = 0;
|
||||
requiresFullSave = true;
|
||||
}
|
||||
|
||||
member.LastLoginDate = DateTime.Now;
|
||||
@@ -554,15 +559,23 @@ namespace Umbraco.Web.Security.Providers
|
||||
Current.Logger.Info<UmbracoMembershipProviderBase>("Login attempt succeeded for username {Username} from IP address {IpAddress}", username, _ipResolver.GetCurrentRequestIpAddress());
|
||||
}
|
||||
|
||||
//don't raise events for this! It just sets the member dates, if we do raise events this will
|
||||
// don't raise events for this! It just sets the member dates, if we do raise events this will
|
||||
// cause all distributed cache to execute - which will clear out some caches we don't want.
|
||||
// http://issues.umbraco.org/issue/U4-3451
|
||||
// TODO: In v8 we aren't going to have an overload to disable events, so we'll need to make a different method
|
||||
// for this type of thing (i.e. UpdateLastLogin or similar).
|
||||
|
||||
// when upgrading from 7.2 to 7.3 trying to save will throw
|
||||
if (_umbracoVersion.Current >= new Version(7, 3, 0, 0))
|
||||
MemberService.Save(member, false);
|
||||
if (requiresFullSave)
|
||||
{
|
||||
// when upgrading from 7.2 to 7.3 trying to save will throw
|
||||
if (_umbracoVersion.Current >= new Version(7, 3, 0, 0))
|
||||
MemberService.Save(member, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set the last login date without full save (fast, no locks)
|
||||
MemberService.SetLastLogin(member.Username, member.LastLoginDate);
|
||||
}
|
||||
|
||||
return new ValidateUserResult
|
||||
{
|
||||
|
||||
@@ -156,7 +156,6 @@
|
||||
<Compile Include="PropertyEditors\Validation\ContentPropertyValidationResult.cs" />
|
||||
<Compile Include="PropertyEditors\Validation\ValidationResultConverter.cs" />
|
||||
<Compile Include="Security\IdentityFactoryMiddleware.cs" />
|
||||
<Compile Include="Services\IconService.cs" />
|
||||
<Compile Include="WebApi\Filters\OnlyLocalRequestsAttribute.cs" />
|
||||
<Compile Include="WebAssets\CDF\ClientDependencyRuntimeMinifier.cs" />
|
||||
<Compile Include="Models\NoNodesViewModel.cs" />
|
||||
@@ -234,6 +233,7 @@
|
||||
<Compile Include="Trees\ITreeNodeController.cs" />
|
||||
<Compile Include="Trees\TreeCollectionBuilder.cs" />
|
||||
<Compile Include="UmbracoContext.cs" />
|
||||
<Compile Include="Services\IconService.cs" />
|
||||
<Compile Include="UmbracoContextFactory.cs" />
|
||||
<Compile Include="Mvc\UmbracoVirtualNodeByUdiRouteHandler.cs" />
|
||||
<Compile Include="UmbracoDbProviderFactoryCreator.cs" />
|
||||
|
||||
Reference in New Issue
Block a user