From 3eeb6576d8425c8d2917f861b466dfa36e3994df Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 15 May 2020 17:24:01 -0400 Subject: Migrate User DB to EF Core --- .../Users/UserManager.cs | 763 +++++++++++++++++++++ 1 file changed, 763 insertions(+) create mode 100644 Jellyfin.Server.Implementations/Users/UserManager.cs (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs new file mode 100644 index 000000000..ddc05055b --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -0,0 +1,763 @@ +#pragma warning disable CA1307 +#pragma warning disable CS1591 + +using System; +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; +using Jellyfin.Data.Enums; +using MediaBrowser.Common.Cryptography; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Users +{ + public class UserManager : IUserManager + { + private readonly JellyfinDbProvider _dbProvider; + private readonly ICryptoProvider _cryptoProvider; + private readonly INetworkManager _networkManager; + private readonly ILogger _logger; + + private IAuthenticationProvider[] _authenticationProviders; + private DefaultAuthenticationProvider _defaultAuthenticationProvider; + private InvalidAuthProvider _invalidAuthProvider; + private IPasswordResetProvider[] _passwordResetProviders; + private DefaultPasswordResetProvider _defaultPasswordResetProvider; + + public UserManager( + JellyfinDbProvider dbProvider, + ICryptoProvider cryptoProvider, + INetworkManager networkManager, + ILogger logger) + { + _dbProvider = dbProvider; + _cryptoProvider = cryptoProvider; + _networkManager = networkManager; + _logger = logger; + } + + public event EventHandler> OnUserPasswordChanged; + + /// + public event EventHandler> OnUserUpdated; + + /// + public event EventHandler> OnUserCreated; + + /// + public event EventHandler> OnUserDeleted; + + public event EventHandler> OnUserLockedOut; + + public IEnumerable Users + { + get + { + var dbContext = _dbProvider.CreateContext(); + return dbContext.Users; + } + } + + public IEnumerable UsersIds + { + get + { + var dbContext = _dbProvider.CreateContext(); + return dbContext.Users.Select(u => u.Id); + } + } + + public User GetUserById(Guid id) + { + if (id == Guid.Empty) + { + throw new ArgumentException("Guid can't be empty", nameof(id)); + } + + var dbContext = _dbProvider.CreateContext(); + + return dbContext.Users.Find(id); + } + + public User GetUserByName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Invalid username", nameof(name)); + } + + var dbContext = _dbProvider.CreateContext(); + + // This can't use an overload with StringComparer because that would cause the query to + // have to be evaluated client-side. + return dbContext.Users.FirstOrDefault(u => string.Equals(u.Username, name)); + } + + public async Task RenameUser(User user, string newName) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrWhiteSpace(newName)) + { + throw new ArgumentException("Invalid username", nameof(newName)); + } + + if (user.Username.Equals(newName, StringComparison.Ordinal)) + { + throw new ArgumentException("The new and old names must be different."); + } + + if (Users.Any( + u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) + { + 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)); + } + + public void UpdateUser(User user) + { + var dbContext = _dbProvider.CreateContext(); + dbContext.Users.Update(user); + dbContext.SaveChanges(); + } + + public async Task UpdateUserAsync(User user) + { + var dbContext = _dbProvider.CreateContext(); + dbContext.Users.Update(user); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + public User CreateUser(string name) + { + if (!IsValidUsername(name)) + { + throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); + } + + var dbContext = _dbProvider.CreateContext(); + + var newUser = new User(name, _defaultAuthenticationProvider.GetType().FullName); + dbContext.Users.Add(newUser); + dbContext.SaveChanges(); + + OnUserCreated?.Invoke(this, new GenericEventArgs(newUser)); + + return newUser; + } + + public void DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var dbContext = _dbProvider.CreateContext(); + + if (!dbContext.Users.Contains(user)) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "The user cannot be deleted because there is no user with the Name {0} and Id {1}.", + user.Username, + user.Id)); + } + + if (dbContext.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(user)); + } + + dbContext.Users.Remove(user); + dbContext.SaveChanges(); + OnUserDeleted?.Invoke(this, new GenericEventArgs(user)); + } + + public Task ResetPassword(User user) + { + return ChangePassword(user, string.Empty); + } + + public void ResetEasyPassword(User user) + { + ChangeEasyPassword(user, string.Empty, null); + } + + public async Task ChangePassword(User user, string newPassword) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); + await UpdateUserAsync(user).ConfigureAwait(false); + + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1) + { + GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1); + UpdateUser(user); + + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public UserDto GetUserDto(User user, string remoteEndPoint = null) + { + return new UserDto + { + Id = user.Id, + HasPassword = user.Password == null, + EnableAutoLogin = user.EnableAutoLogin, + LastLoginDate = user.LastLoginDate, + LastActivityDate = user.LastActivityDate, + Configuration = new UserConfiguration + { + SubtitleMode = user.SubtitleMode, + HidePlayedInLatest = user.HidePlayedInLatest, + EnableLocalPassword = user.EnableLocalPassword, + PlayDefaultAudioTrack = user.PlayDefaultAudioTrack, + DisplayCollectionsView = user.DisplayCollectionsView, + DisplayMissingEpisodes = user.DisplayMissingEpisodes, + AudioLanguagePreference = user.AudioLanguagePreference, + RememberAudioSelections = user.RememberAudioSelections, + EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay, + RememberSubtitleSelections = user.RememberSubtitleSelections, + SubtitleLanguagePreference = user.SubtitleLanguagePreference, + OrderedViews = user.GetPreference(PreferenceKind.OrderedViews), + GroupedFolders = user.GetPreference(PreferenceKind.GroupedFolders), + MyMediaExcludes = user.GetPreference(PreferenceKind.MyMediaExcludes), + LatestItemsExcludes = user.GetPreference(PreferenceKind.LatestItemExcludes) + }, + Policy = new UserPolicy + { + MaxParentalRating = user.MaxParentalAgeRating, + EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, + RemoteClientBitrateLimit = user.RemoteClientBitrateLimit.GetValueOrDefault(), + AuthenticationProviderId = user.AuthenticationProviderId, + PasswordResetProviderId = user.PasswordResetProviderId, + InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, + LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout.GetValueOrDefault(), + IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator), + IsHidden = user.HasPermission(PermissionKind.IsHidden), + IsDisabled = user.HasPermission(PermissionKind.IsDisabled), + EnableSharedDeviceControl = user.HasPermission(PermissionKind.EnableSharedDeviceControl), + EnableRemoteAccess = user.HasPermission(PermissionKind.EnableRemoteAccess), + EnableLiveTvManagement = user.HasPermission(PermissionKind.EnableLiveTvManagement), + EnableLiveTvAccess = user.HasPermission(PermissionKind.EnableLiveTvAccess), + EnableMediaPlayback = user.HasPermission(PermissionKind.EnableMediaPlayback), + EnableAudioPlaybackTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding), + EnableVideoPlaybackTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding), + EnableContentDeletion = user.HasPermission(PermissionKind.EnableContentDeletion), + EnableContentDownloading = user.HasPermission(PermissionKind.EnableContentDownloading), + EnableSyncTranscoding = user.HasPermission(PermissionKind.EnableSyncTranscoding), + EnableMediaConversion = user.HasPermission(PermissionKind.EnableMediaConversion), + EnableAllChannels = user.HasPermission(PermissionKind.EnableAllChannels), + EnableAllDevices = user.HasPermission(PermissionKind.EnableAllDevices), + EnableAllFolders = user.HasPermission(PermissionKind.EnableAllFolders), + EnableRemoteControlOfOtherUsers = user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers), + EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing), + ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding), + EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), + AccessSchedules = user.AccessSchedules.ToArray(), + BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), + EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels), + EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), + EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), + EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders) + } + }; + } + + public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user); + bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); + + bool hasPassword = user.EnableLocalPassword && + !string.IsNullOrEmpty(remoteEndPoint) && + _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword; + + return new PublicUserDto + { + Name = user.Username, + HasPassword = hasPassword, + HasConfiguredPassword = hasConfiguredPassword + }; + } + + public async Task AuthenticateUser( + string username, + string password, + string passwordSha1, + string remoteEndPoint, + bool isUserSession) + { + if (string.IsNullOrWhiteSpace(username)) + { + _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint); + throw new ArgumentNullException(nameof(username)); + } + + var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + bool success; + IAuthenticationProvider authenticationProvider; + + if (user != null) + { + var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint) + .ConfigureAwait(false); + authenticationProvider = authResult.authenticationProvider; + success = authResult.success; + } + else + { + var authResult = await AuthenticateLocalUser(username, password, null, remoteEndPoint) + .ConfigureAwait(false); + authenticationProvider = authResult.authenticationProvider; + string updatedUsername = authResult.username; + success = authResult.success; + + if (success + && authenticationProvider != null + && !(authenticationProvider is DefaultAuthenticationProvider)) + { + // Trust the username returned by the authentication provider + username = updatedUsername; + + // Search the database for the user again + // the authentication provider might have created it + user = Users + .ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) + { + UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy()); + + await UpdateUserAsync(user).ConfigureAwait(false); + } + } + } + + if (success && user != null && authenticationProvider != null) + { + var providerId = authenticationProvider.GetType().FullName; + + if (!string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.AuthenticationProviderId = providerId; + await UpdateUserAsync(user).ConfigureAwait(false); + } + } + + if (user == null) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + username, + remoteEndPoint); + throw new AuthenticationException("Invalid username or password entered."); + } + + if (user.HasPermission(PermissionKind.IsDisabled)) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException( + $"The {user.Username} account is currently disabled. Please consult with your administrator."); + } + + if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && + !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + _logger.LogInformation( + "Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + _logger.LogInformation( + "Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("User is not allowed access at this time."); + } + + // Update LastActivityDate and LastLoginDate, then save + if (success) + { + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + await UpdateUserAsync(user).ConfigureAwait(false); + } + + user.InvalidLoginAttemptCount = 0; + _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username); + } + else + { + IncrementInvalidLoginAttemptCount(user); + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + user.Username, + remoteEndPoint); + } + + return success ? user : null; + } + + public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + { + var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); + + var action = ForgotPasswordAction.InNetworkRequired; + + if (user != null && isInNetwork) + { + var passwordResetProvider = GetPasswordResetProvider(user); + return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); + } + + return new ForgotPasswordResult + { + Action = action, + PinFile = string.Empty + }; + } + + public async Task RedeemPasswordResetPin(string pin) + { + foreach (var provider in _passwordResetProviders) + { + var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); + + if (result.Success) + { + return result; + } + } + + return new PinRedeemResult + { + Success = false, + UsersReset = Array.Empty() + }; + } + + public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) + { + _authenticationProviders = authenticationProviders.ToArray(); + _passwordResetProviders = passwordResetProviders.ToArray(); + + _invalidAuthProvider = _authenticationProviders.OfType().First(); + _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); + } + + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + public NameIdPair[] GetPasswordResetProviders() + { + return _passwordResetProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + public void UpdateConfiguration(Guid userId, UserConfiguration config) + { + var user = GetUserById(userId); + 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); + + UpdateUser(user); + } + + public void UpdatePolicy(Guid userId, UserPolicy policy) + { + var user = GetUserById(userId); + + 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 = policy.LoginAttemptsBeforeLockout == -1 + ? null + : new int?(policy.LoginAttemptsBeforeLockout); + 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); + } + + 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); + } + + private 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 ('), and periods (.) + return Regex.IsMatch(name, @"^[\w\-'._@]*$"); + } + + private IAuthenticationProvider GetAuthenticationProvider(User user) + { + return GetAuthenticationProviders(user)[0]; + } + + private IPasswordResetProvider GetPasswordResetProvider(User user) + { + return GetPasswordResetProviders(user)[0]; + } + + private IList GetAuthenticationProviders(User user) + { + var authenticationProviderId = user?.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToList(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) + { + providers = providers.Where(i => string.Equals(authenticationProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)).ToList(); + } + + if (providers.Count == 0) + { + // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found + _logger.LogWarning( + "User {Username} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", + user?.Username, + user?.AuthenticationProviderId); + providers = new List + { + _invalidAuthProvider + }; + } + + return providers; + } + + private IList GetPasswordResetProviders(User user) + { + var passwordResetProviderId = user?.PasswordResetProviderId; + var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(passwordResetProviderId)) + { + providers = providers.Where(i => + string.Equals(passwordResetProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + } + + if (providers.Length == 0) + { + providers = new IPasswordResetProvider[] + { + _defaultPasswordResetProvider + }; + } + + return providers; + } + + private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser( + string username, + string password, + User user, + string remoteEndPoint) + { + bool success = false; + IAuthenticationProvider authenticationProvider = null; + + foreach (var provider in GetAuthenticationProviders(user)) + { + var providerAuthResult = + await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + var updatedUsername = providerAuthResult.username; + success = providerAuthResult.success; + + if (success) + { + authenticationProvider = provider; + username = updatedUsername; + break; + } + } + + 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( + IAuthenticationProvider provider, + string username, + string password, + User resolvedUser) + { + try + { + var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser + ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) + : await provider.Authenticate(username, password).ConfigureAwait(false); + + if (authenticationResult.Username != username) + { + _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); + username = authenticationResult.Username; + } + + return (username, true); + } + catch (AuthenticationException ex) + { + _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name); + + return (username, false); + } + } + + private void IncrementInvalidLoginAttemptCount(User user) + { + int invalidLogins = user.InvalidLoginAttemptCount; + int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; + if (maxInvalidLogins.HasValue && invalidLogins >= maxInvalidLogins) + { + user.SetPermission(PermissionKind.IsDisabled, true); + OnUserLockedOut?.Invoke(this, new GenericEventArgs(user)); + _logger.LogWarning( + "Disabling user {Username} due to {Attempts} unsuccessful login attempts.", + user.Username, + invalidLogins); + } + + UpdateUser(user); + } + } +} -- cgit v1.2.3 From e7b297c67baee73a737c61170ad6a55c2f1d08d3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 19 May 2020 15:58:25 -0400 Subject: Add some missing properties --- Jellyfin.Server.Implementations/Users/UserManager.cs | 16 +++++++++++++--- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 1 - 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index ddc05055b..2d0caee29 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -10,9 +10,11 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; @@ -29,6 +31,8 @@ namespace Jellyfin.Server.Implementations.Users private readonly JellyfinDbProvider _dbProvider; private readonly ICryptoProvider _cryptoProvider; private readonly INetworkManager _networkManager; + private readonly IApplicationHost _appHost; + private readonly IImageProcessor _imageProcessor; private readonly ILogger _logger; private IAuthenticationProvider[] _authenticationProviders; @@ -41,11 +45,15 @@ namespace Jellyfin.Server.Implementations.Users JellyfinDbProvider dbProvider, ICryptoProvider cryptoProvider, INetworkManager networkManager, + IApplicationHost appHost, + IImageProcessor imageProcessor, ILogger logger) { _dbProvider = dbProvider; _cryptoProvider = cryptoProvider; _networkManager = networkManager; + _appHost = appHost; + _imageProcessor = imageProcessor; _logger = logger; } @@ -123,8 +131,7 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("The new and old names must be different."); } - if (Users.Any( - u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) + if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(string.Format( CultureInfo.InvariantCulture, @@ -248,11 +255,14 @@ namespace Jellyfin.Server.Implementations.Users { return new UserDto { + Name = user.Username, Id = user.Id, + ServerId = _appHost.SystemId, HasPassword = user.Password == null, EnableAutoLogin = user.EnableAutoLogin, LastLoginDate = user.LastLoginDate, LastActivityDate = user.LastActivityDate, + PrimaryImageTag = user.ProfileImage != null ? _imageProcessor.GetImageCacheTag(user) : null, Configuration = new UserConfiguration { SubtitleMode = user.SubtitleMode, @@ -265,7 +275,7 @@ namespace Jellyfin.Server.Implementations.Users RememberAudioSelections = user.RememberAudioSelections, EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay, RememberSubtitleSelections = user.RememberSubtitleSelections, - SubtitleLanguagePreference = user.SubtitleLanguagePreference, + SubtitleLanguagePreference = user.SubtitleLanguagePreference ?? string.Empty, OrderedViews = user.GetPreference(PreferenceKind.OrderedViews), GroupedFolders = user.GetPreference(PreferenceKind.GroupedFolders), MyMediaExcludes = user.GetPreference(PreferenceKind.MyMediaExcludes), diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 0271098f4..9a428a747 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -9,7 +9,6 @@ using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Users; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; using JsonSerializer = System.Text.Json.JsonSerializer; -- cgit v1.2.3 From 292993d8efabf0380d7b6a53e074324b4c92f377 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 19 May 2020 19:44:55 -0400 Subject: Document various classes. --- Jellyfin.Data/Entities/Preference.cs | 23 ++++-- Jellyfin.Data/Enums/PermissionKind.cs | 86 ++++++++++++++++++++++ Jellyfin.Data/Enums/PreferenceKind.cs | 50 +++++++++++++ .../Users/UserManager.cs | 38 +++++++++- 4 files changed, 190 insertions(+), 7 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index 56a07d440..0ca9d7eff 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -35,30 +35,42 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the id of this preference. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } /// - /// Required + /// Gets or sets the type of this preference. /// + /// + /// Required. + /// [Required] - public PreferenceKind Kind { get; set; } + public PreferenceKind Kind { get; protected set; } /// - /// Required, Max length = 65535 + /// Gets or sets the value of this preference. /// + /// + /// Required, Max length = 65535. + /// [Required] [MaxLength(65535)] [StringLength(65535)] public string Value { get; set; } /// - /// Required, ConcurrencyToken. + /// Gets or sets the row version. /// + /// + /// Required, ConcurrencyToken. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } @@ -81,4 +93,3 @@ namespace Jellyfin.Data.Entities } } } - diff --git a/Jellyfin.Data/Enums/PermissionKind.cs b/Jellyfin.Data/Enums/PermissionKind.cs index df18261e6..8b1472f97 100644 --- a/Jellyfin.Data/Enums/PermissionKind.cs +++ b/Jellyfin.Data/Enums/PermissionKind.cs @@ -1,27 +1,113 @@ namespace Jellyfin.Data.Enums { + /// + /// The types of user permissions. + /// public enum PermissionKind { + /// + /// Whether the user is an administrator. + /// IsAdministrator, + + /// + /// Whether the user is hidden. + /// IsHidden, + + /// + /// Whether the user is disabled. + /// IsDisabled, + + /// + /// Whether the user can control shared devices. + /// EnableSharedDeviceControl, + + /// + /// Whether the user can access the server remotely. + /// EnableRemoteAccess, + + /// + /// Whether the user can manage live tv. + /// EnableLiveTvManagement, + + /// + /// Whether the user can access live tv. + /// EnableLiveTvAccess, + + /// + /// Whether the user can play media. + /// EnableMediaPlayback, + + /// + /// Whether the server should transcode audio for the user if requested. + /// EnableAudioPlaybackTranscoding, + + /// + /// Whether the server should transcode video for the user if requested. + /// EnableVideoPlaybackTranscoding, + + /// + /// Whether the user can delete content. + /// EnableContentDeletion, + + /// + /// Whether the user can download content. + /// EnableContentDownloading, + + /// + /// Whether to enable sync transcoding for the user. + /// EnableSyncTranscoding, + + /// + /// Whether the user can do media conversion. + /// EnableMediaConversion, + + /// + /// Whether the user has access to all devices. + /// EnableAllDevices, + + /// + /// Whether the user has access to all channels. + /// EnableAllChannels, + + /// + /// Whether the user has access to all folders. + /// EnableAllFolders, + + /// + /// Whether to enable public sharing for the user. + /// EnablePublicSharing, + + /// + /// Whether the user can remotely control other users. + /// EnableRemoteControlOfOtherUsers, + + /// + /// Whether the user is permitted to do playback remuxing. + /// EnablePlaybackRemuxing, + + /// + /// Whether the server should force transcoding on remote connections for the user. + /// ForceRemoteSourceTranscoding } } diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs index ea1221e1a..e0e9cfe04 100644 --- a/Jellyfin.Data/Enums/PreferenceKind.cs +++ b/Jellyfin.Data/Enums/PreferenceKind.cs @@ -1,18 +1,68 @@ namespace Jellyfin.Data.Enums { + /// + /// The types of user preferences. + /// public enum PreferenceKind { + /// + /// A list of blocked tags. + /// BlockedTags, + + /// + /// A list of blocked channels. + /// BlockedChannels, + + /// + /// A list of blocked media folders. + /// BlockedMediaFolders, + + /// + /// A list of enabled devices. + /// EnabledDevices, + + /// + /// A list of enabled channels + /// EnabledChannels, + + /// + /// A list of enabled folders. + /// EnabledFolders, + + /// + /// A list of folders to allow content deletion from. + /// EnableContentDeletionFromFolders, + + /// + /// A list of latest items to exclude. + /// LatestItemExcludes, + + /// + /// A list of media to exclude. + /// MyMediaExcludes, + + /// + /// A list of grouped folders. + /// GroupedFolders, + + /// + /// A list of unrated items to block. + /// BlockUnratedItems, + + /// + /// A list of ordered views. + /// OrderedViews } } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 2d0caee29..291574155 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CA1307 -#pragma warning disable CS1591 using System; using System.Collections.Generic; @@ -26,6 +25,9 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Implementations.Users { + /// + /// Manages the creation and retrieval of instances. + /// public class UserManager : IUserManager { private readonly JellyfinDbProvider _dbProvider; @@ -41,6 +43,15 @@ namespace Jellyfin.Server.Implementations.Users private IPasswordResetProvider[] _passwordResetProviders; private DefaultPasswordResetProvider _defaultPasswordResetProvider; + /// + /// Initializes a new instance of the class. + /// + /// The database provider. + /// The cryptography provider. + /// The network manager. + /// The application host. + /// The image processor. + /// The logger. public UserManager( JellyfinDbProvider dbProvider, ICryptoProvider cryptoProvider, @@ -57,6 +68,7 @@ namespace Jellyfin.Server.Implementations.Users _logger = logger; } + /// public event EventHandler> OnUserPasswordChanged; /// @@ -68,8 +80,10 @@ namespace Jellyfin.Server.Implementations.Users /// public event EventHandler> OnUserDeleted; + /// public event EventHandler> OnUserLockedOut; + /// public IEnumerable Users { get @@ -79,6 +93,7 @@ namespace Jellyfin.Server.Implementations.Users } } + /// public IEnumerable UsersIds { get @@ -88,6 +103,7 @@ namespace Jellyfin.Server.Implementations.Users } } + /// public User GetUserById(Guid id) { if (id == Guid.Empty) @@ -100,6 +116,7 @@ namespace Jellyfin.Server.Implementations.Users return dbContext.Users.Find(id); } + /// public User GetUserByName(string name) { if (string.IsNullOrWhiteSpace(name)) @@ -114,6 +131,7 @@ namespace Jellyfin.Server.Implementations.Users return dbContext.Users.FirstOrDefault(u => string.Equals(u.Username, name)); } + /// public async Task RenameUser(User user, string newName) { if (user == null) @@ -145,6 +163,7 @@ namespace Jellyfin.Server.Implementations.Users OnUserUpdated?.Invoke(this, new GenericEventArgs(user)); } + /// public void UpdateUser(User user) { var dbContext = _dbProvider.CreateContext(); @@ -152,6 +171,7 @@ namespace Jellyfin.Server.Implementations.Users dbContext.SaveChanges(); } + /// public async Task UpdateUserAsync(User user) { var dbContext = _dbProvider.CreateContext(); @@ -160,6 +180,7 @@ namespace Jellyfin.Server.Implementations.Users await dbContext.SaveChangesAsync().ConfigureAwait(false); } + /// public User CreateUser(string name) { if (!IsValidUsername(name)) @@ -178,6 +199,7 @@ namespace Jellyfin.Server.Implementations.Users return newUser; } + /// public void DeleteUser(User user) { if (user == null) @@ -220,16 +242,19 @@ namespace Jellyfin.Server.Implementations.Users OnUserDeleted?.Invoke(this, new GenericEventArgs(user)); } + /// public Task ResetPassword(User user) { return ChangePassword(user, string.Empty); } + /// public void ResetEasyPassword(User user) { ChangeEasyPassword(user, string.Empty, null); } + /// public async Task ChangePassword(User user, string newPassword) { if (user == null) @@ -243,6 +268,7 @@ namespace Jellyfin.Server.Implementations.Users OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); } + /// public void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1) { GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1); @@ -251,6 +277,7 @@ namespace Jellyfin.Server.Implementations.Users OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); } + /// public UserDto GetUserDto(User user, string remoteEndPoint = null) { return new UserDto @@ -321,6 +348,7 @@ namespace Jellyfin.Server.Implementations.Users }; } + /// public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null) { if (user == null) @@ -343,6 +371,7 @@ namespace Jellyfin.Server.Implementations.Users }; } + /// public async Task AuthenticateUser( string username, string password, @@ -469,6 +498,7 @@ namespace Jellyfin.Server.Implementations.Users return success ? user : null; } + /// public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) { var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); @@ -488,6 +518,7 @@ namespace Jellyfin.Server.Implementations.Users }; } + /// public async Task RedeemPasswordResetPin(string pin) { foreach (var provider in _passwordResetProviders) @@ -507,6 +538,7 @@ namespace Jellyfin.Server.Implementations.Users }; } + /// public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) { _authenticationProviders = authenticationProviders.ToArray(); @@ -517,6 +549,7 @@ namespace Jellyfin.Server.Implementations.Users _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); } + /// public NameIdPair[] GetAuthenticationProviders() { return _authenticationProviders @@ -531,6 +564,7 @@ namespace Jellyfin.Server.Implementations.Users .ToArray(); } + /// public NameIdPair[] GetPasswordResetProviders() { return _passwordResetProviders @@ -545,6 +579,7 @@ namespace Jellyfin.Server.Implementations.Users .ToArray(); } + /// public void UpdateConfiguration(Guid userId, UserConfiguration config) { var user = GetUserById(userId); @@ -568,6 +603,7 @@ namespace Jellyfin.Server.Implementations.Users UpdateUser(user); } + /// public void UpdatePolicy(Guid userId, UserPolicy policy) { var user = GetUserById(userId); -- cgit v1.2.3 From 1d1a145ad4386b39b983d539001abfccf5f53b23 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 19 May 2020 22:12:03 -0400 Subject: Fix issues and add profile image support --- Jellyfin.Data/Entities/ImageInfo.cs | 13 +++++++++++-- Jellyfin.Data/Entities/Permission.cs | 18 ++++++++++-------- Jellyfin.Data/Entities/User.cs | 8 +++++--- Jellyfin.Server.Implementations/Users/UserManager.cs | 5 ++++- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 17 +++++++++++++++-- MediaBrowser.Api/Images/ImageService.cs | 3 ++- MediaBrowser.Providers/Manager/ImageSaver.cs | 9 ++------- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- 8 files changed, 50 insertions(+), 25 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs index 336c13b36..659369545 100644 --- a/Jellyfin.Data/Entities/ImageInfo.cs +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -1,24 +1,33 @@ using System; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities { public class ImageInfo { - public ImageInfo(string path) + public ImageInfo(string path, int width, int height) { Path = path; + Width = width; + Height = height; LastModified = DateTime.UtcNow; } [Key] [Required] - + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } [Required] public string Path { get; set; } + [Required] + public int Width { get; set; } + + [Required] + public int Height { get; set; } + [Required] public DateTime LastModified { get; set; } } diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index 136e7abd3..706128028 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -12,14 +12,7 @@ namespace Jellyfin.Data.Entities partial void Init(); /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Permission() - { - Init(); - } - - /// + /// Initializes a new instance of the class. /// Public constructor with required data /// /// @@ -33,6 +26,15 @@ namespace Jellyfin.Data.Entities Init(); } + /// + /// Initializes a new instance of the class. + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Permission() + { + Init(); + } + /// /// Static create function (for use in LINQ queries, etc.) /// diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index bd1cde31c..783823a17 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities /// /// The username for the new user. /// The authentication provider's Id - public User(string username, string authenticationProviderId) + public User(string username, string authenticationProviderId, string passwordResetProviderId) { if (string.IsNullOrEmpty(username)) { @@ -39,6 +39,7 @@ namespace Jellyfin.Data.Entities Username = username; AuthenticationProviderId = authenticationProviderId; + PasswordResetProviderId = passwordResetProviderId; Groups = new HashSet(); Permissions = new HashSet(); @@ -85,10 +86,11 @@ namespace Jellyfin.Data.Entities /// /// The username for the created user. /// The Id of the user's authentication provider. + /// The Id of the user's password reset provider. /// The created instance. - public static User Create(string username, string authenticationProviderId) + public static User Create(string username, string authenticationProviderId, string passwordResetProviderId) { - return new User(username, authenticationProviderId); + return new User(username, authenticationProviderId, passwordResetProviderId); } /************************************************************************* diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 291574155..18616f75f 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -190,7 +190,10 @@ namespace Jellyfin.Server.Implementations.Users var dbContext = _dbProvider.CreateContext(); - var newUser = new User(name, _defaultAuthenticationProvider.GetType().FullName); + var newUser = new User( + name, + _defaultAuthenticationProvider.GetType().FullName, + _defaultPasswordResetProvider.GetType().FullName); dbContext.Users.Add(newUser); dbContext.SaveChanges(); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 9a428a747..987c85f4c 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -7,6 +7,7 @@ using Jellyfin.Data.Enums; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; @@ -84,9 +85,9 @@ namespace Jellyfin.Server.Migrations.Routines StringComparison.Ordinal) ?? typeof(DefaultAuthenticationProvider).FullName; - policy.PasswordResetProviderId ??= typeof(DefaultPasswordResetProvider).FullName; + policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName; - var user = new User(mockup.Name, policy.AuthenticationProviderId) + var user = new User(mockup.Name, policy.AuthenticationProviderId, string.Empty) { Id = entry[1].ReadGuidFromBlob(), InternalId = entry[0].ToInt64(), @@ -113,6 +114,16 @@ namespace Jellyfin.Server.Migrations.Routines LastActivityDate = mockup.LastActivityDate ?? DateTime.MinValue }; + if (mockup.ImageInfos.Length > 0) + { + ItemImageInfo info = mockup.ImageInfos[0]; + + user.ProfileImage = new ImageInfo(info.Path, info.Width, info.Height) + { + LastModified = info.DateModified + }; + } + user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); @@ -184,6 +195,8 @@ namespace Jellyfin.Server.Migrations.Routines public DateTime? LastActivityDate { get; set; } public string Name { get; set; } + + public ItemImageInfo[] ImageInfos { get; set; } } } } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 5bdf1618b..1392184df 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -17,6 +17,7 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -887,7 +888,7 @@ namespace MediaBrowser.Api.Images var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username); await _providerManager - .SaveImage(user, memoryStream, mimeType, Path.Combine(userDataPath, _imageProcessor.GetImageCacheTag(user))) + .SaveImage(user, memoryStream, mimeType, Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType))) .ConfigureAwait(false); await _userManager.UpdateUserAsync(user); } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 6bed38780..3c94f6215 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -131,7 +131,7 @@ namespace MediaBrowser.Providers.Manager var currentImage = GetCurrentImage(item, type, index); var currentImageIsLocalFile = currentImage != null && currentImage.IsLocalFile; - var currentImagePath = currentImage == null ? null : currentImage.Path; + var currentImagePath = currentImage?.Path; var savedPaths = new List(); @@ -179,13 +179,8 @@ namespace MediaBrowser.Providers.Manager } } - public async Task SaveImage(User user, Stream source, string mimeType, string path) + public async Task SaveImage(User user, Stream source, string path) { - if (string.IsNullOrEmpty(mimeType)) - { - throw new ArgumentNullException(nameof(mimeType)); - } - await SaveImageToLocation(source, path, path, CancellationToken.None).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index f4e875a24..be8fe3e1c 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -191,7 +191,7 @@ namespace MediaBrowser.Providers.Manager public Task SaveImage(User user, Stream source, string mimeType, string path) { return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger) - .SaveImage(user, source, mimeType, path); + .SaveImage(user, source, path); } public async Task> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken) -- cgit v1.2.3 From 64c14beb27348daf0f440b5b1bc31ccb2987f9ff Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 00:23:56 -0400 Subject: Fix default permissions and HasPassword property --- Jellyfin.Data/Entities/User.cs | 6 +++--- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 783823a17..52bbe8b18 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -325,10 +325,10 @@ namespace Jellyfin.Data.Entities { Permissions.Add(new Permission(PermissionKind.IsAdministrator, false)); Permissions.Add(new Permission(PermissionKind.IsDisabled, false)); - Permissions.Add(new Permission(PermissionKind.IsHidden, false)); - Permissions.Add(new Permission(PermissionKind.EnableAllChannels, false)); + Permissions.Add(new Permission(PermissionKind.IsHidden, true)); + Permissions.Add(new Permission(PermissionKind.EnableAllChannels, true)); Permissions.Add(new Permission(PermissionKind.EnableAllDevices, true)); - Permissions.Add(new Permission(PermissionKind.EnableAllFolders, false)); + Permissions.Add(new Permission(PermissionKind.EnableAllFolders, true)); Permissions.Add(new Permission(PermissionKind.EnableContentDeletion, false)); Permissions.Add(new Permission(PermissionKind.EnableContentDownloading, true)); Permissions.Add(new Permission(PermissionKind.EnableMediaConversion, true)); diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 18616f75f..4dd41792d 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -288,7 +288,7 @@ namespace Jellyfin.Server.Implementations.Users Name = user.Username, Id = user.Id, ServerId = _appHost.SystemId, - HasPassword = user.Password == null, + HasPassword = user.Password != null, EnableAutoLogin = user.EnableAutoLogin, LastLoginDate = user.LastLoginDate, LastActivityDate = user.LastActivityDate, -- cgit v1.2.3 From becfe018f0e3796bbd776068ea92d22daf483f2f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 13:49:44 -0400 Subject: Add internal id for new users --- Jellyfin.Server.Implementations/Users/UserManager.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 4dd41792d..599efe583 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -190,10 +190,16 @@ namespace Jellyfin.Server.Implementations.Users var dbContext = _dbProvider.CreateContext(); + // Temporary measure until user item data is migrated. + var max = dbContext.Users.Select(u => u.InternalId).Max(); + var newUser = new User( name, _defaultAuthenticationProvider.GetType().FullName, - _defaultPasswordResetProvider.GetType().FullName); + _defaultPasswordResetProvider.GetType().FullName) + { + InternalId = max + 1 + }; dbContext.Users.Add(newUser); dbContext.SaveChanges(); -- cgit v1.2.3 From d72ea709955f17ad2034cd4c728d02cf3265e7e9 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 19:47:41 -0400 Subject: Document user class and fix a few minor issues --- .../Devices/DeviceManager.cs | 9 +- Jellyfin.Data/Entities/User.cs | 219 ++++++++++++++++++--- .../Users/UserManager.cs | 6 +- 3 files changed, 194 insertions(+), 40 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 2d44a7c28..a3c53035f 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -5,13 +5,11 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Threading.Tasks; using Jellyfin.Data.Enums; using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Devices; @@ -19,7 +17,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Users; namespace Emby.Server.Implementations.Devices { @@ -30,11 +27,10 @@ namespace Emby.Server.Implementations.Devices private readonly IServerConfigurationManager _config; private readonly IAuthenticationRepository _authRepo; private readonly Dictionary _capabilitiesCache; + private readonly object _capabilitiesSyncLock = new object(); public event EventHandler>> DeviceOptionsUpdated; - private readonly object _capabilitiesSyncLock = new object(); - public DeviceManager( IAuthenticationRepository authRepo, IJsonSerializer json, @@ -184,8 +180,7 @@ namespace Emby.Server.Implementations.Devices throw new ArgumentNullException(nameof(deviceId)); } - if (user.HasPermission(PermissionKind.EnableAllDevices) - || user.HasPermission(PermissionKind.IsAdministrator)) + if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator)) { return true; } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index dd1dcfc6f..afda6169c 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -24,7 +24,8 @@ namespace Jellyfin.Data.Entities /// Public constructor with required data. /// /// The username for the new user. - /// The authentication provider's Id + /// The Id of the user's authentication provider. + /// The Id of the user's password reset provider. public User(string username, string authenticationProviderId, string passwordResetProviderId) { if (string.IsNullOrEmpty(username)) @@ -81,139 +82,234 @@ namespace Jellyfin.Data.Entities Init(); } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The username for the created user. - /// The Id of the user's authentication provider. - /// The Id of the user's password reset provider. - /// The created instance. - public static User Create(string username, string authenticationProviderId, string passwordResetProviderId) - { - return new User(username, authenticationProviderId, passwordResetProviderId); - } - /************************************************************************* * Properties *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the Id of the user. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] [JsonIgnore] public Guid Id { get; set; } /// - /// Required, Max length = 255 + /// Gets or sets the user's name. /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string Username { get; set; } /// - /// Max length = 65535 + /// Gets or sets the user's password, or null if none is set. /// + /// + /// Max length = 65535. + /// [MaxLength(65535)] [StringLength(65535)] public string Password { get; set; } /// - /// Max length = 65535. + /// Gets or sets the user's easy password, or null if none is set. /// + /// + /// Max length = 65535. + /// [MaxLength(65535)] [StringLength(65535)] public string EasyPassword { get; set; } /// - /// Required + /// Gets or sets a value indicating whether the user must update their password. /// + /// + /// Required. + /// [Required] public bool MustUpdatePassword { get; set; } /// - /// Max length = 255. + /// Gets or sets the audio language preference. /// + /// + /// Max length = 255. + /// [MaxLength(255)] [StringLength(255)] public string AudioLanguagePreference { get; set; } /// - /// Required, Max length = 255 + /// Gets or sets the authentication provider id. /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string AuthenticationProviderId { get; set; } + /// + /// Gets or sets the password reset provider id. + /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string PasswordResetProviderId { get; set; } /// - /// Required + /// Gets or sets the invalid login attempt count. /// + /// + /// Required. + /// [Required] public int InvalidLoginAttemptCount { get; set; } + /// + /// Gets or sets the last activity date. + /// public DateTime LastActivityDate { get; set; } + /// + /// Gets or sets the last login date. + /// public DateTime LastLoginDate { get; set; } + /// + /// Gets or sets the number of login attempts the user can make before they are locked out. + /// public int? LoginAttemptsBeforeLockout { get; set; } /// - /// Required. + /// Gets or sets the subtitle mode. /// + /// + /// Required. + /// [Required] public SubtitlePlaybackMode SubtitleMode { get; set; } /// - /// Required + /// Gets or sets a value indicating whether the default audio track should be played. /// + /// + /// Required. + /// [Required] public bool PlayDefaultAudioTrack { get; set; } /// /// Gets or sets the subtitle language preference. - /// Max length = 255 /// + /// + /// Max length = 255. + /// [MaxLength(255)] [StringLength(255)] public string SubtitleLanguagePreference { get; set; } + /// + /// Gets or sets a value indicating whether missing episodes should be displayed. + /// + /// + /// Required. + /// [Required] public bool DisplayMissingEpisodes { get; set; } + /// + /// Gets or sets a value indicating whether to display the collections view. + /// + /// + /// Required. + /// [Required] public bool DisplayCollectionsView { get; set; } + /// + /// Gets or sets a value indicating whether the user has a local password. + /// + /// + /// Required. + /// [Required] public bool EnableLocalPassword { get; set; } + /// + /// Gets or sets a value indicating whether the server should hide played content in "Latest". + /// + /// + /// Required. + /// [Required] public bool HidePlayedInLatest { get; set; } + /// + /// Gets or sets a value indicating whether to remember audio selections on played content. + /// + /// + /// Required. + /// [Required] public bool RememberAudioSelections { get; set; } + /// + /// Gets or sets a value indicating whether to remember subtitle selections on played content. + /// + /// + /// Required. + /// [Required] public bool RememberSubtitleSelections { get; set; } + /// + /// Gets or sets a value indicating whether to enable auto-play for the next episode. + /// + /// + /// Required. + /// [Required] public bool EnableNextEpisodeAutoPlay { get; set; } + /// + /// Gets or sets a value indicating whether the user should auto-login. + /// + /// + /// Required. + /// [Required] public bool EnableAutoLogin { get; set; } + /// + /// Gets or sets a value indicating whether the user can change their preferences. + /// + /// + /// Required. + /// [Required] public bool EnableUserPreferenceAccess { get; set; } + /// + /// Gets or sets the maximum parental age rating. + /// public int? MaxParentalAgeRating { get; set; } + /// + /// Gets or sets the remote client bitrate limit. + /// public int? RemoteClientBitrateLimit { get; set; } /// @@ -224,51 +320,100 @@ namespace Jellyfin.Data.Entities [Required] public long InternalId { get; set; } + /// + /// Gets or sets the user's profile image. Can be null. + /// public virtual ImageInfo ProfileImage { get; set; } /// /// Gets or sets the row version. - /// Required, ConcurrenyToken. /// + /// + /// Required, Concurrency Token. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } - public void OnSavingChanges() - { - RowVersion++; - } - /************************************************************************* * Navigation properties *************************************************************************/ + + /// + /// Gets or sets the list of groups this user is a member of. + /// [ForeignKey("Group_Groups_Guid")] public virtual ICollection Groups { get; protected set; } + /// + /// Gets or sets the list of permissions this user has. + /// [ForeignKey("Permission_Permissions_Guid")] public virtual ICollection Permissions { get; protected set; } + /// + /// Gets or sets the list of provider mappings this user has. + /// [ForeignKey("ProviderMapping_ProviderMappings_Id")] public virtual ICollection ProviderMappings { get; protected set; } + /// + /// Gets or sets the list of preferences this user has. + /// [ForeignKey("Preference_Preferences_Guid")] public virtual ICollection Preferences { get; protected set; } + /// + /// Gets or sets the list of access schedules this user has. + /// public virtual ICollection AccessSchedules { get; protected set; } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The username for the created user. + /// The Id of the user's authentication provider. + /// The Id of the user's password reset provider. + /// The created instance. + public static User Create(string username, string authenticationProviderId, string passwordResetProviderId) + { + return new User(username, authenticationProviderId, passwordResetProviderId); + } + + /// + public void OnSavingChanges() + { + RowVersion++; + } + partial void Init(); + /// + /// Checks whether the user has the specified permission. + /// + /// The permission kind. + /// True if the user has the specified permission. public bool HasPermission(PermissionKind permission) { return Permissions.First(p => p.Kind == permission).Value; } + /// + /// Sets the given permission kind to the provided value. + /// + /// The permission kind. + /// The value to set. public void SetPermission(PermissionKind kind, bool value) { var permissionObj = Permissions.First(p => p.Kind == kind); permissionObj.Value = value; } + /// + /// Gets the user's preferences for the given preference kind. + /// + /// The preference kind. + /// A string array containing the user's preferences. public string[] GetPreference(PreferenceKind preference) { var val = Preferences @@ -279,18 +424,32 @@ namespace Jellyfin.Data.Entities return Equals(val, string.Empty) ? Array.Empty() : val.Split(Delimiter); } + /// + /// Sets the specified preference to the given value. + /// + /// The preference kind. + /// The values. public void SetPreference(PreferenceKind preference, string[] values) { Preferences.First(p => p.Kind == preference).Value = string.Join(Delimiter.ToString(CultureInfo.InvariantCulture), values); } + /// + /// Checks whether this user is currently allowed to use the server. + /// + /// True if the current time is within an access schedule, or there are no access schedules. public bool IsParentalScheduleAllowed() { return AccessSchedules.Count == 0 || AccessSchedules.Any(i => IsParentalScheduleAllowed(i, DateTime.UtcNow)); } + /// + /// Checks whether the provided folder is in this user's grouped folders. + /// + /// The Guid of the folder. + /// True if the folder is in the user's grouped folders. public bool IsFolderGrouped(Guid id) { return GetPreference(PreferenceKind.GroupedFolders).Any(i => new Guid(i) == id); diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 599efe583..e16b1fb7b 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -35,7 +35,7 @@ namespace Jellyfin.Server.Implementations.Users private readonly INetworkManager _networkManager; private readonly IApplicationHost _appHost; private readonly IImageProcessor _imageProcessor; - private readonly ILogger _logger; + private readonly ILogger _logger; private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; @@ -58,7 +58,7 @@ namespace Jellyfin.Server.Implementations.Users INetworkManager networkManager, IApplicationHost appHost, IImageProcessor imageProcessor, - ILogger logger) + ILogger logger) { _dbProvider = dbProvider; _cryptoProvider = cryptoProvider; @@ -190,7 +190,7 @@ namespace Jellyfin.Server.Implementations.Users var dbContext = _dbProvider.CreateContext(); - // Temporary measure until user item data is migrated. + // TODO: Remove after user item data is migrated. var max = dbContext.Users.Select(u => u.InternalId).Max(); var newUser = new User( -- cgit v1.2.3 From e3f9aaa9c62d44854c3ea667c670d6b5a76c0254 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 22 May 2020 21:45:31 -0400 Subject: Fix bugs relating to users not being properly locked out. --- .../Activity/ActivityLogEntryPoint.cs | 18 ++++-------------- Jellyfin.Server.Implementations/Users/UserManager.cs | 7 ++++--- 2 files changed, 8 insertions(+), 17 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 31d9609a6..05aa44338 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -104,8 +104,10 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Username), NotificationType.UserLockedOut.ToString(), - e.Argument.Id)) - .ConfigureAwait(false); + e.Argument.Id) + { + LogSeverity = LogLevel.Error + }).ConfigureAwait(false); } private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) @@ -303,18 +305,6 @@ namespace Emby.Server.Implementations.Activity }).ConfigureAwait(false); } - private async void OnUserPolicyUpdated(object sender, GenericEventArgs e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserPolicyUpdatedWithName"), - e.Argument.Username), - "UserPolicyUpdated", - e.Argument.Id)) - .ConfigureAwait(false); - } - private async void OnUserDeleted(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index e16b1fb7b..23646de61 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -800,16 +801,16 @@ namespace Jellyfin.Server.Implementations.Users private void IncrementInvalidLoginAttemptCount(User user) { - int invalidLogins = user.InvalidLoginAttemptCount; + user.InvalidLoginAttemptCount++; int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; - if (maxInvalidLogins.HasValue && invalidLogins >= maxInvalidLogins) + if (maxInvalidLogins.HasValue && user.InvalidLoginAttemptCount >= maxInvalidLogins) { user.SetPermission(PermissionKind.IsDisabled, true); OnUserLockedOut?.Invoke(this, new GenericEventArgs(user)); _logger.LogWarning( "Disabling user {Username} due to {Attempts} unsuccessful login attempts.", user.Username, - invalidLogins); + user.InvalidLoginAttemptCount); } UpdateUser(user); -- cgit v1.2.3 From 99511b3be8dd63d832336c65b72d0c17efb9bc6b Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 23 May 2020 14:53:24 -0400 Subject: Fix a couple bugs --- .../Users/DefaultAuthenticationProvider.cs | 2 +- Jellyfin.Server.Implementations/Users/UserManager.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index df730731a..c15312a72 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -53,7 +53,7 @@ namespace Jellyfin.Server.Implementations.Users bool success = false; // As long as jellyfin supports passwordless users, we need this little block here to accommodate - if (!HasPassword(resolvedUser)) + if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password)) { return Task.FromResult(new ProviderAuthenticationResult { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 23646de61..62c4b9b87 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Runtime.InteropServices.ComTypes; @@ -617,6 +618,12 @@ namespace Jellyfin.Server.Implementations.Users public void UpdatePolicy(Guid userId, UserPolicy policy) { var user = GetUserById(userId); + int? loginAttempts = policy.LoginAttemptsBeforeLockout switch + { + -1 => null, + 0 => 3, + _ => policy.LoginAttemptsBeforeLockout + }; user.MaxParentalAgeRating = policy.MaxParentalRating; user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess; @@ -624,9 +631,7 @@ namespace Jellyfin.Server.Implementations.Users user.AuthenticationProviderId = policy.AuthenticationProviderId; user.PasswordResetProviderId = policy.PasswordResetProviderId; user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; - user.LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 - ? null - : new int?(policy.LoginAttemptsBeforeLockout); + user.LoginAttemptsBeforeLockout = loginAttempts; user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); -- cgit v1.2.3 From e8173df9dc67470748c3293745fe3948363373b4 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 23 May 2020 15:33:14 -0400 Subject: Cleanup --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 -- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 62c4b9b87..7c27a3c41 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -2,10 +2,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.Linq; -using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index a1895247f..a19638abf 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -87,14 +87,13 @@ namespace Jellyfin.Server.Migrations.Routines policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName; - var user = new User(mockup.Name, policy.AuthenticationProviderId, string.Empty) + var user = new User(mockup.Name, policy.AuthenticationProviderId, policy.PasswordResetProviderId) { Id = entry[1].ReadGuidFromBlob(), InternalId = entry[0].ToInt64(), MaxParentalAgeRating = policy.MaxParentalRating, EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess, RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit, - PasswordResetProviderId = policy.PasswordResetProviderId, InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount, LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 ? null : new int?(policy.LoginAttemptsBeforeLockout), SubtitleMode = config.SubtitleMode, -- cgit v1.2.3 From e052128c527672ed586a78257da1d2b07319e598 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 23 May 2020 16:07:42 -0400 Subject: Cleanup and fix more bugs --- Jellyfin.Server.Implementations/Users/UserManager.cs | 9 +++++++-- Jellyfin.Server/CoreAppHost.cs | 4 +--- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 8 +++++++- 3 files changed, 15 insertions(+), 6 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 7c27a3c41..41116c251 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -556,6 +556,11 @@ namespace Jellyfin.Server.Implementations.Users _invalidAuthProvider = _authenticationProviders.OfType().First(); _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); + + if (_authenticationProviders.Length > 2) + { + _logger.LogCritical("INVALID NUMBER OF LOGGERS: {0}", _authenticationProviders.Length); + } } /// @@ -616,7 +621,7 @@ namespace Jellyfin.Server.Implementations.Users public void UpdatePolicy(Guid userId, UserPolicy policy) { var user = GetUserById(userId); - int? loginAttempts = policy.LoginAttemptsBeforeLockout switch + int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch { -1 => null, 0 => 3, @@ -629,7 +634,7 @@ namespace Jellyfin.Server.Implementations.Users user.AuthenticationProviderId = policy.AuthenticationProviderId; user.PasswordResetProviderId = policy.PasswordResetProviderId; user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; - user.LoginAttemptsBeforeLockout = loginAttempts; + user.LoginAttemptsBeforeLockout = maxLoginAttempts; user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 81ae38467..716abcb63 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -85,9 +85,7 @@ namespace Jellyfin.Server protected override IEnumerable GetAssembliesWithPartsInternal() { yield return typeof(CoreAppHost).Assembly; - yield return typeof(DefaultAuthenticationProvider).Assembly; - yield return typeof(DefaultPasswordResetProvider).Assembly; - yield return typeof(InvalidAuthProvider).Assembly; + yield return Assembly.Load("Jellyfin.Server.Implementations"); } /// diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index a19638abf..af74d3a1d 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -86,6 +86,12 @@ namespace Jellyfin.Server.Migrations.Routines ?? typeof(DefaultAuthenticationProvider).FullName; policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName; + int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch + { + -1 => null, + 0 => 3, + _ => policy.LoginAttemptsBeforeLockout + }; var user = new User(mockup.Name, policy.AuthenticationProviderId, policy.PasswordResetProviderId) { @@ -95,7 +101,7 @@ namespace Jellyfin.Server.Migrations.Routines EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess, RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit, InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount, - LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 ? null : new int?(policy.LoginAttemptsBeforeLockout), + LoginAttemptsBeforeLockout = maxLoginAttempts, SubtitleMode = config.SubtitleMode, HidePlayedInLatest = config.HidePlayedInLatest, EnableLocalPassword = config.EnableLocalPassword, -- cgit v1.2.3 From 7c823464bca70570f2f53f8af6913e53d385b784 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 26 May 2020 20:52:05 -0400 Subject: Fix merge conflicts with SyncPlay --- .../SyncPlay/SyncPlayManager.cs | 64 ++++++++-------------- Jellyfin.Data/Entities/User.cs | 4 ++ Jellyfin.Data/Enums/SyncPlayAccess.cs | 23 ++++++++ .../Users/UserManager.cs | 27 +-------- MediaBrowser.Model/Configuration/SyncplayAccess.cs | 23 -------- MediaBrowser.Model/Users/UserPolicy.cs | 4 +- 6 files changed, 55 insertions(+), 90 deletions(-) create mode 100644 Jellyfin.Data/Enums/SyncPlayAccess.cs delete mode 100644 MediaBrowser.Model/Configuration/SyncplayAccess.cs (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 1f76dd4e3..6a3e684ca 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; -using Microsoft.Extensions.Logging; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.SyncPlay { @@ -102,14 +103,6 @@ namespace Emby.Server.Implementations.SyncPlay _disposed = true; } - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; @@ -143,37 +136,26 @@ namespace Emby.Server.Implementations.SyncPlay // Check ParentalRating access var hasParentalRatingAccess = true; - if (user.Policy.MaxParentalRating.HasValue) + if (user.MaxParentalAgeRating.HasValue) { - hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; + hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.MaxParentalAgeRating.Value; } - if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) + if (!user.HasPermission(PermissionKind.EnableAllFolders) && hasParentalRatingAccess) { var collections = _libraryManager.GetCollectionFolders(item).Select( - folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) - ); - var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Any(); - } - else - { - return hasParentalRatingAccess; + folder => folder.Id.ToString("N", CultureInfo.InvariantCulture)); + + return collections.Intersect(user.GetPreference(PreferenceKind.EnabledFolders)).Any(); } + + return hasParentalRatingAccess; } private Guid? GetSessionGroup(SessionInfo session) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - if (group != null) - { - return group.GetGroupId(); - } - else - { - return null; - } + _sessionToGroupMap.TryGetValue(session.Id, out var group); + return group?.GetGroupId(); } /// @@ -181,7 +163,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) + if (user.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) { _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); @@ -189,7 +171,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.CreateGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -212,7 +194,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { _logger.LogWarning("JoinGroup: {0} does not have access to SyncPlay.", session.Id); @@ -220,7 +202,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -237,7 +219,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.GroupDoesNotExist }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -250,7 +232,7 @@ namespace Emby.Server.Implementations.SyncPlay GroupId = group.GetGroupId().ToString(), Type = GroupUpdateType.LibraryAccessDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -285,7 +267,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -304,7 +286,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { return new List(); } @@ -334,7 +316,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { _logger.LogWarning("HandleRequest: {0} does not have access to SyncPlay.", session.Id); @@ -342,7 +324,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 2287d802b..0a4661780 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -61,6 +61,7 @@ namespace Jellyfin.Data.Entities EnableAutoLogin = false; PlayDefaultAudioTrack = true; SubtitleMode = SubtitlePlaybackMode.Default; + SyncPlayAccess = SyncPlayAccess.CreateAndJoinGroups; AddDefaultPermissions(); AddDefaultPreferences(); @@ -319,6 +320,9 @@ namespace Jellyfin.Data.Entities /// public virtual ImageInfo ProfileImage { get; set; } + [Required] + public SyncPlayAccess SyncPlayAccess { get; set; } + /// /// Gets or sets the row version. /// diff --git a/Jellyfin.Data/Enums/SyncPlayAccess.cs b/Jellyfin.Data/Enums/SyncPlayAccess.cs new file mode 100644 index 000000000..8c13b37a1 --- /dev/null +++ b/Jellyfin.Data/Enums/SyncPlayAccess.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Data.Enums +{ + /// + /// Enum SyncPlayAccess. + /// + public enum SyncPlayAccess + { + /// + /// User can create groups and join them. + /// + CreateAndJoinGroups = 0, + + /// + /// User can only join already existing groups. + /// + JoinGroups = 1, + + /// + /// SyncPlay is disabled for the user. + /// + None = 2 + } +} diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 41116c251..886c08b4c 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -352,34 +352,12 @@ namespace Jellyfin.Server.Implementations.Users EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels), EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), - EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders) + EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders), + SyncPlayAccess = user.SyncPlayAccess } }; } - /// - public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user); - bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); - - bool hasPassword = user.EnableLocalPassword && - !string.IsNullOrEmpty(remoteEndPoint) && - _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword; - - return new PublicUserDto - { - Name = user.Username, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword - }; - } - /// public async Task AuthenticateUser( string username, @@ -635,6 +613,7 @@ namespace Jellyfin.Server.Implementations.Users user.PasswordResetProviderId = policy.PasswordResetProviderId; user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; user.LoginAttemptsBeforeLockout = maxLoginAttempts; + user.SyncPlayAccess = policy.SyncPlayAccess; user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); diff --git a/MediaBrowser.Model/Configuration/SyncplayAccess.cs b/MediaBrowser.Model/Configuration/SyncplayAccess.cs deleted file mode 100644 index d891a8167..000000000 --- a/MediaBrowser.Model/Configuration/SyncplayAccess.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - /// - /// Enum SyncPlayAccess. - /// - public enum SyncPlayAccess - { - /// - /// User can create groups and join them. - /// - CreateAndJoinGroups, - - /// - /// User can only join already existing groups. - /// - JoinGroups, - - /// - /// SyncPlay is disabled for the user. - /// - None - } -} diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 7ac63a0ac..66e5529e3 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -1,10 +1,10 @@ #pragma warning disable CS1591 using System; -using System.Text.Json.Serialization; using System.Xml.Serialization; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using MediaBrowser.Model.Configuration; +using AccessSchedule = Jellyfin.Data.Entities.AccessSchedule; namespace MediaBrowser.Model.Users { -- cgit v1.2.3 From 82b0786cc6a5c40725a94098daa4e707476fa089 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 28 May 2020 00:59:31 -0400 Subject: Remove unnecessary logging statement --- Jellyfin.Server.Implementations/Users/UserManager.cs | 5 ----- 1 file changed, 5 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 886c08b4c..5ad9e9863 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -534,11 +534,6 @@ namespace Jellyfin.Server.Implementations.Users _invalidAuthProvider = _authenticationProviders.OfType().First(); _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); - - if (_authenticationProviders.Length > 2) - { - _logger.LogCritical("INVALID NUMBER OF LOGGERS: {0}", _authenticationProviders.Length); - } } /// -- cgit v1.2.3 From d1164979123a03c5f591dc04a4809adc695d0ae0 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 28 May 2020 01:08:37 -0400 Subject: Optimize number of created DbContexts and fix default values for some fields --- Jellyfin.Server.Implementations/Users/UserManager.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 5ad9e9863..60d78afd0 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -321,11 +321,11 @@ namespace Jellyfin.Server.Implementations.Users { MaxParentalRating = user.MaxParentalAgeRating, EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, - RemoteClientBitrateLimit = user.RemoteClientBitrateLimit.GetValueOrDefault(), + RemoteClientBitrateLimit = user.RemoteClientBitrateLimit ?? -1, AuthenticationProviderId = user.AuthenticationProviderId, PasswordResetProviderId = user.PasswordResetProviderId, InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, - LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout.GetValueOrDefault(), + LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout ?? -1, IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator), IsHidden = user.HasPermission(PermissionKind.IsHidden), IsDisabled = user.HasPermission(PermissionKind.IsDisabled), @@ -490,8 +490,6 @@ namespace Jellyfin.Server.Implementations.Users { var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); - var action = ForgotPasswordAction.InNetworkRequired; - if (user != null && isInNetwork) { var passwordResetProvider = GetPasswordResetProvider(user); @@ -500,7 +498,7 @@ namespace Jellyfin.Server.Implementations.Users return new ForgotPasswordResult { - Action = action, + Action = ForgotPasswordAction.InNetworkRequired, PinFile = string.Empty }; } @@ -569,7 +567,8 @@ namespace Jellyfin.Server.Implementations.Users /// public void UpdateConfiguration(Guid userId, UserConfiguration config) { - var user = GetUserById(userId); + var dbContext = _dbProvider.CreateContext(); + var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!"); user.SubtitleMode = config.SubtitleMode; user.HidePlayedInLatest = config.HidePlayedInLatest; user.EnableLocalPassword = config.EnableLocalPassword; @@ -587,13 +586,15 @@ namespace Jellyfin.Server.Implementations.Users user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); - UpdateUser(user); + dbContext.Update(user); + dbContext.SaveChanges(); } /// public void UpdatePolicy(Guid userId, UserPolicy policy) { - var user = GetUserById(userId); + var dbContext = _dbProvider.CreateContext(); + var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!"); int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch { -1 => null, @@ -642,6 +643,9 @@ namespace Jellyfin.Server.Implementations.Users user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); + + dbContext.Update(user); + dbContext.SaveChanges(); } private bool IsValidUsername(string name) -- cgit v1.2.3 From 8ca78f33e9769f823fe079e90b62b561646709d7 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 28 May 2020 14:21:26 -0400 Subject: Fix bug when migrating user db with users that have never logged in. --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- .../SyncPlay/SyncPlayManager.cs | 18 ++++++------------ Jellyfin.Data/Entities/User.cs | 4 ++-- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 4 ++-- 5 files changed, 12 insertions(+), 18 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 70b3bda6e..94423c287 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -282,7 +282,7 @@ namespace Emby.Server.Implementations.Session if (user != null) { - var userLastActivityDate = user.LastActivityDate; + var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue; user.LastActivityDate = activityDate; if ((activityDate - userLastActivityDate).TotalSeconds > 60) diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index f44b32c36..8885266d3 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -134,11 +134,8 @@ namespace Emby.Server.Implementations.SyncPlay var item = _libraryManager.GetItemById(itemId); // Check ParentalRating access - var hasParentalRatingAccess = true; - if (user.MaxParentalAgeRating.HasValue) - { - hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.MaxParentalAgeRating.Value; - } + var hasParentalRatingAccess = !user.MaxParentalAgeRating.HasValue + || item.InheritedParentalRatingValue <= user.MaxParentalAgeRating; if (!user.HasPermission(PermissionKind.EnableAllFolders) && hasParentalRatingAccess) { @@ -255,8 +252,7 @@ namespace Emby.Server.Implementations.SyncPlay // TODO: determine what happens to users that are in a group and get their permissions revoked lock (_groupsLock) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out var group); if (group == null) { @@ -329,8 +325,7 @@ namespace Emby.Server.Implementations.SyncPlay lock (_groupsLock) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out var group); if (group == null) { @@ -340,7 +335,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -367,8 +362,7 @@ namespace Emby.Server.Implementations.SyncPlay throw new InvalidOperationException("Session not in any group!"); } - ISyncPlayController tempGroup; - _sessionToGroupMap.Remove(session.Id, out tempGroup); + _sessionToGroupMap.Remove(session.Id, out var tempGroup); if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) { diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index cef2edfa9..1098cdb2f 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -181,12 +181,12 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets the last activity date. /// - public DateTime LastActivityDate { get; set; } + public DateTime? LastActivityDate { get; set; } /// /// Gets or sets the last login date. /// - public DateTime LastLoginDate { get; set; } + public DateTime? LastLoginDate { get; set; } /// /// Gets or sets the number of login attempts the user can make before they are locked out. diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 60d78afd0..3d473f5f2 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -218,7 +218,7 @@ namespace Jellyfin.Server.Implementations.Users var dbContext = _dbProvider.CreateContext(); - if (!dbContext.Users.Contains(user)) + if (dbContext.Users.Find(user.Id) == null) { throw new ArgumentException(string.Format( CultureInfo.InvariantCulture, diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 53c93f64b..2be10c708 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -196,9 +196,9 @@ namespace Jellyfin.Server.Migrations.Routines public string EasyPassword { get; set; } - public DateTime LastLoginDate { get; set; } + public DateTime? LastLoginDate { get; set; } - public DateTime LastActivityDate { get; set; } + public DateTime? LastActivityDate { get; set; } public string Name { get; set; } -- cgit v1.2.3 From 4857b7d62097848eda7f3666b60e039b1df5b6b1 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 29 May 2020 15:32:31 -0400 Subject: Make UserManager.IsValidUsername static --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 3d473f5f2..b62c64830 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -648,7 +648,7 @@ namespace Jellyfin.Server.Implementations.Users dbContext.SaveChanges(); } - private bool IsValidUsername(string 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 -- cgit v1.2.3 From 1b297eae7834e8604698ae780c4b2ced7d20897d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 30 May 2020 00:20:59 -0400 Subject: Reset invalid login attempt count properly --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index b62c64830..91d0e5b80 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -467,10 +467,10 @@ namespace Jellyfin.Server.Implementations.Users if (isUserSession) { user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - await UpdateUserAsync(user).ConfigureAwait(false); } user.InvalidLoginAttemptCount = 0; + await UpdateUserAsync(user).ConfigureAwait(false); _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username); } else -- cgit v1.2.3 From 98142613e8cd8266130847210fb6bfadc1ae11bd Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 7 Jun 2020 13:36:43 -0400 Subject: Apply review suggestions and fix bug --- Jellyfin.Data/Entities/User.cs | 5 +---- .../Users/UserManager.cs | 26 ++++------------------ MediaBrowser.Controller/Entities/Folder.cs | 7 +++--- 3 files changed, 9 insertions(+), 29 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index f09b36bde..cd6cad992 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -420,10 +420,7 @@ namespace Jellyfin.Data.Entities /// A string array containing the user's preferences. public string[] GetPreference(PreferenceKind preference) { - var val = Preferences - .Where(p => p.Kind == preference) - .Select(p => p.Value) - .First(); + var val = Preferences.First(p => p.Kind == preference).Value; return Equals(val, string.Empty) ? Array.Empty() : val.Split(Delimiter); } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 91d0e5b80..01151e65e 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -84,24 +84,10 @@ namespace Jellyfin.Server.Implementations.Users public event EventHandler> OnUserLockedOut; /// - public IEnumerable Users - { - get - { - var dbContext = _dbProvider.CreateContext(); - return dbContext.Users; - } - } + public IEnumerable Users => _dbProvider.CreateContext().Users; /// - public IEnumerable UsersIds - { - get - { - var dbContext = _dbProvider.CreateContext(); - return dbContext.Users.Select(u => u.Id); - } - } + public IEnumerable UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id); /// public User GetUserById(Guid id) @@ -111,9 +97,7 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Guid can't be empty", nameof(id)); } - var dbContext = _dbProvider.CreateContext(); - - return dbContext.Users.Find(id); + return _dbProvider.CreateContext().Users.Find(id); } /// @@ -124,11 +108,9 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Invalid username", nameof(name)); } - var dbContext = _dbProvider.CreateContext(); - // This can't use an overload with StringComparer because that would cause the query to // have to be evaluated client-side. - return dbContext.Users.FirstOrDefault(u => string.Equals(u.Username, name)); + return _dbProvider.CreateContext().Users.FirstOrDefault(u => string.Equals(u.Username, name)); } /// diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 2f9bc38df..4af74f9cd 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -182,12 +182,13 @@ namespace MediaBrowser.Controller.Entities { if (this is ICollectionFolder && !(this is BasePluginFolder)) { - if (user.GetPreference(PreferenceKind.BlockedMediaFolders) != null) + var blockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders); + if (blockedMediaFolders.Length > 0) { - if (user.GetPreference(PreferenceKind.BlockedMediaFolders).Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || + if (blockedMediaFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || // Backwards compatibility - user.GetPreference(PreferenceKind.BlockedMediaFolders).Contains(Name, StringComparer.OrdinalIgnoreCase)) + blockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) { return false; } -- cgit v1.2.3 From 75af0a4e57ac29fb8143717055c8df1fedeba6ec Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 7 Jun 2020 19:37:47 -0400 Subject: Implement more review suggestions --- Jellyfin.Server.Implementations/Users/UserManager.cs | 4 ++-- MediaBrowser.Api/UserService.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 01151e65e..d3cd29909 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -276,7 +276,7 @@ namespace Jellyfin.Server.Implementations.Users Name = user.Username, Id = user.Id, ServerId = _appHost.SystemId, - HasPassword = user.Password != null, + HasPassword = GetAuthenticationProvider(user).HasPassword(user), EnableAutoLogin = user.EnableAutoLogin, LastLoginDate = user.LastLoginDate, LastActivityDate = user.LastActivityDate, @@ -303,7 +303,7 @@ namespace Jellyfin.Server.Implementations.Users { MaxParentalRating = user.MaxParentalAgeRating, EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, - RemoteClientBitrateLimit = user.RemoteClientBitrateLimit ?? -1, + RemoteClientBitrateLimit = user.RemoteClientBitrateLimit ?? 0, AuthenticationProviderId = user.AuthenticationProviderId, PasswordResetProviderId = user.PasswordResetProviderId, InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 7bf4f88f4..9cb9baf63 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -566,7 +566,6 @@ namespace MediaBrowser.Api AssertCanUpdateUser(_authContext, _userManager, request.Id, false); _userManager.UpdateConfiguration(request.Id, request); - } public void Post(UpdateUserPolicy request) -- cgit v1.2.3 From 824cd87b75154176d6fd35f74f907f8f6ef264be Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 7 Jun 2020 20:16:51 -0400 Subject: Add missing property --- Jellyfin.Server.Implementations/Users/UserManager.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index d3cd29909..5ed3758fb 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -277,6 +277,7 @@ namespace Jellyfin.Server.Implementations.Users Id = user.Id, ServerId = _appHost.SystemId, HasPassword = GetAuthenticationProvider(user).HasPassword(user), + HasConfiguredEasyPassword = !string.IsNullOrEmpty(user.EasyPassword), EnableAutoLogin = user.EnableAutoLogin, LastLoginDate = user.LastLoginDate, LastActivityDate = user.LastActivityDate, -- cgit v1.2.3 From d85308b4747b62ec510760229c25f8b863ff7abc Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 7 Jun 2020 23:11:51 -0400 Subject: Add another missing property --- Jellyfin.Server.Implementations/Users/UserManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 5ed3758fb..2d077a6b2 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -271,12 +271,14 @@ namespace Jellyfin.Server.Implementations.Users /// public UserDto GetUserDto(User user, string remoteEndPoint = null) { + var hasPassword = GetAuthenticationProvider(user).HasPassword(user); return new UserDto { Name = user.Username, Id = user.Id, ServerId = _appHost.SystemId, - HasPassword = GetAuthenticationProvider(user).HasPassword(user), + HasPassword = hasPassword, + HasConfiguredPassword = hasPassword, HasConfiguredEasyPassword = !string.IsNullOrEmpty(user.EasyPassword), EnableAutoLogin = user.EnableAutoLogin, LastLoginDate = user.LastLoginDate, -- cgit v1.2.3 From ce737c31ec3673caed8673253bd7c5efe7bde4a8 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 9 Jun 2020 12:21:21 -0400 Subject: Enable nullable annotations --- .../Users/DefaultAuthenticationProvider.cs | 4 +- .../Users/DefaultPasswordResetProvider.cs | 3 ++ .../Users/DeviceAccessEntryPoint.cs | 5 ++- .../Users/InvalidAuthProvider.cs | 2 + .../Users/UserManager.cs | 45 +++++++++++----------- 5 files changed, 34 insertions(+), 25 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index b0c02030e..162dc6f5e 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Linq; using System.Text; @@ -129,7 +131,7 @@ namespace Jellyfin.Server.Implementations.Users } /// - public string GetEasyPasswordHash(User user) + public string? GetEasyPasswordHash(User user) { return string.IsNullOrEmpty(user.EasyPassword) ? null diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 36c95586a..cf5a01f08 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Collections.Generic; using System.IO; @@ -128,6 +130,7 @@ namespace Jellyfin.Server.Implementations.Users }; } +#nullable disable private class SerializablePasswordReset : PasswordPinCreationResult { public string Pin { get; set; } diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs index d94a27b9d..140853e52 100644 --- a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs @@ -1,4 +1,5 @@ -#pragma warning disable CS1591 +#nullable enable +#pragma warning disable CS1591 using System.Threading.Tasks; using Jellyfin.Data.Entities; @@ -38,7 +39,7 @@ namespace Jellyfin.Server.Implementations.Users { } - private void OnUserUpdated(object sender, GenericEventArgs e) + private void OnUserUpdated(object? sender, GenericEventArgs e) { var user = e.Argument; if (!user.HasPermission(PermissionKind.EnableAllDevices)) diff --git a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs index b6e65b559..491aba1d4 100644 --- a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs +++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs @@ -1,3 +1,5 @@ +#nullable enable + using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Authentication; diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 2d077a6b2..e1084627b 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -1,4 +1,5 @@ -#pragma warning disable CA1307 +#nullable enable +#pragma warning disable CA1307 using System; using System.Collections.Generic; @@ -37,11 +38,11 @@ namespace Jellyfin.Server.Implementations.Users private readonly IImageProcessor _imageProcessor; private readonly ILogger _logger; - private IAuthenticationProvider[] _authenticationProviders; - private DefaultAuthenticationProvider _defaultAuthenticationProvider; - private InvalidAuthProvider _invalidAuthProvider; - private IPasswordResetProvider[] _passwordResetProviders; - private DefaultPasswordResetProvider _defaultPasswordResetProvider; + private IAuthenticationProvider[] _authenticationProviders = null!; + private DefaultAuthenticationProvider _defaultAuthenticationProvider = null!; + private InvalidAuthProvider _invalidAuthProvider = null!; + private IPasswordResetProvider[] _passwordResetProviders = null!; + private DefaultPasswordResetProvider _defaultPasswordResetProvider = null!; /// /// Initializes a new instance of the class. @@ -69,19 +70,19 @@ namespace Jellyfin.Server.Implementations.Users } /// - public event EventHandler> OnUserPasswordChanged; + public event EventHandler>? OnUserPasswordChanged; /// - public event EventHandler> OnUserUpdated; + public event EventHandler>? OnUserUpdated; /// - public event EventHandler> OnUserCreated; + public event EventHandler>? OnUserCreated; /// - public event EventHandler> OnUserDeleted; + public event EventHandler>? OnUserDeleted; /// - public event EventHandler> OnUserLockedOut; + public event EventHandler>? OnUserLockedOut; /// public IEnumerable Users => _dbProvider.CreateContext().Users; @@ -90,7 +91,7 @@ namespace Jellyfin.Server.Implementations.Users public IEnumerable UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id); /// - public User GetUserById(Guid id) + public User? GetUserById(Guid id) { if (id == Guid.Empty) { @@ -101,7 +102,7 @@ namespace Jellyfin.Server.Implementations.Users } /// - public User GetUserByName(string name) + public User? GetUserByName(string name) { if (string.IsNullOrWhiteSpace(name)) { @@ -260,7 +261,7 @@ namespace Jellyfin.Server.Implementations.Users } /// - public void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1) + public void ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1) { GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1); UpdateUser(user); @@ -269,7 +270,7 @@ namespace Jellyfin.Server.Implementations.Users } /// - public UserDto GetUserDto(User user, string remoteEndPoint = null) + public UserDto GetUserDto(User user, string? remoteEndPoint = null) { var hasPassword = GetAuthenticationProvider(user).HasPassword(user); return new UserDto @@ -344,7 +345,7 @@ namespace Jellyfin.Server.Implementations.Users } /// - public async Task AuthenticateUser( + public async Task AuthenticateUser( string username, string password, string passwordSha1, @@ -359,7 +360,7 @@ namespace Jellyfin.Server.Implementations.Users var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); bool success; - IAuthenticationProvider authenticationProvider; + IAuthenticationProvider? authenticationProvider; if (user != null) { @@ -651,7 +652,7 @@ namespace Jellyfin.Server.Implementations.Users return GetPasswordResetProviders(user)[0]; } - private IList GetAuthenticationProviders(User user) + private IList GetAuthenticationProviders(User? user) { var authenticationProviderId = user?.AuthenticationProviderId; @@ -701,14 +702,14 @@ 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, + User? user, string remoteEndPoint) { bool success = false; - IAuthenticationProvider authenticationProvider = null; + IAuthenticationProvider? authenticationProvider = null; foreach (var provider in GetAuthenticationProviders(user)) { @@ -746,7 +747,7 @@ namespace Jellyfin.Server.Implementations.Users IAuthenticationProvider provider, string username, string password, - User resolvedUser) + User? resolvedUser) { try { -- cgit v1.2.3 From d105bc728d0ca44b601b311eca493c1171fa71fa Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 9 Jun 2020 14:01:21 -0400 Subject: Fix startup wizard. --- Jellyfin.Api/Controllers/StartupController.cs | 4 ++- .../Users/UserManager.cs | 33 ++++++++++++++++++++++ MediaBrowser.Controller/Library/IUserManager.cs | 5 ++++ 3 files changed, 41 insertions(+), 1 deletion(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index f965d83f3..6ec0a4e26 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -95,6 +95,8 @@ namespace Jellyfin.Api.Controllers [HttpGet("User")] public StartupUserDto GetFirstUser() { + // TODO: Remove this method when startup wizard no longer requires an existing user. + _userManager.Initialize(); var user = _userManager.Users.First(); return new StartupUserDto { @@ -115,7 +117,7 @@ namespace Jellyfin.Api.Controllers user.Username = startupUserDto.Name; - _userManager.UpdateUser(user); + await _userManager.UpdateUserAsync(user).ConfigureAwait(false); if (!string.IsNullOrEmpty(startupUserDto.Password)) { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index e1084627b..0ea13f4e7 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -520,6 +520,39 @@ namespace Jellyfin.Server.Implementations.Users _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); } + /// + public void Initialize() + { + // TODO: Refactor the startup wizard so that it doesn't require a user to already exist. + var dbContext = _dbProvider.CreateContext(); + + if (dbContext.Users.Any()) + { + return; + } + + var defaultName = Environment.UserName; + if (string.IsNullOrWhiteSpace(defaultName)) + { + defaultName = "MyJellyfinUser"; + } + + _logger.LogWarning("No users, creating one with username {UserName}", defaultName); + + if (!IsValidUsername(defaultName)) + { + throw new ArgumentException("Provided username is not valid!", defaultName); + } + + var newUser = CreateUser(defaultName); + newUser.SetPermission(PermissionKind.IsAdministrator, true); + newUser.SetPermission(PermissionKind.EnableContentDeletion, true); + newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true); + + dbContext.Users.Add(newUser); + dbContext.SaveChanges(); + } + /// public NameIdPair[] GetAuthenticationProviders() { diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 1e385dcb9..74f117f15 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -52,6 +52,11 @@ namespace MediaBrowser.Controller.Library /// The users ids. IEnumerable UsersIds { get; } + /// + /// Initializes the user manager and ensures that a user exists. + /// + void Initialize(); + /// /// Gets a user by Id. /// -- cgit v1.2.3 From 4d559b4ec45c66554230d402c5dcc3f616576b0e Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 10 Jun 2020 20:14:24 -0400 Subject: Fix bugs for fresh installs. --- Jellyfin.Server.Implementations/Users/UserManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 0ea13f4e7..d05fc2549 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -22,6 +22,7 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Implementations.Users @@ -174,7 +175,7 @@ namespace Jellyfin.Server.Implementations.Users var dbContext = _dbProvider.CreateContext(); // TODO: Remove after user item data is migrated. - var max = dbContext.Users.Select(u => u.InternalId).Max(); + var max = dbContext.Users.Any() ? dbContext.Users.Select(u => u.InternalId).Max() : 0; var newUser = new User( name, @@ -549,7 +550,7 @@ namespace Jellyfin.Server.Implementations.Users newUser.SetPermission(PermissionKind.EnableContentDeletion, true); newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true); - dbContext.Users.Add(newUser); + dbContext.Users.Update(newUser); dbContext.SaveChanges(); } -- cgit v1.2.3 From 7fba0b778ed7d1d8f8c5ff4e60945872319722c5 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 11 Jun 2020 17:51:02 -0400 Subject: Properly remove profile images --- Jellyfin.Server.Implementations/Users/UserManager.cs | 11 ++++++++++- MediaBrowser.Api/Images/ImageService.cs | 2 +- MediaBrowser.Controller/Library/IUserManager.cs | 10 ++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index d05fc2549..68f038ae8 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -22,7 +22,6 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Implementations.Users @@ -668,6 +667,16 @@ namespace Jellyfin.Server.Implementations.Users dbContext.SaveChanges(); } + public void ClearProfileImage(User user) + { +#nullable disable + // TODO: Remove these when User has nullable annotations + + // Can't just set the value to null, as it hasn't been loaded yet, so EF Core wouldn't see the change + _dbProvider.CreateContext().Entry(user).Reference(u => u.ProfileImage).CurrentValue = null; +#nullable enable + } + private static bool IsValidUsername(string name) { // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index d284dd55c..fa73dca63 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -489,7 +489,7 @@ namespace MediaBrowser.Api.Images Logger.LogError(e, "Error deleting user profile image:"); } - user.ProfileImage = null; + _userManager.ClearProfileImage(user); _userManager.UpdateUser(user); } diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 74f117f15..b5b2e4729 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -175,7 +175,7 @@ namespace MediaBrowser.Controller.Library /// /// This method updates the user's configuration. /// This is only included as a stopgap until the new API, using this internally is not recommended. - /// Instead, modify the user object directlu, then call . + /// Instead, modify the user object directly, then call . /// /// The user's Id. /// The request containing the new user configuration. @@ -184,10 +184,16 @@ namespace MediaBrowser.Controller.Library /// /// This method updates the user's policy. /// This is only included as a stopgap until the new API, using this internally is not recommended. - /// Instead, modify the user object directlu, then call . + /// Instead, modify the user object directly, then call . /// /// The user's Id. /// The request containing the new user policy. void UpdatePolicy(Guid userId, UserPolicy policy); + + /// + /// Clears the user's profile image. + /// + /// The user. + void ClearProfileImage(User user); } } -- cgit v1.2.3 From fc02157b42fe5e4f04681cb82ace9865aaa550cc Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 11 Jun 2020 18:28:49 -0400 Subject: Fix build errors --- Emby.Server.Implementations/Library/UserDataManager.cs | 1 + Jellyfin.Server.Implementations/Users/UserManager.cs | 1 + MediaBrowser.Api/Library/LibraryService.cs | 1 + MediaBrowser.Api/Movies/MoviesService.cs | 1 + MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs | 1 + MediaBrowser.Controller/Entities/Audio/MusicArtist.cs | 1 + MediaBrowser.Controller/Entities/TV/Series.cs | 1 + MediaBrowser.Controller/Entities/UserViewBuilder.cs | 1 + 8 files changed, 8 insertions(+) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index e56ef2328..803fe34fb 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -14,6 +14,7 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using Microsoft.Extensions.Logging; +using Book = MediaBrowser.Controller.Entities.Book; namespace Emby.Server.Implementations.Library { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 68f038ae8..169f89467 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -667,6 +667,7 @@ namespace Jellyfin.Server.Implementations.Users dbContext.SaveChanges(); } + /// public void ClearProfileImage(User user) { #nullable disable diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 99a457cee..2b4694925 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -29,6 +29,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using Book = MediaBrowser.Controller.Entities.Book; using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; using Movie = MediaBrowser.Controller.Entities.Movies.Movie; using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; using Series = MediaBrowser.Controller.Entities.TV.Series; diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index c5c996a67..2d61299c7 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -15,6 +15,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; using Movie = MediaBrowser.Controller.Entities.Movies.Movie; namespace MediaBrowser.Api.Movies diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 6c33831ba..f7b2f9549 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; namespace MediaBrowser.Controller.Entities.Audio { diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index db1edfe27..63db3cfab 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using Microsoft.Extensions.Logging; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; namespace MediaBrowser.Controller.Entities.Audio { diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 10a8b3370..a519089b3 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; namespace MediaBrowser.Controller.Entities.TV { diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index d5896eae4..52fbdd825 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -11,6 +11,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; using Movie = MediaBrowser.Controller.Entities.Movies.Movie; using Season = MediaBrowser.Controller.Entities.TV.Season; using Series = MediaBrowser.Controller.Entities.TV.Series; -- cgit v1.2.3 From 103c9b716286204011606a22156c01250d69a0fd Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jun 2020 22:47:09 -0400 Subject: Actually fix deleting profile images (hopefully) --- Jellyfin.Server.Implementations/JellyfinDb.cs | 4 ++++ Jellyfin.Server.Implementations/Users/UserManager.cs | 9 +++------ 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index d3c0267e8..f574ebc66 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -23,8 +23,12 @@ namespace Jellyfin.Server.Implementations /// public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; + public virtual DbSet AccessSchedules { get; set; } + public virtual DbSet ActivityLogs { get; set; } + public virtual DbSet ImageInfos { get; set; } + public virtual DbSet Permissions { get; set; } public virtual DbSet Preferences { get; set; } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 169f89467..bab6cf3fc 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -670,12 +670,9 @@ namespace Jellyfin.Server.Implementations.Users /// public void ClearProfileImage(User user) { -#nullable disable - // TODO: Remove these when User has nullable annotations - - // Can't just set the value to null, as it hasn't been loaded yet, so EF Core wouldn't see the change - _dbProvider.CreateContext().Entry(user).Reference(u => u.ProfileImage).CurrentValue = null; -#nullable enable + var dbContext = _dbProvider.CreateContext(); + dbContext.ImageInfos.Remove(user.ProfileImage); + dbContext.SaveChanges(); } private static bool IsValidUsername(string name) -- cgit v1.2.3 From 32780154440d5e7d26d3cb4b32a43d1d8d2735f3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 13 Jun 2020 16:38:17 -0400 Subject: Actually fix profile images --- Jellyfin.Data/Entities/AccessSchedule.cs | 1 - Jellyfin.Data/Entities/ImageInfo.cs | 2 + Jellyfin.Data/Entities/User.cs | 1 + .../Migrations/20200613155524_AddUsers.Designer.cs | 312 --------------------- .../Migrations/20200613155524_AddUsers.cs | 196 ------------- .../Migrations/20200613202153_AddUsers.Designer.cs | 312 +++++++++++++++++++++ .../Migrations/20200613202153_AddUsers.cs | 197 +++++++++++++ .../Migrations/JellyfinDbModelSnapshot.cs | 25 +- .../Users/UserManager.cs | 2 +- 9 files changed, 526 insertions(+), 522 deletions(-) delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200613155524_AddUsers.Designer.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200613155524_AddUsers.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs index 15c4e4cde..7d1b76a3f 100644 --- a/Jellyfin.Data/Entities/AccessSchedule.cs +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -52,7 +52,6 @@ namespace Jellyfin.Data.Entities /// [XmlIgnore] [Required] - [ForeignKey("Id")] public Guid UserId { get; protected set; } /// diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs index 8bbce95e4..64e36a791 100644 --- a/Jellyfin.Data/Entities/ImageInfo.cs +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -17,6 +17,8 @@ namespace Jellyfin.Data.Entities [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } + public Guid? UserId { get; protected set; } + [Required] [MaxLength(512)] [StringLength(512)] diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 7d99be542..b89b0a8f4 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -324,6 +324,7 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets the user's profile image. Can be null. /// + // [ForeignKey("UserId")] public virtual ImageInfo ProfileImage { get; set; } [Required] diff --git a/Jellyfin.Server.Implementations/Migrations/20200613155524_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200613155524_AddUsers.Designer.cs deleted file mode 100644 index c43b035d9..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200613155524_AddUsers.Designer.cs +++ /dev/null @@ -1,312 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1601 - -// -using System; -using Jellyfin.Server.Implementations; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Jellyfin.Server.Implementations.Migrations -{ - [DbContext(typeof(JellyfinDb))] - [Migration("20200613155524_AddUsers")] - partial class AddUsers - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "3.1.4"); - - modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DayOfWeek") - .HasColumnType("INTEGER"); - - b.Property("EndHour") - .HasColumnType("REAL"); - - b.Property("StartHour") - .HasColumnType("REAL"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AccessSchedules"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateCreated") - .HasColumnType("TEXT"); - - b.Property("ItemId") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("LogSeverity") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(512); - - b.Property("Overview") - .HasColumnType("TEXT") - .HasMaxLength(512); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("ShortOverview") - .HasColumnType("TEXT") - .HasMaxLength(512); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ActivityLogs"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("LastModified") - .HasColumnType("TEXT"); - - b.Property("Path") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(512); - - b.HasKey("Id"); - - b.ToTable("ImageInfos"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Kind") - .HasColumnType("INTEGER"); - - b.Property("Permission_Permissions_Guid") - .HasColumnType("TEXT"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Value") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Permission_Permissions_Guid"); - - b.ToTable("Permissions"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Kind") - .HasColumnType("INTEGER"); - - b.Property("Preference_Preferences_Guid") - .HasColumnType("TEXT"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.HasKey("Id"); - - b.HasIndex("Preference_Preferences_Guid"); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("AudioLanguagePreference") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("AuthenticationProviderId") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("DisplayCollectionsView") - .HasColumnType("INTEGER"); - - b.Property("DisplayMissingEpisodes") - .HasColumnType("INTEGER"); - - b.Property("EasyPassword") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("EnableAutoLogin") - .HasColumnType("INTEGER"); - - b.Property("EnableLocalPassword") - .HasColumnType("INTEGER"); - - b.Property("EnableNextEpisodeAutoPlay") - .HasColumnType("INTEGER"); - - b.Property("EnableUserPreferenceAccess") - .HasColumnType("INTEGER"); - - b.Property("HidePlayedInLatest") - .HasColumnType("INTEGER"); - - b.Property("InternalId") - .HasColumnType("INTEGER"); - - b.Property("InvalidLoginAttemptCount") - .HasColumnType("INTEGER"); - - b.Property("LastActivityDate") - .HasColumnType("TEXT"); - - b.Property("LastLoginDate") - .HasColumnType("TEXT"); - - b.Property("LoginAttemptsBeforeLockout") - .HasColumnType("INTEGER"); - - b.Property("MaxParentalAgeRating") - .HasColumnType("INTEGER"); - - b.Property("MustUpdatePassword") - .HasColumnType("INTEGER"); - - b.Property("Password") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("PasswordResetProviderId") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("PlayDefaultAudioTrack") - .HasColumnType("INTEGER"); - - b.Property("ProfileImageId") - .HasColumnType("INTEGER"); - - b.Property("RememberAudioSelections") - .HasColumnType("INTEGER"); - - b.Property("RememberSubtitleSelections") - .HasColumnType("INTEGER"); - - b.Property("RemoteClientBitrateLimit") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("SubtitleLanguagePreference") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("SubtitleMode") - .HasColumnType("INTEGER"); - - b.Property("SyncPlayAccess") - .HasColumnType("INTEGER"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.HasKey("Id"); - - b.HasIndex("ProfileImageId"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => - { - b.HasOne("Jellyfin.Data.Entities.User", null) - .WithMany("AccessSchedules") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => - { - b.HasOne("Jellyfin.Data.Entities.User", null) - .WithMany("Permissions") - .HasForeignKey("Permission_Permissions_Guid"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => - { - b.HasOne("Jellyfin.Data.Entities.User", null) - .WithMany("Preferences") - .HasForeignKey("Preference_Preferences_Guid"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.User", b => - { - b.HasOne("Jellyfin.Data.Entities.ImageInfo", "ProfileImage") - .WithMany() - .HasForeignKey("ProfileImageId"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20200613155524_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200613155524_AddUsers.cs deleted file mode 100644 index 369458b6c..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200613155524_AddUsers.cs +++ /dev/null @@ -1,196 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1601 - -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Jellyfin.Server.Implementations.Migrations -{ - public partial class AddUsers : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "ImageInfos", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Path = table.Column(maxLength: 512, nullable: false), - LastModified = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ImageInfos", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Users", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false), - Username = table.Column(maxLength: 255, nullable: false), - Password = table.Column(maxLength: 65535, nullable: true), - EasyPassword = table.Column(maxLength: 65535, nullable: true), - MustUpdatePassword = table.Column(nullable: false), - AudioLanguagePreference = table.Column(maxLength: 255, nullable: true), - AuthenticationProviderId = table.Column(maxLength: 255, nullable: false), - PasswordResetProviderId = table.Column(maxLength: 255, nullable: false), - InvalidLoginAttemptCount = table.Column(nullable: false), - LastActivityDate = table.Column(nullable: true), - LastLoginDate = table.Column(nullable: true), - LoginAttemptsBeforeLockout = table.Column(nullable: true), - SubtitleMode = table.Column(nullable: false), - PlayDefaultAudioTrack = table.Column(nullable: false), - SubtitleLanguagePreference = table.Column(maxLength: 255, nullable: true), - DisplayMissingEpisodes = table.Column(nullable: false), - DisplayCollectionsView = table.Column(nullable: false), - EnableLocalPassword = table.Column(nullable: false), - HidePlayedInLatest = table.Column(nullable: false), - RememberAudioSelections = table.Column(nullable: false), - RememberSubtitleSelections = table.Column(nullable: false), - EnableNextEpisodeAutoPlay = table.Column(nullable: false), - EnableAutoLogin = table.Column(nullable: false), - EnableUserPreferenceAccess = table.Column(nullable: false), - MaxParentalAgeRating = table.Column(nullable: true), - RemoteClientBitrateLimit = table.Column(nullable: true), - InternalId = table.Column(nullable: false), - ProfileImageId = table.Column(nullable: true), - SyncPlayAccess = table.Column(nullable: false), - RowVersion = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - table.ForeignKey( - name: "FK_Users_ImageInfos_ProfileImageId", - column: x => x.ProfileImageId, - principalSchema: "jellyfin", - principalTable: "ImageInfos", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "AccessSchedules", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - UserId = table.Column(nullable: false), - DayOfWeek = table.Column(nullable: false), - StartHour = table.Column(nullable: false), - EndHour = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AccessSchedules", x => x.Id); - table.ForeignKey( - name: "FK_AccessSchedules_Users_UserId", - column: x => x.UserId, - principalSchema: "jellyfin", - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Permissions", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Kind = table.Column(nullable: false), - Value = table.Column(nullable: false), - RowVersion = table.Column(nullable: false), - Permission_Permissions_Guid = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Permissions", x => x.Id); - table.ForeignKey( - name: "FK_Permissions_Users_Permission_Permissions_Guid", - column: x => x.Permission_Permissions_Guid, - principalSchema: "jellyfin", - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Preferences", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Kind = table.Column(nullable: false), - Value = table.Column(maxLength: 65535, nullable: false), - RowVersion = table.Column(nullable: false), - Preference_Preferences_Guid = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Preferences", x => x.Id); - table.ForeignKey( - name: "FK_Preferences_Users_Preference_Preferences_Guid", - column: x => x.Preference_Preferences_Guid, - principalSchema: "jellyfin", - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_AccessSchedules_UserId", - schema: "jellyfin", - table: "AccessSchedules", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_Permissions_Permission_Permissions_Guid", - schema: "jellyfin", - table: "Permissions", - column: "Permission_Permissions_Guid"); - - migrationBuilder.CreateIndex( - name: "IX_Preferences_Preference_Preferences_Guid", - schema: "jellyfin", - table: "Preferences", - column: "Preference_Preferences_Guid"); - - migrationBuilder.CreateIndex( - name: "IX_Users_ProfileImageId", - schema: "jellyfin", - table: "Users", - column: "ProfileImageId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AccessSchedules", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Permissions", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Preferences", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Users", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "ImageInfos", - schema: "jellyfin"); - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs new file mode 100644 index 000000000..6342ce9cf --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs @@ -0,0 +1,312 @@ +#pragma warning disable CS1591 + +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20200613202153_AddUsers")] + partial class AddUsers + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "3.1.4"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Overview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Permission_Permissions_Guid"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.HasKey("Id"); + + b.HasIndex("Preference_Preferences_Guid"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("Permission_Permissions_Guid"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("Preference_Preferences_Guid"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs new file mode 100644 index 000000000..7e5a76850 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs @@ -0,0 +1,197 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class AddUsers : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false), + Username = table.Column(maxLength: 255, nullable: false), + Password = table.Column(maxLength: 65535, nullable: true), + EasyPassword = table.Column(maxLength: 65535, nullable: true), + MustUpdatePassword = table.Column(nullable: false), + AudioLanguagePreference = table.Column(maxLength: 255, nullable: true), + AuthenticationProviderId = table.Column(maxLength: 255, nullable: false), + PasswordResetProviderId = table.Column(maxLength: 255, nullable: false), + InvalidLoginAttemptCount = table.Column(nullable: false), + LastActivityDate = table.Column(nullable: true), + LastLoginDate = table.Column(nullable: true), + LoginAttemptsBeforeLockout = table.Column(nullable: true), + SubtitleMode = table.Column(nullable: false), + PlayDefaultAudioTrack = table.Column(nullable: false), + SubtitleLanguagePreference = table.Column(maxLength: 255, nullable: true), + DisplayMissingEpisodes = table.Column(nullable: false), + DisplayCollectionsView = table.Column(nullable: false), + EnableLocalPassword = table.Column(nullable: false), + HidePlayedInLatest = table.Column(nullable: false), + RememberAudioSelections = table.Column(nullable: false), + RememberSubtitleSelections = table.Column(nullable: false), + EnableNextEpisodeAutoPlay = table.Column(nullable: false), + EnableAutoLogin = table.Column(nullable: false), + EnableUserPreferenceAccess = table.Column(nullable: false), + MaxParentalAgeRating = table.Column(nullable: true), + RemoteClientBitrateLimit = table.Column(nullable: true), + InternalId = table.Column(nullable: false), + SyncPlayAccess = table.Column(nullable: false), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AccessSchedules", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: false), + DayOfWeek = table.Column(nullable: false), + StartHour = table.Column(nullable: false), + EndHour = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AccessSchedules", x => x.Id); + table.ForeignKey( + name: "FK_AccessSchedules_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ImageInfos", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: true), + Path = table.Column(maxLength: 512, nullable: false), + LastModified = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ImageInfos", x => x.Id); + table.ForeignKey( + name: "FK_ImageInfos_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Permissions", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Kind = table.Column(nullable: false), + Value = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + Permission_Permissions_Guid = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Permissions", x => x.Id); + table.ForeignKey( + name: "FK_Permissions_Users_Permission_Permissions_Guid", + column: x => x.Permission_Permissions_Guid, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Preferences", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Kind = table.Column(nullable: false), + Value = table.Column(maxLength: 65535, nullable: false), + RowVersion = table.Column(nullable: false), + Preference_Preferences_Guid = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Preferences", x => x.Id); + table.ForeignKey( + name: "FK_Preferences_Users_Preference_Preferences_Guid", + column: x => x.Preference_Preferences_Guid, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_AccessSchedules_UserId", + schema: "jellyfin", + table: "AccessSchedules", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_ImageInfos_UserId", + schema: "jellyfin", + table: "ImageInfos", + column: "UserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions", + column: "Permission_Permissions_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Preferences_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences", + column: "Preference_Preferences_Guid"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AccessSchedules", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "ImageInfos", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Permissions", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Preferences", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Users", + schema: "jellyfin"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 9548c38a3..51fad8224 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -102,8 +102,14 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT") .HasMaxLength(512); + b.Property("UserId") + .HasColumnType("TEXT"); + b.HasKey("Id"); + b.HasIndex("UserId") + .IsUnique(); + b.ToTable("ImageInfos"); }); @@ -234,9 +240,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("PlayDefaultAudioTrack") .HasColumnType("INTEGER"); - b.Property("ProfileImageId") - .HasColumnType("INTEGER"); - b.Property("RememberAudioSelections") .HasColumnType("INTEGER"); @@ -267,8 +270,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("ProfileImageId"); - b.ToTable("Users"); }); @@ -281,6 +282,13 @@ namespace Jellyfin.Server.Implementations.Migrations .IsRequired(); }); + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => { b.HasOne("Jellyfin.Data.Entities.User", null) @@ -294,13 +302,6 @@ namespace Jellyfin.Server.Implementations.Migrations .WithMany("Preferences") .HasForeignKey("Preference_Preferences_Guid"); }); - - modelBuilder.Entity("Jellyfin.Data.Entities.User", b => - { - b.HasOne("Jellyfin.Data.Entities.ImageInfo", "ProfileImage") - .WithMany() - .HasForeignKey("ProfileImageId"); - }); #pragma warning restore 612, 618 } } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index bab6cf3fc..63b8e1d94 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -671,7 +671,7 @@ namespace Jellyfin.Server.Implementations.Users public void ClearProfileImage(User user) { var dbContext = _dbProvider.CreateContext(); - dbContext.ImageInfos.Remove(user.ProfileImage); + dbContext.Remove(user.ProfileImage); dbContext.SaveChanges(); } -- cgit v1.2.3 From 8405ae9b1f2dd0414737c9db9d5d709890fec30c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 13 Jun 2020 18:23:13 -0400 Subject: Actually set `BlockUnratedItems` --- Jellyfin.Server.Implementations/Users/UserManager.cs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 63b8e1d94..123bf2740 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -657,6 +657,10 @@ namespace Jellyfin.Server.Implementations.Users user.AccessSchedules.Add(policyAccessSchedule); } + // TODO: fix this at some point + user.SetPreference( + PreferenceKind.BlockUnratedItems, + policy.BlockUnratedItems?.Select(i => i.ToString()).ToArray() ?? Array.Empty()); user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); -- cgit v1.2.3 From 6f325fea611160061e47e7108d1645514d7b0305 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 13 Jun 2020 18:26:46 -0400 Subject: Add missing properties to UserDto --- Jellyfin.Server.Implementations/Users/UserManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 123bf2740..49f6dcdfd 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -20,6 +20,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; @@ -339,7 +340,10 @@ namespace Jellyfin.Server.Implementations.Users EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders), - SyncPlayAccess = user.SyncPlayAccess + SyncPlayAccess = user.SyncPlayAccess, + BlockedChannels = user.GetPreference(PreferenceKind.BlockedChannels), + BlockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders), + BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems).Select(Enum.Parse).ToArray() } }; } -- cgit v1.2.3 From e8f8307521232e3e3c5cbcf747d1b5aba0c50919 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 13 Jun 2020 19:30:45 -0400 Subject: Add comment --- Jellyfin.Server.Implementations/Users/UserManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Jellyfin.Server.Implementations/Users/UserManager.cs') diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 49f6dcdfd..904114758 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -20,7 +20,6 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; @@ -618,6 +617,8 @@ namespace Jellyfin.Server.Implementations.Users { var dbContext = _dbProvider.CreateContext(); var user = dbContext.Users.Find(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, -- cgit v1.2.3