Types the spec asks for beyond the payment card itself: settlement account (20 digits), BIK, card expiry date (kept separate from birth date), OGRN/OGRNIP (negative lookahead so OGRNIP isn't half- swallowed by the OGRN rule), KPP, income/salary amounts, and mentions of biometric enrollment (ЕБС, voice/fingerprint templates). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
81 lines
4.5 KiB
Java
81 lines
4.5 KiB
Java
package ru.pdguard.mask;
|
|
|
|
import jakarta.enterprise.context.ApplicationScoped;
|
|
import ru.pdguard.detect.RuleRegistry;
|
|
|
|
import java.util.Map;
|
|
import java.util.function.UnaryOperator;
|
|
|
|
/**
|
|
* Превращает найденное значение в замену согласно настройкам системы.
|
|
*
|
|
* <p>Тип, для которого вид маски не задан, скрывается звёздочками целиком —
|
|
* безопасное поведение по умолчанию для вновь добавленных правил.
|
|
*/
|
|
@ApplicationScoped
|
|
public class Masker {
|
|
|
|
private static final UnaryOperator<String> EDGES = v -> Strategies.keepEdges(v, 2, 2);
|
|
private static final UnaryOperator<String> SHORT_SERIES = v -> Strategies.keepEdges(v, 0, 2);
|
|
|
|
private static final Map<String, UnaryOperator<String>> BY_TYPE = Map.ofEntries(
|
|
Map.entry(RuleRegistry.EMAIL, Strategies::email),
|
|
Map.entry(RuleRegistry.PHONE, EDGES),
|
|
Map.entry(RuleRegistry.CARD, EDGES),
|
|
Map.entry(RuleRegistry.INN, EDGES),
|
|
Map.entry(RuleRegistry.SNILS, EDGES),
|
|
Map.entry(RuleRegistry.PASSPORT, EDGES),
|
|
Map.entry(RuleRegistry.DRIVER_LICENSE, EDGES),
|
|
Map.entry(RuleRegistry.DEPT_CODE, EDGES),
|
|
// У этих документов серия короткая — две цифры или две буквы. Оставь мы
|
|
// первые два знака, серия оказалась бы открыта целиком, поэтому видны
|
|
// только последние. У паспорта РФ и водительского удостоверения серия
|
|
// из четырёх знаков, там открывается половина.
|
|
Map.entry(RuleRegistry.FOREIGN_PASSPORT, SHORT_SERIES),
|
|
Map.entry(RuleRegistry.MILITARY_ID, SHORT_SERIES),
|
|
Map.entry(RuleRegistry.BIRTH_CERTIFICATE, SHORT_SERIES),
|
|
Map.entry(RuleRegistry.MEDICAL_POLICY, EDGES),
|
|
Map.entry(RuleRegistry.CARDHOLDER, Strategies::initials),
|
|
Map.entry(RuleRegistry.FIO, Strategies::initials),
|
|
|
|
// Код проверки и пин-код не показываем даже частично: у них слишком
|
|
// мало знаков, чтобы открывать хотя бы один.
|
|
Map.entry(RuleRegistry.CVV, Strategies::stars),
|
|
Map.entry(RuleRegistry.PIN, Strategies::stars),
|
|
|
|
Map.entry(RuleRegistry.PASSPORT_ISSUER, Strategies::stars),
|
|
|
|
// У дат сохраняем разделители: модель видит, что это дата, но не какая.
|
|
Map.entry(RuleRegistry.BIRTH_DATE, Strategies::starsKeepingPunctuation),
|
|
Map.entry(RuleRegistry.PASSPORT_DATE, Strategies::starsKeepingPunctuation),
|
|
Map.entry(RuleRegistry.DATE, Strategies::starsKeepingPunctuation),
|
|
|
|
Map.entry(RuleRegistry.ADDRESS_COUNTRY, Strategies::stars),
|
|
Map.entry(RuleRegistry.ADDRESS_POSTCODE, Strategies::stars),
|
|
Map.entry(RuleRegistry.ADDRESS_CITY, Strategies::stars),
|
|
Map.entry(RuleRegistry.ADDRESS_STREET, Strategies::stars),
|
|
Map.entry(RuleRegistry.ADDRESS_HOUSE, Strategies::stars),
|
|
Map.entry(RuleRegistry.ADDRESS_FLAT, Strategies::stars),
|
|
Map.entry(RuleRegistry.BIRTH_PLACE, Strategies::stars),
|
|
Map.entry(RuleRegistry.CITIZENSHIP, Strategies::stars),
|
|
|
|
Map.entry(RuleRegistry.ACCOUNT_NUMBER, EDGES),
|
|
Map.entry(RuleRegistry.OGRN, EDGES),
|
|
Map.entry(RuleRegistry.OGRNIP, EDGES),
|
|
Map.entry(RuleRegistry.KPP, EDGES),
|
|
// Срок действия карты — разделитель виден, сам месяц/год нет.
|
|
Map.entry(RuleRegistry.CARD_EXPIRY, Strategies::starsKeepingPunctuation),
|
|
Map.entry(RuleRegistry.BIK, Strategies::stars),
|
|
Map.entry(RuleRegistry.INCOME, Strategies::stars),
|
|
Map.entry(RuleRegistry.BIOMETRIC, Strategies::stars)
|
|
);
|
|
|
|
public String mask(String type, String value, MaskMode mode, MaskContext context) {
|
|
return switch (mode) {
|
|
case MASK -> BY_TYPE.getOrDefault(type, Strategies::stars).apply(value);
|
|
case TOKEN -> context.resolve(type, value, (t, n) -> "[" + t + "_" + n + "]");
|
|
case SYNTHETIC -> context.resolve(type, value, (t, n) -> Synthetic.forType(t, value, n));
|
|
};
|
|
}
|
|
}
|