Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.prebid.server.settings.model.Purposes;
import org.prebid.server.settings.model.activity.AccountActivityConfiguration;
import org.prebid.server.settings.model.activity.privacy.AccountPrivacyModuleConfig;
import org.prebid.server.settings.model.activity.rule.AccountActivityRuleConfig;

import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -171,7 +172,8 @@ private ActivityController from(Activity activity,
final boolean allow = allowFromConfig(activityConfiguration.getAllow());
final List<Rule> rules = ListUtils.emptyIfNull(activityConfiguration.getRules()).stream()
.filter(Objects::nonNull)
.map(ruleConfiguration -> activityRuleFactory.from(ruleConfiguration, creationContext))
.map(ruleConfiguration -> createRule(ruleConfiguration, creationContext))
.filter(Objects::nonNull)
.toList();

return ActivityController.of(allow, rules, debug);
Expand All @@ -181,6 +183,20 @@ private static boolean allowFromConfig(Boolean configValue) {
return configValue != null ? configValue : ActivityInfrastructure.ALLOW_ACTIVITY_BY_DEFAULT;
}

private Rule createRule(AccountActivityRuleConfig ruleConfiguration,
ActivityControllerCreationContext creationContext) {

try {
return activityRuleFactory.from(ruleConfiguration, creationContext);
} catch (Exception e) {
logger.error("ActivityInfrastructure rule creation failed: %s. Configuration: %s"
.formatted(e.getMessage(), ruleConfiguration));
metrics.updateAlertsMetrics(MetricName.general);

return null;
}
}

private static Supplier<Map<Activity, ActivityController>> enumMapFactory() {
return () -> new EnumMap<>(Activity.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
import org.prebid.server.activity.infrastructure.privacy.uscustomlogic.USCustomLogicDataSupplier;
import org.prebid.server.activity.infrastructure.privacy.uscustomlogic.USCustomLogicModule;
import org.prebid.server.auction.gpp.model.GppContext;
import org.prebid.server.exception.InvalidAccountConfigException;
import org.prebid.server.json.DecodeException;
import org.prebid.server.json.JsonLogic;
import org.prebid.server.log.ConditionalLogger;
import org.prebid.server.log.Logger;
import org.prebid.server.log.LoggerFactory;
import org.prebid.server.metric.MetricName;
import org.prebid.server.metric.Metrics;
import org.prebid.server.settings.SettingsCache;
Expand All @@ -34,6 +35,9 @@

public class USCustomLogicModuleCreator implements PrivacyModuleCreator {

private static final Logger logger = LoggerFactory.getLogger(USCustomLogicModuleCreator.class);
private static final ConditionalLogger conditionalLogger = new ConditionalLogger(logger);

private static final Set<Integer> ALLOWED_SECTIONS_IDS =
PrivacySection.US_PRIVACY_SECTIONS.stream()
.map(PrivacySection::sectionId)
Expand All @@ -43,16 +47,19 @@ public class USCustomLogicModuleCreator implements PrivacyModuleCreator {
private final JsonLogic jsonLogic;
private final Map<String, JsonLogicNode> jsonLogicNodesCache;
private final Metrics metrics;
private final double samplingRate;

public USCustomLogicModuleCreator(USCustomLogicGppReaderFactory gppReaderFactory,
JsonLogic jsonLogic,
Integer cacheTtl,
Integer cacheSize,
Metrics metrics) {
Metrics metrics,
double samplingRate) {

this.gppReaderFactory = Objects.requireNonNull(gppReaderFactory);
this.jsonLogic = Objects.requireNonNull(jsonLogic);
this.metrics = Objects.requireNonNull(metrics);
this.samplingRate = samplingRate;

jsonLogicNodesCache = cacheTtl != null && cacheSize != null
? SettingsCache.createCache(cacheTtl, cacheSize, 0)
Expand All @@ -76,6 +83,7 @@ public PrivacyModule from(PrivacyModuleCreationContext creationContext) {
? SetUtils.emptyIfNull(scope.getSectionsIds()).stream()
.filter(sectionId -> shouldApplyPrivacy(sectionId, moduleConfig))
.map(sectionId -> forConfig(sectionId, normalizeSection, scope.getGppModel(), jsonLogicConfig))
.filter(Objects::nonNull)
.toList()
: Collections.emptyList();

Expand Down Expand Up @@ -123,25 +131,25 @@ private PrivacyModule forConfig(int sectionId,
GppModel gppModel,
ObjectNode jsonLogicConfig) {

return new USCustomLogicModule(
jsonLogic,
jsonLogicNode(jsonLogicConfig),
USCustomLogicDataSupplier.of(gppReaderFactory.forSection(sectionId, normalizeSection, gppModel)));
try {
return new USCustomLogicModule(
jsonLogic,
jsonLogicNode(jsonLogicConfig),
USCustomLogicDataSupplier.of(gppReaderFactory.forSection(sectionId, normalizeSection, gppModel)));
} catch (Exception e) {
conditionalLogger.error(
"USCustomLogic creation failed: %s. Config: %s".formatted(e.getMessage(), jsonLogicConfig),
samplingRate);
metrics.updateAlertsMetrics(MetricName.general);

return null;
}
}

private JsonLogicNode jsonLogicNode(ObjectNode jsonLogicConfig) {
final String jsonAsString = jsonLogicConfig.toString();
return jsonLogicNodesCache != null
? jsonLogicNodesCache.computeIfAbsent(jsonAsString, this::parseJsonLogicNode)
: parseJsonLogicNode(jsonAsString);
}

private JsonLogicNode parseJsonLogicNode(String jsonLogicConfig) {
try {
return jsonLogic.parse(jsonLogicConfig);
} catch (DecodeException e) {
metrics.updateAlertsMetrics(MetricName.general);
throw new InvalidAccountConfigException("JsonLogic exception: " + e.getMessage());
}
? jsonLogicNodesCache.computeIfAbsent(jsonAsString, jsonLogic::parse)
: jsonLogic.parse(jsonAsString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
import org.prebid.server.activity.infrastructure.privacy.PrivacySection;
import org.prebid.server.activity.infrastructure.privacy.usnat.USNatModule;
import org.prebid.server.auction.gpp.model.GppContext;
import org.prebid.server.log.ConditionalLogger;
import org.prebid.server.log.Logger;
import org.prebid.server.log.LoggerFactory;
import org.prebid.server.metric.MetricName;
import org.prebid.server.metric.Metrics;
import org.prebid.server.settings.model.activity.privacy.AccountUSNatModuleConfig;

import java.util.List;
Expand All @@ -20,15 +25,22 @@

public class USNatModuleCreator implements PrivacyModuleCreator {

private static final Logger logger = LoggerFactory.getLogger(USNatModuleCreator.class);
private static final ConditionalLogger conditionalLogger = new ConditionalLogger(logger);

private static final Set<Integer> ALLOWED_SECTIONS_IDS =
PrivacySection.US_PRIVACY_SECTIONS.stream()
.map(PrivacySection::sectionId)
.collect(Collectors.toSet());

private final USNatGppReaderFactory gppReaderFactory;
private final Metrics metrics;
private final double samplingRate;

public USNatModuleCreator(USNatGppReaderFactory gppReaderFactory) {
public USNatModuleCreator(USNatGppReaderFactory gppReaderFactory, Metrics metrics, double samplingRate) {
this.gppReaderFactory = Objects.requireNonNull(gppReaderFactory);
this.metrics = Objects.requireNonNull(metrics);
this.samplingRate = samplingRate;
}

@Override
Expand All @@ -48,6 +60,7 @@ public PrivacyModule from(PrivacyModuleCreationContext creationContext) {
sectionId,
scope.getGppModel(),
moduleConfig.getConfig()))
.filter(Objects::nonNull)
.toList();

return new AndPrivacyModules(innerPrivacyModules);
Expand All @@ -70,6 +83,16 @@ private PrivacyModule forSection(Activity activity,
GppModel gppModel,
AccountUSNatModuleConfig.Config config) {

return new USNatModule(activity, gppReaderFactory.forSection(sectionId, gppModel), config);
try {
return new USNatModule(activity, gppReaderFactory.forSection(sectionId, gppModel), config);
} catch (Exception e) {
conditionalLogger.error(
"UsNat privacy module creation failed: %s. Activity: %s. Section: %s. Gpp: %s.".formatted(
e.getMessage(), activity, sectionId, gppModel != null ? gppModel.encode() : null),
samplingRate);
metrics.updateAlertsMetrics(MetricName.general);

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
import org.prebid.server.activity.infrastructure.privacy.SkippedPrivacyModule;
import org.prebid.server.activity.infrastructure.rule.AndRule;
import org.prebid.server.activity.infrastructure.rule.Rule;
import org.prebid.server.log.Logger;
import org.prebid.server.log.LoggerFactory;
import org.prebid.server.metric.MetricName;
import org.prebid.server.metric.Metrics;
import org.prebid.server.settings.model.activity.privacy.AccountPrivacyModuleConfig;
import org.prebid.server.settings.model.activity.rule.AccountActivityPrivacyModulesRuleConfig;

Expand All @@ -23,15 +27,19 @@

public class PrivacyModulesRuleCreator extends AbstractRuleCreator<AccountActivityPrivacyModulesRuleConfig> {

private static final Logger logger = LoggerFactory.getLogger(PrivacyModulesRuleCreator.class);

private static final String WILDCARD = "*";

private final Map<PrivacyModuleQualifier, PrivacyModuleCreator> privacyModulesCreators;
private final Metrics metrics;

public PrivacyModulesRuleCreator(List<PrivacyModuleCreator> privacyModulesCreators) {
public PrivacyModulesRuleCreator(List<PrivacyModuleCreator> privacyModulesCreators, Metrics metrics) {
super(AccountActivityPrivacyModulesRuleConfig.class);

this.privacyModulesCreators = Objects.requireNonNull(privacyModulesCreators).stream()
.collect(Collectors.toMap(PrivacyModuleCreator::qualifier, UnaryOperator.identity()));
this.metrics = Objects.requireNonNull(metrics);
}

@Override
Expand All @@ -44,8 +52,8 @@ protected Rule fromConfiguration(AccountActivityPrivacyModulesRuleConfig ruleCon
.map(configuredModuleName -> mapToModulesQualifiers(configuredModuleName, creationContext))
.flatMap(Collection::stream)
.filter(qualifier -> !creationContext.isUsed(qualifier))
.peek(creationContext::use)
.map(qualifier -> createPrivacyModule(qualifier, creationContext))
.filter(Objects::nonNull)
.toList();

return new AndRule(privacyModules);
Expand Down Expand Up @@ -84,11 +92,22 @@ private PrivacyModule createPrivacyModule(PrivacyModuleQualifier privacyModuleQu
ActivityControllerCreationContext creationContext) {

if (creationContext.getSkipPrivacyModules().contains(privacyModuleQualifier)) {
creationContext.use(privacyModuleQualifier);
return new SkippedPrivacyModule(privacyModuleQualifier);
}

return privacyModulesCreators.get(privacyModuleQualifier)
.from(creationContext(privacyModuleQualifier, creationContext));
try {
final PrivacyModule privacyModule = privacyModulesCreators.get(privacyModuleQualifier)
.from(creationContext(privacyModuleQualifier, creationContext));
creationContext.use(privacyModuleQualifier);

return privacyModule;
} catch (Exception e) {
logger.error("PrivacyModule %s creation failed: %s.".formatted(privacyModuleQualifier, e.getMessage()));
metrics.updateAlertsMetrics(MetricName.general);

return null;
}
}

private static PrivacyModuleCreationContext creationContext(PrivacyModuleQualifier privacyModuleQualifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ USNatGppReaderFactory usNatGppReaderFactory() {
}

@Bean
USNatModuleCreator usNatModuleCreator(USNatGppReaderFactory gppReaderFactory) {
return new USNatModuleCreator(gppReaderFactory);
USNatModuleCreator usNatModuleCreator(USNatGppReaderFactory gppReaderFactory,
Metrics metrics,
@Value("${logging.sampling-rate:0.01}") double logSamplingRate) {

return new USNatModuleCreator(gppReaderFactory, metrics, logSamplingRate);
}
}

Expand All @@ -54,9 +57,16 @@ USCustomLogicModuleCreator usCustomLogicModuleCreator(
JsonLogic jsonLogic,
@Value("${settings.in-memory-cache.ttl-seconds:#{null}}") Integer ttlSeconds,
@Value("${settings.in-memory-cache.cache-size:#{null}}") Integer cacheSize,
Metrics metrics) {

return new USCustomLogicModuleCreator(gppReaderFactory, jsonLogic, ttlSeconds, cacheSize, metrics);
Metrics metrics,
@Value("${logging.sampling-rate:0.01}") double logSamplingRate) {

return new USCustomLogicModuleCreator(
gppReaderFactory,
jsonLogic,
ttlSeconds,
cacheSize,
metrics,
logSamplingRate);
}
}
}
Expand All @@ -65,13 +75,15 @@ USCustomLogicModuleCreator usCustomLogicModuleCreator(
static class RuleCreatorConfiguration {

@Bean
ConditionsRuleCreator geoRuleCreator() {
ConditionsRuleCreator conditionsRuleCreator() {
return new ConditionsRuleCreator();
}

@Bean
PrivacyModulesRuleCreator privacyModulesRuleCreator(List<PrivacyModuleCreator> privacyModuleCreators) {
return new PrivacyModulesRuleCreator(privacyModuleCreators);
PrivacyModulesRuleCreator privacyModulesRuleCreator(List<PrivacyModuleCreator> privacyModuleCreators,
Metrics metrics) {

return new PrivacyModulesRuleCreator(privacyModuleCreators, metrics);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import org.prebid.server.functional.util.privacy.gpp.data.UsUtahSensitiveData

import java.time.Instant

import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST
import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED
import static org.prebid.server.functional.model.config.DataActivity.CONSENT
import static org.prebid.server.functional.model.config.DataActivity.NOTICE_NOT_PROVIDED
Expand Down Expand Up @@ -825,8 +824,11 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec {
new EqualityValueRule(PERSONAL_DATA_CONSENTS, NOTICE_NOT_PROVIDED)]
}

def "PBS auction call when custom privacy regulation empty and normalize is disabled should respond with an error and update metric"() {
given: "Generic BidRequest with gpp and account setup"
def "PBS auction call when custom privacy regulation empty and normalize is disabled should process request and emit error log"() {
given: "Test start time"
def startTime = Instant.now()

and: "Generic BidRequest with gpp and account setup"
def gppConsent = new UsNatV1Consent.Builder().setGpc(true).build()
def accountId = PBSUtils.randomNumber as String
def bidRequest = BidRequest.defaultBidRequest.tap {
Expand Down Expand Up @@ -860,14 +862,16 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec {
when: "PBS processes auction requests"
activityPbsService.sendAuctionRequest(bidRequest)

then: "Response should contain error"
def error = thrown(PrebidServerException)
assert error.statusCode == BAD_REQUEST.code()
assert error.responseBody == "JsonLogic exception: objects must have exactly 1 key defined, found 0"
then: "Generic bidder should be called due to positive allow in activities"
assert bidder.getBidderRequest(bidRequest.id)

and: "Metrics for disallowed activities should be updated"
def metrics = activityPbsService.sendCollectedMetricsRequest()
assert metrics[ALERT_GENERAL] == 1

and: "Logs should contain error"
def logs = activityPbsService.getLogsByTime(startTime)
assert getLogsByText(logs, "USCustomLogic creation failed: objects must have exactly 1 key defined, found 0").size() == 1
}

def "PBS auction call when custom privacy regulation with normalizing should ignore call to bidder"() {
Expand Down Expand Up @@ -1571,8 +1575,11 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec {
new EqualityValueRule(PERSONAL_DATA_CONSENTS, NOTICE_NOT_PROVIDED)]
}

def "PBS amp call when custom privacy regulation empty and normalize is disabled should respond with an error and update metric"() {
given: "Store bid request with gpp string and link for account"
def "PBS amp call when custom privacy regulation empty and normalize is disabled should process request and emit error log"() {
given: "Test start time"
def startTime = Instant.now()

and: "Store bid request with gpp string and link for account"
def accountId = PBSUtils.randomNumber as String
def gppConsent = new UsNatV1Consent.Builder().setGpc(true).build()
def ampStoredRequest = BidRequest.defaultBidRequest.tap {
Expand Down Expand Up @@ -1615,15 +1622,16 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec {
when: "PBS processes amp requests"
activityPbsService.sendAmpRequest(ampRequest)

then: "Response should contain error"
def error = thrown(PrebidServerException)
assert error.statusCode == BAD_REQUEST.code()
assert error.responseBody == "Invalid account configuration: JsonLogic exception: " +
"objects must have exactly 1 key defined, found 0"
then: "Generic bidder should be called"
assert bidder.getBidderRequests(ampStoredRequest.id)

and: "Metrics for disallowed activities should be updated"
def metrics = activityPbsService.sendCollectedMetricsRequest()
assert metrics[ALERT_GENERAL] == 1

and: "Logs should contain error"
def logs = activityPbsService.getLogsByTime(startTime)
assert getLogsByText(logs, "USCustomLogic creation failed: objects must have exactly 1 key defined, found 0").size() == 1
}

def "PBS amp call when custom privacy regulation with normalizing should ignore call to bidder"() {
Expand Down
Loading
Loading