aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server.Implementations')
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj4
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDb.cs45
-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.cs29
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs63
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs3
-rw-r--r--Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs5
-rw-r--r--Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs2
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs106
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
{