diff options
Diffstat (limited to 'Jellyfin.Server.Implementations')
| -rw-r--r-- | Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj | 4 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/JellyfinDb.cs | 45 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs (renamed from Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.Designer.cs) | 32 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs (renamed from Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.cs) | 79 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs | 29 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs | 63 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs | 3 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs | 5 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs | 2 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/UserManager.cs | 106 |
10 files changed, 253 insertions, 115 deletions
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 7e61c9931..dcac1b34b 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -24,11 +24,11 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4"> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.5"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.4"> + <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.5"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index d3c0267e8..53120a763 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -23,57 +23,100 @@ namespace Jellyfin.Server.Implementations /// </summary> public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; + public virtual DbSet<AccessSchedule> AccessSchedules { get; set; } + public virtual DbSet<ActivityLog> ActivityLogs { get; set; } + public virtual DbSet<ImageInfo> ImageInfos { get; set; } + public virtual DbSet<Permission> Permissions { get; set; } public virtual DbSet<Preference> Preferences { get; set; } public virtual DbSet<User> Users { get; set; } + /*public virtual DbSet<Artwork> Artwork { get; set; } + public virtual DbSet<Book> Books { get; set; } + public virtual DbSet<BookMetadata> BookMetadata { get; set; } + public virtual DbSet<Chapter> Chapters { get; set; } + public virtual DbSet<Collection> Collections { get; set; } + public virtual DbSet<CollectionItem> CollectionItems { get; set; } + public virtual DbSet<Company> Companies { get; set; } + public virtual DbSet<CompanyMetadata> CompanyMetadata { get; set; } + public virtual DbSet<CustomItem> CustomItems { get; set; } + public virtual DbSet<CustomItemMetadata> CustomItemMetadata { get; set; } + public virtual DbSet<Episode> Episodes { get; set; } + public virtual DbSet<EpisodeMetadata> EpisodeMetadata { get; set; } + public virtual DbSet<Genre> Genres { get; set; } + public virtual DbSet<Group> Groups { get; set; } + public virtual DbSet<Library> Libraries { get; set; } + public virtual DbSet<LibraryItem> LibraryItems { get; set; } + public virtual DbSet<LibraryRoot> LibraryRoot { get; set; } + public virtual DbSet<MediaFile> MediaFiles { get; set; } + public virtual DbSet<MediaFileStream> MediaFileStream { get; set; } + public virtual DbSet<Metadata> Metadata { get; set; } + public virtual DbSet<MetadataProvider> MetadataProviders { get; set; } + public virtual DbSet<MetadataProviderId> MetadataProviderIds { get; set; } + public virtual DbSet<Movie> Movies { get; set; } + public virtual DbSet<MovieMetadata> MovieMetadata { get; set; } + public virtual DbSet<MusicAlbum> MusicAlbums { get; set; } + public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; } + public virtual DbSet<Person> People { get; set; } + public virtual DbSet<PersonRole> PersonRoles { get; set; } + public virtual DbSet<Photo> Photo { get; set; } + public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; } + public virtual DbSet<ProviderMapping> ProviderMappings { get; set; } + public virtual DbSet<Rating> Ratings { get; set; } /// <summary> /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to - /// store review ratings, not age ratings + /// store review ratings, not age ratings. /// </summary> public virtual DbSet<RatingSource> RatingSources { get; set; } + public virtual DbSet<Release> Releases { get; set; } + public virtual DbSet<Season> Seasons { get; set; } + public virtual DbSet<SeasonMetadata> SeasonMetadata { get; set; } + public virtual DbSet<Series> Series { get; set; } + public virtual DbSet<SeriesMetadata> SeriesMetadata { get; set; } + public virtual DbSet<Track> Tracks { get; set; } + public virtual DbSet<TrackMetadata> TrackMetadata { get; set; }*/ /// <inheritdoc/> diff --git a/Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs index 04fb9132d..6342ce9cf 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1601 // <auto-generated /> using System; @@ -12,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { [DbContext(typeof(JellyfinDb))] - [Migration("20200531020729_AddUsers")] + [Migration("20200613202153_AddUsers")] partial class AddUsers { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -44,7 +43,7 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasIndex("UserId"); - b.ToTable("AccessSchedule"); + b.ToTable("AccessSchedules"); }); modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => @@ -107,9 +106,15 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT") .HasMaxLength(512); + b.Property<Guid?>("UserId") + .HasColumnType("TEXT"); + b.HasKey("Id"); - b.ToTable("ImageInfo"); + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => @@ -239,9 +244,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property<bool>("PlayDefaultAudioTrack") .HasColumnType("INTEGER"); - b.Property<int?>("ProfileImageId") - .HasColumnType("INTEGER"); - b.Property<bool>("RememberAudioSelections") .HasColumnType("INTEGER"); @@ -272,8 +274,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("ProfileImageId"); - b.ToTable("Users"); }); @@ -286,6 +286,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) @@ -299,13 +306,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/Migrations/20200531020729_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs index ec6b374ec..7e5a76850 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs @@ -11,21 +11,6 @@ namespace Jellyfin.Server.Implementations.Migrations protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "ImageInfo", - schema: "jellyfin", - columns: table => new - { - Id = table.Column<int>(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Path = table.Column<string>(maxLength: 512, nullable: false), - LastModified = table.Column<DateTime>(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ImageInfo", x => x.Id); - }); - - migrationBuilder.CreateTable( name: "Users", schema: "jellyfin", columns: table => new @@ -57,24 +42,16 @@ namespace Jellyfin.Server.Implementations.Migrations MaxParentalAgeRating = table.Column<int>(nullable: true), RemoteClientBitrateLimit = table.Column<int>(nullable: true), InternalId = table.Column<long>(nullable: false), - ProfileImageId = table.Column<int>(nullable: true), SyncPlayAccess = table.Column<int>(nullable: false), RowVersion = table.Column<uint>(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Users", x => x.Id); - table.ForeignKey( - name: "FK_Users_ImageInfo_ProfileImageId", - column: x => x.ProfileImageId, - principalSchema: "jellyfin", - principalTable: "ImageInfo", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "AccessSchedule", + name: "AccessSchedules", schema: "jellyfin", columns: table => new { @@ -87,9 +64,9 @@ namespace Jellyfin.Server.Implementations.Migrations }, constraints: table => { - table.PrimaryKey("PK_AccessSchedule", x => x.Id); + table.PrimaryKey("PK_AccessSchedules", x => x.Id); table.ForeignKey( - name: "FK_AccessSchedule_Users_UserId", + name: "FK_AccessSchedules_Users_UserId", column: x => x.UserId, principalSchema: "jellyfin", principalTable: "Users", @@ -98,6 +75,29 @@ namespace Jellyfin.Server.Implementations.Migrations }); migrationBuilder.CreateTable( + name: "ImageInfos", + schema: "jellyfin", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column<Guid>(nullable: true), + Path = table.Column<string>(maxLength: 512, nullable: false), + LastModified = table.Column<DateTime>(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 @@ -146,12 +146,19 @@ namespace Jellyfin.Server.Implementations.Migrations }); migrationBuilder.CreateIndex( - name: "IX_AccessSchedule_UserId", + name: "IX_AccessSchedules_UserId", schema: "jellyfin", - table: "AccessSchedule", + 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", @@ -162,34 +169,28 @@ namespace Jellyfin.Server.Implementations.Migrations 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: "AccessSchedule", + name: "AccessSchedules", schema: "jellyfin"); migrationBuilder.DropTable( - name: "Permissions", + name: "ImageInfos", schema: "jellyfin"); migrationBuilder.DropTable( - name: "Preferences", + name: "Permissions", schema: "jellyfin"); migrationBuilder.DropTable( - name: "Users", + name: "Preferences", schema: "jellyfin"); migrationBuilder.DropTable( - name: "ImageInfo", + name: "Users", schema: "jellyfin"); } } diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 115c98aa3..51fad8224 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -39,7 +39,7 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasIndex("UserId"); - b.ToTable("AccessSchedule"); + b.ToTable("AccessSchedules"); }); modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => @@ -102,9 +102,15 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT") .HasMaxLength(512); + b.Property<Guid?>("UserId") + .HasColumnType("TEXT"); + b.HasKey("Id"); - b.ToTable("ImageInfo"); + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => @@ -234,9 +240,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property<bool>("PlayDefaultAudioTrack") .HasColumnType("INTEGER"); - b.Property<int?>("ProfileImageId") - .HasColumnType("INTEGER"); - b.Property<bool>("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/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index 9c5056a6b..9e1183c76 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; @@ -61,25 +63,29 @@ namespace Jellyfin.Server.Implementations.Users }); } - byte[] passwordBytes = Encoding.UTF8.GetBytes(password); - - PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); - if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) - || _cryptographyProvider.DefaultHashMethod == readyHash.Id) + // Handle the case when the stored password is null, but the user tried to login with a password + if (resolvedUser.Password != null) { - byte[] calculatedHash = _cryptographyProvider.ComputeHash( - readyHash.Id, - passwordBytes, - readyHash.Salt.ToArray()); + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); - if (readyHash.Hash.SequenceEqual(calculatedHash)) + PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) + || _cryptographyProvider.DefaultHashMethod == readyHash.Id) { - success = true; + byte[] calculatedHash = _cryptographyProvider.ComputeHash( + readyHash.Id, + passwordBytes, + readyHash.Salt.ToArray()); + + if (readyHash.Hash.SequenceEqual(calculatedHash)) + { + success = true; + } + } + else + { + throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}"); } - } - else - { - throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}"); } if (!success) @@ -111,5 +117,32 @@ namespace Jellyfin.Server.Implementations.Users return Task.CompletedTask; } +<<<<<<< HEAD +======= + + /// <inheritdoc /> + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + if (newPassword != null) + { + newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString(); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException(nameof(newPasswordHash)); + } + + user.EasyPassword = newPasswordHash; + } + + /// <inheritdoc /> + public string? GetEasyPasswordHash(User user) + { + return string.IsNullOrEmpty(user.EasyPassword) + ? null + : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); + } +>>>>>>> upstream/master } } 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<User> e) + private void OnUserUpdated(object? sender, GenericEventArgs<User> 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 8ad2fecdb..e38cd07f0 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 35ec78f5c..97ea03596 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<UserManager> _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!; /// <summary> /// Initializes a new instance of the <see cref="UserManager"/> class. @@ -69,19 +70,19 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - public event EventHandler<GenericEventArgs<User>> OnUserPasswordChanged; + public event EventHandler<GenericEventArgs<User>>? OnUserPasswordChanged; /// <inheritdoc/> - public event EventHandler<GenericEventArgs<User>> OnUserUpdated; + public event EventHandler<GenericEventArgs<User>>? OnUserUpdated; /// <inheritdoc/> - public event EventHandler<GenericEventArgs<User>> OnUserCreated; + public event EventHandler<GenericEventArgs<User>>? OnUserCreated; /// <inheritdoc/> - public event EventHandler<GenericEventArgs<User>> OnUserDeleted; + public event EventHandler<GenericEventArgs<User>>? OnUserDeleted; /// <inheritdoc/> - public event EventHandler<GenericEventArgs<User>> OnUserLockedOut; + public event EventHandler<GenericEventArgs<User>>? OnUserLockedOut; /// <inheritdoc/> public IEnumerable<User> Users => _dbProvider.CreateContext().Users; @@ -90,7 +91,7 @@ namespace Jellyfin.Server.Implementations.Users public IEnumerable<Guid> UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id); /// <inheritdoc/> - public User GetUserById(Guid id) + public User? GetUserById(Guid id) { if (id == Guid.Empty) { @@ -101,7 +102,7 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - public User GetUserByName(string name) + public User? GetUserByName(string name) { if (string.IsNullOrWhiteSpace(name)) { @@ -173,7 +174,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, @@ -260,7 +261,7 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - public void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1) + public void ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1) { user.EasyPassword = _cryptoProvider.CreatePasswordHash(newPassword).ToString(); UpdateUser(user); @@ -269,14 +270,17 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - 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 { Name = user.Username, Id = user.Id, ServerId = _appHost.SystemId, - HasPassword = user.Password != null, + HasPassword = hasPassword, + HasConfiguredPassword = hasPassword, + HasConfiguredEasyPassword = !string.IsNullOrEmpty(user.EasyPassword), EnableAutoLogin = user.EnableAutoLogin, LastLoginDate = user.LastLoginDate, LastActivityDate = user.LastActivityDate, @@ -303,7 +307,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, @@ -335,13 +339,16 @@ 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<UnratedItem>).ToArray() } }; } /// <inheritdoc/> - public async Task<User> AuthenticateUser( + public async Task<User?> AuthenticateUser( string username, string password, string passwordSha1, @@ -356,7 +363,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) { @@ -516,6 +523,39 @@ namespace Jellyfin.Server.Implementations.Users _defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); } + /// <inheritdoc /> + 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.Update(newUser); + dbContext.SaveChanges(); + } + /// <inheritdoc/> public NameIdPair[] GetAuthenticationProviders() { @@ -577,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, @@ -620,6 +662,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<string>()); user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); @@ -630,6 +676,14 @@ namespace Jellyfin.Server.Implementations.Users dbContext.SaveChanges(); } + /// <inheritdoc/> + public void ClearProfileImage(User user) + { + var dbContext = _dbProvider.CreateContext(); + dbContext.Remove(user.ProfileImage); + dbContext.SaveChanges(); + } + private static bool IsValidUsername(string name) { // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ @@ -648,7 +702,7 @@ namespace Jellyfin.Server.Implementations.Users return GetPasswordResetProviders(user)[0]; } - private IList<IAuthenticationProvider> GetAuthenticationProviders(User user) + private IList<IAuthenticationProvider> GetAuthenticationProviders(User? user) { var authenticationProviderId = user?.AuthenticationProviderId; @@ -698,14 +752,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)) { @@ -743,7 +797,7 @@ namespace Jellyfin.Server.Implementations.Users IAuthenticationProvider provider, string username, string password, - User resolvedUser) + User? resolvedUser) { try { |
