Previously the shared account's libraries were chosen independently of its members, so a group could see a library that one of its members was blocked from - joining a group became a way to gain access. That was especially sharp with auto-created groups, where no admin is in the loop. A shared account is now granted exactly the libraries every member can already reach. If one member is blocked from a library, no group containing them can see it. The account is therefore always a subset of what each member could reach alone, which is what makes creating groups at the login screen safe to leave on by default. Details: - "Enable all folders" is expanded to concrete library ids before intersecting, since it cannot otherwise be compared with an explicit list. Shared accounts are always given an explicit list, never the all-folders permission, so newly added libraries do not silently widen an existing group. - Explicitly blocked folders are subtracted even for members who otherwise have access to everything. - Fails closed: an unresolvable member contributes no access rather than being treated as unrestricted. - Recomputed when membership changes, and re-applied to every group at startup so narrowing a member's own access narrows their groups. Drops the now-meaningless EnableAllFolders/EnabledFolders provisioning inputs and the DynamicGroupsEnableAllFolders setting. Adds 8 tests covering the intersection rules.
49 lines
2.1 KiB
C#
49 lines
2.1 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>
|
|
/// <returns>The updated group.</returns>
|
|
Task<SharedGroup> UpdateGroupAsync(
|
|
Guid sharedUserId,
|
|
IReadOnlyList<Guid> memberIds,
|
|
bool syncUnwatched,
|
|
bool syncPlayCount,
|
|
bool isDisabled);
|
|
|
|
/// <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);
|
|
}
|