From dd31a5801388544ea69f808f68d952c580afad23 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Mon, 14 Sep 2026 13:32:00 +0330 Subject: [PATCH] fix(bookmarks): strictly filter playlists by bookmarked ID and eliminate item-level leakage - video: remove Q(playlist_items__video__id__in=...) to strictly filter by bookmarked playlist ID - podcast: ensure playlist bookmark filter targets playlist IDs and accepts both PODCAST_PLAYLIST and PODCAST - security/correctness: return empty queryset if is_bookmark is requested without authentication across video, podcast, library, and article views --- apps/article/views.py | 4 +++- apps/library/views.py | 4 +++- apps/podcast/views.py | 12 ++++++++---- apps/video/views.py | 38 ++++++++++++-------------------------- 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/apps/article/views.py b/apps/article/views.py index 36fae93..d15d6c5 100755 --- a/apps/article/views.py +++ b/apps/article/views.py @@ -204,7 +204,9 @@ class ArticleListAPIView(generics.ListAPIView): # Filter by bookmarks if provided is_bookmark = (self.request.query_params.get('is_bookmark') or self.request.query_params.get('is_bookmarked') or '').lower() - if is_bookmark == 'true': + if is_bookmark in ('true', '1'): + if not self.request.user.is_authenticated: + return queryset.none() # Import Bookmark model here to avoid circular imports from apps.bookmark.models import Bookmark diff --git a/apps/library/views.py b/apps/library/views.py index a94cc78..f5c9b8c 100644 --- a/apps/library/views.py +++ b/apps/library/views.py @@ -158,7 +158,9 @@ class BookListView(ListAPIView): # Filter by bookmarked books if requested is_bookmark = (self.request.query_params.get('is_bookmark') or self.request.query_params.get('is_bookmarked') or '').lower() - if is_bookmark in ('true', '1') and self.request.user.is_authenticated: + if is_bookmark in ('true', '1'): + if not self.request.user.is_authenticated: + return queryset.none() # Import Bookmark model here to avoid circular imports from apps.bookmark.models import Bookmark diff --git a/apps/podcast/views.py b/apps/podcast/views.py index 83103a9..98d94e7 100644 --- a/apps/podcast/views.py +++ b/apps/podcast/views.py @@ -218,12 +218,14 @@ class PodcastListAPIView(generics.ListAPIView): # Filter by bookmarks if provided is_bookmark = (self.request.query_params.get('is_bookmark') or self.request.query_params.get('is_bookmarked') or '').lower() - if is_bookmark == 'true': + if is_bookmark in ('true', '1'): + if not self.request.user.is_authenticated: + return queryset.none() from apps.bookmark.models import Bookmark bookmarked_ids = Bookmark.objects.filter( user=self.request.user, - service=Bookmark.ServiceChoices.PODCAST, + service__in=[Bookmark.ServiceChoices.PODCAST_PLAYLIST, Bookmark.ServiceChoices.PODCAST], status=True ).values_list('content_id', flat=True) @@ -304,11 +306,13 @@ class UserPlaylistListAPIView(generics.ListAPIView): def get_queryset(self): # Filter by bookmarks if provided is_bookmark = (self.request.query_params.get('is_bookmark') or self.request.query_params.get('is_bookmarked') or '').lower() - if is_bookmark in ('true', '1') and self.request.user.is_authenticated: + if is_bookmark in ('true', '1'): + if not self.request.user.is_authenticated: + return Podcast.objects.none() from apps.bookmark.models.bookmark import Bookmark bookmarked_ids = Bookmark.objects.filter( user=self.request.user, - service=Bookmark.ServiceChoices.PODCAST, + service__in=[Bookmark.ServiceChoices.PODCAST, Bookmark.ServiceChoices.PODCAST_PLAYLIST], status=True ).values_list('content_id', flat=True) return Podcast.objects.filter( diff --git a/apps/video/views.py b/apps/video/views.py index e194944..9bcb0b0 100644 --- a/apps/video/views.py +++ b/apps/video/views.py @@ -225,25 +225,18 @@ class VideoPlaylistListAPIView(generics.ListAPIView): queryset = queryset.filter(collections__slug=collection_slug) is_bookmark = (self.request.query_params.get('is_bookmark') or self.request.query_params.get('is_bookmarked') or '').lower() - if is_bookmark in ('true', '1') and self.request.user.is_authenticated: + if is_bookmark in ('true', '1'): + if not self.request.user.is_authenticated: + return queryset.none() from apps.bookmark.models import Bookmark - from django.db.models import Q - + bookmarked_playlist_ids = Bookmark.objects.filter( user=self.request.user, - service=Bookmark.ServiceChoices.VIDEO_PLAYLIST, + service__in=[Bookmark.ServiceChoices.VIDEO_PLAYLIST, Bookmark.ServiceChoices.VIDEO], status=True ).values_list('content_id', flat=True) - bookmarked_video_ids = Bookmark.objects.filter( - user=self.request.user, - service=Bookmark.ServiceChoices.VIDEO, - status=True - ).values_list('content_id', flat=True) - - queryset = queryset.filter( - Q(id__in=bookmarked_playlist_ids) | Q(playlist_items__video__id__in=bookmarked_video_ids) - ).distinct() + queryset = queryset.filter(id__in=bookmarked_playlist_ids) sort = self.request.query_params.get('sort', '-created_at') allowed_sorts = [ 'created_at', '-created_at', 'view_count', '-view_count', @@ -343,25 +336,18 @@ class VideoListAPIView(generics.ListAPIView): queryset = queryset.filter(collections__slug=collection_slug) is_bookmark = (self.request.query_params.get('is_bookmark') or self.request.query_params.get('is_bookmarked') or '').lower() - if is_bookmark in ('true', '1') and self.request.user.is_authenticated: + if is_bookmark in ('true', '1'): + if not self.request.user.is_authenticated: + return queryset.none() from apps.bookmark.models import Bookmark - from django.db.models import Q - + bookmarked_playlist_ids = Bookmark.objects.filter( user=self.request.user, - service=Bookmark.ServiceChoices.VIDEO_PLAYLIST, + service__in=[Bookmark.ServiceChoices.VIDEO_PLAYLIST, Bookmark.ServiceChoices.VIDEO], status=True ).values_list('content_id', flat=True) - bookmarked_video_ids = Bookmark.objects.filter( - user=self.request.user, - service=Bookmark.ServiceChoices.VIDEO, - status=True - ).values_list('content_id', flat=True) - - queryset = queryset.filter( - Q(id__in=bookmarked_playlist_ids) | Q(playlist_items__video__id__in=bookmarked_video_ids) - ).distinct() + queryset = queryset.filter(id__in=bookmarked_playlist_ids) sort = self.request.query_params.get('sort', '-created_at') allowed_sorts = [ 'created_at', '-created_at', 'view_count', '-view_count',