package ru.pdguard.config; import java.util.Set; import ru.pdguard.detect.PdTypes; import ru.pdguard.mask.MaskMode; /** * Правила обработки для одной системы-потребителя. * * @param name имя системы; им же разделяется хранилище соответствий, чтобы одна система не могла * достать данные другой * @param enabled разрешено ли системе обращаться в модуль * @param demask выполняется ли для системы обратное преобразование * @param maskMode вид замены: звёздочки, токен или синтетическое значение * @param types типы ПД к маскированию; {@code "*"} — все известные * @param key общий секрет системы; задан — заголовок {@code X-System-Key} обязан совпасть, иначе * имя системы можно было бы просто назвать. Только знаки ASCII: заголовки HTTP передаются в * Latin-1, и кириллица в ключе до сервиса доедет искажённой * @param requireCompanion типы, которые маскируются только вместе с ПД другого типа: пин-код сам по * себе безвреден, пин-код рядом с номером карты — уже нет; то же для даты без якорного слова, * места рождения («Нижний Новгород» в рассказе о городе — не адрес клиента) и страны («цены * выросли в Казахстане» — не гражданство). Сюда же банковские реквизиты — счёт, БИК, ОГРН, * ОГРНИП, КПП: сами по себе они опознают организацию или счёт, а не человека, и в перечне типов * из задания их нет. Рядом с именем клиента они становятся его данными и маскируются. Сюда же * доход и биометрия. Сумма заработка без человека — статистика («доход домохозяйств вырос до 74 * 500 руб»), а не персональные данные. Биометрия же в тексте не встречается вовсе: это шаблон в * базе, и правило маскирует лишь само упоминание, то есть слово, а не данные. Чувствителен * здесь факт, что биометрию сдал названный человек, — а он и существует только при имени рядом */ public record SystemPolicy( String name, boolean enabled, boolean demask, MaskMode maskMode, Set types, Set requireCompanion, String key) { public static final String ALL = "*"; /** Имя политики по умолчанию; оно же разделяет хранилище для запросов без заголовка. */ public static final String DEFAULT_NAME = "default"; /** Политика по умолчанию: маскируем всё, что умеем, обратное преобразование включено. */ public static final SystemPolicy DEFAULT = new SystemPolicy( DEFAULT_NAME, true, true, MaskMode.MASK, Set.of(ALL), Set.of( PdTypes.CVV, PdTypes.PIN, PdTypes.DATE, PdTypes.BIRTH_PLACE, PdTypes.ADDRESS_COUNTRY, PdTypes.ACCOUNT_NUMBER, PdTypes.BIK, PdTypes.OGRN, PdTypes.OGRNIP, PdTypes.KPP, PdTypes.INCOME, PdTypes.BIOMETRIC), null); public SystemPolicy { types = Set.copyOf(types); requireCompanion = Set.copyOf(requireCompanion); } /** Политика только для перечисленных типов, с остальными настройками по умолчанию. */ public static SystemPolicy forTypes(String... types) { return new SystemPolicy( DEFAULT_NAME, true, true, MaskMode.MASK, Set.of(types), DEFAULT.requireCompanion(), null); } /** Совпадает ли предъявленный ключ. Ключ не задан — проверка не применяется. */ public boolean accepts(String presentedKey) { if (key == null || key.isBlank()) { return true; } return java.security.MessageDigest.isEqual( key.getBytes(java.nio.charset.StandardCharsets.UTF_8), (presentedKey == null ? "" : presentedKey) .getBytes(java.nio.charset.StandardCharsets.UTF_8)); } public boolean allows(String type) { return types.contains(ALL) || types.contains(type); } public boolean needsCompanion(String type) { return requireCompanion.contains(type); } }