Close the gaps an audit of courses, tasks, files and grades turned up

Every area — courses, rooms, boards, topics, tasks, files, quizzes, teams,
groups, submissions, grades — was checked for data the instance has and the
tools did not show.

Grades and feedback. A teacher's /homework page is a different page from a
student's: grade and comment live in the grading form, one block per
submission, so a teacher account reported every graded submission as having
neither. parseTeacherGrading reads the form, and list_submissions can now
include the written feedback and who handed the work in.

Names. /api/v1 is partly served: courses, users and classes survive in the
deployment's ingress table, and users/{id} is the only route from an id to a
name. Submitters, file creators and course teachers resolve through it, and
degrade to "not visible to this account" where a student may not read them.

Courses, rooms and classes. get_course adds the description, teachers,
member count and weekly timetable from /api/v1/courses. list_classes is new.
get_room reports what the account may do — allowedOperations is an object of
booleans, not the list it was typed as — and applicants and invitation links
where it may manage them.

Board and topic content. Link descriptions, image alt text, drawing and
video-conference titles, the ids behind external tools and H5P content (the
only thing resembling a quiz), and what a deleted element used to be. Topic
Etherpad pads are read like board pads, and htmlToText keeps table columns
apart and drops template indentation.

Files. A scan with no text layer falls back to the preview endpoint, whose
width and outputFormat are undocumented enums, so Claude gets a picture of
the page; list_files reports counts and sizes. Teams stay documented as
unreadable at any API version; their files come later.

What the crawl missed. Tasks attached to topics (18 of 60 on the live
account), each course's own file area, and — behind INDEX_PERSONAL_FILES —
personal files and submissions with their grade comments, so search and
what_changed cover grading. A submission hit points at get_task.

The local instance's preview profile gets an ImageMagick policy that allows
the coders its 7.1.2 build needs; the image's own denies them all.

110 tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-09-16 20:19:16 +02:00
parent a3b17a680c
commit 5ae2210459
25 changed files with 1462 additions and 89 deletions

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policymap [
<!ELEMENT policymap (policy)*>
<!ATTLIST policymap xmlns CDATA #FIXED "">
<!ELEMENT policy EMPTY>
<!ATTLIST policy xmlns CDATA #FIXED "">
<!ATTLIST policy domain NMTOKEN #REQUIRED>
<!ATTLIST policy name NMTOKEN #IMPLIED>
<!ATTLIST policy pattern CDATA #IMPLIED>
<!ATTLIST policy rights NMTOKEN #IMPLIED>
<!ATTLIST policy stealth NMTOKEN #IMPLIED>
<!ATTLIST policy value CDATA #IMPLIED>
]>
<policymap>
<!-- Resource limits to prevent OOM based on 4000 MB memory from AMQP_FILE_PREVIEW_MEMORY_LIMITS used in
https://github.com/hpi-schul-cloud/file-storage/blob/main/ansible/roles/file-storage/templates/preview-generator-deployment.yml.j2#L64-L68 -->
<policy domain="resource" name="memory" value="3.5GiB"/>
<policy domain="resource" name="map" value="3.5GiB"/>
<policy domain="resource" name="area" value="1GB"/>
<policy domain="resource" name="disk" value="2GiB"/>
<policy domain="resource" name="width" value="16KP"/>
<policy domain="resource" name="height" value="16KP"/>
<policy domain="resource" name="time" value="60"/>
<policy domain="resource" name="list-length" value="1024"/>
<policy domain="resource" name="thread" value="4"/>
<!-- Security: Disable dangerous format handlers -->
<policy domain="coder" rights="none" pattern="EPHEMERAL"/>
<policy domain="coder" rights="none" pattern="URL"/>
<policy domain="coder" rights="none" pattern="HTTPS"/>
<policy domain="coder" rights="none" pattern="MVG"/>
<policy domain="coder" rights="none" pattern="MSL"/>
<policy domain="coder" rights="none" pattern="PS"/>
<policy domain="coder" rights="none" pattern="EPS"/>
<policy domain="coder" rights="none" pattern="LABEL"/>
<policy domain="coder" rights="none" pattern="CAPTION"/>
<policy domain="coder" rights="none" pattern="TEXT"/>
<policy domain="coder" rights="none" pattern="DOT"/>
<policy domain="coder" rights="none" pattern="PLT"/>
<policy domain="coder" rights="none" pattern="HPGL"/>
<policy domain="coder" rights="none" pattern="PCL"/>
<policy domain="coder" rights="none" pattern="XPS"/>
<policy domain="coder" rights="none" pattern="FIG"/>
<!-- Input formats.
Upstream ships these as rights="read", which ImageMagick 7.1.2 — the
version in this image — rejects at IsCoderAuthorized: every preview
fails with "attempt to perform an operation not authorized by the
security policy `PNG'" (or `PDF'), the record is flagged
previewGenerationFailed, and /api/v3/file/preview answers 404
PREVIEW_NOT_POSSIBLE even while the file record still reports
previewStatus: preview_possible. Granting write as well is what makes
the preview profile do anything at all here. -->
<policy domain="coder" rights="read|write" pattern="JPEG"/>
<policy domain="coder" rights="read|write" pattern="PNG"/>
<policy domain="coder" rights="read|write" pattern="TIFF"/>
<policy domain="coder" rights="read|write" pattern="HEIC"/>
<policy domain="coder" rights="read|write" pattern="PDF"/>
<policy domain="coder" rights="read|write" pattern="SVG"/>
<!-- Output format: READ + WRITE -->
<policy domain="coder" rights="read|write" pattern="WEBP"/>
</policymap>