JellyfinGuestUserService looked up IUserManager methods by reflection, trying eight candidate signatures for ChangePassword alone, and ItemTagService did the same for UpdateItemAsync. That fails at runtime on any API drift and only logs a warning, which is exactly how the DbUpdateConcurrencyException hunt started. We already pin Jellyfin.Controller 10.11, so these are now plain typed calls and any future drift is a compile error. 427 lines of shim gone, behaviour unchanged (UpdateItemAsync still gets ItemUpdateType.None, password still set before the policy update). Guest accounts also get their own authentication provider now, which refuses every interactive sign-in. Redemption is unaffected: AuthenticateDirect passes enforcePassword false and never consults a provider. If the plugin is disabled the provider id stops resolving and Jellyfin assigns the account to its own InvalidAuthProvider, which refuses too, so this fails closed. A random password is still set as a second line of defence.
54 lignes
2.1 KiB
C#
54 lignes
2.1 KiB
C#
using System.Threading.Tasks;
|
|
using Jellyfin.Database.Implementations.Entities;
|
|
using MediaBrowser.Controller.Authentication;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Jellyfin.Plugin.ShareLinks.Services;
|
|
|
|
/// <summary>
|
|
/// The authentication provider assigned to ShareLinks guest accounts. It refuses
|
|
/// every interactive sign-in, so a guest account cannot be used on the normal
|
|
/// login page even if its name and password were to leak. Guest sessions are
|
|
/// minted server side through <c>ISessionManager.AuthenticateDirect</c>, which
|
|
/// does not enforce a password and so never reaches a provider at all.
|
|
/// </summary>
|
|
public sealed class GuestAuthenticationProvider : IAuthenticationProvider
|
|
{
|
|
private readonly ILogger<GuestAuthenticationProvider> _logger;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="GuestAuthenticationProvider"/> class.</summary>
|
|
public GuestAuthenticationProvider(ILogger<GuestAuthenticationProvider> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the value Jellyfin stores on a user to select this provider. Jellyfin
|
|
/// matches it against the provider's full type name.
|
|
/// </summary>
|
|
public static string ProviderId => typeof(GuestAuthenticationProvider).FullName!;
|
|
|
|
/// <inheritdoc />
|
|
public string Name => "ShareLinks guest accounts (blocks sign-in)";
|
|
|
|
/// <inheritdoc />
|
|
public bool IsEnabled => true;
|
|
|
|
/// <inheritdoc />
|
|
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
|
{
|
|
_logger.LogWarning("ShareLinks: refused an interactive sign-in attempt for guest account {UserName}.", username);
|
|
return Task.FromException<ProviderAuthenticationResult>(
|
|
new AuthenticationException("ShareLinks guest accounts cannot sign in interactively."));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reports the account as having a password so nothing offers it as a
|
|
/// passwordless login.
|
|
/// </summary>
|
|
public bool HasPassword(User user) => true;
|
|
|
|
/// <inheritdoc />
|
|
public Task ChangePassword(User user, string newPassword) => Task.CompletedTask;
|
|
}
|