< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.Users.UserManager
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/Users/UserManager.cs
Line coverage
44%
Covered lines: 215
Uncovered lines: 271
Coverable lines: 486
Total lines: 896
Line coverage: 44.2%
Branch coverage
39%
Covered branches: 57
Total branches: 146
Branch coverage: 39%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 70.9% (117/165) Branch coverage: 42.5% (17/40) Total lines: 8944/19/2026 - 12:14:27 AM Line coverage: 44.4% (215/484) Branch coverage: 39% (57/146) Total lines: 8945/4/2026 - 12:15:16 AM Line coverage: 44.2% (215/486) Branch coverage: 39% (57/146) Total lines: 896 1/23/2026 - 12:11:06 AM Line coverage: 70.9% (117/165) Branch coverage: 42.5% (17/40) Total lines: 8944/19/2026 - 12:14:27 AM Line coverage: 44.4% (215/484) Branch coverage: 39% (57/146) Total lines: 8945/4/2026 - 12:15:16 AM Line coverage: 44.2% (215/486) Branch coverage: 39% (57/146) Total lines: 896

Coverage delta

Coverage delta 27 -27

Metrics

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/Users/UserManager.cs

#LineLine coverage
 1#pragma warning disable CA1307
 2
 3using System;
 4using System.Collections.Concurrent;
 5using System.Collections.Generic;
 6using System.Globalization;
 7using System.Linq;
 8using System.Text.RegularExpressions;
 9using System.Threading.Tasks;
 10using Jellyfin.Data;
 11using Jellyfin.Data.Enums;
 12using Jellyfin.Data.Events;
 13using Jellyfin.Data.Events.Users;
 14using Jellyfin.Database.Implementations;
 15using Jellyfin.Database.Implementations.Entities;
 16using Jellyfin.Database.Implementations.Enums;
 17using Jellyfin.Extensions;
 18using MediaBrowser.Common;
 19using MediaBrowser.Common.Extensions;
 20using MediaBrowser.Common.Net;
 21using MediaBrowser.Controller.Authentication;
 22using MediaBrowser.Controller.Configuration;
 23using MediaBrowser.Controller.Drawing;
 24using MediaBrowser.Controller.Events;
 25using MediaBrowser.Controller.Library;
 26using MediaBrowser.Controller.Net;
 27using MediaBrowser.Model.Configuration;
 28using MediaBrowser.Model.Dto;
 29using MediaBrowser.Model.Users;
 30using Microsoft.EntityFrameworkCore;
 31using Microsoft.Extensions.Logging;
 32
 33namespace Jellyfin.Server.Implementations.Users
 34{
 35    /// <summary>
 36    /// Manages the creation and retrieval of <see cref="User"/> instances.
 37    /// </summary>
 38    public partial class UserManager : IUserManager
 39    {
 40        private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 41        private readonly IEventManager _eventManager;
 42        private readonly INetworkManager _networkManager;
 43        private readonly IApplicationHost _appHost;
 44        private readonly IImageProcessor _imageProcessor;
 45        private readonly ILogger<UserManager> _logger;
 46        private readonly IReadOnlyCollection<IPasswordResetProvider> _passwordResetProviders;
 47        private readonly IReadOnlyCollection<IAuthenticationProvider> _authenticationProviders;
 48        private readonly InvalidAuthProvider _invalidAuthProvider;
 49        private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider;
 50        private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
 51        private readonly IServerConfigurationManager _serverConfigurationManager;
 52
 53        private readonly IDictionary<Guid, User> _users;
 54
 55        /// <summary>
 56        /// Initializes a new instance of the <see cref="UserManager"/> class.
 57        /// </summary>
 58        /// <param name="dbProvider">The database provider.</param>
 59        /// <param name="eventManager">The event manager.</param>
 60        /// <param name="networkManager">The network manager.</param>
 61        /// <param name="appHost">The application host.</param>
 62        /// <param name="imageProcessor">The image processor.</param>
 63        /// <param name="logger">The logger.</param>
 64        /// <param name="serverConfigurationManager">The system config manager.</param>
 65        /// <param name="passwordResetProviders">The password reset providers.</param>
 66        /// <param name="authenticationProviders">The authentication providers.</param>
 67        public UserManager(
 68            IDbContextFactory<JellyfinDbContext> dbProvider,
 69            IEventManager eventManager,
 70            INetworkManager networkManager,
 71            IApplicationHost appHost,
 72            IImageProcessor imageProcessor,
 73            ILogger<UserManager> logger,
 74            IServerConfigurationManager serverConfigurationManager,
 75            IEnumerable<IPasswordResetProvider> passwordResetProviders,
 76            IEnumerable<IAuthenticationProvider> authenticationProviders)
 77        {
 2178            _dbProvider = dbProvider;
 2179            _eventManager = eventManager;
 2180            _networkManager = networkManager;
 2181            _appHost = appHost;
 2182            _imageProcessor = imageProcessor;
 2183            _logger = logger;
 2184            _serverConfigurationManager = serverConfigurationManager;
 85
 2186            _passwordResetProviders = passwordResetProviders.ToList();
 2187            _authenticationProviders = authenticationProviders.ToList();
 88
 2189            _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
 2190            _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
 2191            _defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
 92
 2193            _users = new ConcurrentDictionary<Guid, User>();
 2194            using var dbContext = _dbProvider.CreateDbContext();
 4295            foreach (var user in dbContext.Users
 2196                .AsSingleQuery()
 2197                .Include(user => user.Permissions)
 2198                .Include(user => user.Preferences)
 2199                .Include(user => user.AccessSchedules)
 21100                .Include(user => user.ProfileImage)
 21101                .AsEnumerable())
 102            {
 0103                _users.Add(user.Id, user);
 104            }
 21105        }
 106
 107        /// <inheritdoc/>
 108        public event EventHandler<GenericEventArgs<User>>? OnUserUpdated;
 109
 110        /// <inheritdoc/>
 37111        public IEnumerable<User> Users => _users.Values;
 112
 113        /// <inheritdoc/>
 0114        public IEnumerable<Guid> UsersIds => _users.Keys;
 115
 116        // This is some regex that matches only on unicode "word" characters, as well as -, _ and @
 117        // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
 118        // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes
 119        [GeneratedRegex(@"^(?!\s)[\w\ \-'._@+]+(?<!\s)$")]
 120        private static partial Regex ValidUsernameRegex();
 121
 122        /// <inheritdoc/>
 123        public User? GetUserById(Guid id)
 124        {
 237125            if (id.IsEmpty())
 126            {
 0127                throw new ArgumentException("Guid can't be empty", nameof(id));
 128            }
 129
 237130            _users.TryGetValue(id, out var user);
 237131            return user;
 132        }
 133
 134        /// <inheritdoc/>
 135        public User? GetUserByName(string name)
 136        {
 15137            if (string.IsNullOrWhiteSpace(name))
 138            {
 0139                throw new ArgumentException("Invalid username", nameof(name));
 140            }
 141
 15142            return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase)
 143        }
 144
 145        /// <inheritdoc/>
 146        public async Task RenameUser(User user, string newName)
 147        {
 0148            ArgumentNullException.ThrowIfNull(user);
 149
 0150            ThrowIfInvalidUsername(newName);
 151
 0152            if (user.Username.Equals(newName, StringComparison.Ordinal))
 153            {
 0154                throw new ArgumentException("The new and old names must be different.");
 155            }
 156
 0157            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0158            await using (dbContext.ConfigureAwait(false))
 159            {
 160#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string compari
 161#pragma warning disable CA1311 // Specify a culture or use an invariant version to avoid implicit dependency on current 
 162#pragma warning disable CA1304 // The behavior of 'string.ToUpper()' could vary based on the current user's locale setti
 0163                if (await dbContext.Users
 0164                        .AnyAsync(u => u.Username.ToUpper() == newName.ToUpper() && !u.Id.Equals(user.Id))
 0165                        .ConfigureAwait(false))
 166                {
 0167                    throw new ArgumentException(string.Format(
 0168                        CultureInfo.InvariantCulture,
 0169                        "A user with the name '{0}' already exists.",
 0170                        newName));
 171                }
 172#pragma warning restore CA1304 // The behavior of 'string.ToUpper()' could vary based on the current user's locale setti
 173#pragma warning restore CA1311 // Specify a culture or use an invariant version to avoid implicit dependency on current 
 174#pragma warning restore CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string compari
 175
 0176                user.Username = newName;
 0177                await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false);
 178            }
 179
 0180            var eventArgs = new UserUpdatedEventArgs(user);
 0181            await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
 0182            OnUserUpdated?.Invoke(this, eventArgs);
 0183        }
 184
 185        /// <inheritdoc/>
 186        public async Task UpdateUserAsync(User user)
 187        {
 19188            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 19189            await using (dbContext.ConfigureAwait(false))
 190            {
 19191                await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false);
 192            }
 19193        }
 194
 195        internal async Task<User> CreateUserInternalAsync(string name, JellyfinDbContext dbContext)
 196        {
 197            // TODO: Remove after user item data is migrated.
 17198            var max = await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false)
 17199                ? await dbContext.Users.AsQueryable().Select(u => u.InternalId).MaxAsync().ConfigureAwait(false)
 17200                : 0;
 201
 17202            var user = new User(
 17203                name,
 17204                _defaultAuthenticationProvider.GetType().FullName!,
 17205                _defaultPasswordResetProvider.GetType().FullName!)
 17206            {
 17207                InternalId = max + 1
 17208            };
 209
 17210            user.AddDefaultPermissions();
 17211            user.AddDefaultPreferences();
 212
 17213            return user;
 17214        }
 215
 216        /// <inheritdoc/>
 217        public async Task<User> CreateUserAsync(string name)
 218        {
 2219            ThrowIfInvalidUsername(name);
 220
 1221            if (Users.Any(u => u.Username.Equals(name, StringComparison.OrdinalIgnoreCase)))
 222            {
 0223                throw new ArgumentException(string.Format(
 0224                    CultureInfo.InvariantCulture,
 0225                    "A user with the name '{0}' already exists.",
 0226                    name));
 227            }
 228
 229            User newUser;
 1230            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 1231            await using (dbContext.ConfigureAwait(false))
 232            {
 1233                newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false);
 234
 1235                dbContext.Users.Add(newUser);
 1236                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 1237                _users.Add(newUser.Id, newUser);
 238            }
 239
 1240            await _eventManager.PublishAsync(new UserCreatedEventArgs(newUser)).ConfigureAwait(false);
 241
 1242            return newUser;
 1243        }
 244
 245        /// <inheritdoc/>
 246        public async Task DeleteUserAsync(Guid userId)
 247        {
 0248            if (!_users.TryGetValue(userId, out var user))
 249            {
 0250                throw new ResourceNotFoundException(nameof(userId));
 251            }
 252
 0253            if (_users.Count == 1)
 254            {
 0255                throw new InvalidOperationException(string.Format(
 0256                    CultureInfo.InvariantCulture,
 0257                    "The user '{0}' cannot be deleted because there must be at least one user in the system.",
 0258                    user.Username));
 259            }
 260
 0261            if (user.HasPermission(PermissionKind.IsAdministrator)
 0262                && Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1)
 263            {
 0264                throw new ArgumentException(
 0265                    string.Format(
 0266                        CultureInfo.InvariantCulture,
 0267                        "The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
 0268                        user.Username),
 0269                    nameof(userId));
 270            }
 271
 0272            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0273            await using (dbContext.ConfigureAwait(false))
 274            {
 0275                dbContext.Users.Attach(user);
 0276                dbContext.Users.Remove(user);
 0277                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 278            }
 279
 0280            _users.Remove(userId);
 281
 0282            await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
 0283        }
 284
 285        /// <inheritdoc/>
 286        public Task ResetPassword(User user)
 287        {
 0288            return ChangePassword(user, string.Empty);
 289        }
 290
 291        /// <inheritdoc/>
 292        public async Task ChangePassword(User user, string newPassword)
 293        {
 3294            ArgumentNullException.ThrowIfNull(user);
 3295            if (user.HasPermission(PermissionKind.IsAdministrator) && string.IsNullOrWhiteSpace(newPassword))
 296            {
 0297                throw new ArgumentException("Admin user passwords must not be empty", nameof(newPassword));
 298            }
 299
 3300            await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
 3301            await UpdateUserAsync(user).ConfigureAwait(false);
 302
 3303            await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false);
 3304        }
 305
 306        /// <inheritdoc/>
 307        public UserDto GetUserDto(User user, string? remoteEndPoint = null)
 308        {
 34309            var castReceiverApplications = _serverConfigurationManager.Configuration.CastReceiverApplications;
 34310            return new UserDto
 34311            {
 34312                Name = user.Username,
 34313                Id = user.Id,
 34314                ServerId = _appHost.SystemId,
 34315                EnableAutoLogin = user.EnableAutoLogin,
 34316                LastLoginDate = user.LastLoginDate,
 34317                LastActivityDate = user.LastActivityDate,
 34318                PrimaryImageTag = user.ProfileImage is not null ? _imageProcessor.GetImageCacheTag(user) : null,
 34319                Configuration = new UserConfiguration
 34320                {
 34321                    SubtitleMode = user.SubtitleMode,
 34322                    HidePlayedInLatest = user.HidePlayedInLatest,
 34323                    EnableLocalPassword = user.EnableLocalPassword,
 34324                    PlayDefaultAudioTrack = user.PlayDefaultAudioTrack,
 34325                    DisplayCollectionsView = user.DisplayCollectionsView,
 34326                    DisplayMissingEpisodes = user.DisplayMissingEpisodes,
 34327                    AudioLanguagePreference = user.AudioLanguagePreference,
 34328                    RememberAudioSelections = user.RememberAudioSelections,
 34329                    EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay,
 34330                    RememberSubtitleSelections = user.RememberSubtitleSelections,
 34331                    SubtitleLanguagePreference = user.SubtitleLanguagePreference ?? string.Empty,
 34332                    OrderedViews = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews),
 34333                    GroupedFolders = user.GetPreferenceValues<Guid>(PreferenceKind.GroupedFolders),
 34334                    MyMediaExcludes = user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes),
 34335                    LatestItemsExcludes = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes),
 34336                    CastReceiverId = string.IsNullOrEmpty(user.CastReceiverId)
 34337                        ? castReceiverApplications.FirstOrDefault()?.Id
 34338                        : castReceiverApplications.FirstOrDefault(c => string.Equals(c.Id, user.CastReceiverId, StringCo
 34339                          ?? castReceiverApplications.FirstOrDefault()?.Id
 34340                },
 34341                Policy = new UserPolicy
 34342                {
 34343                    MaxParentalRating = user.MaxParentalRatingScore,
 34344                    MaxParentalSubRating = user.MaxParentalRatingSubScore,
 34345                    EnableUserPreferenceAccess = user.EnableUserPreferenceAccess,
 34346                    RemoteClientBitrateLimit = user.RemoteClientBitrateLimit ?? 0,
 34347                    AuthenticationProviderId = user.AuthenticationProviderId,
 34348                    PasswordResetProviderId = user.PasswordResetProviderId,
 34349                    InvalidLoginAttemptCount = user.InvalidLoginAttemptCount,
 34350                    LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout ?? -1,
 34351                    MaxActiveSessions = user.MaxActiveSessions,
 34352                    IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator),
 34353                    IsHidden = user.HasPermission(PermissionKind.IsHidden),
 34354                    IsDisabled = user.HasPermission(PermissionKind.IsDisabled),
 34355                    EnableSharedDeviceControl = user.HasPermission(PermissionKind.EnableSharedDeviceControl),
 34356                    EnableRemoteAccess = user.HasPermission(PermissionKind.EnableRemoteAccess),
 34357                    EnableLiveTvManagement = user.HasPermission(PermissionKind.EnableLiveTvManagement),
 34358                    EnableLiveTvAccess = user.HasPermission(PermissionKind.EnableLiveTvAccess),
 34359                    EnableMediaPlayback = user.HasPermission(PermissionKind.EnableMediaPlayback),
 34360                    EnableAudioPlaybackTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding),
 34361                    EnableVideoPlaybackTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding),
 34362                    EnableContentDeletion = user.HasPermission(PermissionKind.EnableContentDeletion),
 34363                    EnableContentDownloading = user.HasPermission(PermissionKind.EnableContentDownloading),
 34364                    EnableSyncTranscoding = user.HasPermission(PermissionKind.EnableSyncTranscoding),
 34365                    EnableMediaConversion = user.HasPermission(PermissionKind.EnableMediaConversion),
 34366                    EnableAllChannels = user.HasPermission(PermissionKind.EnableAllChannels),
 34367                    EnableAllDevices = user.HasPermission(PermissionKind.EnableAllDevices),
 34368                    EnableAllFolders = user.HasPermission(PermissionKind.EnableAllFolders),
 34369                    EnableRemoteControlOfOtherUsers = user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)
 34370                    EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing),
 34371                    ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding),
 34372                    EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing),
 34373                    EnableCollectionManagement = user.HasPermission(PermissionKind.EnableCollectionManagement),
 34374                    EnableSubtitleManagement = user.HasPermission(PermissionKind.EnableSubtitleManagement),
 34375                    AccessSchedules = user.AccessSchedules.ToArray(),
 34376                    BlockedTags = user.GetPreference(PreferenceKind.BlockedTags),
 34377                    AllowedTags = user.GetPreference(PreferenceKind.AllowedTags),
 34378                    EnabledChannels = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels),
 34379                    EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices),
 34380                    EnabledFolders = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders),
 34381                    EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolder
 34382                    SyncPlayAccess = user.SyncPlayAccess,
 34383                    BlockedChannels = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedChannels),
 34384                    BlockedMediaFolders = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedMediaFolders),
 34385                    BlockUnratedItems = user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems)
 34386                }
 34387            };
 388        }
 389
 390        /// <inheritdoc/>
 391        public async Task<User?> AuthenticateUser(
 392            string username,
 393            string password,
 394            string remoteEndPoint,
 395            bool isUserSession)
 396        {
 15397            if (string.IsNullOrWhiteSpace(username))
 398            {
 0399                _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndP
 0400                throw new ArgumentNullException(nameof(username));
 401            }
 402
 15403            var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)
 15404            var authResult = await AuthenticateLocalUser(username, password, user)
 15405                .ConfigureAwait(false);
 15406            var authenticationProvider = authResult.AuthenticationProvider;
 15407            var success = authResult.Success;
 408
 15409            if (user is null)
 410            {
 0411                string updatedUsername = authResult.Username;
 412
 0413                if (success
 0414                    && authenticationProvider is not null
 0415                    && authenticationProvider is not DefaultAuthenticationProvider)
 416                {
 417                    // Trust the username returned by the authentication provider
 0418                    username = updatedUsername;
 419
 420                    // Search the database for the user again
 421                    // the authentication provider might have created it
 0422                    user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreC
 423
 0424                    if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user is not null)
 425                    {
 0426                        await UpdatePolicyAsync(user.Id, hasNewUserPolicy.GetNewUserPolicy()).ConfigureAwait(false);
 427                    }
 428                }
 429            }
 430
 15431            if (success && user is not null && authenticationProvider is not null)
 432            {
 15433                var providerId = authenticationProvider.GetType().FullName;
 434
 15435                if (providerId is not null && !string.Equals(providerId, user.AuthenticationProviderId, StringComparison
 436                {
 0437                    user.AuthenticationProviderId = providerId;
 0438                    await UpdateUserAsync(user).ConfigureAwait(false);
 439                }
 440            }
 441
 15442            if (user is null)
 443            {
 0444                _logger.LogInformation(
 0445                    "Authentication request for {UserName} has been denied (IP: {IP}).",
 0446                    username,
 0447                    remoteEndPoint);
 0448                throw new AuthenticationException("Invalid username or password entered.");
 449            }
 450
 15451            if (user.HasPermission(PermissionKind.IsDisabled))
 452            {
 0453                _logger.LogInformation(
 0454                    "Authentication request for {UserName} has been denied because this account is currently disabled (I
 0455                    username,
 0456                    remoteEndPoint);
 0457                throw new SecurityException(
 0458                    $"The {user.Username} account is currently disabled. Please consult with your administrator.");
 459            }
 460
 15461            if (!user.HasPermission(PermissionKind.EnableRemoteAccess) &&
 15462                !_networkManager.IsInLocalNetwork(remoteEndPoint))
 463            {
 0464                _logger.LogInformation(
 0465                    "Authentication request for {UserName} forbidden: remote access disabled and user not in local netwo
 0466                    username,
 0467                    remoteEndPoint);
 0468                throw new SecurityException("Forbidden.");
 469            }
 470
 15471            if (!user.IsParentalScheduleAllowed())
 472            {
 0473                _logger.LogInformation(
 0474                    "Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {I
 0475                    username,
 0476                    remoteEndPoint);
 0477                throw new SecurityException("User is not allowed access at this time.");
 478            }
 479
 480            // Update LastActivityDate and LastLoginDate, then save
 15481            if (success)
 482            {
 15483                if (isUserSession)
 484                {
 15485                    user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
 486                }
 487
 15488                user.InvalidLoginAttemptCount = 0;
 15489                await UpdateUserAsync(user).ConfigureAwait(false);
 15490                _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username);
 491            }
 492            else
 493            {
 0494                await IncrementInvalidLoginAttemptCount(user).ConfigureAwait(false);
 0495                _logger.LogInformation(
 0496                    "Authentication request for {UserName} has been denied (IP: {IP}).",
 0497                    user.Username,
 0498                    remoteEndPoint);
 499            }
 500
 15501            return success ? user : null;
 15502        }
 503
 504        /// <inheritdoc/>
 505        public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
 506        {
 0507            var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername);
 0508            var passwordResetProvider = GetPasswordResetProvider(user);
 509
 0510            var result = await passwordResetProvider
 0511                .StartForgotPasswordProcess(user, enteredUsername, isInNetwork)
 0512                .ConfigureAwait(false);
 513
 0514            if (user is not null && isInNetwork)
 515            {
 0516                await UpdateUserAsync(user).ConfigureAwait(false);
 517            }
 518
 0519            return result;
 0520        }
 521
 522        /// <inheritdoc/>
 523        public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
 524        {
 0525            foreach (var provider in _passwordResetProviders)
 526            {
 0527                var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false);
 528
 0529                if (result.Success)
 530                {
 0531                    return result;
 532                }
 533            }
 534
 0535            return new PinRedeemResult();
 0536        }
 537
 538        /// <inheritdoc />
 539        public async Task InitializeAsync()
 540        {
 541            // TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
 17542            if (_users.Any())
 543            {
 1544                return;
 545            }
 546
 16547            var defaultName = Environment.UserName;
 16548            if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
 549            {
 0550                defaultName = "MyJellyfinUser";
 551            }
 552
 16553            _logger.LogWarning("No users, creating one with username {UserName}", defaultName);
 554
 16555            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 16556            await using (dbContext.ConfigureAwait(false))
 557            {
 16558                var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
 16559                newUser.SetPermission(PermissionKind.IsAdministrator, true);
 16560                newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
 16561                newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
 562
 16563                dbContext.Users.Add(newUser);
 16564                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 16565                _users.Add(newUser.Id, newUser);
 16566            }
 17567        }
 568
 569        /// <inheritdoc/>
 570        public NameIdPair[] GetAuthenticationProviders()
 571        {
 0572            return _authenticationProviders
 0573                .Where(provider => provider.IsEnabled)
 0574                .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1)
 0575                .ThenBy(i => i.Name)
 0576                .Select(i => new NameIdPair
 0577                {
 0578                    Name = i.Name,
 0579                    Id = i.GetType().FullName
 0580                })
 0581                .ToArray();
 582        }
 583
 584        /// <inheritdoc/>
 585        public NameIdPair[] GetPasswordResetProviders()
 586        {
 0587            return _passwordResetProviders
 0588                .Where(provider => provider.IsEnabled)
 0589                .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1)
 0590                .ThenBy(i => i.Name)
 0591                .Select(i => new NameIdPair
 0592                {
 0593                    Name = i.Name,
 0594                    Id = i.GetType().FullName
 0595                })
 0596                .ToArray();
 597        }
 598
 599        /// <inheritdoc/>
 600        public async Task UpdateConfigurationAsync(Guid userId, UserConfiguration config)
 601        {
 0602            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0603            await using (dbContext.ConfigureAwait(false))
 604            {
 0605                var user = dbContext.Users
 0606                               .Include(u => u.Permissions)
 0607                               .Include(u => u.Preferences)
 0608                               .Include(u => u.AccessSchedules)
 0609                               .Include(u => u.ProfileImage)
 0610                               .AsSingleQuery()
 0611                               .FirstOrDefault(u => u.Id.Equals(userId))
 0612                           ?? throw new ArgumentException("No user exists with given Id!");
 613
 0614                user.SubtitleMode = config.SubtitleMode;
 0615                user.HidePlayedInLatest = config.HidePlayedInLatest;
 0616                user.EnableLocalPassword = config.EnableLocalPassword;
 0617                user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack;
 0618                user.DisplayCollectionsView = config.DisplayCollectionsView;
 0619                user.DisplayMissingEpisodes = config.DisplayMissingEpisodes;
 0620                user.AudioLanguagePreference = config.AudioLanguagePreference;
 0621                user.RememberAudioSelections = config.RememberAudioSelections;
 0622                user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay;
 0623                user.RememberSubtitleSelections = config.RememberSubtitleSelections;
 0624                user.SubtitleLanguagePreference = config.SubtitleLanguagePreference;
 625
 626                // Only set cast receiver id if it is passed in and it exists in the server config.
 0627                if (!string.IsNullOrEmpty(config.CastReceiverId)
 0628                    && _serverConfigurationManager.Configuration.CastReceiverApplications.Any(c => string.Equals(c.Id, c
 629                {
 0630                    user.CastReceiverId = config.CastReceiverId;
 631                }
 632
 0633                user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
 0634                user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
 0635                user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
 0636                user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
 637
 0638                dbContext.Update(user);
 0639                _users[user.Id] = user;
 0640                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 641            }
 0642        }
 643
 644        /// <inheritdoc/>
 645        public async Task UpdatePolicyAsync(Guid userId, UserPolicy policy)
 646        {
 0647            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0648            await using (dbContext.ConfigureAwait(false))
 649            {
 0650                var user = dbContext.Users
 0651                               .Include(u => u.Permissions)
 0652                               .Include(u => u.Preferences)
 0653                               .Include(u => u.AccessSchedules)
 0654                               .Include(u => u.ProfileImage)
 0655                               .AsSingleQuery()
 0656                               .FirstOrDefault(u => u.Id.Equals(userId))
 0657                           ?? throw new ArgumentException("No user exists with given Id!");
 658
 659                // The default number of login attempts is 3, but for some god forsaken reason it's sent to the server a
 0660                int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
 0661                {
 0662                    -1 => null,
 0663                    0 => 3,
 0664                    _ => policy.LoginAttemptsBeforeLockout
 0665                };
 666
 0667                user.MaxParentalRatingScore = policy.MaxParentalRating;
 0668                user.MaxParentalRatingSubScore = policy.MaxParentalSubRating;
 0669                user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess;
 0670                user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit;
 0671                user.AuthenticationProviderId = policy.AuthenticationProviderId;
 0672                user.PasswordResetProviderId = policy.PasswordResetProviderId;
 0673                user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount;
 0674                user.LoginAttemptsBeforeLockout = maxLoginAttempts;
 0675                user.MaxActiveSessions = policy.MaxActiveSessions;
 0676                user.SyncPlayAccess = policy.SyncPlayAccess;
 0677                user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
 0678                user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
 0679                user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
 0680                user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
 0681                user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
 0682                user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
 0683                user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
 0684                user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
 0685                user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding)
 0686                user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding)
 0687                user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
 0688                user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
 0689                user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
 0690                user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
 0691                user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
 0692                user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
 0693                user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
 0694                user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUser
 0695                user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
 0696                user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
 0697                user.SetPermission(PermissionKind.EnableSubtitleManagement, policy.EnableSubtitleManagement);
 0698                user.SetPermission(PermissionKind.EnableLyricManagement, policy.EnableLyricManagement);
 0699                user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
 0700                user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
 701
 0702                user.AccessSchedules.Clear();
 0703                foreach (var policyAccessSchedule in policy.AccessSchedules)
 704                {
 0705                    user.AccessSchedules.Add(policyAccessSchedule);
 706                }
 707
 708                // TODO: fix this at some point
 0709                user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty<UnratedItem
 0710                user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
 0711                user.SetPreference(PreferenceKind.AllowedTags, policy.AllowedTags);
 0712                user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
 0713                user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
 0714                user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
 0715                user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFold
 716
 0717                dbContext.Update(user);
 0718                _users[user.Id] = user;
 0719                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 720            }
 0721        }
 722
 723        /// <inheritdoc/>
 724        public async Task ClearProfileImageAsync(User user)
 725        {
 0726            if (user.ProfileImage is null)
 727            {
 0728                return;
 729            }
 730
 0731            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0732            await using (dbContext.ConfigureAwait(false))
 733            {
 0734                dbContext.Remove(user.ProfileImage);
 0735                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 736            }
 737
 0738            user.ProfileImage = null;
 0739            _users[user.Id] = user;
 0740        }
 741
 742        internal static void ThrowIfInvalidUsername(string name)
 743        {
 15744            if (!string.IsNullOrWhiteSpace(name) && ValidUsernameRegex().IsMatch(name))
 745            {
 7746                return;
 747            }
 748
 8749            throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (
 750        }
 751
 752        private IAuthenticationProvider GetAuthenticationProvider(User user)
 753        {
 3754            return GetAuthenticationProviders(user)[0];
 755        }
 756
 757        private IPasswordResetProvider GetPasswordResetProvider(User? user)
 758        {
 0759            if (user is null)
 760            {
 0761                return _defaultPasswordResetProvider;
 762            }
 763
 0764            return GetPasswordResetProviders(user)[0];
 765        }
 766
 767        private List<IAuthenticationProvider> GetAuthenticationProviders(User? user)
 768        {
 18769            var authenticationProviderId = user?.AuthenticationProviderId;
 770
 18771            var providers = _authenticationProviders.Where(i => i.IsEnabled).ToList();
 772
 18773            if (!string.IsNullOrEmpty(authenticationProviderId))
 774            {
 18775                providers = providers.Where(i => string.Equals(authenticationProviderId, i.GetType().FullName, StringCom
 776            }
 777
 18778            if (providers.Count == 0)
 779            {
 780                // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found
 0781                _logger.LogWarning(
 0782                    "User {Username} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. 
 0783                    user?.Username,
 0784                    user?.AuthenticationProviderId);
 0785                providers = new List<IAuthenticationProvider>
 0786                {
 0787                    _invalidAuthProvider
 0788                };
 789            }
 790
 18791            return providers;
 792        }
 793
 794        private IPasswordResetProvider[] GetPasswordResetProviders(User user)
 795        {
 0796            var passwordResetProviderId = user.PasswordResetProviderId;
 0797            var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
 798
 0799            if (!string.IsNullOrEmpty(passwordResetProviderId))
 800            {
 0801                providers = providers.Where(i =>
 0802                        string.Equals(passwordResetProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)
 0803                    .ToArray();
 804            }
 805
 0806            if (providers.Length == 0)
 807            {
 0808                providers = new IPasswordResetProvider[]
 0809                {
 0810                    _defaultPasswordResetProvider
 0811                };
 812            }
 813
 0814            return providers;
 815        }
 816
 817        private async Task<(IAuthenticationProvider? AuthenticationProvider, string Username, bool Success)> Authenticat
 818                string username,
 819                string password,
 820                User? user)
 821        {
 15822            bool success = false;
 15823            IAuthenticationProvider? authenticationProvider = null;
 824
 45825            foreach (var provider in GetAuthenticationProviders(user))
 826            {
 15827                var providerAuthResult =
 15828                    await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
 15829                var updatedUsername = providerAuthResult.Username;
 15830                success = providerAuthResult.Success;
 831
 15832                if (success)
 833                {
 15834                    authenticationProvider = provider;
 15835                    username = updatedUsername;
 15836                    break;
 837                }
 0838            }
 839
 15840            return (authenticationProvider, username, success);
 15841        }
 842
 843        private async Task<(string Username, bool Success)> AuthenticateWithProvider(
 844            IAuthenticationProvider provider,
 845            string username,
 846            string password,
 847            User? resolvedUser)
 848        {
 849            try
 850            {
 15851                var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
 15852                    ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
 15853                    : await provider.Authenticate(username, password).ConfigureAwait(false);
 854
 15855                if (authenticationResult.Username != username)
 856                {
 0857                    _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Usern
 0858                    username = authenticationResult.Username;
 859                }
 860
 15861                return (username, true);
 862            }
 0863            catch (AuthenticationException ex)
 864            {
 0865                _logger.LogDebug(ex, "Error authenticating with provider {Provider}", provider.Name);
 866
 0867                return (username, false);
 868            }
 15869        }
 870
 871        private async Task IncrementInvalidLoginAttemptCount(User user)
 872        {
 0873            user.InvalidLoginAttemptCount++;
 0874            int? maxInvalidLogins = user.LoginAttemptsBeforeLockout;
 0875            if (maxInvalidLogins.HasValue && user.InvalidLoginAttemptCount >= maxInvalidLogins)
 876            {
 0877                user.SetPermission(PermissionKind.IsDisabled, true);
 0878                await _eventManager.PublishAsync(new UserLockedOutEventArgs(user)).ConfigureAwait(false);
 0879                _logger.LogWarning(
 0880                    "Disabling user {Username} due to {Attempts} unsuccessful login attempts.",
 0881                    user.Username,
 0882                    user.InvalidLoginAttemptCount);
 883            }
 884
 0885            await UpdateUserAsync(user).ConfigureAwait(false);
 0886        }
 887
 888        private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user)
 889        {
 19890            dbContext.Users.Attach(user);
 19891            dbContext.Entry(user).State = EntityState.Modified;
 19892            _users[user.Id] = user;
 19893            await dbContext.SaveChangesAsync().ConfigureAwait(false);
 19894        }
 895    }
 896}

Methods/Properties

.ctor(Microsoft.EntityFrameworkCore.IDbContextFactory`1<Jellyfin.Database.Implementations.JellyfinDbContext>,MediaBrowser.Controller.Events.IEventManager,MediaBrowser.Common.Net.INetworkManager,MediaBrowser.Common.IApplicationHost,MediaBrowser.Controller.Drawing.IImageProcessor,Microsoft.Extensions.Logging.ILogger`1<Jellyfin.Server.Implementations.Users.UserManager>,MediaBrowser.Controller.Configuration.IServerConfigurationManager,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Authentication.IPasswordResetProvider>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Authentication.IAuthenticationProvider>)
get_Users()
get_UsersIds()
GetUserById(System.Guid)
GetUserByName(System.String)
RenameUser()
UpdateUserAsync()
CreateUserInternalAsync()
CreateUserAsync()
DeleteUserAsync()
ResetPassword(Jellyfin.Database.Implementations.Entities.User)
ChangePassword()
GetUserDto(Jellyfin.Database.Implementations.Entities.User,System.String)
AuthenticateUser()
StartForgotPasswordProcess()
RedeemPasswordResetPin()
InitializeAsync()
GetAuthenticationProviders()
GetPasswordResetProviders()
UpdateConfigurationAsync()
UpdatePolicyAsync()
ClearProfileImageAsync()
ThrowIfInvalidUsername(System.String)
GetAuthenticationProvider(Jellyfin.Database.Implementations.Entities.User)
GetPasswordResetProvider(Jellyfin.Database.Implementations.Entities.User)
GetAuthenticationProviders(Jellyfin.Database.Implementations.Entities.User)
GetPasswordResetProviders(Jellyfin.Database.Implementations.Entities.User)
AuthenticateLocalUser()
AuthenticateWithProvider()
IncrementInvalidLoginAttemptCount()
UpdateUserInternalAsync()