mirror of
https://github.com/dsec-hub/dsec-discord-bot.git
synced 2026-09-22 07:44:26 +00:00
NEW-UXA11Y-11: require first AND last name to match (close a verification bypass)
Codex review: name_matches accepted any non-empty ordered subset, so a single
common token ("John" or "Doe") matched "John Michael Doe" — a student id plus one
name token could claim the verified role for someone else. Now both the first and
last tokens must match, middle tokens the student typed must appear in the roster
in order (omitted middles still fine), and a single token, an arbitrary subset,
or a wrong first/last name is rejected. Add tests: single-token input, wrong
surname and wrong first name do NOT verify; full name and first+last with the
middle omitted do.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XrE7F9ZuBWdQnS8CZvYDE
This commit is contained in:
parent
5751be129d
commit
e8b611ede4
1 changed files with 45 additions and 10 deletions
|
|
@ -152,22 +152,40 @@ fn normalise_student_id(raw: &str) -> String {
|
|||
lowered.strip_prefix('s').unwrap_or(&lowered).to_string()
|
||||
}
|
||||
|
||||
/// True when the submitted name is the roster name, or every word the student
|
||||
/// typed appears in the roster name in the same order. Lets "John Doe" match a
|
||||
/// roster row of "John Michael Doe" without matching an unrelated person.
|
||||
/// Whether the submitted name matches the roster name closely enough to be the
|
||||
/// same person.
|
||||
///
|
||||
/// The first and last name tokens must BOTH match, and any tokens the student
|
||||
/// typed in between must appear in the roster name in order — so an omitted
|
||||
/// middle name is fine, but a single token, an arbitrary subset, a reordered
|
||||
/// name, or a wrong surname is not. This is deliberately strict: verification is
|
||||
/// already weak identity evidence (a name plus a student id), and a looser rule
|
||||
/// would let a student id plus one common name token ("John", "Doe") claim the
|
||||
/// verified role for someone else.
|
||||
fn name_matches(roster: &str, submitted: &str) -> bool {
|
||||
let roster = normalise_name(roster);
|
||||
let submitted = normalise_name(submitted);
|
||||
if submitted.is_empty() {
|
||||
|
||||
let roster_words: Vec<&str> = roster.split_whitespace().collect();
|
||||
let submitted_words: Vec<&str> = submitted.split_whitespace().collect();
|
||||
|
||||
// A single token (or empty) is far too weak to identify a person, and a roster
|
||||
// row without a distinct first and last name cannot be matched safely.
|
||||
if submitted_words.len() < 2 || roster_words.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
if roster == submitted {
|
||||
return true;
|
||||
|
||||
// The first and last name must both match.
|
||||
if submitted_words.first() != roster_words.first()
|
||||
|| submitted_words.last() != roster_words.last()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let roster_words: Vec<&str> = roster.split(' ').collect();
|
||||
|
||||
// Every token the student typed must appear in the roster name in order.
|
||||
let mut idx = 0usize;
|
||||
for word in submitted.split(' ') {
|
||||
match roster_words[idx..].iter().position(|w| *w == word) {
|
||||
for &word in &submitted_words {
|
||||
match roster_words[idx..].iter().position(|&w| w == word) {
|
||||
Some(offset) => idx += offset + 1,
|
||||
None => return false,
|
||||
}
|
||||
|
|
@ -386,11 +404,28 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn allows_a_missing_middle_name() {
|
||||
fn verifies_full_name_and_omitted_middle() {
|
||||
assert!(name_matches("John Michael Doe", "John Michael Doe"));
|
||||
assert!(name_matches("John Michael Doe", "John Doe"));
|
||||
assert!(name_matches("John Michael Doe", "john michael doe"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_a_single_token() {
|
||||
// A student id plus one common name token must never verify.
|
||||
assert!(!name_matches("John Michael Doe", "John"));
|
||||
assert!(!name_matches("John Michael Doe", "Doe"));
|
||||
assert!(!name_matches("John Michael Doe", "Michael"));
|
||||
assert!(!name_matches("John Doe", "John"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_wrong_surname_or_first_name() {
|
||||
assert!(!name_matches("John Michael Doe", "John Smith"));
|
||||
assert!(!name_matches("John Michael Doe", "Jane Doe"));
|
||||
assert!(!name_matches("John Doe", "Jack Doe"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_a_different_person() {
|
||||
assert!(!name_matches("John Michael Doe", "Jane Doe"));
|
||||
|
|
|
|||
Loading…
Reference in a new issue