mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-24 02:02:29 -04:00
Remove UserManager Cache (#10781)
* Remove redundant user cache * Use DI for IPasswordResetProvider and IAuthenticationProvider
This commit is contained in:
parent
5ff7c17daf
commit
04dddd3a7b
@ -1,7 +1,7 @@
|
|||||||
#pragma warning disable CA1307
|
#pragma warning disable CA1307
|
||||||
|
#pragma warning disable CA1309 // Use ordinal string comparison - EF can't translate this
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -46,8 +46,6 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
|
private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
|
||||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||||
|
|
||||||
private readonly IDictionary<Guid, User> _users;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UserManager"/> class.
|
/// Initializes a new instance of the <see cref="UserManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -58,6 +56,8 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
/// <param name="imageProcessor">The image processor.</param>
|
/// <param name="imageProcessor">The image processor.</param>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="serverConfigurationManager">The system config manager.</param>
|
/// <param name="serverConfigurationManager">The system config manager.</param>
|
||||||
|
/// <param name="passwordResetProviders">The password reset providers.</param>
|
||||||
|
/// <param name="authenticationProviders">The authentication providers.</param>
|
||||||
public UserManager(
|
public UserManager(
|
||||||
IDbContextFactory<JellyfinDbContext> dbProvider,
|
IDbContextFactory<JellyfinDbContext> dbProvider,
|
||||||
IEventManager eventManager,
|
IEventManager eventManager,
|
||||||
@ -65,7 +65,9 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
IApplicationHost appHost,
|
IApplicationHost appHost,
|
||||||
IImageProcessor imageProcessor,
|
IImageProcessor imageProcessor,
|
||||||
ILogger<UserManager> logger,
|
ILogger<UserManager> logger,
|
||||||
IServerConfigurationManager serverConfigurationManager)
|
IServerConfigurationManager serverConfigurationManager,
|
||||||
|
IEnumerable<IPasswordResetProvider> passwordResetProviders,
|
||||||
|
IEnumerable<IAuthenticationProvider> authenticationProviders)
|
||||||
{
|
{
|
||||||
_dbProvider = dbProvider;
|
_dbProvider = dbProvider;
|
||||||
_eventManager = eventManager;
|
_eventManager = eventManager;
|
||||||
@ -75,35 +77,36 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
|
|
||||||
_passwordResetProviders = appHost.GetExports<IPasswordResetProvider>();
|
_passwordResetProviders = passwordResetProviders.ToList();
|
||||||
_authenticationProviders = appHost.GetExports<IAuthenticationProvider>();
|
_authenticationProviders = authenticationProviders.ToList();
|
||||||
|
|
||||||
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
|
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
|
||||||
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
|
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
|
||||||
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
||||||
|
|
||||||
_users = new ConcurrentDictionary<Guid, User>();
|
|
||||||
using var dbContext = _dbProvider.CreateDbContext();
|
|
||||||
foreach (var user in dbContext.Users
|
|
||||||
.AsSplitQuery()
|
|
||||||
.Include(user => user.Permissions)
|
|
||||||
.Include(user => user.Preferences)
|
|
||||||
.Include(user => user.AccessSchedules)
|
|
||||||
.Include(user => user.ProfileImage)
|
|
||||||
.AsEnumerable())
|
|
||||||
{
|
|
||||||
_users.Add(user.Id, user);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler<GenericEventArgs<User>>? OnUserUpdated;
|
public event EventHandler<GenericEventArgs<User>>? OnUserUpdated;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<User> Users => _users.Values;
|
public IEnumerable<User> Users
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using var dbContext = _dbProvider.CreateDbContext();
|
||||||
|
return GetUsersInternal(dbContext).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Guid> UsersIds => _users.Keys;
|
public IEnumerable<Guid> UsersIds
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using var dbContext = _dbProvider.CreateDbContext();
|
||||||
|
return dbContext.Users.Select(u => u.Id).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
|
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
|
||||||
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
|
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
|
||||||
@ -119,8 +122,8 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
_users.TryGetValue(id, out var user);
|
using var dbContext = _dbProvider.CreateDbContext();
|
||||||
return user;
|
return GetUsersInternal(dbContext).FirstOrDefault(u => u.Id.Equals(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -131,7 +134,9 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
throw new ArgumentException("Invalid username", nameof(name));
|
throw new ArgumentException("Invalid username", nameof(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
|
using var dbContext = _dbProvider.CreateDbContext();
|
||||||
|
return GetUsersInternal(dbContext)
|
||||||
|
.FirstOrDefault(u => string.Equals(u.Username, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -196,8 +201,6 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
user.AddDefaultPermissions();
|
user.AddDefaultPermissions();
|
||||||
user.AddDefaultPreferences();
|
user.AddDefaultPreferences();
|
||||||
|
|
||||||
_users.Add(user.Id, user);
|
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,40 +235,46 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task DeleteUserAsync(Guid userId)
|
public async Task DeleteUserAsync(Guid userId)
|
||||||
{
|
{
|
||||||
if (!_users.TryGetValue(userId, out var user))
|
|
||||||
{
|
|
||||||
throw new ResourceNotFoundException(nameof(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_users.Count == 1)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"The user '{0}' cannot be deleted because there must be at least one user in the system.",
|
|
||||||
user.Username));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.HasPermission(PermissionKind.IsAdministrator)
|
|
||||||
&& Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1)
|
|
||||||
{
|
|
||||||
throw new ArgumentException(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
|
|
||||||
user.Username),
|
|
||||||
nameof(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
|
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
await using (dbContext.ConfigureAwait(false))
|
await using (dbContext.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
|
var user = await dbContext.Users
|
||||||
|
.AsSingleQuery()
|
||||||
|
.Include(u => u.Permissions)
|
||||||
|
.FirstOrDefaultAsync(u => u.Id.Equals(userId))
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
throw new ResourceNotFoundException(nameof(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await dbContext.Users.CountAsync().ConfigureAwait(false) == 1)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"The user '{0}' cannot be deleted because there must be at least one user in the system.",
|
||||||
|
user.Username));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.HasPermission(PermissionKind.IsAdministrator)
|
||||||
|
&& await dbContext.Users
|
||||||
|
.CountAsync(u => u.Permissions.Any(p => p.Kind == PermissionKind.IsAdministrator && p.Value))
|
||||||
|
.ConfigureAwait(false) == 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
|
||||||
|
user.Username),
|
||||||
|
nameof(userId));
|
||||||
|
}
|
||||||
|
|
||||||
dbContext.Users.Remove(user);
|
dbContext.Users.Remove(user);
|
||||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_users.Remove(userId);
|
|
||||||
|
|
||||||
await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -532,23 +541,23 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task InitializeAsync()
|
public async Task InitializeAsync()
|
||||||
{
|
{
|
||||||
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
|
|
||||||
if (_users.Any())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultName = Environment.UserName;
|
|
||||||
if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
|
|
||||||
{
|
|
||||||
defaultName = "MyJellyfinUser";
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
|
|
||||||
|
|
||||||
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
|
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
|
||||||
await using (dbContext.ConfigureAwait(false))
|
await using (dbContext.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
|
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
|
||||||
|
if (await dbContext.Users.AnyAsync().ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultName = Environment.UserName;
|
||||||
|
if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
|
||||||
|
{
|
||||||
|
defaultName = "MyJellyfinUser";
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
|
||||||
|
|
||||||
var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
|
var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
|
||||||
newUser.SetPermission(PermissionKind.IsAdministrator, true);
|
newUser.SetPermission(PermissionKind.IsAdministrator, true);
|
||||||
newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
|
newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
|
||||||
@ -595,12 +604,9 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
|
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
|
||||||
await using (dbContext.ConfigureAwait(false))
|
await using (dbContext.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var user = dbContext.Users
|
var user = await GetUsersInternal(dbContext)
|
||||||
.Include(u => u.Permissions)
|
.FirstOrDefaultAsync(u => u.Id.Equals(userId))
|
||||||
.Include(u => u.Preferences)
|
.ConfigureAwait(false)
|
||||||
.Include(u => u.AccessSchedules)
|
|
||||||
.Include(u => u.ProfileImage)
|
|
||||||
.FirstOrDefault(u => u.Id.Equals(userId))
|
|
||||||
?? throw new ArgumentException("No user exists with given Id!");
|
?? throw new ArgumentException("No user exists with given Id!");
|
||||||
|
|
||||||
user.SubtitleMode = config.SubtitleMode;
|
user.SubtitleMode = config.SubtitleMode;
|
||||||
@ -628,7 +634,6 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
|
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
|
||||||
|
|
||||||
dbContext.Update(user);
|
dbContext.Update(user);
|
||||||
_users[user.Id] = user;
|
|
||||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -639,12 +644,9 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
|
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
|
||||||
await using (dbContext.ConfigureAwait(false))
|
await using (dbContext.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var user = dbContext.Users
|
var user = await GetUsersInternal(dbContext)
|
||||||
.Include(u => u.Permissions)
|
.FirstOrDefaultAsync(u => u.Id.Equals(userId))
|
||||||
.Include(u => u.Preferences)
|
.ConfigureAwait(false)
|
||||||
.Include(u => u.AccessSchedules)
|
|
||||||
.Include(u => u.ProfileImage)
|
|
||||||
.FirstOrDefault(u => u.Id.Equals(userId))
|
|
||||||
?? throw new ArgumentException("No user exists with given Id!");
|
?? throw new ArgumentException("No user exists with given Id!");
|
||||||
|
|
||||||
// The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
|
// The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
|
||||||
@ -704,7 +706,6 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
|
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
|
||||||
|
|
||||||
dbContext.Update(user);
|
dbContext.Update(user);
|
||||||
_users[user.Id] = user;
|
|
||||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -725,7 +726,6 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.ProfileImage = null;
|
user.ProfileImage = null;
|
||||||
_users[user.Id] = user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ThrowIfInvalidUsername(string name)
|
internal static void ThrowIfInvalidUsername(string name)
|
||||||
@ -872,8 +872,15 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user)
|
private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user)
|
||||||
{
|
{
|
||||||
dbContext.Users.Update(user);
|
dbContext.Users.Update(user);
|
||||||
_users[user.Id] = user;
|
|
||||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IQueryable<User> GetUsersInternal(JellyfinDbContext dbContext)
|
||||||
|
=> dbContext.Users
|
||||||
|
.AsSplitQuery()
|
||||||
|
.Include(user => user.Permissions)
|
||||||
|
.Include(user => user.Preferences)
|
||||||
|
.Include(user => user.AccessSchedules)
|
||||||
|
.Include(user => user.ProfileImage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using Jellyfin.Server.Implementations.Security;
|
|||||||
using Jellyfin.Server.Implementations.Trickplay;
|
using Jellyfin.Server.Implementations.Trickplay;
|
||||||
using Jellyfin.Server.Implementations.Users;
|
using Jellyfin.Server.Implementations.Users;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.BaseItemManager;
|
using MediaBrowser.Controller.BaseItemManager;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
@ -78,6 +79,9 @@ namespace Jellyfin.Server
|
|||||||
|
|
||||||
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
||||||
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
||||||
|
serviceCollection.AddSingleton<IAuthenticationProvider, DefaultAuthenticationProvider>();
|
||||||
|
serviceCollection.AddSingleton<IAuthenticationProvider, InvalidAuthProvider>();
|
||||||
|
serviceCollection.AddSingleton<IPasswordResetProvider, DefaultPasswordResetProvider>();
|
||||||
serviceCollection.AddScoped<IDisplayPreferencesManager, DisplayPreferencesManager>();
|
serviceCollection.AddScoped<IDisplayPreferencesManager, DisplayPreferencesManager>();
|
||||||
serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
||||||
serviceCollection.AddSingleton<ITrickplayManager, TrickplayManager>();
|
serviceCollection.AddSingleton<ITrickplayManager, TrickplayManager>();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user