Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
53824f0
Bump pillow from 12.1.1 to 12.2.0 (#1441)
dependabot[bot] Jul 8, 2026
5a5fc52
Merge branch 'main' into dev_v4
Badatos Jul 8, 2026
567ef77
Bump Pod version to 4.3.1-dev
Badatos Jul 8, 2026
daf8dc7
Fix Podfile invalid type URLs and video channel link markup (#1494)
LoicBonavent Jul 20, 2026
16cf976
Fixes for 4.3 (#1492)
Badatos Jul 20, 2026
d394f1b
Auto-update configuration files
invalid-email-address Jul 20, 2026
c5aaafb
Bump pillow from 12.2.0 to 12.3.0 (#1495)
dependabot[bot] Jul 21, 2026
c2325bb
Prevent remote encoding from clearing thumbnails (#1500)
LoicBonavent Aug 25, 2026
1c1967d
Fixup. Format code with Black
invalid-email-address Aug 25, 2026
852e6ef
Bump pillow from 12.2.0 to 12.3.0 (#1496)
dependabot[bot] Aug 25, 2026
470e9a4
Fix minor stuff for archive (#1502)
cbissler Aug 28, 2026
d39daa7
Bump djangorestframework from 3.15.2 to 3.17.2 (#1509)
dependabot[bot] Sep 7, 2026
48009c9
Improve File Type Identification in Podfile (#1506)
LoicBonavent Sep 7, 2026
344f57b
Fixup. Format code with Black
invalid-email-address Sep 7, 2026
0798070
Improve Runner Manager Administration and Task Logging (#1503)
LoicBonavent Sep 8, 2026
4b8a41c
Fixup. Format code with Black
invalid-email-address Sep 8, 2026
df779dc
Add the check_missing_migrations command (#1508)
Badatos Sep 8, 2026
67dec67
Fixup. Format code with Black
invalid-email-address Sep 8, 2026
b219e14
Harden Video Security, Fix Runner Imports and v3-to-v4 Export, and St…
LoicBonavent Sep 11, 2026
77da900
Fixup. Format code with Black
invalid-email-address Sep 11, 2026
d7fa42e
Version 4.3.1
Badatos Sep 11, 2026
1532135
Fixes before 4.4.0 (#1511)
Badatos Sep 11, 2026
dceebe1
Fix Playlist Permissions, Playback and Favorites (#1512)
LoicBonavent Sep 16, 2026
788fa40
Fixup. Format code with Prettier
invalid-email-address Sep 16, 2026
51a2468
Restore YouTube Video Imports (#1513)
LoicBonavent Sep 16, 2026
5de212e
Secure Playlist Redirects and Password Hashing (#1515)
LoicBonavent Sep 16, 2026
0730fed
Fix Encoding Notification Text and Email Subject Prefixes (#1514)
LoicBonavent Sep 16, 2026
acd6e03
More fixes for 4.4 (#1516)
Badatos Sep 17, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/code_formatting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
branches: [master, develop, main, dev_v4]
workflow_dispatch:

permissions:
contents: write

jobs:
code-formatting:
runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/pod_dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
- main
workflow_dispatch:

permissions:
contents: read

env:
PYTHON_VERSION: '3.10'
DJANGO_SUPERUSER_USERNAME: "admin"
Expand Down Expand Up @@ -93,6 +96,9 @@ jobs:
- name: Flake8 compliance
run: flake8

- name: Run JavaScript tests
run: find pod -type f -name '*.test.cjs' -print0 | xargs -0 node --test

- name: Check French translations
run: |
make lang
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/pod_main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ on:
- master
workflow_dispatch:

permissions:
contents: read

jobs:
Pod-Main:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -70,6 +73,9 @@ jobs:
cd pod
yarn

- name: Run JavaScript tests
run: find pod -type f -name '*.test.cjs' -print0 | xargs -0 node --test

- name: Run Tests without coverage
env:
PYTHONUNBUFFERED: 1
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ transcription/*
chromedriver

## Others
.venv
pod/static/
*.bak
.coverage
Expand Down
13 changes: 11 additions & 2 deletions CONFIGURATION_FR.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,9 +509,12 @@ Il faudra pour cela créer un fichier de langue et traduire chaque entrée.<br>
* `ARCHIVE_OWNER_USERNAME`
> valeur par défaut : `"archive"`
>> Nom de l’utilisateur pour l’archivage des vidéos.<br>
* `ARCHIVE_ROOT`
> valeur par défaut : `'/video_archiving'`
>> Dossier dans lequel sont déplacées les vidéos après ARCHIVE_HOW_MANY_DAYS jours.<br>
* `ARCHIVE_HOW_MANY_DAYS`
> valeur par défaut : `365`
>> Délai avant qu'une vidéo archivée ne soit déplacée vers archive_ROOT.<br>
>> Délai avant qu'une vidéo archivée ne soit déplacée vers ARCHIVE_ROOT.<br>
* `POD_ARCHIVE_AFFILIATION`
> valeur par défaut : `[]`
>> Affiliations pour lesquelles on souhaite archiver la vidéo plutôt que de la supprimer.<br>
Expand Down Expand Up @@ -560,7 +563,13 @@ Il faudra pour cela créer un fichier de langue et traduire chaque entrée.<br>
>> 'respite_criteria_parameter': [...],
>> 'archiving_criteria_parameter': {
>> 'minimum_expected_score': value,
>> 'attribute_scores': [...],
>> 'attribute_scores': [
>> 'title': 2,
>> 'description',: 3,
>> 'discipline',: 2,
>> 'tags',: 2,
>> 'date_evt',: 1,
>> ],
>> 'excluded_title_terms': [...],
>> 'excluded_discipline_terms': [...],
>> }
Expand Down
24 changes: 24 additions & 0 deletions pod/authentication/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.contrib.auth.models import User
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from urllib.parse import parse_qs, urlsplit

# ggignore-start
# gitguardian:ignore
Expand Down Expand Up @@ -43,6 +44,29 @@ def test_authentication_login(self) -> None:

print(" ---> test_authentication_login of authenticationViewsTestCase: OK!")

def test_authentication_login_rejects_external_referrers(self) -> None:
"""Authentication redirects must remain on the current host."""
login_url = settings.LOGIN_URL

for referrer in ("//evil.example", "https://testserver.evil.example/path"):
with self.subTest(referrer=referrer):
response = self.client.get(login_url, {"referrer": referrer})
self.assertEqual(response.status_code, 400)

def test_authentication_login_encodes_local_referrer(self) -> None:
"""Keep a local return URL as one encoded query-string value."""
referrer = "/video/example/?first=1&second=2"
response = self.client.get(
settings.LOGIN_URL,
{"referrer": referrer, "is_iframe": "true"},
)

location = urlsplit(response.url)
query = parse_qs(location.query)
self.assertEqual(location.path, "/accounts/login/")
self.assertEqual(query["next"], [referrer])
self.assertEqual(query["is_iframe"], ["true"])

def test_authentication_logout(self) -> None:
self.client = Client()
# USE_CAS is valued to False
Expand Down
20 changes: 12 additions & 8 deletions pod/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from django.urls import reverse
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.http import QueryDict
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.translation import gettext_lazy as _
from django.contrib import auth

Expand All @@ -29,14 +31,12 @@ def authentication_login(request):
elif referrer.startswith("http:/") and not referrer.startswith("http://"):
referrer = "http://" + referrer[len("http:/") :]

host = (
"https://%s" % request.get_host()
if (request.is_secure())
else "http://%s" % request.get_host()
)
if not referrer.startswith(("/", host)):
if not url_has_allowed_host_and_scheme(
referrer,
allowed_hosts={request.get_host()},
require_https=request.is_secure(),
):
raise SuspiciousOperation("referrer is not internal")
iframe_param = "is_iframe=true&" if (request.GET.get("is_iframe")) else ""
if request.user.is_authenticated:
return redirect(referrer)
if USE_CAS or USE_SHIB or USE_OIDC:
Expand All @@ -55,7 +55,11 @@ def authentication_login(request):
)
else:
url = reverse("local-login")
url += "?%snext=%s" % (iframe_param, referrer.replace("&", "%26"))
query = QueryDict(mutable=True)
if request.GET.get("is_iframe"):
query["is_iframe"] = "true"
query["next"] = referrer
url += "?" + query.urlencode(safe="/")
return redirect(url)


Expand Down
3 changes: 2 additions & 1 deletion pod/completion/static/js/caption_maker.js
Original file line number Diff line number Diff line change
Expand Up @@ -1408,7 +1408,8 @@ function processProxyVttResponse(obj) {
* @returns stripped line
*/
function stripHtmlTags(line) {
return String(line).replace(/[<>]/g, "").trim();
const cue = new VTTCue(0, 1, String(line));
return (cue.getCueAsHTML().textContent || "").trim();
}

/**
Expand Down
10 changes: 7 additions & 3 deletions pod/cut/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.test import TestCase, override_settings
from django.contrib.auth.models import User
from pod.cut.models import CutVideo
from pod.video import forms as video_forms
from pod.video.models import Video, Type
from django.urls import reverse
from pod.main.models import Configuration
Expand All @@ -11,6 +12,7 @@
from django.utils.translation import gettext_lazy as _
from .. import views
from importlib import reload
from unittest.mock import patch

# ggignore-start
# gitguardian:ignore
Expand Down Expand Up @@ -95,10 +97,12 @@ def test_post_cut_valid_form(self) -> None:
"duration": 10,
}

response = self.client.post(
reverse("cut:video_cut", kwargs={"slug": self.video.slug}), data=post_data
)
with patch.object(video_forms.encode, video_forms.ENCODE_VIDEO) as start_encode:
response = self.client.post(
reverse("cut:video_cut", kwargs={"slug": self.video.slug}), data=post_data
)

start_encode.assert_called_once_with(self.video.id)
self.assertTrue(CutVideo.objects.filter(video=self.video).exists())
self.assertRedirects(response, reverse("video:dashboard"))
messages = [m.message for m in get_messages(response.wsgi_request)]
Expand Down
34 changes: 25 additions & 9 deletions pod/duplicate/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
"""Unit tests for Duplicate views."""

from django.test import TestCase
from datetime import date
from tempfile import TemporaryDirectory
from unittest.mock import patch

from django.core.files.base import ContentFile
from django.test import TestCase, override_settings
from django.urls import reverse
from django.contrib.auth.models import User
from pod.video import forms as video_forms
from pod.video.models import Video, Type

# ggignore-start
Expand All @@ -20,20 +26,25 @@ class VideoDuplicateViewTest(TestCase):

def setUp(self) -> None:
"""Set up required objects for the tests."""
media_dir = TemporaryDirectory()
self.addCleanup(media_dir.cleanup)
media_settings = override_settings(MEDIA_ROOT=media_dir.name)
media_settings.enable()
self.addCleanup(media_settings.disable)
self.user = User.objects.create_user(
username="testuser", password=PWD, is_staff=True
)
self.type = Type.objects.create(title="Test Type")
self.video = Video.objects.create(
title="Original Video",
video="test.mp4",
video=ContentFile(b"source video content", name="test.mp4"),
slug="original-video",
type=self.type,
owner=self.user,
description="Original description",
description_fr="Description originale",
description_en="Original description",
date_evt="2023-01-01",
date_evt=date(2023, 1, 1),
main_lang="en",
licence="CC BY-SA",
is_draft=False,
Expand All @@ -42,16 +53,20 @@ def setUp(self) -> None:
is_360=False,
disable_comment=False,
)
self.video.save()

def test_video_duplicate(self) -> None:
"""Test duplicating a video."""
self.client.force_login(self.user)
url = reverse("duplicate:video_duplicate", args=[self.video.slug])
response = self.client.get(url)
with patch.object(video_forms.encode, video_forms.ENCODE_VIDEO) as start_encode:
response = self.client.get(url)

# Check that the duplicated video exists
duplicated_video = Video.objects.get(slug="0002-copy-of-original-video")
start_encode.assert_called_once_with(duplicated_video.id)
self.assertNotEqual(duplicated_video.video.name, self.video.video.name)
with duplicated_video.video.open("rb") as copied_file:
self.assertEqual(copied_file.read(), b"source video content")
self.assertEqual(duplicated_video.title, "Copy of Original Video")
self.assertEqual(duplicated_video.type, self.type)
self.assertEqual(duplicated_video.owner, self.user)
Expand All @@ -67,8 +82,9 @@ def test_video_duplicate(self) -> None:
self.assertEqual(duplicated_video.is_360, False)
self.assertEqual(duplicated_video.disable_comment, False)

# Check the response status code
self.assertEqual(
response.status_code, 302
) # Redirect after successful duplication"
self.assertRedirects(
response,
reverse("video:video_edit", args=[duplicated_video.slug]),
fetch_redirect_response=False,
)
print(" ---> test_video_duplicate ok")
8 changes: 6 additions & 2 deletions pod/enrichment/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.urls import reverse
from pod.playlist.models import Playlist
from pod.playlist.models import Playlist, PlaylistContent
from pod.video.models import Video, Type
from ..models import Enrichment
from django.contrib.sites.models import Site
Expand Down Expand Up @@ -203,8 +203,9 @@ def test_invalid_video_slug(self):
self.assertEqual(response.status_code, 400)

def test_private_playlist_access(self):
"""Test if a random user can't access to a private playlist."""
"""Distinguish missing membership from denied access to a private playlist."""
self.client.force_login(self.student)
Video.objects.filter(pk=self.video.pk).update(is_draft=False)
playlist = Playlist.objects.create(
name="Private playlist",
visibility="private",
Expand All @@ -215,4 +216,7 @@ def test_private_playlist_access(self):
+ f"?playlist={playlist.slug}"
)
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
PlaylistContent.objects.create(playlist=playlist, video=self.video)
response = self.client.get(url)
self.assertEqual(response.status_code, 403)
Loading