Keep redemption working on Jellyfin 10.11.9 and later (#20)
Jellyfin 10.11.9 changed IUserManager.ChangePassword to take the user id instead of the User. A call compiled against either signature throws MissingMethodException on the other, so look up whichever one the server has and call it through a delegate. Bumps to 1.0.5.0.
Cette révision appartient à :
@@ -6,9 +6,9 @@
|
|||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<RootNamespace>Jellyfin.Plugin.ShareLinks</RootNamespace>
|
<RootNamespace>Jellyfin.Plugin.ShareLinks</RootNamespace>
|
||||||
<AssemblyName>Jellyfin.Plugin.ShareLinks</AssemblyName>
|
<AssemblyName>Jellyfin.Plugin.ShareLinks</AssemblyName>
|
||||||
<Version>1.0.4.0</Version>
|
<Version>1.0.5.0</Version>
|
||||||
<AssemblyVersion>1.0.4.0</AssemblyVersion>
|
<AssemblyVersion>1.0.5.0</AssemblyVersion>
|
||||||
<FileVersion>1.0.4.0</FileVersion>
|
<FileVersion>1.0.5.0</FileVersion>
|
||||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ public sealed class JellyfinGuestUserService
|
|||||||
// policy hands the account to GuestAuthenticationProvider, which refuses every
|
// policy hands the account to GuestAuthenticationProvider, which refuses every
|
||||||
// interactive sign-in - but it means the account is never reachable with a blank
|
// interactive sign-in - but it means the account is never reachable with a blank
|
||||||
// password either.
|
// password either.
|
||||||
await _userManager.ChangePassword(user, password).ConfigureAwait(false);
|
await _userManager.ChangePasswordAsync(user, password).ConfigureAwait(false);
|
||||||
await ApplyPolicyAsync(user, record, disabled: false).ConfigureAwait(false);
|
await ApplyPolicyAsync(user, record, disabled: false).ConfigureAwait(false);
|
||||||
|
|
||||||
user = _userManager.GetUserById(user.Id) ?? user;
|
user = _userManager.GetUserById(user.Id) ?? user;
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.ShareLinks.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds <c>IUserManager.ChangePassword</c> at runtime instead of at compile time.
|
||||||
|
/// </summary>
|
||||||
|
internal static class UserManagerCompat
|
||||||
|
{
|
||||||
|
// Jellyfin 10.11.9 changed ChangePassword's parameter from the User itself to its Guid id.
|
||||||
|
// The plugin compiles against 10.11.0 but has to load on servers both before and after that
|
||||||
|
// change, and a call compiled against either signature throws MissingMethodException on a
|
||||||
|
// server that only has the other one. Resolving the method once with reflection and calling
|
||||||
|
// it through a delegate lets the plugin bind to whichever overload the running server has.
|
||||||
|
private static readonly Lazy<Func<IUserManager, User, string, Task>> ChangePasswordInvoker =
|
||||||
|
new(ResolveChangePassword);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes a user's password, binding to whichever <c>IUserManager.ChangePassword</c>
|
||||||
|
/// overload the running Jellyfin server exposes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userManager">The user manager to invoke.</param>
|
||||||
|
/// <param name="user">The user whose password is being changed.</param>
|
||||||
|
/// <param name="newPassword">The new password.</param>
|
||||||
|
/// <returns>A task that completes when the password has been changed.</returns>
|
||||||
|
public static Task ChangePasswordAsync(this IUserManager userManager, User user, string newPassword)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(userManager);
|
||||||
|
ArgumentNullException.ThrowIfNull(user);
|
||||||
|
|
||||||
|
return ChangePasswordInvoker.Value(userManager, user, newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Resolves the available <c>ChangePassword</c> overload into a single callable shape.</summary>
|
||||||
|
private static Func<IUserManager, User, string, Task> ResolveChangePassword()
|
||||||
|
{
|
||||||
|
// Prefer the current (10.11.9+) overload first.
|
||||||
|
var guidOverload = typeof(IUserManager).GetMethod(
|
||||||
|
nameof(IUserManager.ChangePassword),
|
||||||
|
new[] { typeof(Guid), typeof(string) });
|
||||||
|
if (guidOverload is not null)
|
||||||
|
{
|
||||||
|
var call = guidOverload.CreateDelegate<Func<IUserManager, Guid, string, Task>>();
|
||||||
|
return (manager, user, password) => call(manager, user.Id, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the older (10.11.0 - 10.11.8) overload.
|
||||||
|
var userOverload = typeof(IUserManager).GetMethod(
|
||||||
|
nameof(IUserManager.ChangePassword),
|
||||||
|
new[] { typeof(User), typeof(string) });
|
||||||
|
if (userOverload is not null)
|
||||||
|
{
|
||||||
|
return userOverload.CreateDelegate<Func<IUserManager, User, string, Task>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (_, _, _) => throw new InvalidOperationException(
|
||||||
|
"This Jellyfin version has neither IUserManager.ChangePassword(Guid, string) nor ChangePassword(User, string).");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"guid": "68540b76-ee74-436d-85ff-2abc884bbea6",
|
"guid": "68540b76-ee74-436d-85ff-2abc884bbea6",
|
||||||
"name": "ShareLinks",
|
"name": "ShareLinks",
|
||||||
"version": "1.0.4.0",
|
"version": "1.0.5.0",
|
||||||
"targetAbi": "10.11.0.0",
|
"targetAbi": "10.11.0.0",
|
||||||
"framework": "net9.0",
|
"framework": "net9.0",
|
||||||
"owner": "Franciskid",
|
"owner": "Franciskid",
|
||||||
"overview": "Secure expiring guest-share links for Jellyfin items.",
|
"overview": "Secure expiring guest-share links for Jellyfin items.",
|
||||||
"description": "Adds secure, expiring share links for Jellyfin items with JSON-backed storage, token hashing, and cleanup scaffolding.",
|
"description": "Adds secure, expiring share links for Jellyfin items with JSON-backed storage, token hashing, and cleanup scaffolding.",
|
||||||
"category": "General",
|
"category": "General",
|
||||||
"timestamp": "2026-07-26T20:15:00.0000000Z"
|
"timestamp": "2026-09-11T12:00:00.0000000Z"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -181,7 +181,7 @@ it in `token-secret.key`.
|
|||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Jellyfin **10.11** (`targetAbi 10.11.0.0`), .NET 9. Tested on 10.11.8. The web-client injection
|
Jellyfin **10.11** (`targetAbi 10.11.0.0`), .NET 9. Tested on 10.11.8 and 10.11.11. The web-client injection
|
||||||
targets the shipped client, in English and in French.
|
targets the shipped client, in English and in French.
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|||||||
Référencer dans un nouveau ticket
Bloquer un utilisateur