refactor: подтянуть Legal NER, переформатировать код и добавить тесты
Слияние с 685ec97 (третья ступень NER для юридических реквизитов),
код приведён к google-java-format, добавлены юнит-тесты
AdaptiveConcurrencyLimiter/SystemsConfig/PayloadCipher.
This commit is contained in:
@@ -1,81 +1,96 @@
|
||||
package ru.pdguard.config;
|
||||
|
||||
import java.util.Set;
|
||||
import ru.pdguard.detect.PdTypes;
|
||||
import ru.pdguard.mask.MaskMode;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Правила обработки для одной системы-потребителя.
|
||||
*
|
||||
* @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 руб»), а не
|
||||
* персональные данные. Биометрия же в тексте не встречается
|
||||
* вовсе: это шаблон в базе, и правило маскирует лишь само
|
||||
* упоминание, то есть слово, а не данные. Чувствителен здесь
|
||||
* факт, что биометрию сдал названный человек, — а он и
|
||||
* существует только при имени рядом
|
||||
* @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<String> types, Set<String> requireCompanion, String key) {
|
||||
public record SystemPolicy(
|
||||
String name,
|
||||
boolean enabled,
|
||||
boolean demask,
|
||||
MaskMode maskMode,
|
||||
Set<String> types,
|
||||
Set<String> requireCompanion,
|
||||
String key) {
|
||||
|
||||
public static final String ALL = "*";
|
||||
public static final String ALL = "*";
|
||||
|
||||
/** Имя политики по умолчанию; оно же разделяет хранилище для запросов без заголовка. */
|
||||
public static final String DEFAULT_NAME = "default";
|
||||
/** Имя политики по умолчанию; оно же разделяет хранилище для запросов без заголовка. */
|
||||
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 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 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 static SystemPolicy forTypes(String... types) {
|
||||
return new SystemPolicy(DEFAULT_NAME, true, true, MaskMode.MASK,
|
||||
Set.of(types), DEFAULT.requireCompanion(), null);
|
||||
}
|
||||
public boolean allows(String type) {
|
||||
return types.contains(ALL) || types.contains(type);
|
||||
}
|
||||
|
||||
/** Совпадает ли предъявленный ключ. Ключ не задан — проверка не применяется. */
|
||||
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);
|
||||
}
|
||||
public boolean needsCompanion(String type) {
|
||||
return requireCompanion.contains(type);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user