Importing a CV: the website and ORCID are stored without the checks the profile forms apply #610

Closed
opened 2026-10-01 10:48:22 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the importer's writer and trying it against the test settings.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at plugins/europass/reader.py:342-344 and :643-644 (the website taken as written) and plugins/europass/common.py:80-95 (_orcid_from, the checksum check); resume/importing.py, core/web_links.py and resume/candidate.py have not moved.

importing.apply writes a record's personal details without running any form or validator. The website goes through web_links.save_only_link, which assigns the value and calls save() with no full_clean, and the ORCID through PersonIdentifier.objects.create. The candidate-file importer, by contrast, sends the same values through WebLinkForm and PersonIdentifierForm. The Europass reader takes the website from the first website address in the file without checking it. The importer contract in the wiki lets a third-party importer put any string in person.

  • Where: resume/importing.py:141-145 (website), :177-180 (ORCID); core/web_links.py:210-241 (save_only_link); plugins/europass/reader.py:466-468 and :767-768 (website taken as written); compare resume/candidate.py:954 and :992.
  • What happens: tried on the test settings: importing.apply(user, Record(person={"website": "javascript:alert(1)", "orcid": "not-an-orcid"})) stored a WebLink with that URL and a PersonIdentifier with that value, and reported both as filled. full_clean() on the stored link then fails with "Enter a valid URL." The value appears in the CV's contact details. The profile's link form will not accept a row the person never typed, so they have to find and delete it themselves. The Europass reader already checks an ORCID's checksum (reader.py:189-204); a third-party importer's ORCID gets no check.
  • Why it matters: an import can store values that no form of Postulo would accept, and they are printed on the CV.
  • Fix: in importing.apply, validate each personal value as its page does before writing it: the URL field's validator (or WebLinkForm) for the website, identifiers.clean(identifiers.ORCID, …) (or PersonIdentifierForm) for the ORCID. A value that fails goes into Report.skipped as a sentence instead of being stored. The telephone number is written the same way (save_only_number, :159); give it the same treatment once #304 settles what a valid number is.

Related to #304 and #305, which change what the telephone and web link forms check; this path skips the forms, whatever they check.

Done when

  • A test in tests/test_importers.py applies a record whose website is not an http(s) URL and whose ORCID fails its checksum, and finds neither stored and both named in report.skipped.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the importer's writer and trying it against the test settings. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `plugins/europass/reader.py:342-344` and `:643-644` (the website taken as written) and `plugins/europass/common.py:80-95` (`_orcid_from`, the checksum check); `resume/importing.py`, `core/web_links.py` and `resume/candidate.py` have not moved. `importing.apply` writes a record's personal details without running any form or validator. The website goes through `web_links.save_only_link`, which assigns the value and calls `save()` with no `full_clean`, and the ORCID through `PersonIdentifier.objects.create`. The candidate-file importer, by contrast, sends the same values through `WebLinkForm` and `PersonIdentifierForm`. The Europass reader takes the website from the first website address in the file without checking it. The importer contract in the wiki lets a third-party importer put any string in `person`. - **Where:** `resume/importing.py:141-145` (website), `:177-180` (ORCID); `core/web_links.py:210-241` (`save_only_link`); `plugins/europass/reader.py:466-468` and `:767-768` (website taken as written); compare `resume/candidate.py:954` and `:992`. - **What happens:** tried on the test settings: `importing.apply(user, Record(person={"website": "javascript:alert(1)", "orcid": "not-an-orcid"}))` stored a `WebLink` with that URL and a `PersonIdentifier` with that value, and reported both as filled. `full_clean()` on the stored link then fails with "Enter a valid URL." The value appears in the CV's contact details. The profile's link form will not accept a row the person never typed, so they have to find and delete it themselves. The Europass reader already checks an ORCID's checksum (`reader.py:189-204`); a third-party importer's ORCID gets no check. - **Why it matters:** an import can store values that no form of Postulo would accept, and they are printed on the CV. - **Fix:** in `importing.apply`, validate each personal value as its page does before writing it: the URL field's validator (or `WebLinkForm`) for the website, `identifiers.clean(identifiers.ORCID, …)` (or `PersonIdentifierForm`) for the ORCID. A value that fails goes into `Report.skipped` as a sentence instead of being stored. The telephone number is written the same way (`save_only_number`, `:159`); give it the same treatment once #304 settles what a valid number is. Related to #304 and #305, which change what the telephone and web link forms check; this path skips the forms, whatever they check. ## Done when - A test in `tests/test_importers.py` applies a record whose website is not an http(s) URL and whose ORCID fails its checksum, and finds neither stored and both named in `report.skipped`.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:48:22 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#610
No description provided.