Identifiers: accented LinkedIn names and newer ResearcherIDs are refused; a pasted Scopus link stores a wrong ID #638
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#638
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, running each built-in identifier scheme against the forms of a value people paste.Several schemes in the built-in table refuse a real value, or read a pasted address wrongly. Most of them cost a person a refusal they cannot explain; the Scopus one stores an identifier that is not theirs, without a warning.
Checked again on 2026-10-01 against
mainat0a5cad587: still present.plugins/identifiers/schemes.pyis unchanged;value_from_urlis now atcore/identifiers.py:128-139.1. LinkedIn: a name with an accent is refused
plugins/identifiers/schemes.py:178-189. The pattern is^[a-z0-9][a-z0-9._\-]{0,99}$, andvalue_from_url(core/identifiers.py:123-134) lifts the slug fromurlsplit(url).pathwithout percent-decoding it.https://www.linkedin.com/in/jo%C3%A3o-silva-1a2b3c/normalises tojo%c3%a3o-silva-1a2b3cand is refused with "That does not look like a LinkedIn identifier". The same address written withã, the bare slugjoão-silva-1a2b3candhttps://www.linkedin.com/company/cr%C3%A9dit-agricole/are refused too. LinkedIn builds a profile's default address from the person's name, accents included, so this is the address many people outside the English-speaking world have.value_from_url, and let the pattern accept letters of any script ([^\W_]for the first character,[\w.\-]after it). Percent-encode the value again whereurl_forbuilds the link.2. ResearcherID: two- and three-letter prefixes, and the scheme's own link, are refused
plugins/identifiers/schemes.py:138-146. The pattern is^[A-Z]-\d{4}-\d{4}$, and the scheme has nohostsorurl_paths.AAB-1234-2020andHGU-0221-2022are refused. Web of Science has issued identifiers with two and three letters before the digits for several years; Wikidata's format constraint for the property (P1053) allows one to three. Because the scheme lifts nothing from a URL, pasting the address the scheme itself links to,https://www.webofscience.com/wos/author/record/J-7586-2012, is upper-cased whole and refused.^[A-Z]{1,3}-\d{4}-\d{4}$, withhosts=("webofscience.com", "researcherid.com")andurl_paths=("/record/", "/rid/").3. Scopus: digits from the rest of a pasted link are appended to the ID
plugins/identifiers/schemes.py:147-155and_digits_onlyat:79-80. The scheme has nohostsorurl_paths, so a pasted link reachestidywhole, andtidydeletes every character that is not a digit. The pattern accepts ten or eleven digits.https://www.scopus.com/authid/detail.uri?authorId=7004212771&origin=1is stored as70042127711. The form of the link ORCID shows,…/inward/authorDetails.uri?authorID=7004212771&partnerID=MN8TOARS, is stored as70042127718. Both pass the pattern, so the person sees no error and their CV links to an author ID that is not theirs. A link with no other digit in it happens to work.scopus.com, take theauthorId(orauthorID) query parameter and nothing else;tidythen removes only spaces. This needsvalue_from_urlto be able to read a query parameter, or aliftcallable on the scheme.4. A fragment is appended to the path for every scheme
core/identifiers.py:128-129:haystack = parts.path + ("#" + parts.fragment if parts.fragment else ""). The comment says it is there for GLEIF.https://www.wikidata.org/wiki/Q95#P31, the address the browser shows after following a link to a statement, liftsQ95#P31and is refused.5.
\daccepts digits that are not ASCIIplugins/identifiers/schemes.py:125,:141,:150,:160,:170and:194use\donstrpatterns compiled withoutre.ASCII.7004212771, what a Japanese input method produces) or Arabic-Indic digits passes the pattern and is stored as typed. So does an LEI whose check digits are full-width, and an ORCID with full-width digits anywhere but its last place: both checksums pass, becauseint()reads those digits. The stored value builds a link that does not resolve, and it is not equal to the same identifier written in ASCII, soone_company_per_identifier_per_owner(jobs/models.py:525) does not see an LEI typed both ways as one.Scheme.normalise(unicodedata.normalize("NFKC", …)covers the full-width forms;str(int(char))per digit covers the rest), or compile the patterns withre.ASCIIso the value is refused.Why it matters: an identifier is typed once and printed on every CV. A refusal of a correct value sends the person to Other, which has no link; a wrong Scopus ID is printed as theirs.
Related to #311, which lets an administrator define schemes in JSON: these corrections belong to the built-in table and to
value_from_url, whichever form the table takes.Done when
tests/test_person_identifiers.pyhas a parametrised test that fails today: the two LinkedIn addresses above clean tojoão-silva-1a2b3c;AAB-1234-2020and the Web of Science address clean to their IDs; both Scopus links clean to7004212771; a Scopus ID in full-width digits cleans to ASCII or is refused.tests/test_identifiers.pyhas a test thathttps://www.wikidata.org/wiki/Q95#P31cleans toQ95for a company, and that the GLEIF addresshttps://search.gleif.org/#/record/HWUPKR0MPOU8FGXBT394still cleans to the LEI.