A shared account previously inherited its members' library access but none of their content restrictions, so a child could log into "alice+kid" with their own password and get around their own rating cap. The shared account now gets the strictest member's parental rating, unrated-item block, blocked tags and allowed tags, recomputed at creation, on membership change and at startup. An admin can raise the rating cap on a slider between the strictest and the loosest member; unrated and tag rules stay strictest-wins. What makes raising the cap safe is the unlock rule: after a member's password matches, both users' live policies are compared and the login is refused if the account is looser than the member on any field. So raising the cap above the child's rating means the child's password no longer opens the account, while the parent's still does. The same rule bounds the slider - past the loosest member nobody could unlock the account - so a chosen cap is clamped back into range whenever applied. Allowed tags need care: Jellyfin reads an empty list as "no whitelist", so an empty intersection of members' whitelists is written as a sentinel tag no item carries. Access schedules and channels are not inherited yet. The shared account is never an administrator. Groups created at the login screen always inherit and are restricted before the first session exists. The dashboard shows each member's cap, who a chosen cap shuts out, and the restrictions in effect, and gains a per-group edit form for the sync options. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
56 lines
2.6 KiB
C#
56 lines
2.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using Jellyfin.Plugin.WatchedTogether.Configuration;
|
|
|
|
namespace Jellyfin.Plugin.WatchedTogether.Services;
|
|
|
|
/// <summary>
|
|
/// Creates, updates and removes shared accounts and their groups.
|
|
/// </summary>
|
|
public interface IProvisioningService
|
|
{
|
|
/// <summary>
|
|
/// Creates a shared account for the given members and records the group.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The account is granted exactly the libraries every member can already reach, so it can never
|
|
/// be used to see more than any one member could alone.
|
|
/// </remarks>
|
|
/// <param name="memberIds">The members whose passwords will unlock the account. At least two.</param>
|
|
/// <param name="name">An explicit account name, or <c>null</c> to generate one from the member names.</param>
|
|
/// <returns>The created group.</returns>
|
|
Task<SharedGroup> CreateGroupAsync(IReadOnlyList<Guid> memberIds, string? name);
|
|
|
|
/// <summary>
|
|
/// Replaces the membership and options of an existing group.
|
|
/// </summary>
|
|
/// <param name="sharedUserId">The shared account identifying the group.</param>
|
|
/// <param name="memberIds">The new member list. At least two.</param>
|
|
/// <param name="syncUnwatched">Whether unwatched state propagates too.</param>
|
|
/// <param name="syncPlayCount">Whether play counts are raised on watch.</param>
|
|
/// <param name="isDisabled">Whether the group is suspended.</param>
|
|
/// <param name="inheritParentalRating">Whether the rating cap is the strictest member's.</param>
|
|
/// <param name="parentalRatingCap">
|
|
/// The rating cap to use instead, as Jellyfin's numeric score (<c>null</c> for none). Ignored
|
|
/// when inheriting. Kept within the members' range: see <see cref="SharedGroup.ParentalRatingCap"/>.
|
|
/// </param>
|
|
/// <returns>The updated group, with the cap as actually stored.</returns>
|
|
Task<SharedGroup> UpdateGroupAsync(
|
|
Guid sharedUserId,
|
|
IReadOnlyList<Guid> memberIds,
|
|
bool syncUnwatched,
|
|
bool syncPlayCount,
|
|
bool isDisabled,
|
|
bool inheritParentalRating,
|
|
int? parentalRatingCap);
|
|
|
|
/// <summary>
|
|
/// Removes a group, optionally deleting its shared account.
|
|
/// </summary>
|
|
/// <param name="sharedUserId">The shared account identifying the group.</param>
|
|
/// <param name="deleteSharedUser">Whether to delete the shared Jellyfin account as well.</param>
|
|
/// <returns>A task representing the removal.</returns>
|
|
Task DeleteGroupAsync(Guid sharedUserId, bool deleteSharedUser);
|
|
}
|