Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/Umbraco.Core/Cache/Refreshers/Implement/UserCacheRefresher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,18 @@ public override void Refresh(Guid id)
base.Refresh(id);
}

public override void Refresh(int id)
{
Remove(id);
base.Refresh(id);
}

public override void Remove(int id)
{
string cacheKey = $"uRepo_{typeof(IUser).Name}_" + id;
Comment thread
Zeegaan marked this conversation as resolved.
Outdated
AppCaches.RuntimeCache.Clear(cacheKey);
base.Remove(id);
}

#endregion
}
9 changes: 9 additions & 0 deletions src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ public interface IUserRepository : IReadWriteQueryRepository<Guid, IUser>
/// <returns></returns>
bool ExistsByUserName(string username);

/// <summary>
/// Returns a user by id
/// </summary>
/// <param name="id"></param>
/// <returns>
/// A cached <see cref="IUser" /> instance
/// </returns>
IUser? Get(int id);

/// <summary>
/// Checks if a user with the login exists
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Persistence.Querying;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
Expand All @@ -20,6 +21,7 @@
using Umbraco.Cms.Infrastructure.Persistence.Querying;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Extensions;
using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope;

namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
/// <summary>
Expand All @@ -36,6 +38,8 @@ internal class UserRepository : EntityRepositoryBase<Guid, IUser>, IUserReposito
private bool _passwordConfigInitialized;
private readonly object _sqliteValidateSessionLock = new();
private readonly IDictionary<string, IPermissionMapper> _permissionMappers;
private readonly IAppPolicyCache _globalCache;
private readonly IScopeAccessor _scopeAccessor;

/// <summary>
/// Initializes a new instance of the <see cref="UserRepository" /> class.
Expand All @@ -52,6 +56,7 @@ internal class UserRepository : EntityRepositoryBase<Guid, IUser>, IUserReposito
/// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="runtimeState">State of the runtime.</param>
/// <param name="permissionMappers">The permission mappers.</param>
/// <param name="globalCache">The app policy cache.</param>
/// <exception cref="System.ArgumentNullException">
/// mapperCollection
/// or
Expand All @@ -68,15 +73,18 @@ public UserRepository(
IOptions<UserPasswordConfigurationSettings> passwordConfiguration,
IJsonSerializer jsonSerializer,
IRuntimeState runtimeState,
IEnumerable<IPermissionMapper> permissionMappers)
IEnumerable<IPermissionMapper> permissionMappers,
IAppPolicyCache globalCache)
: base(scopeAccessor, appCaches, logger)
{
_scopeAccessor = scopeAccessor;
_mapperCollection = mapperCollection ?? throw new ArgumentNullException(nameof(mapperCollection));
_globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings));
_passwordConfiguration =
passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration));
_jsonSerializer = jsonSerializer;
_runtimeState = runtimeState;
_globalCache = globalCache;
_permissionMappers = permissionMappers.ToDictionary(x => x.Context);
}

Expand Down Expand Up @@ -917,6 +925,39 @@ public bool ExistsByUserName(string username)
return Database.ExecuteScalar<int>(sql) > 0;
}

// This is a bit hacky, as we're stealing some of the cache implementation, so we also can cache user by id
// We do however need this, as all content have creatorId (as int) and thus when we index content
// this gets called for each content item, and we need to cache the user to avoid a lot of db calls
// TODO: Remove this once CreatorId gets migrated to a key.
public IUser? Get(int id)
{
string cacheKey = $"uRepo_{typeof(IUser).Name}_" + id;
IUser? cachedUser = IsolatedCache.GetCacheItem<IUser>(cacheKey);
if (cachedUser is not null)
{
return cachedUser;
}

Sql<ISqlContext> sql = SqlContext.Sql()
.Select<UserDto>()
.From<UserDto>()
.Where<UserDto>(x => x.Id == id);

List<UserDto>? dtos = Database.Fetch<UserDto>(sql);

if (dtos.Count == 0)
{
return null;
}

PerformGetReferencedDtos(dtos);

IUser user = UserFactory.BuildEntity(_globalSettings, dtos[0], _permissionMappers);
IsolatedCache.Insert(cacheKey, () => user, TimeSpan.FromMinutes(5), true);

return user;
}

public bool ExistsByLogin(string login)
{
Sql<ISqlContext> sql = SqlContext.Sql()
Expand Down
3 changes: 1 addition & 2 deletions src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,7 @@ public Task<UserOperationStatus> DisableAsync(IUser user)

try
{
IQuery<IUser> query = _scopeProvider.CreateQuery<IUser>().Where(x => x.Id == id);
return Task.FromResult(_userRepository.Get(query).FirstOrDefault());
return Task.FromResult(_userRepository.Get(id));
}
catch (DbException)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class UserRepositoryTest : UmbracoIntegrationTest
private IMediaRepository MediaRepository => GetRequiredService<IMediaRepository>();
private IEnumerable<IPermissionMapper> PermissionMappers => GetRequiredService<IEnumerable<IPermissionMapper>>();

private IAppPolicyCache AppPolicyCache => GetRequiredService<IAppPolicyCache>();

private UserRepository CreateRepository(ICoreScopeProvider provider)
{
var accessor = (IScopeAccessor)provider;
Expand All @@ -54,7 +56,8 @@ private UserRepository CreateRepository(ICoreScopeProvider provider)
Options.Create(new UserPasswordConfigurationSettings()),
new SystemTextJsonSerializer(),
mockRuntimeState.Object,
PermissionMappers);
PermissionMappers,
AppPolicyCache);
return repository;
}

Expand Down Expand Up @@ -161,7 +164,8 @@ public void Can_Perform_Delete_On_UserRepository()
Options.Create(new UserPasswordConfigurationSettings()),
new SystemTextJsonSerializer(),
mockRuntimeState.Object,
PermissionMappers);
PermissionMappers,
AppPolicyCache);

repository2.Delete(user);

Expand Down