package ru.pdguard.mask; import org.springframework.stereotype.Component; import ru.pdguard.detect.PdTypes; import java.util.Map; import java.util.function.UnaryOperator; /** * Превращает найденное значение в замену согласно настройкам системы. * *

Тип, для которого вид маски не задан, скрывается звёздочками целиком — * безопасное поведение по умолчанию для вновь добавленных правил. */ @Component public class Masker { private static final UnaryOperator EDGES = v -> Strategies.keepEdges(v, 2, 2); private static final UnaryOperator SHORT_SERIES = v -> Strategies.keepEdges(v, 0, 2); private static final Map> BY_TYPE = Map.ofEntries( Map.entry(PdTypes.EMAIL, Strategies::email), Map.entry(PdTypes.PHONE, EDGES), Map.entry(PdTypes.CARD, EDGES), Map.entry(PdTypes.INN, EDGES), Map.entry(PdTypes.SNILS, EDGES), Map.entry(PdTypes.PASSPORT, EDGES), Map.entry(PdTypes.DRIVER_LICENSE, EDGES), Map.entry(PdTypes.DEPT_CODE, EDGES), // У этих документов серия короткая — две цифры или две буквы. Оставь мы // первые два знака, серия оказалась бы открыта целиком, поэтому видны // только последние. У паспорта РФ и водительского удостоверения серия // из четырёх знаков, там открывается половина. Map.entry(PdTypes.FOREIGN_PASSPORT, SHORT_SERIES), Map.entry(PdTypes.MILITARY_ID, SHORT_SERIES), Map.entry(PdTypes.BIRTH_CERTIFICATE, SHORT_SERIES), Map.entry(PdTypes.MEDICAL_POLICY, EDGES), Map.entry(PdTypes.CARDHOLDER, Strategies::initials), Map.entry(PdTypes.FIO, Strategies::initials), // Код проверки и пин-код не показываем даже частично: у них слишком // мало знаков, чтобы открывать хотя бы один. Map.entry(PdTypes.CVV, Strategies::stars), Map.entry(PdTypes.PIN, Strategies::stars), Map.entry(PdTypes.PASSPORT_ISSUER, Strategies::stars), // У дат сохраняем разделители: модель видит, что это дата, но не какая. Map.entry(PdTypes.BIRTH_DATE, Strategies::starsKeepingPunctuation), Map.entry(PdTypes.PASSPORT_DATE, Strategies::starsKeepingPunctuation), Map.entry(PdTypes.DATE, Strategies::starsKeepingPunctuation), Map.entry(PdTypes.ADDRESS_COUNTRY, Strategies::stars), Map.entry(PdTypes.ADDRESS_POSTCODE, Strategies::stars), Map.entry(PdTypes.ADDRESS_CITY, Strategies::stars), Map.entry(PdTypes.ADDRESS_STREET, Strategies::stars), Map.entry(PdTypes.ADDRESS_HOUSE, Strategies::stars), Map.entry(PdTypes.ADDRESS_FLAT, Strategies::stars), Map.entry(PdTypes.ADDRESS_REGION, Strategies::stars), Map.entry(PdTypes.ADDRESS_DISTRICT, Strategies::stars), Map.entry(PdTypes.BIRTH_PLACE, Strategies::stars), Map.entry(PdTypes.CITIZENSHIP, Strategies::stars), Map.entry(PdTypes.ACCOUNT_NUMBER, EDGES), Map.entry(PdTypes.OGRN, EDGES), Map.entry(PdTypes.OGRNIP, EDGES), Map.entry(PdTypes.KPP, EDGES), // Срок действия карты — разделитель виден, сам месяц/год нет. Map.entry(PdTypes.CARD_EXPIRY, Strategies::starsKeepingPunctuation), Map.entry(PdTypes.BIK, Strategies::stars), Map.entry(PdTypes.INCOME, Strategies::stars), Map.entry(PdTypes.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); // STRICT игнорирует BY_TYPE целиком — ни один тип не открывает края // и ФИО не превращается в инициалы, только сплошные звёздочки. case STRICT -> Strategies.stars(value); case TOKEN -> context.resolve(type, value, (t, n) -> "[" + t + "_" + n + "]"); case SYNTHETIC -> context.resolve(type, value, (t, n) -> Synthetic.forType(t, value, n)); }; } }