aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Users/UserManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs')
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs436
1 files changed, 209 insertions, 227 deletions
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 27d4f40d3..5010751dd 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -5,7 +5,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
@@ -13,7 +12,6 @@ using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using Jellyfin.Data.Events.Users;
using MediaBrowser.Common;
-using MediaBrowser.Common.Cryptography;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication;
@@ -33,9 +31,9 @@ namespace Jellyfin.Server.Implementations.Users
/// <summary>
/// Manages the creation and retrieval of <see cref="User"/> instances.
/// </summary>
- public class UserManager : IUserManager
+ public partial class UserManager : IUserManager
{
- private readonly JellyfinDbProvider _dbProvider;
+ private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IEventManager _eventManager;
private readonly ICryptoProvider _cryptoProvider;
private readonly INetworkManager _networkManager;
@@ -61,7 +59,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <param name="imageProcessor">The image processor.</param>
/// <param name="logger">The logger.</param>
public UserManager(
- JellyfinDbProvider dbProvider,
+ IDbContextFactory<JellyfinDbContext> dbProvider,
IEventManager eventManager,
ICryptoProvider cryptoProvider,
INetworkManager networkManager,
@@ -85,8 +83,9 @@ namespace Jellyfin.Server.Implementations.Users
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
_users = new ConcurrentDictionary<Guid, User>();
- using var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateDbContext();
foreach (var user in dbContext.Users
+ .AsSplitQuery()
.Include(user => user.Permissions)
.Include(user => user.Preferences)
.Include(user => user.AccessSchedules)
@@ -106,10 +105,16 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public IEnumerable<Guid> UsersIds => _users.Keys;
+ // 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
+ // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
+ [GeneratedRegex("^[\\w\\ \\-'._@]+$")]
+ private static partial Regex ValidUsernameRegex();
+
/// <inheritdoc/>
public User? GetUserById(Guid id)
{
- if (id == Guid.Empty)
+ if (id.Equals(default))
{
throw new ArgumentException("Guid can't be empty", nameof(id));
}
@@ -132,10 +137,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task RenameUser(User user, string newName)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
ThrowIfInvalidUsername(newName);
@@ -144,44 +146,39 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("The new and old names must be different.");
}
- await using var dbContext = _dbProvider.CreateContext();
-
- if (await dbContext.Users
- .AsQueryable()
- .Where(u => u.Username == newName && u.Id != user.Id)
- .AnyAsync()
- .ConfigureAwait(false))
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
{
- throw new ArgumentException(string.Format(
- CultureInfo.InvariantCulture,
- "A user with the name '{0}' already exists.",
- newName));
- }
+ if (await dbContext.Users
+ .AnyAsync(u => u.Username == newName && !u.Id.Equals(user.Id))
+ .ConfigureAwait(false))
+ {
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "A user with the name '{0}' already exists.",
+ newName));
+ }
- user.Username = newName;
- await UpdateUserAsync(user).ConfigureAwait(false);
- OnUserUpdated?.Invoke(this, new GenericEventArgs<User>(user));
- }
+ user.Username = newName;
+ await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false);
+ }
- /// <inheritdoc/>
- public void UpdateUser(User user)
- {
- using var dbContext = _dbProvider.CreateContext();
- dbContext.Users.Update(user);
- _users[user.Id] = user;
- dbContext.SaveChanges();
+ var eventArgs = new UserUpdatedEventArgs(user);
+ await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
+ OnUserUpdated?.Invoke(this, eventArgs);
}
/// <inheritdoc/>
public async Task UpdateUserAsync(User user)
{
- await using var dbContext = _dbProvider.CreateContext();
- dbContext.Users.Update(user);
- _users[user.Id] = user;
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false);
+ }
}
- internal async Task<User> CreateUserInternalAsync(string name, JellyfinDb dbContext)
+ internal async Task<User> CreateUserInternalAsync(string name, JellyfinDbContext dbContext)
{
// TODO: Remove after user item data is migrated.
var max = await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false)
@@ -217,12 +214,15 @@ namespace Jellyfin.Server.Implementations.Users
name));
}
- await using var dbContext = _dbProvider.CreateContext();
-
- var newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false);
+ User newUser;
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false);
- dbContext.Users.Add(newUser);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ dbContext.Users.Add(newUser);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
await _eventManager.PublishAsync(new UserCreatedEventArgs(newUser)).ConfigureAwait(false);
@@ -256,9 +256,13 @@ namespace Jellyfin.Server.Implementations.Users
nameof(userId));
}
- await using var dbContext = _dbProvider.CreateContext();
- dbContext.Users.Remove(user);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ dbContext.Users.Remove(user);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
+
_users.Remove(userId);
await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
@@ -271,17 +275,12 @@ namespace Jellyfin.Server.Implementations.Users
}
/// <inheritdoc/>
- public void ResetEasyPassword(User user)
- {
- ChangeEasyPassword(user, string.Empty, null);
- }
-
- /// <inheritdoc/>
public async Task ChangePassword(User user, string newPassword)
{
- if (user == null)
+ ArgumentNullException.ThrowIfNull(user);
+ if (user.HasPermission(PermissionKind.IsAdministrator) && string.IsNullOrWhiteSpace(newPassword))
{
- throw new ArgumentNullException(nameof(user));
+ throw new ArgumentException("Admin user passwords must not be empty", nameof(newPassword));
}
await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
@@ -291,25 +290,6 @@ namespace Jellyfin.Server.Implementations.Users
}
/// <inheritdoc/>
- public void ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1)
- {
- if (newPassword != null)
- {
- newPasswordSha1 = _cryptoProvider.CreatePasswordHash(newPassword).ToString();
- }
-
- if (string.IsNullOrWhiteSpace(newPasswordSha1))
- {
- throw new ArgumentNullException(nameof(newPasswordSha1));
- }
-
- user.EasyPassword = newPasswordSha1;
- UpdateUser(user);
-
- _eventManager.Publish(new UserPasswordChangedEventArgs(user));
- }
-
- /// <inheritdoc/>
public UserDto GetUserDto(User user, string? remoteEndPoint = null)
{
var hasPassword = GetAuthenticationProvider(user).HasPassword(user);
@@ -320,11 +300,10 @@ namespace Jellyfin.Server.Implementations.Users
ServerId = _appHost.SystemId,
HasPassword = hasPassword,
HasConfiguredPassword = hasPassword,
- HasConfiguredEasyPassword = !string.IsNullOrEmpty(user.EasyPassword),
EnableAutoLogin = user.EnableAutoLogin,
LastLoginDate = user.LastLoginDate,
LastActivityDate = user.LastActivityDate,
- PrimaryImageTag = user.ProfileImage != null ? _imageProcessor.GetImageCacheTag(user) : null,
+ PrimaryImageTag = user.ProfileImage is not null ? _imageProcessor.GetImageCacheTag(user) : null,
Configuration = new UserConfiguration
{
SubtitleMode = user.SubtitleMode,
@@ -338,10 +317,10 @@ namespace Jellyfin.Server.Implementations.Users
EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay,
RememberSubtitleSelections = user.RememberSubtitleSelections,
SubtitleLanguagePreference = user.SubtitleLanguagePreference ?? string.Empty,
- OrderedViews = user.GetPreference(PreferenceKind.OrderedViews),
- GroupedFolders = user.GetPreference(PreferenceKind.GroupedFolders),
- MyMediaExcludes = user.GetPreference(PreferenceKind.MyMediaExcludes),
- LatestItemsExcludes = user.GetPreference(PreferenceKind.LatestItemExcludes)
+ OrderedViews = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews),
+ GroupedFolders = user.GetPreferenceValues<Guid>(PreferenceKind.GroupedFolders),
+ MyMediaExcludes = user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes),
+ LatestItemsExcludes = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes)
},
Policy = new UserPolicy
{
@@ -374,8 +353,10 @@ namespace Jellyfin.Server.Implementations.Users
EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing),
ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding),
EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing),
+ EnableCollectionManagement = user.HasPermission(PermissionKind.EnableCollectionManagement),
AccessSchedules = user.AccessSchedules.ToArray(),
BlockedTags = user.GetPreference(PreferenceKind.BlockedTags),
+ AllowedTags = user.GetPreference(PreferenceKind.AllowedTags),
EnabledChannels = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels),
EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices),
EnabledFolders = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders),
@@ -405,15 +386,15 @@ namespace Jellyfin.Server.Implementations.Users
var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint)
.ConfigureAwait(false);
- var authenticationProvider = authResult.authenticationProvider;
- var success = authResult.success;
+ var authenticationProvider = authResult.AuthenticationProvider;
+ var success = authResult.Success;
- if (user == null)
+ if (user is null)
{
- string updatedUsername = authResult.username;
+ string updatedUsername = authResult.Username;
if (success
- && authenticationProvider != null
+ && authenticationProvider is not null
&& authenticationProvider is not DefaultAuthenticationProvider)
{
// Trust the username returned by the authentication provider
@@ -423,25 +404,25 @@ namespace Jellyfin.Server.Implementations.Users
// the authentication provider might have created it
user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
- if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user != null)
+ if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user is not null)
{
await UpdatePolicyAsync(user.Id, hasNewUserPolicy.GetNewUserPolicy()).ConfigureAwait(false);
}
}
}
- if (success && user != null && authenticationProvider != null)
+ if (success && user is not null && authenticationProvider is not null)
{
var providerId = authenticationProvider.GetType().FullName;
- if (providerId != null && !string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase))
+ if (providerId is not null && !string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase))
{
user.AuthenticationProviderId = providerId;
await UpdateUserAsync(user).ConfigureAwait(false);
}
}
- if (user == null)
+ if (user is null)
{
_logger.LogInformation(
"Authentication request for {UserName} has been denied (IP: {IP}).",
@@ -508,7 +489,7 @@ namespace Jellyfin.Server.Implementations.Users
{
var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername);
- if (user != null && isInNetwork)
+ if (user is not null && isInNetwork)
{
var passwordResetProvider = GetPasswordResetProvider(user);
var result = await passwordResetProvider
@@ -539,11 +520,7 @@ namespace Jellyfin.Server.Implementations.Users
}
}
- return new PinRedeemResult
- {
- Success = false,
- UsersReset = Array.Empty<string>()
- };
+ return new PinRedeemResult();
}
/// <inheritdoc />
@@ -556,21 +533,24 @@ namespace Jellyfin.Server.Implementations.Users
}
var defaultName = Environment.UserName;
- if (string.IsNullOrWhiteSpace(defaultName) || !IsValidUsername(defaultName))
+ if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
{
defaultName = "MyJellyfinUser";
}
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
- await using var dbContext = _dbProvider.CreateContext();
- var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
- newUser.SetPermission(PermissionKind.IsAdministrator, true);
- newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
- newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
+ newUser.SetPermission(PermissionKind.IsAdministrator, true);
+ newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
+ newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
- dbContext.Users.Add(newUser);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ dbContext.Users.Add(newUser);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
/// <inheritdoc/>
@@ -606,120 +586,137 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task UpdateConfigurationAsync(Guid userId, UserConfiguration config)
{
- await using var dbContext = _dbProvider.CreateContext();
- var user = dbContext.Users
- .Include(u => u.Permissions)
- .Include(u => u.Preferences)
- .Include(u => u.AccessSchedules)
- .Include(u => u.ProfileImage)
- .FirstOrDefault(u => u.Id == userId)
- ?? throw new ArgumentException("No user exists with given Id!");
-
- user.SubtitleMode = config.SubtitleMode;
- user.HidePlayedInLatest = config.HidePlayedInLatest;
- user.EnableLocalPassword = config.EnableLocalPassword;
- user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack;
- user.DisplayCollectionsView = config.DisplayCollectionsView;
- user.DisplayMissingEpisodes = config.DisplayMissingEpisodes;
- user.AudioLanguagePreference = config.AudioLanguagePreference;
- user.RememberAudioSelections = config.RememberAudioSelections;
- user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay;
- user.RememberSubtitleSelections = config.RememberSubtitleSelections;
- user.SubtitleLanguagePreference = config.SubtitleLanguagePreference;
-
- user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
- user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
- user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
- user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
-
- dbContext.Update(user);
- _users[user.Id] = user;
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id.Equals(userId))
+ ?? throw new ArgumentException("No user exists with given Id!");
+
+ user.SubtitleMode = config.SubtitleMode;
+ user.HidePlayedInLatest = config.HidePlayedInLatest;
+ user.EnableLocalPassword = config.EnableLocalPassword;
+ user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack;
+ user.DisplayCollectionsView = config.DisplayCollectionsView;
+ user.DisplayMissingEpisodes = config.DisplayMissingEpisodes;
+ user.AudioLanguagePreference = config.AudioLanguagePreference;
+ user.RememberAudioSelections = config.RememberAudioSelections;
+ user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay;
+ user.RememberSubtitleSelections = config.RememberSubtitleSelections;
+ user.SubtitleLanguagePreference = config.SubtitleLanguagePreference;
+
+ user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
+ user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
+ user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
+ user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
+
+ dbContext.Update(user);
+ _users[user.Id] = user;
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
/// <inheritdoc/>
public async Task UpdatePolicyAsync(Guid userId, UserPolicy policy)
{
- await using var dbContext = _dbProvider.CreateContext();
- var user = dbContext.Users
- .Include(u => u.Permissions)
- .Include(u => u.Preferences)
- .Include(u => u.AccessSchedules)
- .Include(u => u.ProfileImage)
- .FirstOrDefault(u => u.Id == userId)
- ?? 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"
- int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
- {
- -1 => null,
- 0 => 3,
- _ => policy.LoginAttemptsBeforeLockout
- };
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id.Equals(userId))
+ ?? throw new ArgumentException("No user exists with given Id!");
- user.MaxParentalAgeRating = policy.MaxParentalRating;
- user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess;
- user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit;
- user.AuthenticationProviderId = policy.AuthenticationProviderId;
- user.PasswordResetProviderId = policy.PasswordResetProviderId;
- user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount;
- user.LoginAttemptsBeforeLockout = maxLoginAttempts;
- user.MaxActiveSessions = policy.MaxActiveSessions;
- user.SyncPlayAccess = policy.SyncPlayAccess;
- user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
- user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
- user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
- user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
- user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
- user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
- user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
- user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
- user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
- user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
- user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
- user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
- user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
- user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
- user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
- user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
- user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
- user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
- user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
- user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
- user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
-
- user.AccessSchedules.Clear();
- foreach (var policyAccessSchedule in policy.AccessSchedules)
- {
- user.AccessSchedules.Add(policyAccessSchedule);
- }
-
- // TODO: fix this at some point
- user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty<UnratedItem>());
- user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
- user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
- user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
- user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
- user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
-
- dbContext.Update(user);
- _users[user.Id] = user;
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ // The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
+ int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
+ {
+ -1 => null,
+ 0 => 3,
+ _ => policy.LoginAttemptsBeforeLockout
+ };
+
+ user.MaxParentalAgeRating = policy.MaxParentalRating;
+ user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess;
+ user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit;
+ user.AuthenticationProviderId = policy.AuthenticationProviderId;
+ user.PasswordResetProviderId = policy.PasswordResetProviderId;
+ user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount;
+ user.LoginAttemptsBeforeLockout = maxLoginAttempts;
+ user.MaxActiveSessions = policy.MaxActiveSessions;
+ user.SyncPlayAccess = policy.SyncPlayAccess;
+ user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
+ user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
+ user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
+ user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
+ user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
+ user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
+ user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
+ user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
+ user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
+ user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
+ user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
+ user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
+ user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
+ user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
+ user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
+ user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
+ user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
+ user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
+ user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
+ user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
+ user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
+ user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
+
+ user.AccessSchedules.Clear();
+ foreach (var policyAccessSchedule in policy.AccessSchedules)
+ {
+ user.AccessSchedules.Add(policyAccessSchedule);
+ }
+
+ // TODO: fix this at some point
+ user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty<UnratedItem>());
+ user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
+ user.SetPreference(PreferenceKind.AllowedTags, policy.AllowedTags);
+ user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
+ user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
+ user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
+ user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
+
+ dbContext.Update(user);
+ _users[user.Id] = user;
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
/// <inheritdoc/>
public async Task ClearProfileImageAsync(User user)
{
- await using var dbContext = _dbProvider.CreateContext();
- dbContext.Remove(user.ProfileImage);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ if (user.ProfileImage is null)
+ {
+ return;
+ }
+
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ dbContext.Remove(user.ProfileImage);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
+
user.ProfileImage = null;
_users[user.Id] = user;
}
internal static void ThrowIfInvalidUsername(string name)
{
- if (!string.IsNullOrWhiteSpace(name) && IsValidUsername(name))
+ if (!string.IsNullOrWhiteSpace(name) && ValidUsernameRegex().IsMatch(name))
{
return;
}
@@ -727,14 +724,6 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)", nameof(name));
}
- private static bool IsValidUsername(string name)
- {
- // 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
- // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
- return Regex.IsMatch(name, @"^[\w\ \-'._@]+$");
- }
-
private IAuthenticationProvider GetAuthenticationProvider(User user)
{
return GetAuthenticationProviders(user)[0];
@@ -795,7 +784,7 @@ namespace Jellyfin.Server.Implementations.Users
return providers;
}
- private async Task<(IAuthenticationProvider? authenticationProvider, string username, bool success)> AuthenticateLocalUser(
+ private async Task<(IAuthenticationProvider? AuthenticationProvider, string Username, bool Success)> AuthenticateLocalUser(
string username,
string password,
User? user,
@@ -808,8 +797,8 @@ namespace Jellyfin.Server.Implementations.Users
{
var providerAuthResult =
await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
- var updatedUsername = providerAuthResult.username;
- success = providerAuthResult.success;
+ var updatedUsername = providerAuthResult.Username;
+ success = providerAuthResult.Success;
if (success)
{
@@ -819,24 +808,10 @@ namespace Jellyfin.Server.Implementations.Users
}
}
- if (!success
- && _networkManager.IsInLocalNetwork(remoteEndPoint)
- && user?.EnableLocalPassword == true
- && !string.IsNullOrEmpty(user.EasyPassword))
- {
- // Check easy password
- var passwordHash = PasswordHash.Parse(user.EasyPassword);
- var hash = _cryptoProvider.ComputeHash(
- passwordHash.Id,
- Encoding.UTF8.GetBytes(password),
- passwordHash.Salt.ToArray());
- success = passwordHash.Hash.SequenceEqual(hash);
- }
-
return (authenticationProvider, username, success);
}
- private async Task<(string username, bool success)> AuthenticateWithProvider(
+ private async Task<(string Username, bool Success)> AuthenticateWithProvider(
IAuthenticationProvider provider,
string username,
string password,
@@ -858,7 +833,7 @@ namespace Jellyfin.Server.Implementations.Users
}
catch (AuthenticationException ex)
{
- _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name);
+ _logger.LogDebug(ex, "Error authenticating with provider {Provider}", provider.Name);
return (username, false);
}
@@ -880,5 +855,12 @@ namespace Jellyfin.Server.Implementations.Users
await UpdateUserAsync(user).ConfigureAwait(false);
}
+
+ private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user)
+ {
+ dbContext.Users.Update(user);
+ _users[user.Id] = user;
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
}