fix(auth): gate media file paths on metadata curation permission
- Allow curators (not just admins) to view media file paths and locations - Apply library access filter to file-level access checks
This commit is contained in:
@@ -78,7 +78,7 @@ func (h *CatalogResourceHandler) HandleGetItemVersions(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if !requestIsAdmin(r) {
|
||||
if !h.items.requestCanViewFilePaths(r) {
|
||||
for i := range detail.Versions {
|
||||
detail.Versions[i].FilePath = ""
|
||||
}
|
||||
@@ -502,7 +502,7 @@ func (h *CatalogResourceHandler) enrichItemDetail(r *http.Request, detail *catal
|
||||
applyEffectiveEditionPreference(detail.SeasonUserData, &detail.EffectiveVersionEditionKey)
|
||||
}
|
||||
|
||||
if !requestIsAdmin(r) {
|
||||
if !h.items.requestCanViewFilePaths(r) {
|
||||
for i := range detail.Versions {
|
||||
detail.Versions[i].FilePath = ""
|
||||
}
|
||||
|
||||
@@ -1258,7 +1258,21 @@ func isNotFound(err error) bool {
|
||||
errors.Is(err, catalog.ErrSeasonNotFound)
|
||||
}
|
||||
|
||||
func requestIsAdmin(r *http.Request) bool {
|
||||
func (h *ItemsHandler) requestCanViewFilePaths(r *http.Request) bool {
|
||||
claims := apimw.GetClaims(r.Context())
|
||||
return claims != nil && claims.Role == "admin"
|
||||
if claims == nil {
|
||||
return false
|
||||
}
|
||||
if claims.Role == "admin" {
|
||||
return true
|
||||
}
|
||||
if h == nil || h.UserRepo == nil {
|
||||
return false
|
||||
}
|
||||
user, err := h.UserRepo.GetByID(r.Context(), claims.UserID)
|
||||
if err != nil {
|
||||
slog.WarnContext(r.Context(), "checking file path visibility permissions", "user_id", claims.UserID, "error", err)
|
||||
return false
|
||||
}
|
||||
return auth.HasEffectivePermission(user, auth.PermissionMetadataCuration)
|
||||
}
|
||||
|
||||
@@ -51,9 +51,24 @@ func FileAllowedByAccess(file *models.MediaFile, filter AccessFilter) bool {
|
||||
if file == nil {
|
||||
return false
|
||||
}
|
||||
if filter.AllowedLibraryIDs != nil && !intInSlice(file.MediaFolderID, filter.AllowedLibraryIDs) {
|
||||
return false
|
||||
}
|
||||
if len(filter.DisabledLibraryIDs) > 0 && intInSlice(file.MediaFolderID, filter.DisabledLibraryIDs) {
|
||||
return false
|
||||
}
|
||||
return access.QualityAllowed(file.Resolution, filter.MaxPlaybackQuality)
|
||||
}
|
||||
|
||||
func intInSlice(value int, values []int) bool {
|
||||
for _, candidate := range values {
|
||||
if candidate == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FilterMediaFilesByAccess drops file versions that exceed the viewer's
|
||||
// effective quality ceiling.
|
||||
func FilterMediaFilesByAccess(files []*models.MediaFile, filter AccessFilter) []*models.MediaFile {
|
||||
|
||||
@@ -40,6 +40,7 @@ describe("initializeAuthSession", () => {
|
||||
username: "admin",
|
||||
email: "admin@example.com",
|
||||
role: "admin",
|
||||
permissions: [],
|
||||
download_allowed: true,
|
||||
impersonation: null,
|
||||
});
|
||||
|
||||
@@ -346,7 +346,7 @@ export default function EpisodeContent({ item }: { item: ItemDetail & { type: "e
|
||||
/>
|
||||
|
||||
<div className="page-shell space-y-12 py-10 sm:space-y-14">
|
||||
{isAdmin && <MediaLocations title="Media locations" versions={item.versions} />}
|
||||
{canCurateMetadata && <MediaLocations title="Media locations" versions={item.versions} />}
|
||||
|
||||
{/* More Episodes carousel — most useful, so show first */}
|
||||
{siblingsLoading ? (
|
||||
|
||||
@@ -289,7 +289,7 @@ export default function MovieContent({ item }: { item: ItemDetail & { type: "mov
|
||||
/>
|
||||
|
||||
<div className="page-shell space-y-12 py-10 sm:space-y-14">
|
||||
{isAdmin && <MediaLocations title="Media locations" versions={item.versions} />}
|
||||
{canCurateMetadata && <MediaLocations title="Media locations" versions={item.versions} />}
|
||||
|
||||
{item.cast && item.cast.length > 0 && (
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user