Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
@close="isAboutCommunityLibraryOpen = false"
/>
<div
v-if="loggedIn"
class="community-library-banner"
:style="{
backgroundColor: $themePalette.orange.v_100,
Expand Down Expand Up @@ -277,6 +278,8 @@
} = communityChannelsStrings;
const { copyChannelTokenAction$ } = commonStrings;

const loggedIn = computed(() => store.getters.loggedIn);

const availableLabels = ref(null);

const {
Expand Down Expand Up @@ -458,6 +461,7 @@
return {
windowIsSmall,
windowBreakpoint,
loggedIn,
tokenChannel,
loading,
loadError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@
</VToolbarTitle>
</VToolbar>
<AppBar v-else>
<template
v-if="loggedIn"
#tabs
>
<template #tabs>
<VTab
v-for="listType in lists"
:key="listType.id"
Expand Down Expand Up @@ -61,6 +58,7 @@
{{ communityLibraryLabel$() }}
</VTab>
<VTab
v-if="loggedIn"
:to="channelSetLink"
@click="channelSetsTabClick"
>
Expand Down Expand Up @@ -117,6 +115,11 @@
RouteNames.CATALOG_FAQ,
];

const COMMUNITY_LIBRARY_PAGES = [
RouteNames.COMMUNITY_LIBRARY_ITEMS,
RouteNames.COMMUNITY_LIBRARY_DETAILS,
];

const CHANNEL_SETS = 'channel_sets';
const ListTypeToAnalyticsLabel = {
[ChannelListTypes.EDITABLE]: 'EDITABLE',
Expand Down Expand Up @@ -164,14 +167,20 @@
return this.$route.name === RouteNames.COMMUNITY_LIBRARY_ITEMS;
},
toolbarHeight() {
return this.loggedIn && !this.isFAQPage ? 112 : 64;
return this.libraryMode || this.isFAQPage ? 64 : 112;
},
contentOffset() {
return this.toolbarHeight + (this.offline ? 48 : 0);
},
lists() {
if (!this.loggedIn) {
return [];
}
return Object.values(ChannelListTypes).filter(l => l !== 'public');
},
anonymousPages() {
return this.libraryMode ? CATALOG_PAGES : [...CATALOG_PAGES, ...COMMUNITY_LIBRARY_PAGES];
},
invitationsByListCounts() {
const inviteMap = {};
Object.values(ChannelListTypes).forEach(type => {
Expand Down Expand Up @@ -205,10 +214,12 @@
},
watch: {
$route(route) {
if (route.name === RouteNames.CHANNELS_EDITABLE) {
this.loggedIn
? this.loadInvitationList()
: this.$router.replace({ name: RouteNames.CATALOG_ITEMS });
if (!this.loggedIn) {
if (!this.anonymousPages.includes(route.name)) {
this.$router.replace({ name: RouteNames.CATALOG_ITEMS });
}
} else if (route.name === RouteNames.CHANNELS_EDITABLE) {
this.loadInvitationList();
}
if (this.fullPageError) {
this.$store.dispatch('errors/clearError');
Expand All @@ -222,7 +233,7 @@
created() {
if (this.loggedIn) {
this.loadInvitationList();
} else if (!CATALOG_PAGES.includes(this.$route.name)) {
} else if (!this.anonymousPages.includes(this.$route.name)) {
this.$router.replace({ name: RouteNames.CATALOG_ITEMS });
}
},
Expand Down
18 changes: 16 additions & 2 deletions contentcuration/contentcuration/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1702,13 +1702,27 @@ def new_token(self):

@classmethod
def filter_view_queryset(cls, queryset, user):
live_in_community_library = Q(
Exists(
CommunityLibrarySubmission.objects.filter(
channel=OuterRef("channel"),
channel_version=OuterRef("version"),
status=community_library_submission.STATUS_LIVE,
)
)
)

if user.is_anonymous:
return queryset.none()
return queryset.filter(live_in_community_library)

if user.is_admin:
return queryset

return queryset.filter(Q(channel__viewers=user) | Q(channel__editors=user))
return queryset.filter(
live_in_community_library
| Q(channel__viewers=user)
| Q(channel__editors=user)
)

@classmethod
def filter_edit_queryset(cls, queryset, user):
Expand Down
73 changes: 73 additions & 0 deletions contentcuration/contentcuration/tests/viewsets/test_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1717,3 +1717,76 @@ def test_non_channel_viewer_cannot_access_channel_versions(self):
response = self.client.get(url)
results = response.json()
self.assertEqual(len(results), 0)

def make_community_library_submission(self, channel, version, status):
submission = CommunityLibrarySubmission.objects.create(
channel=channel,
channel_version=version,
author=self.user,
)
submission.status = status
submission.save()
return submission

def test_anonymous_user_cannot_access_non_community_channel_versions(self):
"""Test that an anonymous user gets an empty list rather than a permission error."""
self.client.force_authenticate(user=None)
url = reverse("channelversion-list") + f"?channel={self.channel.id}"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 0)

def test_anonymous_user_can_access_live_community_library_version(self):
"""Test that an anonymous user can read the version live in the Community Library."""
self.make_community_library_submission(
self.channel, 2, community_library_submission.STATUS_LIVE
)
self.client.force_authenticate(user=None)
url = reverse("channelversion-list") + f"?channel={self.channel.id}"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
results = response.json()
self.assertEqual(len(results), 1)
self.assertEqual(results[0]["version"], 2)

def test_anonymous_user_cannot_access_unpublished_community_library_version(self):
"""Test that submissions that never went live stay hidden from anonymous users."""
for status in (
community_library_submission.STATUS_PENDING,
community_library_submission.STATUS_APPROVED,
community_library_submission.STATUS_REJECTED,
community_library_submission.STATUS_SUPERSEDED,
):
with self.subTest(status=status):
submission = self.make_community_library_submission(
self.channel, 2, status
)
self.client.force_authenticate(user=None)
url = reverse("channelversion-list") + f"?channel={self.channel.id}"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 0)
submission.delete()

def test_non_channel_viewer_can_access_live_community_library_version(self):
"""Test that a signed-in non-editor can read the live Community Library version."""
self.make_community_library_submission(
self.channel, 2, community_library_submission.STATUS_LIVE
)
other_user = testdata.user(email="otheruser@example.com")
self.client.force_authenticate(user=other_user)
url = reverse("channelversion-list") + f"?channel={self.channel.id}"
response = self.client.get(url)
results = response.json()
self.assertEqual(len(results), 1)
self.assertEqual(results[0]["version"], 2)

def test_live_community_library_version_does_not_expose_other_versions(self):
"""Test that going live exposes only that version, not the channel's other versions."""
self.make_community_library_submission(
self.channel, 2, community_library_submission.STATUS_LIVE
)
self.client.force_authenticate(user=None)
url = reverse("channelversion-list") + f"?channel={self.channel_2.id}"
response = self.client.get(url)
self.assertEqual(len(response.json()), 0)
2 changes: 1 addition & 1 deletion contentcuration/contentcuration/viewsets/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ class Meta:

class ChannelVersionViewSet(ReadOnlyValuesViewset):
queryset = ChannelVersion.objects.all()
permission_classes = [IsAuthenticated]
permission_classes = [AllowAny]
pagination_class = ChannelVersionListPagination
filterset_class = ChannelVersionFilter
ordering_fields = ["version"]
Expand Down
Loading