From c2cb18a9d1c936d069679052aa83ef7362849d91 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 26 Jun 2026 11:42:28 +0200 Subject: Fix local plugin registration --- Emby.Server.Implementations/ApplicationHost.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 14380c33bf..69e23bcb63 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -93,6 +93,9 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; +using MediaBrowser.Providers.Books; +using MediaBrowser.Providers.Books.ComicBookInfo; +using MediaBrowser.Providers.Books.ComicInfo; using MediaBrowser.Providers.Lyric; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.ListenBrainz; @@ -496,6 +499,14 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + // register the generic local metadata provider for comic files + serviceCollection.AddSingleton(); + + // register the actual implementations of the local metadata provider for comic files + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(NetManager); serviceCollection.AddSingleton(); -- cgit v1.2.3 From 62a5ded9205b10ddaef60ce3e05bf80e79f4c742 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 17 Jul 2026 17:04:31 +0200 Subject: Prevent unauthenticated re-run of the startup wizard on misconfiguration --- Emby.Server.Implementations/ApplicationHost.cs | 22 +++++++ Jellyfin.Api/Controllers/StartupController.cs | 5 ++ .../Controllers/StartupControllerTests.cs | 70 ++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 tests/Jellyfin.Api.Tests/Controllers/StartupControllerTests.cs (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 69e23bcb63..0c1c7d3f5b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -39,6 +39,8 @@ using Emby.Server.Implementations.SyncPlay; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using Jellyfin.Api.Helpers; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Drawing; using Jellyfin.MediaEncoding.Hls.Playlist; using Jellyfin.Networking.Manager; @@ -417,6 +419,8 @@ namespace Emby.Server.Implementations { Logger.LogInformation("Running startup tasks"); + EnsureStartupWizardIntegrity(); + Resolve().AddTasks(GetExports(false)); ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; @@ -436,6 +440,24 @@ namespace Emby.Server.Implementations return Task.CompletedTask; } + private void EnsureStartupWizardIntegrity() + { + if (ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted) + { + return; + } + + var hasConfiguredAdministrator = Resolve().GetUsers() + .Any(user => user.HasPermission(PermissionKind.IsAdministrator) && !string.IsNullOrEmpty(user.Password)); + + if (hasConfiguredAdministrator) + { + Logger.LogWarning("The startup wizard is marked incomplete but a configured administrator already exists. Marking setup as completed to prevent the unauthenticated setup endpoints from being reachable."); + ConfigurationManager.Configuration.IsStartupWizardCompleted = true; + ConfigurationManager.SaveConfiguration(); + } + } + /// public void Init(IServiceCollection serviceCollection) { diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index fa6d9efe36..47c8f21241 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -140,6 +140,11 @@ public class StartupController : BaseJellyfinApiController return NotFound(); } + if (!string.IsNullOrEmpty(user.Password)) + { + return Forbid(); + } + if (string.IsNullOrWhiteSpace(startupUserDto.Password)) { return BadRequest("Password must not be empty"); diff --git a/tests/Jellyfin.Api.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/StartupControllerTests.cs new file mode 100644 index 0000000000..bad11a9257 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/Controllers/StartupControllerTests.cs @@ -0,0 +1,70 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Controllers; +using Jellyfin.Api.Models.StartupDtos; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Server.Implementations.Users; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Mvc; +using Moq; +using Xunit; + +namespace Jellyfin.Api.Tests.Controllers; + +public class StartupControllerTests +{ + private readonly StartupController _subject; + private readonly Mock _mockUserManager; + private readonly Mock _mockConfig; + + public StartupControllerTests() + { + _mockUserManager = new Mock(); + _mockConfig = new Mock(); + _subject = new StartupController(_mockConfig.Object, _mockUserManager.Object); + } + + private static User CreateUser() + => new User( + "jellyfin", + typeof(DefaultAuthenticationProvider).FullName!, + typeof(DefaultPasswordResetProvider).FullName!); + + [Fact] + public async Task UpdateStartupUser_WhenNoUserExists_ReturnsNotFound() + { + _mockUserManager.Setup(m => m.GetFirstUser()).Returns((User?)null); + + var result = await _subject.UpdateStartupUser(new StartupUserDto { Name = "admin", Password = "pw" }); + + Assert.IsType(result); + } + + [Fact] + public async Task UpdateStartupUser_WhenPasswordAlreadyConfigured_ReturnsForbidden() + { + var user = CreateUser(); + user.Password = "already-set-hash"; + _mockUserManager.Setup(m => m.GetFirstUser()).Returns(user); + + var result = await _subject.UpdateStartupUser(new StartupUserDto { Name = "attacker", Password = "new-pw" }); + + // The startup wizard must never overwrite the password of an already-provisioned + // account, even if IsStartupWizardCompleted has been cleared. + Assert.IsType(result); + _mockUserManager.Verify(m => m.ChangePassword(It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task UpdateStartupUser_WhenNoPasswordYet_SetsPassword() + { + var user = CreateUser(); + Assert.True(string.IsNullOrEmpty(user.Password)); + _mockUserManager.Setup(m => m.GetFirstUser()).Returns(user); + + var result = await _subject.UpdateStartupUser(new StartupUserDto { Name = "jellyfin", Password = "first-pw" }); + + Assert.IsType(result); + _mockUserManager.Verify(m => m.ChangePassword(user.Id, "first-pw"), Times.Once); + } +} -- cgit v1.2.3