diff options
Diffstat (limited to 'Jellyfin.Server.Implementations')
| -rw-r--r-- | Jellyfin.Server.Implementations/JellyfinDb.cs | 10 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/JellyfinDbProvider.cs | 2 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs (renamed from Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs) | 192 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs (renamed from Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs) | 198 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs | 186 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs (renamed from Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs) | 2 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs (renamed from Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs) | 5 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs (renamed from Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs) | 15 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs (renamed from Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs) | 2 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/UserManager.cs (renamed from Jellyfin.Server.Implementations/User/UserManager.cs) | 154 |
10 files changed, 502 insertions, 264 deletions
diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 9ac97a131..8eb35ec87 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -16,6 +16,13 @@ namespace Jellyfin.Server.Implementations public partial class JellyfinDb : DbContext { public virtual DbSet<ActivityLog> ActivityLogs { get; set; } + + public virtual DbSet<Group> Groups { get; set; } + + public virtual DbSet<Permission> Permissions { get; set; } + + public virtual DbSet<Preference> Preferences { get; set; } + public virtual DbSet<Data.Entities.User> Users { get; set; } /*public virtual DbSet<Artwork> Artwork { get; set; } public virtual DbSet<Book> Books { get; set; } @@ -30,7 +37,6 @@ namespace Jellyfin.Server.Implementations 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; } @@ -43,12 +49,10 @@ namespace Jellyfin.Server.Implementations public virtual DbSet<MovieMetadata> MovieMetadata { get; set; } public virtual DbSet<MusicAlbum> MusicAlbums { get; set; } public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; } - public virtual DbSet<Permission> Permissions { 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<Preference> Preferences { get; set; } public virtual DbSet<ProviderMapping> ProviderMappings { get; set; } public virtual DbSet<Rating> Ratings { get; set; } diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs index eab531d38..8f5c19900 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Implementations public JellyfinDbProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - serviceProvider.GetService<JellyfinDb>().Database.Migrate(); + serviceProvider.GetRequiredService<JellyfinDb>().Database.Migrate(); } /// <summary> diff --git a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs index 8313c6a3b..36c58c8ca 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 #pragma warning disable SA1601 // <auto-generated /> @@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { [DbContext(typeof(JellyfinDb))] - [Migration("20200504195702_UserSchema")] - partial class UserSchema + [Migration("20200517002411_AddUsers")] + partial class AddUsers { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -22,6 +22,31 @@ namespace Jellyfin.Server.Implementations.Migrations .HasDefaultSchema("jellyfin") .HasAnnotation("ProductVersion", "3.1.3"); + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<int>("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property<double>("EndHour") + .HasColumnType("REAL"); + + b.Property<double>("StartHour") + .HasColumnType("REAL"); + + b.Property<Guid>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedule"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => { b.Property<int>("Id") @@ -65,17 +90,17 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.ToTable("ActivityLog"); + b.ToTable("ActivityLogs"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Group", b => { - b.Property<int>("Id") + b.Property<Guid>("Id") .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + .HasColumnType("TEXT"); - b.Property<int?>("Group_Groups_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("Group_Groups_Guid") + .HasColumnType("TEXT"); b.Property<string>("Name") .IsRequired() @@ -88,9 +113,27 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("Group_Groups_Id"); + b.HasIndex("Group_Groups_Guid"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.ToTable("Group"); + b.Property<DateTime>("LastModified") + .HasColumnType("TEXT"); + + b.Property<string>("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ImageInfo"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => @@ -102,11 +145,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property<int>("Kind") .HasColumnType("INTEGER"); - b.Property<int?>("Permission_GroupPermissions_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("Permission_GroupPermissions_Id") + .HasColumnType("TEXT"); - b.Property<int?>("Permission_Permissions_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("Permission_Permissions_Guid") + .HasColumnType("TEXT"); b.Property<uint>("RowVersion") .IsConcurrencyToken() @@ -119,9 +162,9 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasIndex("Permission_GroupPermissions_Id"); - b.HasIndex("Permission_Permissions_Id"); + b.HasIndex("Permission_Permissions_Guid"); - b.ToTable("Permission"); + b.ToTable("Permissions"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => @@ -133,8 +176,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property<int>("Kind") .HasColumnType("INTEGER"); - b.Property<int?>("Preference_Preferences_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property<Guid?>("Preference_Preferences_Id") + .HasColumnType("TEXT"); b.Property<uint>("RowVersion") .IsConcurrencyToken() @@ -147,9 +193,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); + b.HasIndex("Preference_Preferences_Guid"); + b.HasIndex("Preference_Preferences_Id"); - b.ToTable("Preference"); + b.ToTable("Preferences"); }); modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b => @@ -163,8 +211,8 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT") .HasMaxLength(65535); - b.Property<int?>("ProviderMapping_ProviderMappings_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("ProviderMapping_ProviderMappings_Id") + .HasColumnType("TEXT"); b.Property<string>("ProviderName") .IsRequired() @@ -189,12 +237,11 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.User", b => { - b.Property<int>("Id") + b.Property<Guid>("Id") .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + .HasColumnType("TEXT"); b.Property<string>("AudioLanguagePreference") - .IsRequired() .HasColumnType("TEXT") .HasMaxLength(255); @@ -203,71 +250,86 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT") .HasMaxLength(255); - b.Property<bool?>("DisplayCollectionsView") + b.Property<bool>("DisplayCollectionsView") .HasColumnType("INTEGER"); - b.Property<bool?>("DisplayMissingEpisodes") + b.Property<bool>("DisplayMissingEpisodes") .HasColumnType("INTEGER"); - b.Property<bool?>("EnableNextEpisodeAutoPlay") + b.Property<string>("EasyPassword") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property<bool>("EnableAutoLogin") .HasColumnType("INTEGER"); - b.Property<bool?>("EnableUserPreferenceAccess") + b.Property<bool>("EnableLocalPassword") .HasColumnType("INTEGER"); - b.Property<string>("GroupedFolders") - .HasColumnType("TEXT") - .HasMaxLength(65535); + b.Property<bool>("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); - b.Property<bool?>("HidePlayedInLatest") + b.Property<bool>("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property<bool>("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property<long>("InternalId") .HasColumnType("INTEGER"); b.Property<int>("InvalidLoginAttemptCount") .HasColumnType("INTEGER"); - b.Property<string>("LatestItemExcludes") - .HasColumnType("TEXT") - .HasMaxLength(65535); + b.Property<DateTime>("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property<DateTime>("LastLoginDate") + .HasColumnType("TEXT"); b.Property<int?>("LoginAttemptsBeforeLockout") .HasColumnType("INTEGER"); - b.Property<bool>("MustUpdatePassword") + b.Property<int?>("MaxParentalAgeRating") .HasColumnType("INTEGER"); - b.Property<string>("MyMediaExcludes") - .HasColumnType("TEXT") - .HasMaxLength(65535); + b.Property<bool>("MustUpdatePassword") + .HasColumnType("INTEGER"); - b.Property<string>("OrderedViews") + b.Property<string>("Password") .HasColumnType("TEXT") .HasMaxLength(65535); - b.Property<string>("Password") + b.Property<string>("PasswordResetProviderId") + .IsRequired() .HasColumnType("TEXT") - .HasMaxLength(65535); + .HasMaxLength(255); b.Property<bool>("PlayDefaultAudioTrack") .HasColumnType("INTEGER"); - b.Property<bool?>("RememberAudioSelections") + b.Property<int?>("ProfileImageId") + .HasColumnType("INTEGER"); + + b.Property<bool>("RememberAudioSelections") .HasColumnType("INTEGER"); - b.Property<bool?>("RememberSubtitleSelections") + b.Property<bool>("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property<int?>("RemoteClientBitrateLimit") .HasColumnType("INTEGER"); b.Property<uint>("RowVersion") .IsConcurrencyToken() .HasColumnType("INTEGER"); - b.Property<string>("SubtitleLanguagePrefernce") + b.Property<string>("SubtitleLanguagePreference") .HasColumnType("TEXT") .HasMaxLength(255); - b.Property<string>("SubtitleMode") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); + b.Property<int>("SubtitleMode") + .HasColumnType("INTEGER"); b.Property<string>("Username") .IsRequired() @@ -276,34 +338,45 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.ToTable("User"); + 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.Group", b => { b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Groups") - .HasForeignKey("Group_Groups_Id"); + .HasForeignKey("Group_Groups_Guid"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => { b.HasOne("Jellyfin.Data.Entities.Group", null) - .WithMany("GroupPermissions") + .WithMany("Permissions") .HasForeignKey("Permission_GroupPermissions_Id"); b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Permissions") - .HasForeignKey("Permission_Permissions_Id"); + .HasForeignKey("Permission_Permissions_Guid"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => { - b.HasOne("Jellyfin.Data.Entities.Group", null) + b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Preferences") - .HasForeignKey("Preference_Preferences_Id"); + .HasForeignKey("Preference_Preferences_Guid"); - b.HasOne("Jellyfin.Data.Entities.User", null) + b.HasOne("Jellyfin.Data.Entities.Group", null) .WithMany("Preferences") .HasForeignKey("Preference_Preferences_Id"); }); @@ -318,6 +391,13 @@ namespace Jellyfin.Server.Implementations.Migrations .WithMany("ProviderMappings") .HasForeignKey("ProviderMapping_ProviderMappings_Id"); }); + + 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/20200504195702_UserSchema.cs b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs index f24ccccbf..55c6f371c 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs @@ -1,74 +1,125 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 #pragma warning disable SA1601 +using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Jellyfin.Server.Implementations.Migrations { - public partial class UserSchema : Migration + public partial class AddUsers : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "User", + name: "ImageInfo", schema: "jellyfin", columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("Sqlite:Autoincrement", true), + Path = table.Column<string>(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 + { + Id = table.Column<Guid>(nullable: false), Username = table.Column<string>(maxLength: 255, nullable: false), Password = table.Column<string>(maxLength: 65535, nullable: true), + EasyPassword = table.Column<string>(maxLength: 65535, nullable: true), MustUpdatePassword = table.Column<bool>(nullable: false), - AudioLanguagePreference = table.Column<string>(maxLength: 255, nullable: false), + AudioLanguagePreference = table.Column<string>(maxLength: 255, nullable: true), AuthenticationProviderId = table.Column<string>(maxLength: 255, nullable: false), - GroupedFolders = table.Column<string>(maxLength: 65535, nullable: true), + PasswordResetProviderId = table.Column<string>(maxLength: 255, nullable: false), InvalidLoginAttemptCount = table.Column<int>(nullable: false), - LatestItemExcludes = table.Column<string>(maxLength: 65535, nullable: true), + LastActivityDate = table.Column<DateTime>(nullable: false), + LastLoginDate = table.Column<DateTime>(nullable: false), LoginAttemptsBeforeLockout = table.Column<int>(nullable: true), - MyMediaExcludes = table.Column<string>(maxLength: 65535, nullable: true), - OrderedViews = table.Column<string>(maxLength: 65535, nullable: true), - SubtitleMode = table.Column<string>(maxLength: 255, nullable: false), + SubtitleMode = table.Column<int>(nullable: false), PlayDefaultAudioTrack = table.Column<bool>(nullable: false), - SubtitleLanguagePrefernce = table.Column<string>(maxLength: 255, nullable: true), - DisplayMissingEpisodes = table.Column<bool>(nullable: true), - DisplayCollectionsView = table.Column<bool>(nullable: true), - HidePlayedInLatest = table.Column<bool>(nullable: true), - RememberAudioSelections = table.Column<bool>(nullable: true), - RememberSubtitleSelections = table.Column<bool>(nullable: true), - EnableNextEpisodeAutoPlay = table.Column<bool>(nullable: true), - EnableUserPreferenceAccess = table.Column<bool>(nullable: true), + SubtitleLanguagePreference = table.Column<string>(maxLength: 255, nullable: true), + DisplayMissingEpisodes = table.Column<bool>(nullable: false), + DisplayCollectionsView = table.Column<bool>(nullable: false), + EnableLocalPassword = table.Column<bool>(nullable: false), + HidePlayedInLatest = table.Column<bool>(nullable: false), + RememberAudioSelections = table.Column<bool>(nullable: false), + RememberSubtitleSelections = table.Column<bool>(nullable: false), + EnableNextEpisodeAutoPlay = table.Column<bool>(nullable: false), + EnableAutoLogin = table.Column<bool>(nullable: false), + EnableUserPreferenceAccess = table.Column<bool>(nullable: false), + MaxParentalAgeRating = table.Column<int>(nullable: true), + RemoteClientBitrateLimit = table.Column<int>(nullable: true), + InternalId = table.Column<long>(nullable: false), + ProfileImageId = table.Column<int>(nullable: true), RowVersion = table.Column<uint>(nullable: false) }, constraints: table => { - table.PrimaryKey("PK_User", x => x.Id); + 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: "Group", + name: "AccessSchedule", schema: "jellyfin", columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column<Guid>(nullable: false), + DayOfWeek = table.Column<int>(nullable: false), + StartHour = table.Column<double>(nullable: false), + EndHour = table.Column<double>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AccessSchedule", x => x.Id); + table.ForeignKey( + name: "FK_AccessSchedule_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Groups", + schema: "jellyfin", + columns: table => new + { + Id = table.Column<Guid>(nullable: false), Name = table.Column<string>(maxLength: 255, nullable: false), RowVersion = table.Column<uint>(nullable: false), - Group_Groups_Id = table.Column<int>(nullable: true) + Group_Groups_Guid = table.Column<Guid>(nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Group", x => x.Id); + table.PrimaryKey("PK_Groups", x => x.Id); table.ForeignKey( - name: "FK_Group_User_Group_Groups_Id", - column: x => x.Group_Groups_Id, + name: "FK_Groups_Users_Group_Groups_Guid", + column: x => x.Group_Groups_Guid, principalSchema: "jellyfin", - principalTable: "User", + principalTable: "Users", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "Permission", + name: "Permissions", schema: "jellyfin", columns: table => new { @@ -77,30 +128,30 @@ namespace Jellyfin.Server.Implementations.Migrations Kind = table.Column<int>(nullable: false), Value = table.Column<bool>(nullable: false), RowVersion = table.Column<uint>(nullable: false), - Permission_GroupPermissions_Id = table.Column<int>(nullable: true), - Permission_Permissions_Id = table.Column<int>(nullable: true) + Permission_GroupPermissions_Id = table.Column<Guid>(nullable: true), + Permission_Permissions_Guid = table.Column<Guid>(nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Permission", x => x.Id); + table.PrimaryKey("PK_Permissions", x => x.Id); table.ForeignKey( - name: "FK_Permission_Group_Permission_GroupPermissions_Id", + name: "FK_Permissions_Groups_Permission_GroupPermissions_Id", column: x => x.Permission_GroupPermissions_Id, principalSchema: "jellyfin", - principalTable: "Group", + principalTable: "Groups", principalColumn: "Id", onDelete: ReferentialAction.Restrict); table.ForeignKey( - name: "FK_Permission_User_Permission_Permissions_Id", - column: x => x.Permission_Permissions_Id, + name: "FK_Permissions_Users_Permission_Permissions_Guid", + column: x => x.Permission_Permissions_Guid, principalSchema: "jellyfin", - principalTable: "User", + principalTable: "Users", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "Preference", + name: "Preferences", schema: "jellyfin", columns: table => new { @@ -109,23 +160,24 @@ namespace Jellyfin.Server.Implementations.Migrations Kind = table.Column<int>(nullable: false), Value = table.Column<string>(maxLength: 65535, nullable: false), RowVersion = table.Column<uint>(nullable: false), - Preference_Preferences_Id = table.Column<int>(nullable: true) + Preference_Preferences_Guid = table.Column<Guid>(nullable: true), + Preference_Preferences_Id = table.Column<Guid>(nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Preference", x => x.Id); + table.PrimaryKey("PK_Preferences", x => x.Id); table.ForeignKey( - name: "FK_Preference_Group_Preference_Preferences_Id", - column: x => x.Preference_Preferences_Id, + name: "FK_Preferences_Users_Preference_Preferences_Guid", + column: x => x.Preference_Preferences_Guid, principalSchema: "jellyfin", - principalTable: "Group", + principalTable: "Users", principalColumn: "Id", onDelete: ReferentialAction.Restrict); table.ForeignKey( - name: "FK_Preference_User_Preference_Preferences_Id", + name: "FK_Preferences_Groups_Preference_Preferences_Id", column: x => x.Preference_Preferences_Id, principalSchema: "jellyfin", - principalTable: "User", + principalTable: "Groups", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); @@ -141,49 +193,61 @@ namespace Jellyfin.Server.Implementations.Migrations ProviderSecrets = table.Column<string>(maxLength: 65535, nullable: false), ProviderData = table.Column<string>(maxLength: 65535, nullable: false), RowVersion = table.Column<uint>(nullable: false), - ProviderMapping_ProviderMappings_Id = table.Column<int>(nullable: true) + ProviderMapping_ProviderMappings_Id = table.Column<Guid>(nullable: true) }, constraints: table => { table.PrimaryKey("PK_ProviderMapping", x => x.Id); table.ForeignKey( - name: "FK_ProviderMapping_Group_ProviderMapping_ProviderMappings_Id", + name: "FK_ProviderMapping_Groups_ProviderMapping_ProviderMappings_Id", column: x => x.ProviderMapping_ProviderMappings_Id, principalSchema: "jellyfin", - principalTable: "Group", + principalTable: "Groups", principalColumn: "Id", onDelete: ReferentialAction.Restrict); table.ForeignKey( - name: "FK_ProviderMapping_User_ProviderMapping_ProviderMappings_Id", + name: "FK_ProviderMapping_Users_ProviderMapping_ProviderMappings_Id", column: x => x.ProviderMapping_ProviderMappings_Id, principalSchema: "jellyfin", - principalTable: "User", + principalTable: "Users", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateIndex( - name: "IX_Group_Group_Groups_Id", + name: "IX_AccessSchedule_UserId", schema: "jellyfin", - table: "Group", - column: "Group_Groups_Id"); + table: "AccessSchedule", + column: "UserId"); migrationBuilder.CreateIndex( - name: "IX_Permission_Permission_GroupPermissions_Id", + name: "IX_Groups_Group_Groups_Guid", schema: "jellyfin", - table: "Permission", + table: "Groups", + column: "Group_Groups_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_Permission_GroupPermissions_Id", + schema: "jellyfin", + table: "Permissions", column: "Permission_GroupPermissions_Id"); migrationBuilder.CreateIndex( - name: "IX_Permission_Permission_Permissions_Id", + 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: "Permission", - column: "Permission_Permissions_Id"); + table: "Preferences", + column: "Preference_Preferences_Guid"); migrationBuilder.CreateIndex( - name: "IX_Preference_Preference_Preferences_Id", + name: "IX_Preferences_Preference_Preferences_Id", schema: "jellyfin", - table: "Preference", + table: "Preferences", column: "Preference_Preferences_Id"); migrationBuilder.CreateIndex( @@ -191,16 +255,26 @@ namespace Jellyfin.Server.Implementations.Migrations schema: "jellyfin", table: "ProviderMapping", column: "ProviderMapping_ProviderMappings_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Users_ProfileImageId", + schema: "jellyfin", + table: "Users", + column: "ProfileImageId"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "Permission", + name: "AccessSchedule", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Permissions", schema: "jellyfin"); migrationBuilder.DropTable( - name: "Preference", + name: "Preferences", schema: "jellyfin"); migrationBuilder.DropTable( @@ -208,11 +282,15 @@ namespace Jellyfin.Server.Implementations.Migrations schema: "jellyfin"); migrationBuilder.DropTable( - name: "Group", + name: "Groups", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Users", schema: "jellyfin"); migrationBuilder.DropTable( - name: "User", + name: "ImageInfo", schema: "jellyfin"); } } diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 0fb0ba803..46714e865 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -1,7 +1,9 @@ // <auto-generated /> using System; +using Jellyfin.Server.Implementations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { @@ -15,6 +17,31 @@ namespace Jellyfin.Server.Implementations.Migrations .HasDefaultSchema("jellyfin") .HasAnnotation("ProductVersion", "3.1.3"); + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<int>("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property<double>("EndHour") + .HasColumnType("REAL"); + + b.Property<double>("StartHour") + .HasColumnType("REAL"); + + b.Property<Guid>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedule"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => { b.Property<int>("Id") @@ -63,12 +90,12 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.Group", b => { - b.Property<int>("Id") + b.Property<Guid>("Id") .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + .HasColumnType("TEXT"); - b.Property<int?>("Group_Groups_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("Group_Groups_Guid") + .HasColumnType("TEXT"); b.Property<string>("Name") .IsRequired() @@ -81,9 +108,27 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("Group_Groups_Id"); + b.HasIndex("Group_Groups_Guid"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.ToTable("Group"); + b.Property<DateTime>("LastModified") + .HasColumnType("TEXT"); + + b.Property<string>("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ImageInfo"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => @@ -95,11 +140,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property<int>("Kind") .HasColumnType("INTEGER"); - b.Property<int?>("Permission_GroupPermissions_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("Permission_GroupPermissions_Id") + .HasColumnType("TEXT"); - b.Property<int?>("Permission_Permissions_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("Permission_Permissions_Guid") + .HasColumnType("TEXT"); b.Property<uint>("RowVersion") .IsConcurrencyToken() @@ -112,9 +157,9 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasIndex("Permission_GroupPermissions_Id"); - b.HasIndex("Permission_Permissions_Id"); + b.HasIndex("Permission_Permissions_Guid"); - b.ToTable("Permission"); + b.ToTable("Permissions"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => @@ -126,8 +171,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property<int>("Kind") .HasColumnType("INTEGER"); - b.Property<int?>("Preference_Preferences_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property<Guid?>("Preference_Preferences_Id") + .HasColumnType("TEXT"); b.Property<uint>("RowVersion") .IsConcurrencyToken() @@ -140,9 +188,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); + b.HasIndex("Preference_Preferences_Guid"); + b.HasIndex("Preference_Preferences_Id"); - b.ToTable("Preference"); + b.ToTable("Preferences"); }); modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b => @@ -156,8 +206,8 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT") .HasMaxLength(65535); - b.Property<int?>("ProviderMapping_ProviderMappings_Id") - .HasColumnType("INTEGER"); + b.Property<Guid?>("ProviderMapping_ProviderMappings_Id") + .HasColumnType("TEXT"); b.Property<string>("ProviderName") .IsRequired() @@ -182,12 +232,11 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.User", b => { - b.Property<int>("Id") + b.Property<Guid>("Id") .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + .HasColumnType("TEXT"); b.Property<string>("AudioLanguagePreference") - .IsRequired() .HasColumnType("TEXT") .HasMaxLength(255); @@ -196,71 +245,86 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT") .HasMaxLength(255); - b.Property<bool?>("DisplayCollectionsView") + b.Property<bool>("DisplayCollectionsView") .HasColumnType("INTEGER"); - b.Property<bool?>("DisplayMissingEpisodes") + b.Property<bool>("DisplayMissingEpisodes") .HasColumnType("INTEGER"); - b.Property<bool?>("EnableNextEpisodeAutoPlay") + b.Property<string>("EasyPassword") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property<bool>("EnableAutoLogin") .HasColumnType("INTEGER"); - b.Property<bool?>("EnableUserPreferenceAccess") + b.Property<bool>("EnableLocalPassword") .HasColumnType("INTEGER"); - b.Property<string>("GroupedFolders") - .HasColumnType("TEXT") - .HasMaxLength(65535); + b.Property<bool>("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); - b.Property<bool?>("HidePlayedInLatest") + b.Property<bool>("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property<bool>("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property<long>("InternalId") .HasColumnType("INTEGER"); b.Property<int>("InvalidLoginAttemptCount") .HasColumnType("INTEGER"); - b.Property<string>("LatestItemExcludes") - .HasColumnType("TEXT") - .HasMaxLength(65535); + b.Property<DateTime>("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property<DateTime>("LastLoginDate") + .HasColumnType("TEXT"); b.Property<int?>("LoginAttemptsBeforeLockout") .HasColumnType("INTEGER"); - b.Property<bool>("MustUpdatePassword") + b.Property<int?>("MaxParentalAgeRating") .HasColumnType("INTEGER"); - b.Property<string>("MyMediaExcludes") - .HasColumnType("TEXT") - .HasMaxLength(65535); + b.Property<bool>("MustUpdatePassword") + .HasColumnType("INTEGER"); - b.Property<string>("OrderedViews") + b.Property<string>("Password") .HasColumnType("TEXT") .HasMaxLength(65535); - b.Property<string>("Password") + b.Property<string>("PasswordResetProviderId") + .IsRequired() .HasColumnType("TEXT") - .HasMaxLength(65535); + .HasMaxLength(255); b.Property<bool>("PlayDefaultAudioTrack") .HasColumnType("INTEGER"); - b.Property<bool?>("RememberAudioSelections") + b.Property<int?>("ProfileImageId") + .HasColumnType("INTEGER"); + + b.Property<bool>("RememberAudioSelections") .HasColumnType("INTEGER"); - b.Property<bool?>("RememberSubtitleSelections") + b.Property<bool>("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property<int?>("RemoteClientBitrateLimit") .HasColumnType("INTEGER"); b.Property<uint>("RowVersion") .IsConcurrencyToken() .HasColumnType("INTEGER"); - b.Property<string>("SubtitleLanguagePrefernce") + b.Property<string>("SubtitleLanguagePreference") .HasColumnType("TEXT") .HasMaxLength(255); - b.Property<string>("SubtitleMode") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); + b.Property<int>("SubtitleMode") + .HasColumnType("INTEGER"); b.Property<string>("Username") .IsRequired() @@ -269,34 +333,45 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.ToTable("User"); + 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.Group", b => { b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Groups") - .HasForeignKey("Group_Groups_Id"); + .HasForeignKey("Group_Groups_Guid"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => { b.HasOne("Jellyfin.Data.Entities.Group", null) - .WithMany("GroupPermissions") + .WithMany("Permissions") .HasForeignKey("Permission_GroupPermissions_Id"); b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Permissions") - .HasForeignKey("Permission_Permissions_Id"); + .HasForeignKey("Permission_Permissions_Guid"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => { - b.HasOne("Jellyfin.Data.Entities.Group", null) + b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Preferences") - .HasForeignKey("Preference_Preferences_Id"); + .HasForeignKey("Preference_Preferences_Guid"); - b.HasOne("Jellyfin.Data.Entities.User", null) + b.HasOne("Jellyfin.Data.Entities.Group", null) .WithMany("Preferences") .HasForeignKey("Preference_Preferences_Id"); }); @@ -311,6 +386,13 @@ namespace Jellyfin.Server.Implementations.Migrations .WithMany("ProviderMappings") .HasForeignKey("ProviderMapping_ProviderMappings_Id"); }); + + 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/User/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index 024500bf8..38494727c 100644 --- a/Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -7,7 +7,7 @@ using MediaBrowser.Common.Cryptography; using MediaBrowser.Controller.Authentication; using MediaBrowser.Model.Cryptography; -namespace Jellyfin.Server.Implementations.User +namespace Jellyfin.Server.Implementations.Users { /// <summary> /// The default authentication provider. diff --git a/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 80ab3ce00..60b48ec76 100644 --- a/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; @@ -10,7 +11,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; -namespace Jellyfin.Server.Implementations.User +namespace Jellyfin.Server.Implementations.Users { /// <summary> /// The default password reset provider. @@ -94,7 +95,7 @@ namespace Jellyfin.Server.Implementations.User } /// <inheritdoc /> - public async Task<ForgotPasswordResult> StartForgotPasswordProcess(Jellyfin.Data.Entities.User user, bool isInNetwork) + public async Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork) { string pin; using (var cryptoRandom = RandomNumberGenerator.Create()) diff --git a/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs index d33034ab2..d94a27b9d 100644 --- a/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System.Threading.Tasks; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; @@ -9,7 +10,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; -namespace Jellyfin.Server.Implementations.User +namespace Jellyfin.Server.Implementations.Users { public sealed class DeviceAccessEntryPoint : IServerEntryPoint { @@ -33,7 +34,11 @@ namespace Jellyfin.Server.Implementations.User return Task.CompletedTask; } - private void OnUserUpdated(object sender, GenericEventArgs<Data.Entities.User> e) + public void Dispose() + { + } + + private void OnUserUpdated(object sender, GenericEventArgs<User> e) { var user = e.Argument; if (!user.HasPermission(PermissionKind.EnableAllDevices)) @@ -42,11 +47,7 @@ namespace Jellyfin.Server.Implementations.User } } - public void Dispose() - { - } - - private void UpdateDeviceAccess(Data.Entities.User user) + private void UpdateDeviceAccess(User user) { var existing = _authRepo.Get(new AuthenticationInfoQuery { diff --git a/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs index a11ca128a..e430808bf 100644 --- a/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs +++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; -namespace Jellyfin.Server.Implementations.User +namespace Jellyfin.Server.Implementations.Users { /// <summary> /// An invalid authentication provider. diff --git a/Jellyfin.Server.Implementations/User/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 73905ff70..ddc05055b 100644 --- a/Jellyfin.Server.Implementations/User/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS0067 +#pragma warning disable CA1307 #pragma warning disable CS1591 using System; @@ -6,7 +6,9 @@ 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; @@ -20,7 +22,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; -namespace Jellyfin.Server.Implementations.User +namespace Jellyfin.Server.Implementations.Users { public class UserManager : IUserManager { @@ -47,24 +49,24 @@ namespace Jellyfin.Server.Implementations.User _logger = logger; } - public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserPasswordChanged; + public event EventHandler<GenericEventArgs<User>> OnUserPasswordChanged; /// <inheritdoc/> - public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserUpdated; + public event EventHandler<GenericEventArgs<User>> OnUserUpdated; /// <inheritdoc/> - public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserCreated; + public event EventHandler<GenericEventArgs<User>> OnUserCreated; /// <inheritdoc/> - public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserDeleted; + public event EventHandler<GenericEventArgs<User>> OnUserDeleted; - public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserLockedOut; + public event EventHandler<GenericEventArgs<User>> OnUserLockedOut; - public IEnumerable<Data.Entities.User> Users + public IEnumerable<User> Users { get { - using var dbContext = _dbProvider.CreateContext(); + var dbContext = _dbProvider.CreateContext(); return dbContext.Users; } } @@ -73,37 +75,38 @@ namespace Jellyfin.Server.Implementations.User { get { - using var dbContext = _dbProvider.CreateContext(); + var dbContext = _dbProvider.CreateContext(); return dbContext.Users.Select(u => u.Id); } } - public Data.Entities.User GetUserById(Guid id) + public User GetUserById(Guid id) { if (id == Guid.Empty) { throw new ArgumentException("Guid can't be empty", nameof(id)); } - using var dbContext = _dbProvider.CreateContext(); + var dbContext = _dbProvider.CreateContext(); return dbContext.Users.Find(id); } - public Data.Entities.User GetUserByName(string name) + public User GetUserByName(string name) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Invalid username", nameof(name)); } - using var dbContext = _dbProvider.CreateContext(); + var dbContext = _dbProvider.CreateContext(); - return dbContext.Users.FirstOrDefault(u => - string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase)); + // 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(Data.Entities.User user, string newName) + public async Task RenameUser(User user, string newName) { if (user == null) { @@ -132,43 +135,50 @@ namespace Jellyfin.Server.Implementations.User user.Username = newName; await UpdateUserAsync(user).ConfigureAwait(false); - OnUserUpdated?.Invoke(this, new GenericEventArgs<Data.Entities.User>(user)); + OnUserUpdated?.Invoke(this, new GenericEventArgs<User>(user)); } - public void UpdateUser(Data.Entities.User user) + public void UpdateUser(User user) { - using var dbContext = _dbProvider.CreateContext(); + var dbContext = _dbProvider.CreateContext(); dbContext.Users.Update(user); dbContext.SaveChanges(); } - public async Task UpdateUserAsync(Data.Entities.User user) + public async Task UpdateUserAsync(User user) { - await using var dbContext = _dbProvider.CreateContext(); + var dbContext = _dbProvider.CreateContext(); dbContext.Users.Update(user); await dbContext.SaveChangesAsync().ConfigureAwait(false); } - public Data.Entities.User CreateUser(string name) + public User CreateUser(string name) { - using var dbContext = _dbProvider.CreateContext(); + 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 = CreateUserObject(name); + var newUser = new User(name, _defaultAuthenticationProvider.GetType().FullName); dbContext.Users.Add(newUser); dbContext.SaveChanges(); + OnUserCreated?.Invoke(this, new GenericEventArgs<User>(newUser)); + return newUser; } - public void DeleteUser(Data.Entities.User user) + public void DeleteUser(User user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - using var dbContext = _dbProvider.CreateContext(); + var dbContext = _dbProvider.CreateContext(); if (!dbContext.Users.Contains(user)) { @@ -200,19 +210,20 @@ namespace Jellyfin.Server.Implementations.User dbContext.Users.Remove(user); dbContext.SaveChanges(); + OnUserDeleted?.Invoke(this, new GenericEventArgs<User>(user)); } - public Task ResetPassword(Data.Entities.User user) + public Task ResetPassword(User user) { return ChangePassword(user, string.Empty); } - public void ResetEasyPassword(Data.Entities.User user) + public void ResetEasyPassword(User user) { ChangeEasyPassword(user, string.Empty, null); } - public async Task ChangePassword(Data.Entities.User user, string newPassword) + public async Task ChangePassword(User user, string newPassword) { if (user == null) { @@ -222,24 +233,18 @@ namespace Jellyfin.Server.Implementations.User await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); await UpdateUserAsync(user).ConfigureAwait(false); - OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<Data.Entities.User>(user)); + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user)); } - public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordSha1) + public void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1) { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1); - UpdateUser(user); - OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<Data.Entities.User>(user)); + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user)); } - public UserDto GetUserDto(Data.Entities.User user, string remoteEndPoint = null) + public UserDto GetUserDto(User user, string remoteEndPoint = null) { return new UserDto { @@ -271,7 +276,7 @@ namespace Jellyfin.Server.Implementations.User MaxParentalRating = user.MaxParentalAgeRating, EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, RemoteClientBitrateLimit = user.RemoteClientBitrateLimit.GetValueOrDefault(), - AuthenticatioIsnProviderId = user.AuthenticationProviderId, + AuthenticationProviderId = user.AuthenticationProviderId, PasswordResetProviderId = user.PasswordResetProviderId, InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout.GetValueOrDefault(), @@ -306,7 +311,7 @@ namespace Jellyfin.Server.Implementations.User }; } - public PublicUserDto GetPublicUserDto(Data.Entities.User user, string remoteEndPoint = null) + public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null) { if (user == null) { @@ -328,7 +333,7 @@ namespace Jellyfin.Server.Implementations.User }; } - public async Task<Data.Entities.User> AuthenticateUser( + public async Task<User> AuthenticateUser( string username, string password, string passwordSha1, @@ -341,7 +346,7 @@ namespace Jellyfin.Server.Implementations.User throw new ArgumentNullException(nameof(username)); } - var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); bool success; IAuthenticationProvider authenticationProvider; @@ -370,7 +375,7 @@ namespace Jellyfin.Server.Implementations.User // Search the database for the user again // the authentication provider might have created it user = Users - .FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + .ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) { @@ -436,10 +441,10 @@ namespace Jellyfin.Server.Implementations.User if (isUserSession) { user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); + await UpdateUserAsync(user).ConfigureAwait(false); } - ResetInvalidLoginAttemptCount(user); + user.InvalidLoginAttemptCount = 0; _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username); } else @@ -495,14 +500,11 @@ namespace Jellyfin.Server.Implementations.User public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders) { _authenticationProviders = authenticationProviders.ToArray(); - - _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); - - _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First(); - _passwordResetProviders = passwordResetProviders.ToArray(); - _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); + _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First(); + _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); + _defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); } public NameIdPair[] GetAuthenticationProviders() @@ -563,7 +565,7 @@ namespace Jellyfin.Server.Implementations.User user.MaxParentalAgeRating = policy.MaxParentalRating; user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess; user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit; - user.AuthenticationProviderId = policy.AuthenticatioIsnProviderId; + user.AuthenticationProviderId = policy.AuthenticationProviderId; user.PasswordResetProviderId = policy.PasswordResetProviderId; user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; user.LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 @@ -604,28 +606,25 @@ namespace Jellyfin.Server.Implementations.User user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); } - private Data.Entities.User CreateUserObject(string name) + private bool IsValidUsername(string name) { - return new Data.Entities.User( - username: name, - mustUpdatePassword: false, - authenticationProviderId: _defaultAuthenticationProvider.GetType().FullName, - invalidLoginAttemptCount: -1, - subtitleMode: SubtitlePlaybackMode.Default, - playDefaultAudioTrack: true); + // 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(Data.Entities.User user) + private IAuthenticationProvider GetAuthenticationProvider(User user) { return GetAuthenticationProviders(user)[0]; } - private IPasswordResetProvider GetPasswordResetProvider(Data.Entities.User user) + private IPasswordResetProvider GetPasswordResetProvider(User user) { return GetPasswordResetProviders(user)[0]; } - private IList<IAuthenticationProvider> GetAuthenticationProviders(Data.Entities.User user) + private IList<IAuthenticationProvider> GetAuthenticationProviders(User user) { var authenticationProviderId = user?.AuthenticationProviderId; @@ -640,7 +639,7 @@ namespace Jellyfin.Server.Implementations.User { // 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} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user?.Username, user?.AuthenticationProviderId); providers = new List<IAuthenticationProvider> @@ -652,7 +651,7 @@ namespace Jellyfin.Server.Implementations.User return providers; } - private IList<IPasswordResetProvider> GetPasswordResetProviders(Data.Entities.User user) + private IList<IPasswordResetProvider> GetPasswordResetProviders(User user) { var passwordResetProviderId = user?.PasswordResetProviderId; var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); @@ -675,11 +674,10 @@ namespace Jellyfin.Server.Implementations.User 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, - Jellyfin.Data.Entities.User user, + User user, string remoteEndPoint) { bool success = false; @@ -721,7 +719,7 @@ namespace Jellyfin.Server.Implementations.User IAuthenticationProvider provider, string username, string password, - Data.Entities.User resolvedUser) + User resolvedUser) { try { @@ -745,27 +743,21 @@ namespace Jellyfin.Server.Implementations.User } } - private void IncrementInvalidLoginAttemptCount(Data.Entities.User user) + private void IncrementInvalidLoginAttemptCount(User user) { int invalidLogins = user.InvalidLoginAttemptCount; int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; - if (maxInvalidLogins.HasValue - && invalidLogins >= maxInvalidLogins) + if (maxInvalidLogins.HasValue && invalidLogins >= maxInvalidLogins) { user.SetPermission(PermissionKind.IsDisabled, true); - OnUserLockedOut?.Invoke(this, new GenericEventArgs<Data.Entities.User>(user)); + OnUserLockedOut?.Invoke(this, new GenericEventArgs<User>(user)); _logger.LogWarning( - "Disabling user {UserName} due to {Attempts} unsuccessful login attempts.", + "Disabling user {Username} due to {Attempts} unsuccessful login attempts.", user.Username, invalidLogins); } UpdateUser(user); } - - private void ResetInvalidLoginAttemptCount(Data.Entities.User user) - { - user.InvalidLoginAttemptCount = 0; - } } } |
