diff --git a/src/main/java/org/prebid/server/activity/infrastructure/ActivityConfiguration.java b/src/main/java/org/prebid/server/activity/infrastructure/ActivityController.java similarity index 51% rename from src/main/java/org/prebid/server/activity/infrastructure/ActivityConfiguration.java rename to src/main/java/org/prebid/server/activity/infrastructure/ActivityController.java index bfd74f2bede..ea1dd27ad5a 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/ActivityConfiguration.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/ActivityController.java @@ -5,38 +5,35 @@ import java.util.List; import java.util.Objects; -import java.util.Optional; -public class ActivityConfiguration { +public class ActivityController { private final boolean allowByDefault; private final List rules; - private ActivityConfiguration(boolean allowByDefault, List rules) { + private ActivityController(boolean allowByDefault, List rules) { this.allowByDefault = allowByDefault; this.rules = Objects.requireNonNull(rules); } - public static ActivityConfiguration of(boolean allowByDefault, List rules) { - return new ActivityConfiguration(allowByDefault, rules); + public static ActivityController of(boolean allowByDefault, List rules) { + return new ActivityController(allowByDefault, rules); } public ActivityCallResult isAllowed(ActivityCallPayload activityCallPayload) { int processedRulesCount = 0; - Rule matchedRule = null; + boolean result = allowByDefault; for (Rule rule : rules) { processedRulesCount++; - if (rule.matches(activityCallPayload)) { - matchedRule = rule; + + final Rule.Result ruleResult = rule.proceed(activityCallPayload); + if (ruleResult != Rule.Result.ABSTAIN) { + result = ruleResult == Rule.Result.ALLOW; break; } } - return ActivityCallResult.of( - Optional.ofNullable(matchedRule) - .map(Rule::allowed) - .orElse(allowByDefault), - processedRulesCount); + return ActivityCallResult.of(result, processedRulesCount); } } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/ActivityInfrastructure.java b/src/main/java/org/prebid/server/activity/infrastructure/ActivityInfrastructure.java index ae423feda91..d41246441dd 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/ActivityInfrastructure.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/ActivityInfrastructure.java @@ -15,31 +15,31 @@ public class ActivityInfrastructure { public static final boolean ALLOW_ACTIVITY_BY_DEFAULT = true; private final String accountId; - private final Map activitiesConfigurations; + private final Map activitiesControllers; private final TraceLevel traceLevel; private final Metrics metrics; public ActivityInfrastructure(String accountId, - Map activitiesConfigurations, + Map activitiesControllers, TraceLevel traceLevel, Metrics metrics) { - validate(activitiesConfigurations); + validate(activitiesControllers); this.accountId = accountId; - this.activitiesConfigurations = activitiesConfigurations; + this.activitiesControllers = activitiesControllers; this.traceLevel = Objects.requireNonNull(traceLevel); this.metrics = Objects.requireNonNull(metrics); } - private static void validate(Map activitiesConfigurations) { - if (activitiesConfigurations == null || activitiesConfigurations.size() != Activity.values().length) { - throw new AssertionError("Activities configuration must include all possible activities."); + private static void validate(Map activitiesControllers) { + if (activitiesControllers == null || activitiesControllers.size() != Activity.values().length) { + throw new AssertionError("Activities controllers must include all possible activities."); } } public boolean isAllowed(Activity activity, ActivityCallPayload activityCallPayload) { - final ActivityCallResult result = activitiesConfigurations.get(activity).isAllowed(activityCallPayload); + final ActivityCallResult result = activitiesControllers.get(activity).isAllowed(activityCallPayload); updateMetrics(activity, activityCallPayload, result); return result.isAllowed(); } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java new file mode 100644 index 00000000000..1651d1bc3a6 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java @@ -0,0 +1,36 @@ +package org.prebid.server.activity.infrastructure.creator; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.Value; +import org.prebid.server.activity.Activity; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; +import org.prebid.server.auction.gpp.model.GppContext; +import org.prebid.server.settings.model.activity.privacy.AccountPrivacyModuleConfig; + +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; + +@Value(staticConstructor = "of") +public class ActivityControllerCreationContext { + + Activity activity; + + Map privacyModulesConfigs; + + @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) + Set usedPrivacyModules = EnumSet.noneOf(PrivacyModuleQualifier.class); + + GppContext gppContext; + + public boolean isUsed(PrivacyModuleQualifier qualifier) { + return usedPrivacyModules.contains(qualifier); + } + + public void use(PrivacyModuleQualifier qualifier) { + usedPrivacyModules.add(qualifier); + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java index fded14436f9..1330608f327 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java @@ -1,16 +1,21 @@ package org.prebid.server.activity.infrastructure.creator; +import io.vertx.core.logging.Logger; +import io.vertx.core.logging.LoggerFactory; import org.apache.commons.collections4.ListUtils; import org.prebid.server.activity.Activity; -import org.prebid.server.activity.infrastructure.ActivityConfiguration; +import org.prebid.server.activity.infrastructure.ActivityController; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; import org.prebid.server.activity.infrastructure.rule.Rule; import org.prebid.server.auction.gpp.model.GppContext; +import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; import org.prebid.server.proto.openrtb.ext.request.TraceLevel; import org.prebid.server.settings.model.Account; import org.prebid.server.settings.model.AccountPrivacyConfig; import org.prebid.server.settings.model.activity.AccountActivityConfiguration; +import org.prebid.server.settings.model.activity.privacy.AccountPrivacyModuleConfig; import java.util.Arrays; import java.util.Collections; @@ -19,12 +24,15 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.BinaryOperator; import java.util.function.Supplier; import java.util.function.UnaryOperator; import java.util.stream.Collectors; public class ActivityInfrastructureCreator { + private static final Logger logger = LoggerFactory.getLogger(ActivityInfrastructureCreator.class); + private final ActivityRuleFactory activityRuleFactory; private final Metrics metrics; @@ -41,40 +49,67 @@ public ActivityInfrastructure create(Account account, GppContext gppContext, Tra metrics); } - Map parse(Account account, GppContext gppContext) { - final Map activitiesConfiguration = - Optional.ofNullable(account) - .map(Account::getPrivacy) - .map(AccountPrivacyConfig::getActivities) - .orElse(Collections.emptyMap()); + Map parse(Account account, GppContext gppContext) { + final Optional accountPrivacyConfig = Optional.ofNullable(account.getPrivacy()); + + final Map activitiesConfiguration = accountPrivacyConfig + .map(AccountPrivacyConfig::getActivities) + .orElseGet(Collections::emptyMap); + final Map modulesConfigs = accountPrivacyConfig + .map(AccountPrivacyConfig::getModules) + .orElseGet(Collections::emptyList) + .stream() + .collect(Collectors.toMap( + AccountPrivacyModuleConfig::getCode, + UnaryOperator.identity(), + takeFirstAndLogDuplicates(account.getId()))); return Arrays.stream(Activity.values()) .collect(Collectors.toMap( UnaryOperator.identity(), - activity -> from(activitiesConfiguration.get(activity), gppContext), + activity -> from(activity, activitiesConfiguration.get(activity), modulesConfigs, gppContext), (oldValue, newValue) -> newValue, enumMapFactory())); } - private ActivityConfiguration from(AccountActivityConfiguration activityConfiguration, GppContext gppContext) { + private BinaryOperator takeFirstAndLogDuplicates(String accountId) { + return (first, second) -> { + logger.warn("Duplicate configuration found for privacy module %s for account %s" + .formatted(second.getCode(), accountId)); + metrics.updateAlertsMetrics(MetricName.general); + + return first; + }; + } + + private ActivityController from(Activity activity, + AccountActivityConfiguration activityConfiguration, + Map modulesConfigs, + GppContext gppContext) { + if (activityConfiguration == null) { - return ActivityConfiguration.of(ActivityInfrastructure.ALLOW_ACTIVITY_BY_DEFAULT, Collections.emptyList()); + return ActivityController.of(ActivityInfrastructure.ALLOW_ACTIVITY_BY_DEFAULT, Collections.emptyList()); } + final ActivityControllerCreationContext creationContext = ActivityControllerCreationContext.of( + activity, + modulesConfigs, + gppContext); + final boolean allow = allowFromConfig(activityConfiguration.getAllow()); final List rules = ListUtils.emptyIfNull(activityConfiguration.getRules()).stream() .filter(Objects::nonNull) - .map(ruleConfiguration -> activityRuleFactory.from(ruleConfiguration, gppContext)) + .map(ruleConfiguration -> activityRuleFactory.from(ruleConfiguration, creationContext)) .toList(); - return ActivityConfiguration.of(allow, rules); + return ActivityController.of(allow, rules); } private static boolean allowFromConfig(Boolean configValue) { return configValue != null ? configValue : ActivityInfrastructure.ALLOW_ACTIVITY_BY_DEFAULT; } - private static Supplier> enumMapFactory() { + private static Supplier> enumMapFactory() { return () -> new EnumMap<>(Activity.class); } } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityRuleFactory.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityRuleFactory.java index 03b07c3921e..3aaf4f8c21b 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityRuleFactory.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityRuleFactory.java @@ -3,7 +3,6 @@ import org.apache.commons.collections4.CollectionUtils; import org.prebid.server.activity.infrastructure.creator.rule.RuleCreator; import org.prebid.server.activity.infrastructure.rule.Rule; -import org.prebid.server.auction.gpp.model.GppContext; import java.util.List; import java.util.Map; @@ -21,13 +20,13 @@ public ActivityRuleFactory(List> ruleCreators) { Function.identity())); } - public Rule from(Object ruleConfiguration, GppContext gppContext) { + public Rule from(Object ruleConfiguration, ActivityControllerCreationContext activityControllerCreationContext) { final Class ruleConfigurationClass = ruleConfiguration.getClass(); final RuleCreator ruleCreator = ruleCreators.get(ruleConfigurationClass); if (ruleCreator == null) { throw new IllegalStateException("Rule creator for %s not found.".formatted(ruleConfigurationClass)); } - return ruleCreator.from(ruleConfiguration, gppContext); + return ruleCreator.from(ruleConfiguration, activityControllerCreationContext); } } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/PrivacyModuleCreationContext.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/PrivacyModuleCreationContext.java new file mode 100644 index 00000000000..839799eca2d --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/PrivacyModuleCreationContext.java @@ -0,0 +1,16 @@ +package org.prebid.server.activity.infrastructure.creator; + +import lombok.Value; +import org.prebid.server.activity.Activity; +import org.prebid.server.auction.gpp.model.GppContext; +import org.prebid.server.settings.model.activity.privacy.AccountPrivacyModuleConfig; + +@Value(staticConstructor = "of") +public class PrivacyModuleCreationContext { + + Activity activity; + + AccountPrivacyModuleConfig privacyModuleConfig; + + GppContext gppContext; +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/PrivacyModuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/PrivacyModuleCreator.java new file mode 100644 index 00000000000..d8ad41439d4 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/PrivacyModuleCreator.java @@ -0,0 +1,12 @@ +package org.prebid.server.activity.infrastructure.creator.privacy; + +import org.prebid.server.activity.infrastructure.creator.PrivacyModuleCreationContext; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; + +public interface PrivacyModuleCreator { + + PrivacyModuleQualifier qualifier(); + + PrivacyModule from(PrivacyModuleCreationContext creationContext); +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatGppReaderFactory.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatGppReaderFactory.java new file mode 100644 index 00000000000..2fdb7dcfe3c --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatGppReaderFactory.java @@ -0,0 +1,24 @@ +package org.prebid.server.activity.infrastructure.creator.privacy.usnat; + +import com.iab.gpp.encoder.GppModel; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USCaliforniaGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USColoradoGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USConnecticutGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USNationalGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USUtahGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USVirginiaGppReader; + +public class USNatGppReaderFactory { + + public USNatGppReader forSection(Integer sectionId, GppModel gppModel) { + return switch (USNatSection.from(sectionId)) { + case NATIONAL -> new USNationalGppReader(gppModel); + case CALIFORNIA -> new USCaliforniaGppReader(gppModel); + case VIRGINIA -> new USVirginiaGppReader(gppModel); + case COLORADO -> new USColoradoGppReader(gppModel); + case UTAH -> new USUtahGppReader(gppModel); + case CONNECTICUT -> new USConnecticutGppReader(gppModel); + }; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreator.java new file mode 100644 index 00000000000..2c1b5a1cb1c --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreator.java @@ -0,0 +1,68 @@ +package org.prebid.server.activity.infrastructure.creator.privacy.usnat; + +import com.iab.gpp.encoder.GppModel; +import org.apache.commons.collections4.SetUtils; +import org.prebid.server.activity.Activity; +import org.prebid.server.activity.infrastructure.creator.PrivacyModuleCreationContext; +import org.prebid.server.activity.infrastructure.creator.privacy.PrivacyModuleCreator; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatModule; +import org.prebid.server.activity.infrastructure.rule.AndRule; +import org.prebid.server.auction.gpp.model.GppContext; +import org.prebid.server.settings.model.activity.privacy.AccountUSNatModuleConfig; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class USNatModuleCreator implements PrivacyModuleCreator { + + private static final Set ALLOWED_SECTIONS_IDS = + Arrays.stream(USNatSection.values()) + .map(USNatSection::sectionId) + .collect(Collectors.toSet()); + + private final USNatGppReaderFactory gppReaderFactory; + + public USNatModuleCreator(USNatGppReaderFactory gppReaderFactory) { + this.gppReaderFactory = Objects.requireNonNull(gppReaderFactory); + } + + @Override + public PrivacyModuleQualifier qualifier() { + return PrivacyModuleQualifier.US_NAT; + } + + @Override + public PrivacyModule from(PrivacyModuleCreationContext creationContext) { + final AccountUSNatModuleConfig moduleConfig = moduleConfig(creationContext); + final GppContext.Scope scope = creationContext.getGppContext().scope(); + + final List innerPrivacyModules = SetUtils.emptyIfNull(scope.getSectionsIds()).stream() + .filter(sectionId -> !shouldSkip(sectionId, moduleConfig)) + .map(sectionId -> forSection(creationContext.getActivity(), sectionId, scope.getGppModel())) + .toList(); + + final AndRule andRule = new AndRule(innerPrivacyModules); + return andRule::proceed; + } + + private static AccountUSNatModuleConfig moduleConfig(PrivacyModuleCreationContext creationContext) { + return (AccountUSNatModuleConfig) creationContext.getPrivacyModuleConfig(); + } + + private static boolean shouldSkip(Integer sectionId, AccountUSNatModuleConfig moduleConfig) { + final AccountUSNatModuleConfig.Config config = moduleConfig.getConfig(); + final List skipSectionIds = config != null ? config.getSkipSids() : null; + + return !ALLOWED_SECTIONS_IDS.contains(sectionId) + || (skipSectionIds != null && skipSectionIds.contains(sectionId)); + } + + private PrivacyModule forSection(Activity activity, Integer sectionId, GppModel gppModel) { + return new USNatModule(activity, gppReaderFactory.forSection(sectionId, gppModel)); + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatSection.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatSection.java new file mode 100644 index 00000000000..3451c4c1270 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatSection.java @@ -0,0 +1,38 @@ +package org.prebid.server.activity.infrastructure.creator.privacy.usnat; + +import com.iab.gpp.encoder.section.UspCaV1; +import com.iab.gpp.encoder.section.UspCoV1; +import com.iab.gpp.encoder.section.UspCtV1; +import com.iab.gpp.encoder.section.UspNatV1; +import com.iab.gpp.encoder.section.UspUtV1; +import com.iab.gpp.encoder.section.UspVaV1; + +public enum USNatSection { + + NATIONAL(UspNatV1.ID), + CALIFORNIA(UspCaV1.ID), + VIRGINIA(UspVaV1.ID), + COLORADO(UspCoV1.ID), + UTAH(UspUtV1.ID), + CONNECTICUT(UspCtV1.ID); + + private final int sectionId; + + USNatSection(int sectionId) { + this.sectionId = sectionId; + } + + public Integer sectionId() { + return sectionId; + } + + public static USNatSection from(int sectionId) { + final USNatSection[] values = USNatSection.values(); + if (sectionId < NATIONAL.sectionId || sectionId > CONNECTICUT.sectionId) { + throw new IllegalArgumentException("US sectionId must be in [%s, %s]." + .formatted(NATIONAL.sectionId, CONNECTICUT.sectionId)); + } + + return values[sectionId - NATIONAL.sectionId]; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/AbstractRuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/AbstractRuleCreator.java index 19a170be9b5..48952a40691 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/AbstractRuleCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/AbstractRuleCreator.java @@ -1,7 +1,7 @@ package org.prebid.server.activity.infrastructure.creator.rule; +import org.prebid.server.activity.infrastructure.creator.ActivityControllerCreationContext; import org.prebid.server.activity.infrastructure.rule.Rule; -import org.prebid.server.auction.gpp.model.GppContext; import java.util.Objects; @@ -19,13 +19,15 @@ public Class relatedConfigurationClass() { } @Override - public Rule from(Object ruleConfiguration, GppContext gppContext) { + public Rule from(Object ruleConfiguration, ActivityControllerCreationContext creationContext) { if (!relatedConfigurationClass.isInstance(ruleConfiguration)) { throw new AssertionError(); } - return fromConfiguration(relatedConfigurationClass.cast(ruleConfiguration), gppContext); + return fromConfiguration( + relatedConfigurationClass.cast(ruleConfiguration), + creationContext); } - protected abstract Rule fromConfiguration(T ruleConfiguration, GppContext gppContext); + protected abstract Rule fromConfiguration(T ruleConfiguration, ActivityControllerCreationContext creationContext); } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/ComponentRuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/ComponentRuleCreator.java index e8e26a8a04a..fe3fa76bd05 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/ComponentRuleCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/ComponentRuleCreator.java @@ -1,9 +1,9 @@ package org.prebid.server.activity.infrastructure.creator.rule; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; +import org.prebid.server.activity.infrastructure.creator.ActivityControllerCreationContext; import org.prebid.server.activity.infrastructure.rule.ComponentRule; import org.prebid.server.activity.infrastructure.rule.Rule; -import org.prebid.server.auction.gpp.model.GppContext; import org.prebid.server.settings.model.activity.rule.AccountActivityComponentRuleConfig; import java.util.Collection; @@ -17,7 +17,9 @@ public ComponentRuleCreator() { } @Override - protected Rule fromConfiguration(AccountActivityComponentRuleConfig ruleConfiguration, GppContext gppContext) { + protected Rule fromConfiguration(AccountActivityComponentRuleConfig ruleConfiguration, + ActivityControllerCreationContext creationContext) { + final boolean allow = allowFromConfig(ruleConfiguration.getAllow()); final AccountActivityComponentRuleConfig.Condition condition = ruleConfiguration.getCondition(); diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/GeoRuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/GeoRuleCreator.java index 6a6892511ba..55be3685fed 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/GeoRuleCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/GeoRuleCreator.java @@ -3,9 +3,9 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; +import org.prebid.server.activity.infrastructure.creator.ActivityControllerCreationContext; import org.prebid.server.activity.infrastructure.rule.GeoRule; import org.prebid.server.activity.infrastructure.rule.Rule; -import org.prebid.server.auction.gpp.model.GppContext; import org.prebid.server.settings.model.activity.rule.AccountActivityGeoRuleConfig; import java.util.Collection; @@ -21,14 +21,16 @@ public GeoRuleCreator() { } @Override - protected Rule fromConfiguration(AccountActivityGeoRuleConfig ruleConfiguration, GppContext gppContext) { + protected Rule fromConfiguration(AccountActivityGeoRuleConfig ruleConfiguration, + ActivityControllerCreationContext creationContext) { + final boolean allow = allowFromConfig(ruleConfiguration.getAllow()); final AccountActivityGeoRuleConfig.Condition condition = ruleConfiguration.getCondition(); return new GeoRule( condition != null ? setOf(condition.getComponentTypes()) : null, condition != null ? setOf(condition.getComponentNames()) : null, - sidsMatched(condition, gppContext.scope().getSectionsIds()), + sidsMatched(condition, creationContext.getGppContext().scope().getSectionsIds()), condition != null ? geoCodes(condition.getGeoCodes()) : null, condition != null ? condition.getGpc() : null, allow); diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java new file mode 100644 index 00000000000..bb68ff60df0 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java @@ -0,0 +1,97 @@ +package org.prebid.server.activity.infrastructure.creator.rule; + +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.activity.infrastructure.creator.ActivityControllerCreationContext; +import org.prebid.server.activity.infrastructure.creator.PrivacyModuleCreationContext; +import org.prebid.server.activity.infrastructure.creator.privacy.PrivacyModuleCreator; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; +import org.prebid.server.activity.infrastructure.rule.AndRule; +import org.prebid.server.activity.infrastructure.rule.Rule; +import org.prebid.server.settings.model.activity.privacy.AccountPrivacyModuleConfig; +import org.prebid.server.settings.model.activity.rule.AccountActivityPrivacyModulesRuleConfig; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +public class PrivacyModulesRuleCreator extends AbstractRuleCreator { + + private static final String WILDCARD = "*"; + + private final Map privacyModulesCreators; + + public PrivacyModulesRuleCreator(List privacyModulesCreators) { + super(AccountActivityPrivacyModulesRuleConfig.class); + + this.privacyModulesCreators = Objects.requireNonNull(privacyModulesCreators).stream() + .collect(Collectors.toMap(PrivacyModuleCreator::qualifier, UnaryOperator.identity())); + } + + @Override + protected Rule fromConfiguration(AccountActivityPrivacyModulesRuleConfig ruleConfiguration, + ActivityControllerCreationContext creationContext) { + + final List configuredModulesNames = ruleConfiguration.getPrivacyModules(); + + final List privacyModules = ListUtils.emptyIfNull(configuredModulesNames).stream() + .map(configuredModuleName -> mapToModulesQualifiers(configuredModuleName, creationContext)) + .flatMap(Collection::stream) + .filter(qualifier -> !creationContext.isUsed(qualifier)) + .peek(creationContext::use) + .map(qualifier -> createPrivacyModule(qualifier, creationContext)) + .toList(); + + return new AndRule(privacyModules); + } + + private List mapToModulesQualifiers( + String configuredModuleName, + ActivityControllerCreationContext creationContext) { + + if (StringUtils.isBlank(configuredModuleName)) { + return Collections.emptyList(); + } + + final String moduleNamePattern = eraseWildcard(configuredModuleName); + return creationContext.getPrivacyModulesConfigs().entrySet().stream() + .filter(entry -> isModuleEnabled(entry.getValue())) + .map(Map.Entry::getKey) + .filter(qualifier -> qualifier.moduleName().startsWith(moduleNamePattern)) + .filter(privacyModulesCreators::containsKey) + .toList(); + } + + private static String eraseWildcard(String configuredModuleName) { + final int wildcardIndex = configuredModuleName.indexOf(WILDCARD); + return wildcardIndex != -1 + ? configuredModuleName.substring(0, wildcardIndex) + : configuredModuleName; + } + + private static boolean isModuleEnabled(AccountPrivacyModuleConfig accountPrivacyModuleConfig) { + final Boolean enabled = accountPrivacyModuleConfig.enabled(); + return enabled == null || enabled; + } + + private PrivacyModule createPrivacyModule(PrivacyModuleQualifier privacyModuleQualifier, + ActivityControllerCreationContext creationContext) { + + return privacyModulesCreators.get(privacyModuleQualifier) + .from(creationContext(privacyModuleQualifier, creationContext)); + } + + private static PrivacyModuleCreationContext creationContext(PrivacyModuleQualifier privacyModuleQualifier, + ActivityControllerCreationContext creationContext) { + + return PrivacyModuleCreationContext.of( + creationContext.getActivity(), + creationContext.getPrivacyModulesConfigs().get(privacyModuleQualifier), + creationContext.getGppContext()); + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/RuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/RuleCreator.java index 71215e43927..6d06949ccd1 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/RuleCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/RuleCreator.java @@ -1,11 +1,11 @@ package org.prebid.server.activity.infrastructure.creator.rule; +import org.prebid.server.activity.infrastructure.creator.ActivityControllerCreationContext; import org.prebid.server.activity.infrastructure.rule.Rule; -import org.prebid.server.auction.gpp.model.GppContext; public interface RuleCreator { Class relatedConfigurationClass(); - Rule from(Object ruleConfiguration, GppContext gppContext); + Rule from(Object ruleConfiguration, ActivityControllerCreationContext activityControllerCreationContext); } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/PrivacyModule.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/PrivacyModule.java new file mode 100644 index 00000000000..289813f565a --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/PrivacyModule.java @@ -0,0 +1,6 @@ +package org.prebid.server.activity.infrastructure.privacy; + +import org.prebid.server.activity.infrastructure.rule.Rule; + +public interface PrivacyModule extends Rule { +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/PrivacyModuleQualifier.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/PrivacyModuleQualifier.java new file mode 100644 index 00000000000..d0ed9a6b02e --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/PrivacyModuleQualifier.java @@ -0,0 +1,24 @@ +package org.prebid.server.activity.infrastructure.privacy; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum PrivacyModuleQualifier { + + @JsonProperty(Names.US_NAT) + US_NAT(Names.US_NAT); + + private final String moduleName; + + PrivacyModuleQualifier(String moduleName) { + this.moduleName = moduleName; + } + + public String moduleName() { + return moduleName; + } + + public static class Names { + + public static final String US_NAT = "iab.usgeneral"; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/USNatGppReader.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/USNatGppReader.java new file mode 100644 index 00000000000..007041d44aa --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/USNatGppReader.java @@ -0,0 +1,34 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat; + +import java.util.List; + +public interface USNatGppReader { + + Integer getMspaServiceProviderMode(); + + Boolean getGpc(); + + Integer getSaleOptOut(); + + Integer getSaleOptOutNotice(); + + Integer getSharingNotice(); + + Integer getSharingOptOut(); + + Integer getSharingOptOutNotice(); + + Integer getTargetedAdvertisingOptOut(); + + Integer getTargetedAdvertisingOptOutNotice(); + + Integer getSensitiveDataLimitUseNotice(); + + Integer getSensitiveDataProcessingOptOutNotice(); + + List getSensitiveDataProcessing(); + + List getKnownChildSensitiveDataConsents(); + + Integer getPersonalDataConsents(); +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/USNatModule.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/USNatModule.java new file mode 100644 index 00000000000..a6a621dfa15 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/USNatModule.java @@ -0,0 +1,37 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat; + +import org.prebid.server.activity.Activity; +import org.prebid.server.activity.infrastructure.payload.ActivityCallPayload; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.USNatDefault; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.USNatSyncUser; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.USNatTransmitGeo; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.USNatTransmitUfpd; + +import java.util.Objects; + +public class USNatModule implements PrivacyModule { + + private final PrivacyModule innerModule; + + public USNatModule(Activity activity, USNatGppReader gppReader) { + Objects.requireNonNull(activity); + Objects.requireNonNull(gppReader); + + innerModule = innerModule(activity, gppReader); + } + + private static PrivacyModule innerModule(Activity activity, USNatGppReader gppReader) { + return switch (activity) { + case SYNC_USER, MODIFY_UFDP -> new USNatSyncUser(gppReader); + case TRANSMIT_UFPD -> new USNatTransmitUfpd(gppReader); + case TRANSMIT_GEO -> new USNatTransmitGeo(gppReader); + case CALL_BIDDER, REPORT_ANALYTICS -> USNatDefault.instance(); + }; + } + + @Override + public Result proceed(ActivityCallPayload activityCallPayload) { + return innerModule.proceed(activityCallPayload); + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatDefault.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatDefault.java new file mode 100644 index 00000000000..8d2aa28faae --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatDefault.java @@ -0,0 +1,18 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner; + +import org.prebid.server.activity.infrastructure.payload.ActivityCallPayload; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; + +public class USNatDefault implements PrivacyModule { + + private static final USNatDefault INSTANCE = new USNatDefault(); + + public static PrivacyModule instance() { + return INSTANCE; + } + + @Override + public Result proceed(ActivityCallPayload activityCallPayload) { + return Result.ABSTAIN; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatSyncUser.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatSyncUser.java new file mode 100644 index 00000000000..0e79bd57319 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatSyncUser.java @@ -0,0 +1,94 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner; + +import org.prebid.server.activity.infrastructure.payload.ActivityCallPayload; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.Gpc; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.KnownChildSensitiveDataConsent; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.MspaServiceProviderMode; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.PersonalDataConsents; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SaleOptOut; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SaleOptOutNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SharingNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SharingOptOut; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SharingOptOutNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.TargetedAdvertisingOptOut; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.TargetedAdvertisingOptOutNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.USNatField; + +import java.util.List; +import java.util.Objects; + +public class USNatSyncUser implements PrivacyModule { + + private final Result result; + + public USNatSyncUser(USNatGppReader gppReader) { + result = disallow(gppReader) ? Result.DISALLOW : Result.ALLOW; + } + + @Override + public Result proceed(ActivityCallPayload activityCallPayload) { + return result; + } + + public static boolean disallow(USNatGppReader gppReader) { + return equals(gppReader.getMspaServiceProviderMode(), MspaServiceProviderMode.YES) + || equals(gppReader.getGpc(), Gpc.TRUE) + || checkSale(gppReader) + || checkSharing(gppReader) + || checkTargetedAdvertising(gppReader) + || checkKnownChildSensitiveDataConsents(gppReader) + || equals(gppReader.getPersonalDataConsents(), PersonalDataConsents.CONSENT); + } + + private static boolean checkSale(USNatGppReader gppReader) { + final Integer saleOptOut = gppReader.getSaleOptOut(); + final Integer saleOptOutNotice = gppReader.getSaleOptOutNotice(); + + return equals(saleOptOut, SaleOptOut.OPTED_OUT) + || equals(saleOptOutNotice, SaleOptOutNotice.NO) + || (equals(saleOptOutNotice, SaleOptOutNotice.NOT_APPLICABLE) + && equals(saleOptOut, SaleOptOut.DID_NOT_OPT_OUT)); + } + + private static boolean checkSharing(USNatGppReader gppReader) { + final Integer sharingNotice = gppReader.getSharingNotice(); + final Integer sharingOptOut = gppReader.getSharingOptOut(); + final Integer sharingOptOutNotice = gppReader.getSharingOptOutNotice(); + + return equals(sharingNotice, SharingNotice.NO) + || equals(sharingOptOut, SharingOptOut.OPTED_OUT) + || equals(sharingOptOutNotice, SharingOptOutNotice.NO) + || (equals(sharingNotice, SharingNotice.NOT_APPLICABLE) + && equals(sharingOptOut, SharingOptOut.DID_NOT_OPT_OUT)) + || (equals(sharingOptOutNotice, SharingOptOutNotice.NOT_APPLICABLE) + && equals(sharingOptOut, SharingOptOut.DID_NOT_OPT_OUT)); + } + + private static boolean checkTargetedAdvertising(USNatGppReader gppReader) { + final Integer targetedAdvertisingOptOut = gppReader.getTargetedAdvertisingOptOut(); + final Integer targetedAdvertisingOptOutNotice = gppReader.getTargetedAdvertisingOptOutNotice(); + + return equals(targetedAdvertisingOptOut, TargetedAdvertisingOptOut.OPTED_OUT) + || equals(targetedAdvertisingOptOutNotice, TargetedAdvertisingOptOutNotice.NO) + || (equals(targetedAdvertisingOptOutNotice, TargetedAdvertisingOptOutNotice.NOT_APPLICABLE) + && equals(targetedAdvertisingOptOut, TargetedAdvertisingOptOut.DID_NOT_OPT_OUT)); + } + + private static boolean checkKnownChildSensitiveDataConsents(USNatGppReader gppReader) { + final List knownChildSensitiveDataConsents = gppReader.getKnownChildSensitiveDataConsents(); + + return equalsAtIndex(KnownChildSensitiveDataConsent.NO_CONSENT, knownChildSensitiveDataConsents, 0) + || equalsAtIndex(KnownChildSensitiveDataConsent.NO_CONSENT, knownChildSensitiveDataConsents, 1) + || equalsAtIndex(KnownChildSensitiveDataConsent.CONSENT, knownChildSensitiveDataConsents, 1); + } + + private static boolean equalsAtIndex(USNatField expectedValue, List list, int index) { + return list != null && list.size() > index && equals(list.get(index), expectedValue); + } + + private static boolean equals(T providedValue, USNatField expectedValue) { + return Objects.equals(providedValue, expectedValue.value()); + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitGeo.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitGeo.java new file mode 100644 index 00000000000..8e8f7e241fc --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitGeo.java @@ -0,0 +1,67 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner; + +import org.prebid.server.activity.infrastructure.payload.ActivityCallPayload; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.Gpc; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.KnownChildSensitiveDataConsent; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.MspaServiceProviderMode; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.PersonalDataConsents; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SensitiveDataLimitUseNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SensitiveDataProcessing; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SensitiveDataProcessingOptOutNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.USNatField; + +import java.util.List; +import java.util.Objects; + +public class USNatTransmitGeo implements PrivacyModule { + + private final Result result; + + public USNatTransmitGeo(USNatGppReader gppReader) { + result = disallow(gppReader) ? Result.DISALLOW : Result.ALLOW; + } + + @Override + public Result proceed(ActivityCallPayload activityCallPayload) { + return result; + } + + public static boolean disallow(USNatGppReader gppReader) { + return equals(gppReader.getMspaServiceProviderMode(), MspaServiceProviderMode.YES) + || equals(gppReader.getGpc(), Gpc.TRUE) + || checkSensitiveData(gppReader) + || checkKnownChildSensitiveDataConsents(gppReader) + || equals(gppReader.getPersonalDataConsents(), PersonalDataConsents.CONSENT); + } + + private static boolean checkSensitiveData(USNatGppReader gppReader) { + final Integer sensitiveDataProcessingOptOutNotice = gppReader.getSensitiveDataProcessingOptOutNotice(); + final Integer sensitiveDataLimitUseNotice = gppReader.getSensitiveDataLimitUseNotice(); + final List sensitiveDataProcessing = gppReader.getSensitiveDataProcessing(); + + return equals(sensitiveDataProcessingOptOutNotice, SensitiveDataProcessingOptOutNotice.NO) + || equals(sensitiveDataLimitUseNotice, SensitiveDataLimitUseNotice.NO) + || ((equals(sensitiveDataProcessingOptOutNotice, SensitiveDataProcessingOptOutNotice.NOT_APPLICABLE) + || equals(sensitiveDataLimitUseNotice, SensitiveDataLimitUseNotice.NOT_APPLICABLE)) + && equalsAtIndex(SensitiveDataProcessing.CONSENT, sensitiveDataProcessing, 7)) + || equalsAtIndex(SensitiveDataProcessing.NO_CONSENT, sensitiveDataProcessing, 7); + } + + private static boolean checkKnownChildSensitiveDataConsents(USNatGppReader gppReader) { + final List knownChildSensitiveDataConsents = gppReader.getKnownChildSensitiveDataConsents(); + + return equalsAtIndex(KnownChildSensitiveDataConsent.NO_CONSENT, knownChildSensitiveDataConsents, 0) + || equalsAtIndex(KnownChildSensitiveDataConsent.NO_CONSENT, knownChildSensitiveDataConsents, 1) + || equalsAtIndex(KnownChildSensitiveDataConsent.CONSENT, knownChildSensitiveDataConsents, 1); + } + + private static boolean equalsAtIndex(USNatField expectedValue, List list, int index) { + return list != null && list.size() > index && equals(list.get(index), expectedValue); + } + + private static boolean equals(T providedValue, USNatField expectedValue) { + return Objects.equals(providedValue, expectedValue.value()); + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitUfpd.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitUfpd.java new file mode 100644 index 00000000000..80287586528 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitUfpd.java @@ -0,0 +1,134 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner; + +import org.prebid.server.activity.infrastructure.payload.ActivityCallPayload; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.Gpc; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.KnownChildSensitiveDataConsent; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.MspaServiceProviderMode; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.PersonalDataConsents; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SaleOptOut; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SaleOptOutNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SensitiveDataLimitUseNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SensitiveDataProcessing; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SensitiveDataProcessingOptOutNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SharingNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SharingOptOut; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.SharingOptOutNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.TargetedAdvertisingOptOut; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.TargetedAdvertisingOptOutNotice; +import org.prebid.server.activity.infrastructure.privacy.usnat.inner.model.USNatField; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class USNatTransmitUfpd implements PrivacyModule { + + private static final Set SENSITIVE_DATA_INDICES_SET_1 = Set.of(0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11); + private static final Set SENSITIVE_DATA_INDICES_SET_2 = Set.of(0, 1, 2, 3, 4, 10); + private static final Set SENSITIVE_DATA_INDICES_SET_3 = Set.of(5, 6, 8, 9, 11); + + private final Result result; + + public USNatTransmitUfpd(USNatGppReader gppReader) { + result = disallow(gppReader) ? Result.DISALLOW : Result.ALLOW; + } + + @Override + public Result proceed(ActivityCallPayload activityCallPayload) { + return result; + } + + public static boolean disallow(USNatGppReader gppReader) { + return equals(gppReader.getMspaServiceProviderMode(), MspaServiceProviderMode.YES) + || equals(gppReader.getGpc(), Gpc.TRUE) + || checkSale(gppReader) + || checkSharing(gppReader) + || checkTargetedAdvertising(gppReader) + || checkSensitiveData(gppReader) + || checkKnownChildSensitiveDataConsents(gppReader) + || equals(gppReader.getPersonalDataConsents(), PersonalDataConsents.CONSENT); + } + + private static boolean checkSale(USNatGppReader gppReader) { + final Integer saleOptOut = gppReader.getSaleOptOut(); + final Integer saleOptOutNotice = gppReader.getSaleOptOutNotice(); + + return equals(saleOptOut, SaleOptOut.OPTED_OUT) + || equals(saleOptOutNotice, SaleOptOutNotice.NO) + || (equals(saleOptOutNotice, SaleOptOutNotice.NOT_APPLICABLE) + && equals(saleOptOut, SaleOptOut.DID_NOT_OPT_OUT)); + } + + private static boolean checkSharing(USNatGppReader gppReader) { + final Integer sharingNotice = gppReader.getSharingNotice(); + final Integer sharingOptOut = gppReader.getSharingOptOut(); + final Integer sharingOptOutNotice = gppReader.getSharingOptOutNotice(); + + return equals(sharingNotice, SharingNotice.NO) + || equals(sharingOptOut, SharingOptOut.OPTED_OUT) + || equals(sharingOptOutNotice, SharingOptOutNotice.NO) + || (equals(sharingNotice, SharingNotice.NOT_APPLICABLE) + && equals(sharingOptOut, SharingOptOut.DID_NOT_OPT_OUT)) + || (equals(sharingOptOutNotice, SharingOptOutNotice.NOT_APPLICABLE) + && equals(sharingOptOut, SharingOptOut.DID_NOT_OPT_OUT)); + } + + private static boolean checkTargetedAdvertising(USNatGppReader gppReader) { + final Integer targetedAdvertisingOptOut = gppReader.getTargetedAdvertisingOptOut(); + final Integer targetedAdvertisingOptOutNotice = gppReader.getTargetedAdvertisingOptOutNotice(); + + return equals(targetedAdvertisingOptOut, TargetedAdvertisingOptOut.OPTED_OUT) + || equals(targetedAdvertisingOptOutNotice, TargetedAdvertisingOptOutNotice.NO) + || (equals(targetedAdvertisingOptOutNotice, TargetedAdvertisingOptOutNotice.NOT_APPLICABLE) + && equals(targetedAdvertisingOptOut, TargetedAdvertisingOptOut.DID_NOT_OPT_OUT)); + } + + private static boolean checkSensitiveData(USNatGppReader gppReader) { + final Integer sensitiveDataProcessingOptOutNotice = gppReader.getSensitiveDataProcessingOptOutNotice(); + final Integer sensitiveDataLimitUseNotice = gppReader.getSensitiveDataLimitUseNotice(); + final List sensitiveDataProcessing = gppReader.getSensitiveDataProcessing(); + + return equals(sensitiveDataProcessingOptOutNotice, SensitiveDataProcessingOptOutNotice.NO) + || equals(sensitiveDataLimitUseNotice, SensitiveDataLimitUseNotice.NO) + || ((equals(sensitiveDataProcessingOptOutNotice, SensitiveDataProcessingOptOutNotice.NOT_APPLICABLE) + || equals(sensitiveDataLimitUseNotice, SensitiveDataLimitUseNotice.NOT_APPLICABLE)) + && anyEqualsAtIndices( + SensitiveDataProcessing.CONSENT, + sensitiveDataProcessing, + SENSITIVE_DATA_INDICES_SET_1)) + || anyEqualsAtIndices( + SensitiveDataProcessing.NO_CONSENT, + sensitiveDataProcessing, + SENSITIVE_DATA_INDICES_SET_2) + || anyEqualsAtIndices( + SensitiveDataProcessing.NO_CONSENT, + sensitiveDataProcessing, + SENSITIVE_DATA_INDICES_SET_3) + || anyEqualsAtIndices( + SensitiveDataProcessing.CONSENT, + sensitiveDataProcessing, + SENSITIVE_DATA_INDICES_SET_3); + } + + private static boolean checkKnownChildSensitiveDataConsents(USNatGppReader gppReader) { + final List knownChildSensitiveDataConsents = gppReader.getKnownChildSensitiveDataConsents(); + + return equalsAtIndex(KnownChildSensitiveDataConsent.NO_CONSENT, knownChildSensitiveDataConsents, 0) + || equalsAtIndex(KnownChildSensitiveDataConsent.NO_CONSENT, knownChildSensitiveDataConsents, 1) + || equalsAtIndex(KnownChildSensitiveDataConsent.CONSENT, knownChildSensitiveDataConsents, 1); + } + + private static boolean anyEqualsAtIndices(USNatField expectedValue, List list, Set indices) { + return indices.stream().anyMatch(index -> equalsAtIndex(expectedValue, list, index)); + } + + private static boolean equalsAtIndex(USNatField expectedValue, List list, int index) { + return list != null && list.size() > index && equals(list.get(index), expectedValue); + } + + private static boolean equals(T providedValue, USNatField expectedValue) { + return Objects.equals(providedValue, expectedValue.value()); + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/Gpc.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/Gpc.java new file mode 100644 index 00000000000..fba96ce5d1f --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/Gpc.java @@ -0,0 +1,11 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum Gpc implements USNatField { + + FALSE, TRUE; + + @Override + public Boolean value() { + return this == TRUE; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/KnownChildSensitiveDataConsent.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/KnownChildSensitiveDataConsent.java new file mode 100644 index 00000000000..aed7c823acd --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/KnownChildSensitiveDataConsent.java @@ -0,0 +1,18 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum KnownChildSensitiveDataConsent implements USNatField { + + NO_CONSENT(1), + CONSENT(2); + + private final int value; + + KnownChildSensitiveDataConsent(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/MspaServiceProviderMode.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/MspaServiceProviderMode.java new file mode 100644 index 00000000000..2f97cb0e2c3 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/MspaServiceProviderMode.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum MspaServiceProviderMode implements USNatField { + + NOT_APPLICABLE(0), + YES(1), + NO(2); + + private final int value; + + MspaServiceProviderMode(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/PersonalDataConsents.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/PersonalDataConsents.java new file mode 100644 index 00000000000..259b0e4fe2e --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/PersonalDataConsents.java @@ -0,0 +1,18 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum PersonalDataConsents implements USNatField { + + NO_CONSENT(1), + CONSENT(2); + + private final int value; + + PersonalDataConsents(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SaleOptOut.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SaleOptOut.java new file mode 100644 index 00000000000..dcfd65a376b --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SaleOptOut.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum SaleOptOut implements USNatField { + + NOT_APPLICABLE(0), + OPTED_OUT(1), + DID_NOT_OPT_OUT(2); + + private final int value; + + SaleOptOut(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SaleOptOutNotice.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SaleOptOutNotice.java new file mode 100644 index 00000000000..170efd895b8 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SaleOptOutNotice.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum SaleOptOutNotice implements USNatField { + + NOT_APPLICABLE(0), + YES(1), + NO(2); + + private final int value; + + SaleOptOutNotice(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SensitiveDataLimitUseNotice.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SensitiveDataLimitUseNotice.java new file mode 100644 index 00000000000..688c361a44c --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SensitiveDataLimitUseNotice.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum SensitiveDataLimitUseNotice implements USNatField { + + NOT_APPLICABLE(0), + YES(1), + NO(2); + + private final int value; + + SensitiveDataLimitUseNotice(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SensitiveDataProcessing.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SensitiveDataProcessing.java new file mode 100644 index 00000000000..59348d340bb --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SensitiveDataProcessing.java @@ -0,0 +1,18 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum SensitiveDataProcessing implements USNatField { + + NO_CONSENT(1), + CONSENT(2); + + private final int value; + + SensitiveDataProcessing(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SensitiveDataProcessingOptOutNotice.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SensitiveDataProcessingOptOutNotice.java new file mode 100644 index 00000000000..392cb077f7a --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SensitiveDataProcessingOptOutNotice.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum SensitiveDataProcessingOptOutNotice implements USNatField { + + NOT_APPLICABLE(0), + YES(1), + NO(2); + + private final int value; + + SensitiveDataProcessingOptOutNotice(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SharingNotice.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SharingNotice.java new file mode 100644 index 00000000000..43d1c32202c --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SharingNotice.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum SharingNotice implements USNatField { + + NOT_APPLICABLE(0), + YES(1), + NO(2); + + private final int value; + + SharingNotice(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SharingOptOut.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SharingOptOut.java new file mode 100644 index 00000000000..6031f585175 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SharingOptOut.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum SharingOptOut implements USNatField { + + NOT_APPLICABLE(0), + OPTED_OUT(1), + DID_NOT_OPT_OUT(2); + + private final int value; + + SharingOptOut(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SharingOptOutNotice.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SharingOptOutNotice.java new file mode 100644 index 00000000000..0df76bd5f45 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/SharingOptOutNotice.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum SharingOptOutNotice implements USNatField { + + NOT_APPLICABLE(0), + YES(1), + NO(2); + + private final int value; + + SharingOptOutNotice(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/TargetedAdvertisingOptOut.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/TargetedAdvertisingOptOut.java new file mode 100644 index 00000000000..e9ac813c4ed --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/TargetedAdvertisingOptOut.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum TargetedAdvertisingOptOut implements USNatField { + + NOT_APPLICABLE(0), + OPTED_OUT(1), + DID_NOT_OPT_OUT(2); + + private final int value; + + TargetedAdvertisingOptOut(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/TargetedAdvertisingOptOutNotice.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/TargetedAdvertisingOptOutNotice.java new file mode 100644 index 00000000000..1be50c0a613 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/TargetedAdvertisingOptOutNotice.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public enum TargetedAdvertisingOptOutNotice implements USNatField { + + NOT_APPLICABLE(0), + YES(1), + NO(2); + + private final int value; + + TargetedAdvertisingOptOutNotice(int value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/USNatField.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/USNatField.java new file mode 100644 index 00000000000..badec6612fc --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/model/USNatField.java @@ -0,0 +1,6 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner.model; + +public interface USNatField { + + T value(); +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USCaliforniaGppReader.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USCaliforniaGppReader.java new file mode 100644 index 00000000000..2e01b6182c7 --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USCaliforniaGppReader.java @@ -0,0 +1,111 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspCaV1; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.util.ObjectUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class USCaliforniaGppReader implements USNatGppReader { + + private static final List DEFAULT_SENSITIVE_DATA = Collections.nCopies(12, null); + private static final List CHILD_SENSITIVE_DATA = List.of(1, 1); + + private final UspCaV1 consent; + + public USCaliforniaGppReader(GppModel gppModel) { + consent = gppModel != null ? gppModel.getUspCaV1Section() : null; + } + + @Override + public Integer getMspaServiceProviderMode() { + return ObjectUtil.getIfNotNull(consent, UspCaV1::getMspaServiceProviderMode); + } + + @Override + public Boolean getGpc() { + return ObjectUtil.getIfNotNull(consent, UspCaV1::getGpc); + } + + @Override + public Integer getSaleOptOut() { + return ObjectUtil.getIfNotNull(consent, UspCaV1::getSaleOptOut); + } + + @Override + public Integer getSaleOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspCaV1::getSaleOptOutNotice); + } + + @Override + public Integer getSharingNotice() { + return null; + } + + @Override + public Integer getSharingOptOut() { + return ObjectUtil.getIfNotNull(consent, UspCaV1::getSharingOptOut); + } + + @Override + public Integer getSharingOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspCaV1::getSharingOptOutNotice); + } + + @Override + public Integer getTargetedAdvertisingOptOut() { + return null; + } + + @Override + public Integer getTargetedAdvertisingOptOutNotice() { + return null; + } + + @Override + public Integer getSensitiveDataLimitUseNotice() { + return ObjectUtil.getIfNotNull(consent, UspCaV1::getSensitiveDataLimitUseNotice); + } + + @Override + public Integer getSensitiveDataProcessingOptOutNotice() { + return null; + } + + @Override + public List getSensitiveDataProcessing() { + final List originalData = consent != null + ? consent.getSensitiveDataProcessing() + : DEFAULT_SENSITIVE_DATA; + + final List data = new ArrayList<>(DEFAULT_SENSITIVE_DATA); + data.set(0, originalData.get(3)); + data.set(1, originalData.get(3)); + data.set(2, originalData.get(7)); + data.set(3, originalData.get(8)); + data.set(5, originalData.get(5)); + data.set(6, originalData.get(6)); + data.set(7, originalData.get(2)); + data.set(8, originalData.get(0)); + data.set(9, originalData.get(1)); + data.set(11, originalData.get(4)); + + return Collections.unmodifiableList(data); + } + + @Override + public List getKnownChildSensitiveDataConsents() { + final List data = consent != null ? consent.getKnownChildSensitiveDataConsents() : null; + return data != null && data.get(0) == 0 && data.get(1) == 0 + ? data + : CHILD_SENSITIVE_DATA; + } + + @Override + public Integer getPersonalDataConsents() { + return ObjectUtil.getIfNotNull(consent, UspCaV1::getPersonalDataConsents); + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USColoradoGppReader.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USColoradoGppReader.java new file mode 100644 index 00000000000..e3b862a868e --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USColoradoGppReader.java @@ -0,0 +1,97 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspCoV1; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.util.ObjectUtil; + +import java.util.List; + +public class USColoradoGppReader implements USNatGppReader { + + private static final List CHILD_SENSITIVE_DATA = List.of(1, 1); + private static final List NON_CHILD_SENSITIVE_DATA = List.of(0, 0); + + private final UspCoV1 consent; + + public USColoradoGppReader(GppModel gppModel) { + consent = gppModel != null ? gppModel.getUspCoV1Section() : null; + } + + @Override + public Integer getMspaServiceProviderMode() { + return ObjectUtil.getIfNotNull(consent, UspCoV1::getMspaServiceProviderMode); + } + + @Override + public Boolean getGpc() { + return ObjectUtil.getIfNotNull(consent, UspCoV1::getGpc); + } + + @Override + public Integer getSaleOptOut() { + return ObjectUtil.getIfNotNull(consent, UspCoV1::getSaleOptOut); + } + + @Override + public Integer getSaleOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspCoV1::getSaleOptOutNotice); + } + + @Override + public Integer getSharingNotice() { + return ObjectUtil.getIfNotNull(consent, UspCoV1::getSharingNotice); + } + + @Override + public Integer getSharingOptOut() { + return null; + } + + @Override + public Integer getSharingOptOutNotice() { + return null; + } + + @Override + public Integer getTargetedAdvertisingOptOut() { + return ObjectUtil.getIfNotNull(consent, UspCoV1::getTargetedAdvertisingOptOut); + } + + @Override + public Integer getTargetedAdvertisingOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspCoV1::getTargetedAdvertisingOptOutNotice); + } + + @Override + public Integer getSensitiveDataLimitUseNotice() { + return null; + } + + @Override + public Integer getSensitiveDataProcessingOptOutNotice() { + return null; + } + + @Override + public List getSensitiveDataProcessing() { + return ObjectUtil.getIfNotNull(consent, UspCoV1::getSensitiveDataProcessing); + } + + @Override + public List getKnownChildSensitiveDataConsents() { + final Integer originalData = consent != null ? consent.getKnownChildSensitiveDataConsents() : null; + if (originalData == null) { + return null; + } + + return originalData == 1 || originalData == 2 + ? CHILD_SENSITIVE_DATA + : NON_CHILD_SENSITIVE_DATA; + } + + @Override + public Integer getPersonalDataConsents() { + return null; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USConnecticutGppReader.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USConnecticutGppReader.java new file mode 100644 index 00000000000..9786b0e337c --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USConnecticutGppReader.java @@ -0,0 +1,105 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspCtV1; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.util.ObjectUtil; + +import java.util.List; +import java.util.Objects; + +public class USConnecticutGppReader implements USNatGppReader { + + private static final List NON_CHILD_SENSITIVE_DATA = List.of(0, 0); + private static final List MIXED_CHILD_SENSITIVE_DATA = List.of(2, 1); + private static final List CHILD_SENSITIVE_DATA = List.of(1, 1); + + private final UspCtV1 consent; + + public USConnecticutGppReader(GppModel gppModel) { + consent = gppModel != null ? gppModel.getUspCtV1Section() : null; + } + + @Override + public Integer getMspaServiceProviderMode() { + return ObjectUtil.getIfNotNull(consent, UspCtV1::getMspaServiceProviderMode); + } + + @Override + public Boolean getGpc() { + return ObjectUtil.getIfNotNull(consent, UspCtV1::getGpc); + } + + @Override + public Integer getSaleOptOut() { + return ObjectUtil.getIfNotNull(consent, UspCtV1::getSaleOptOut); + } + + @Override + public Integer getSaleOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspCtV1::getSaleOptOutNotice); + } + + @Override + public Integer getSharingNotice() { + return ObjectUtil.getIfNotNull(consent, UspCtV1::getSharingNotice); + } + + @Override + public Integer getSharingOptOut() { + return null; + } + + @Override + public Integer getSharingOptOutNotice() { + return null; + } + + @Override + public Integer getTargetedAdvertisingOptOut() { + return ObjectUtil.getIfNotNull(consent, UspCtV1::getTargetedAdvertisingOptOut); + } + + @Override + public Integer getTargetedAdvertisingOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspCtV1::getTargetedAdvertisingOptOutNotice); + } + + @Override + public Integer getSensitiveDataLimitUseNotice() { + return null; + } + + @Override + public Integer getSensitiveDataProcessingOptOutNotice() { + return null; + } + + @Override + public List getSensitiveDataProcessing() { + return ObjectUtil.getIfNotNull(consent, UspCtV1::getSensitiveDataProcessing); + } + + @Override + public List getKnownChildSensitiveDataConsents() { + final List originalData = ObjectUtil.getIfNotNull( + consent, UspCtV1::getKnownChildSensitiveDataConsents); + + final Integer first = originalData != null ? originalData.get(0) : null; + final Integer second = originalData != null ? originalData.get(1) : null; + final Integer third = originalData != null ? originalData.get(2) : null; + + if (Objects.equals(first, 0) && Objects.equals(second, 0) && Objects.equals(third, 0)) { + return NON_CHILD_SENSITIVE_DATA; + } + + return Objects.equals(second, 2) && Objects.equals(third, 2) + ? MIXED_CHILD_SENSITIVE_DATA + : CHILD_SENSITIVE_DATA; + } + + @Override + public Integer getPersonalDataConsents() { + return null; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USNationalGppReader.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USNationalGppReader.java new file mode 100644 index 00000000000..763630f427d --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USNationalGppReader.java @@ -0,0 +1,87 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspNatV1; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.util.ObjectUtil; + +import java.util.List; + +public class USNationalGppReader implements USNatGppReader { + + private final UspNatV1 consent; + + public USNationalGppReader(GppModel gppModel) { + consent = gppModel != null ? gppModel.getUspNatV1Section() : null; + } + + @Override + public Integer getMspaServiceProviderMode() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getMspaServiceProviderMode); + } + + @Override + public Boolean getGpc() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getGpc); + } + + @Override + public Integer getSaleOptOut() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getSaleOptOut); + } + + @Override + public Integer getSaleOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getSaleOptOutNotice); + } + + @Override + public Integer getSharingNotice() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getSharingNotice); + } + + @Override + public Integer getSharingOptOut() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getSharingOptOut); + } + + @Override + public Integer getSharingOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getSharingOptOutNotice); + } + + @Override + public Integer getTargetedAdvertisingOptOut() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getTargetedAdvertisingOptOut); + } + + @Override + public Integer getTargetedAdvertisingOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getTargetedAdvertisingOptOutNotice); + } + + @Override + public Integer getSensitiveDataLimitUseNotice() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getSensitiveDataLimitUseNotice); + } + + @Override + public Integer getSensitiveDataProcessingOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getSensitiveDataProcessingOptOutNotice); + } + + @Override + public List getSensitiveDataProcessing() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getSensitiveDataProcessing); + } + + @Override + public List getKnownChildSensitiveDataConsents() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getKnownChildSensitiveDataConsents); + } + + @Override + public Integer getPersonalDataConsents() { + return ObjectUtil.getIfNotNull(consent, UspNatV1::getPersonalDataConsents); + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USUtahGppReader.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USUtahGppReader.java new file mode 100644 index 00000000000..853b3c87acc --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USUtahGppReader.java @@ -0,0 +1,115 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspUtV1; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.util.ObjectUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class USUtahGppReader implements USNatGppReader { + + private static final List DEFAULT_SENSITIVE_DATA_PROCESSING = Collections.nCopies(8, null); + private static final List CHILD_SENSITIVE_DATA = List.of(1, 1); + private static final List NON_CHILD_SENSITIVE_DATA = List.of(0, 0); + + private final UspUtV1 consent; + + public USUtahGppReader(GppModel gppModel) { + consent = gppModel != null ? gppModel.getUspUtV1Section() : null; + } + + @Override + public Integer getMspaServiceProviderMode() { + return ObjectUtil.getIfNotNull(consent, UspUtV1::getMspaServiceProviderMode); + } + + @Override + public Boolean getGpc() { + return null; + } + + @Override + public Integer getSaleOptOut() { + return ObjectUtil.getIfNotNull(consent, UspUtV1::getSaleOptOut); + } + + @Override + public Integer getSaleOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspUtV1::getSaleOptOutNotice); + } + + @Override + public Integer getSharingNotice() { + return ObjectUtil.getIfNotNull(consent, UspUtV1::getSharingNotice); + } + + @Override + public Integer getSharingOptOut() { + return null; + } + + @Override + public Integer getSharingOptOutNotice() { + return null; + } + + @Override + public Integer getTargetedAdvertisingOptOut() { + return ObjectUtil.getIfNotNull(consent, UspUtV1::getTargetedAdvertisingOptOut); + } + + @Override + public Integer getTargetedAdvertisingOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspUtV1::getTargetedAdvertisingOptOutNotice); + } + + @Override + public Integer getSensitiveDataLimitUseNotice() { + return null; + } + + @Override + public Integer getSensitiveDataProcessingOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspUtV1::getSensitiveDataProcessingOptOutNotice); + } + + @Override + public List getSensitiveDataProcessing() { + final List originalData = Optional.ofNullable(consent) + .map(UspUtV1::getSensitiveDataProcessing) + .orElse(DEFAULT_SENSITIVE_DATA_PROCESSING); + + final List data = new ArrayList<>(DEFAULT_SENSITIVE_DATA_PROCESSING); + data.set(0, originalData.get(0)); + data.set(1, originalData.get(1)); + data.set(2, originalData.get(4)); + data.set(3, originalData.get(2)); + data.set(4, originalData.get(3)); + data.set(5, originalData.get(5)); + data.set(6, originalData.get(6)); + data.set(7, originalData.get(7)); + + return Collections.unmodifiableList(data); + } + + @Override + public List getKnownChildSensitiveDataConsents() { + final Integer originalData = consent != null ? consent.getKnownChildSensitiveDataConsents() : null; + if (originalData == null) { + return null; + } + + return originalData == 1 || originalData == 2 + ? CHILD_SENSITIVE_DATA + : NON_CHILD_SENSITIVE_DATA; + } + + @Override + public Integer getPersonalDataConsents() { + return null; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USVirginiaGppReader.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USVirginiaGppReader.java new file mode 100644 index 00000000000..5546eedf99e --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USVirginiaGppReader.java @@ -0,0 +1,97 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspVaV1; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.util.ObjectUtil; + +import java.util.List; + +public class USVirginiaGppReader implements USNatGppReader { + + private static final List CHILD_SENSITIVE_DATA = List.of(1, 1); + private static final List NON_CHILD_SENSITIVE_DATA = List.of(0, 0); + + private final UspVaV1 consent; + + public USVirginiaGppReader(GppModel gppModel) { + this.consent = gppModel != null ? gppModel.getUspVaV1Section() : null; + } + + @Override + public Integer getMspaServiceProviderMode() { + return ObjectUtil.getIfNotNull(consent, UspVaV1::getMspaServiceProviderMode); + } + + @Override + public Boolean getGpc() { + return null; + } + + @Override + public Integer getSaleOptOut() { + return ObjectUtil.getIfNotNull(consent, UspVaV1::getSaleOptOut); + } + + @Override + public Integer getSaleOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspVaV1::getSaleOptOutNotice); + } + + @Override + public Integer getSharingNotice() { + return ObjectUtil.getIfNotNull(consent, UspVaV1::getSharingNotice); + } + + @Override + public Integer getSharingOptOut() { + return null; + } + + @Override + public Integer getSharingOptOutNotice() { + return null; + } + + @Override + public Integer getTargetedAdvertisingOptOut() { + return ObjectUtil.getIfNotNull(consent, UspVaV1::getTargetedAdvertisingOptOut); + } + + @Override + public Integer getTargetedAdvertisingOptOutNotice() { + return ObjectUtil.getIfNotNull(consent, UspVaV1::getTargetedAdvertisingOptOutNotice); + } + + @Override + public Integer getSensitiveDataLimitUseNotice() { + return null; + } + + @Override + public Integer getSensitiveDataProcessingOptOutNotice() { + return null; + } + + @Override + public List getSensitiveDataProcessing() { + return ObjectUtil.getIfNotNull(consent, UspVaV1::getSensitiveDataProcessing); + } + + @Override + public List getKnownChildSensitiveDataConsents() { + final Integer originalData = consent != null ? consent.getKnownChildSensitiveDataConsents() : null; + if (originalData == null) { + return null; + } + + return originalData == 1 || originalData == 2 + ? CHILD_SENSITIVE_DATA + : NON_CHILD_SENSITIVE_DATA; + } + + @Override + public Integer getPersonalDataConsents() { + return null; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/rule/AbstractMatchRule.java b/src/main/java/org/prebid/server/activity/infrastructure/rule/AbstractMatchRule.java new file mode 100644 index 00000000000..eb6b4d17d7d --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/rule/AbstractMatchRule.java @@ -0,0 +1,19 @@ +package org.prebid.server.activity.infrastructure.rule; + +import org.prebid.server.activity.infrastructure.payload.ActivityCallPayload; + +public abstract class AbstractMatchRule implements Rule { + + @Override + public Result proceed(ActivityCallPayload activityCallPayload) { + if (!matches(activityCallPayload)) { + return Result.ABSTAIN; + } + + return allowed() ? Result.ALLOW : Result.DISALLOW; + } + + public abstract boolean matches(ActivityCallPayload activityCallPayload); + + public abstract boolean allowed(); +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/rule/AndRule.java b/src/main/java/org/prebid/server/activity/infrastructure/rule/AndRule.java new file mode 100644 index 00000000000..d9e0c666d3b --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/rule/AndRule.java @@ -0,0 +1,33 @@ +package org.prebid.server.activity.infrastructure.rule; + +import org.prebid.server.activity.infrastructure.payload.ActivityCallPayload; + +import java.util.List; +import java.util.Objects; + +public class AndRule implements Rule { + + private final List rules; + + public AndRule(List rules) { + this.rules = Objects.requireNonNull(rules); + } + + @Override + public Result proceed(ActivityCallPayload activityCallPayload) { + Result result = Result.ABSTAIN; + + for (Rule rule : rules) { + final Result ruleResult = rule.proceed(activityCallPayload); + if (ruleResult != Result.ABSTAIN) { + result = ruleResult; + } + + if (result == Result.DISALLOW) { + break; + } + } + + return result; + } +} diff --git a/src/main/java/org/prebid/server/activity/infrastructure/rule/ComponentRule.java b/src/main/java/org/prebid/server/activity/infrastructure/rule/ComponentRule.java index 98ed2bf730c..742f62a4638 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/rule/ComponentRule.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/rule/ComponentRule.java @@ -5,7 +5,7 @@ import java.util.Set; -public final class ComponentRule implements Rule { +public final class ComponentRule extends AbstractMatchRule { private final Set componentTypes; private final Set componentNames; diff --git a/src/main/java/org/prebid/server/activity/infrastructure/rule/GeoRule.java b/src/main/java/org/prebid/server/activity/infrastructure/rule/GeoRule.java index bd46fb3706b..6665bce7c30 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/rule/GeoRule.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/rule/GeoRule.java @@ -10,7 +10,7 @@ import java.util.List; import java.util.Set; -public final class GeoRule implements Rule { +public final class GeoRule extends AbstractMatchRule { private final ComponentRule componentRule; private final boolean sidsMatched; diff --git a/src/main/java/org/prebid/server/activity/infrastructure/rule/Rule.java b/src/main/java/org/prebid/server/activity/infrastructure/rule/Rule.java index a59fe30f241..1bd9364ec40 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/rule/Rule.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/rule/Rule.java @@ -4,7 +4,14 @@ public interface Rule { - boolean matches(ActivityCallPayload activityCallPayload); + Result proceed(ActivityCallPayload activityCallPayload); - boolean allowed(); + enum Result { + + ALLOW, + + DISALLOW, + + ABSTAIN + } } diff --git a/src/main/java/org/prebid/server/metric/AlertsAccountConfigMetric.java b/src/main/java/org/prebid/server/metric/AlertsAccountConfigMetric.java new file mode 100644 index 00000000000..4522b8e1bdf --- /dev/null +++ b/src/main/java/org/prebid/server/metric/AlertsAccountConfigMetric.java @@ -0,0 +1,17 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; + +import java.util.function.Function; + +public class AlertsAccountConfigMetric extends UpdatableMetrics { + + AlertsAccountConfigMetric(MetricRegistry metricRegistry, CounterType counterType, String prefix, String account) { + super(metricRegistry, counterType, nameCreator(prefix, account)); + } + + private static Function nameCreator(String prefix, String account) { + return metricName -> "%s.account_config.%s.%s".formatted(prefix, account, metricName); + } +} + diff --git a/src/main/java/org/prebid/server/metric/AlertsConfigMetrics.java b/src/main/java/org/prebid/server/metric/AlertsConfigMetrics.java index b28c3ce9736..2e7e79daa06 100644 --- a/src/main/java/org/prebid/server/metric/AlertsConfigMetrics.java +++ b/src/main/java/org/prebid/server/metric/AlertsConfigMetrics.java @@ -2,17 +2,33 @@ import com.codahale.metrics.MetricRegistry; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.function.Function; public class AlertsConfigMetrics extends UpdatableMetrics { - AlertsConfigMetrics(MetricRegistry metricRegistry, CounterType counterType, String account) { - super(Objects.requireNonNull(metricRegistry), Objects.requireNonNull(counterType), - nameCreator(account)); + private final Function alertsAccountConfigMetricsCreator; + private final Map alertsAccountConfigMetrics; + + AlertsConfigMetrics(MetricRegistry metricRegistry, CounterType counterType) { + super(Objects.requireNonNull(metricRegistry), Objects.requireNonNull(counterType), nameCreator()); + + alertsAccountConfigMetricsCreator = account -> new AlertsAccountConfigMetric( + metricRegistry, counterType, prefix(), account); + alertsAccountConfigMetrics = new HashMap<>(); + } + + private static Function nameCreator() { + return metricName -> "%s.%s".formatted(prefix(), metricName); + } + + private static String prefix() { + return "alerts"; } - private static Function nameCreator(String account) { - return metricName -> "alerts.account_config.%s.%s".formatted(account, metricName); + public AlertsAccountConfigMetric accountConfig(String account) { + return alertsAccountConfigMetrics.computeIfAbsent(account, alertsAccountConfigMetricsCreator); } } diff --git a/src/main/java/org/prebid/server/metric/MetricName.java b/src/main/java/org/prebid/server/metric/MetricName.java index 913b2133f62..209dc84e939 100644 --- a/src/main/java/org/prebid/server/metric/MetricName.java +++ b/src/main/java/org/prebid/server/metric/MetricName.java @@ -2,6 +2,8 @@ public enum MetricName { + general, + // connection connection_accept_errors, diff --git a/src/main/java/org/prebid/server/metric/Metrics.java b/src/main/java/org/prebid/server/metric/Metrics.java index 01d40e45075..16ff2196ef1 100644 --- a/src/main/java/org/prebid/server/metric/Metrics.java +++ b/src/main/java/org/prebid/server/metric/Metrics.java @@ -35,7 +35,6 @@ public class Metrics extends UpdatableMetrics { private final Function adapterMetricsCreator; private final Function analyticMetricsCreator; private final Function priceFloorsMetricsCreator; - private final Function alertsMetricsCreator; private final Function bidderCardinalityMetricsCreator; private final Function circuitBreakerMetricsCreator; private final Function settingsCacheMetricsCreator; @@ -48,7 +47,7 @@ public class Metrics extends UpdatableMetrics { private final Map adapterMetrics; private final Map analyticMetrics; private final Map priceFloorsMetrics; - private final Map alertsMetrics; + private final AlertsConfigMetrics alertsMetrics; private final Map bidderCardinailtyMetrics; private final UserSyncMetrics userSyncMetrics; private final CookieSyncMetrics cookieSyncMetrics; @@ -78,8 +77,6 @@ public Metrics(MetricRegistry metricRegistry, metricRegistry, counterType, analyticCode); priceFloorsMetricsCreator = moduleType -> new PriceFloorMetrics( metricRegistry, counterType, moduleType); - alertsMetricsCreator = account -> new AlertsConfigMetrics( - metricRegistry, counterType, account); circuitBreakerMetricsCreator = type -> new CircuitBreakerMetrics(metricRegistry, counterType, type); settingsCacheMetricsCreator = type -> new SettingsCacheMetrics(metricRegistry, counterType, type); @@ -89,7 +86,7 @@ public Metrics(MetricRegistry metricRegistry, adapterMetrics = new HashMap<>(); analyticMetrics = new HashMap<>(); priceFloorsMetrics = new HashMap<>(); - alertsMetrics = new HashMap<>(); + alertsMetrics = new AlertsConfigMetrics(metricRegistry, counterType); bidderCardinailtyMetrics = new HashMap<>(); userSyncMetrics = new UserSyncMetrics(metricRegistry, counterType); cookieSyncMetrics = new CookieSyncMetrics(metricRegistry, counterType); @@ -135,8 +132,8 @@ PriceFloorMetrics forPriceFloorGeneralErrors() { return priceFloorsMetrics.computeIfAbsent("general", priceFloorsMetricsCreator); } - AlertsConfigMetrics configFailedForAccount(String accountId) { - return alertsMetrics.computeIfAbsent(accountId, alertsMetricsCreator); + AlertsAccountConfigMetric configFailedForAccount(String accountId) { + return alertsMetrics.accountConfig(accountId); } UserSyncMetrics userSync() { @@ -331,6 +328,10 @@ public void updatePriceFloorGeneralAlertsMetric(MetricName result) { forPriceFloorGeneralErrors().incCounter(result); } + public void updateAlertsMetrics(MetricName metricName) { + alertsMetrics.incCounter(metricName); + } + public void updateAlertsConfigFailed(String accountId, MetricName metricName) { configFailedForAccount(accountId).incCounter(metricName); } diff --git a/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java b/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java index 80ba0393a78..30238a6a761 100644 --- a/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java +++ b/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java @@ -130,7 +130,8 @@ private Account validateAndModifyAccount(Account account) { accountPrivacyConfig.getGdpr(), accountPrivacyConfig.getCcpa(), AccountActivitiesConfigurationUtils - .removeInvalidRules(accountPrivacyConfig.getActivities()))) + .removeInvalidRules(accountPrivacyConfig.getActivities()), + accountPrivacyConfig.getModules())) .build(); } diff --git a/src/main/java/org/prebid/server/settings/model/AccountPrivacyConfig.java b/src/main/java/org/prebid/server/settings/model/AccountPrivacyConfig.java index c43b80fab7e..77e83aadda7 100644 --- a/src/main/java/org/prebid/server/settings/model/AccountPrivacyConfig.java +++ b/src/main/java/org/prebid/server/settings/model/AccountPrivacyConfig.java @@ -4,7 +4,9 @@ import lombok.Value; import org.prebid.server.activity.Activity; import org.prebid.server.settings.model.activity.AccountActivityConfiguration; +import org.prebid.server.settings.model.activity.privacy.AccountPrivacyModuleConfig; +import java.util.List; import java.util.Map; @Value(staticConstructor = "of") @@ -16,4 +18,6 @@ public class AccountPrivacyConfig { @JsonProperty("allowactivities") Map activities; + + List modules; } diff --git a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java new file mode 100644 index 00000000000..2ae12e1a477 --- /dev/null +++ b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java @@ -0,0 +1,23 @@ +package org.prebid.server.settings.model.activity.privacy; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "code", + visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type( + value = AccountUSNatModuleConfig.class, + name = PrivacyModuleQualifier.Names.US_NAT)}) +public sealed interface AccountPrivacyModuleConfig permits AccountUSNatModuleConfig { + + PrivacyModuleQualifier getCode(); + + @JsonProperty + Boolean enabled(); +} diff --git a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java new file mode 100644 index 00000000000..85653f4f004 --- /dev/null +++ b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java @@ -0,0 +1,29 @@ +package org.prebid.server.settings.model.activity.privacy; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; +import lombok.experimental.Accessors; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; + +import java.util.List; + +@Value(staticConstructor = "of") +public class AccountUSNatModuleConfig implements AccountPrivacyModuleConfig { + + @Accessors(fluent = true) + Boolean enabled; + + Config config; + + @Override + public PrivacyModuleQualifier getCode() { + return PrivacyModuleQualifier.US_NAT; + } + + @Value(staticConstructor = "of") + public static class Config { + + @JsonProperty("skipSids") + List skipSids; + } +} diff --git a/src/main/java/org/prebid/server/settings/model/activity/rule/AccountActivityPrivacyModulesRuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/rule/AccountActivityPrivacyModulesRuleConfig.java new file mode 100644 index 00000000000..2592f30e7ff --- /dev/null +++ b/src/main/java/org/prebid/server/settings/model/activity/rule/AccountActivityPrivacyModulesRuleConfig.java @@ -0,0 +1,13 @@ +package org.prebid.server.settings.model.activity.rule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +import java.util.List; + +@Value(staticConstructor = "of") +public class AccountActivityPrivacyModulesRuleConfig implements AccountActivityRuleConfig { + + @JsonProperty("privacyreg") + List privacyModules; +} diff --git a/src/main/java/org/prebid/server/settings/model/activity/rule/AccountActivityRuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/rule/AccountActivityRuleConfig.java index 5b70601a177..6d10010947a 100644 --- a/src/main/java/org/prebid/server/settings/model/activity/rule/AccountActivityRuleConfig.java +++ b/src/main/java/org/prebid/server/settings/model/activity/rule/AccountActivityRuleConfig.java @@ -1,6 +1,4 @@ package org.prebid.server.settings.model.activity.rule; -public sealed interface AccountActivityRuleConfig permits - AccountActivityComponentRuleConfig, - AccountActivityGeoRuleConfig { +public interface AccountActivityRuleConfig { } diff --git a/src/main/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityPrivacyModulesRuleConfigMatcher.java b/src/main/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityPrivacyModulesRuleConfigMatcher.java new file mode 100644 index 00000000000..11be556ba22 --- /dev/null +++ b/src/main/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityPrivacyModulesRuleConfigMatcher.java @@ -0,0 +1,22 @@ +package org.prebid.server.settings.model.activity.rule.resolver; + +import com.fasterxml.jackson.databind.JsonNode; +import org.prebid.server.settings.model.activity.rule.AccountActivityPrivacyModulesRuleConfig; +import org.prebid.server.settings.model.activity.rule.AccountActivityRuleConfig; + +public class AccountActivityPrivacyModulesRuleConfigMatcher implements AccountActivityRuleConfigMatcher { + + @Override + public boolean matches(JsonNode ruleNode) { + return isNotNullObjectNode(ruleNode) && ruleNode.has("privacyreg"); + } + + private static boolean isNotNullObjectNode(JsonNode jsonNode) { + return jsonNode != null && jsonNode.isObject(); + } + + @Override + public Class type() { + return AccountActivityPrivacyModulesRuleConfig.class; + } +} diff --git a/src/main/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityRuleConfigResolver.java b/src/main/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityRuleConfigResolver.java index 663bd63fc5d..f02acacb382 100644 --- a/src/main/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityRuleConfigResolver.java +++ b/src/main/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityRuleConfigResolver.java @@ -11,6 +11,7 @@ private AccountActivityRuleConfigResolver() { } private static final List MATCHERS = List.of( + new AccountActivityPrivacyModulesRuleConfigMatcher(), new AccountActivityGeoRuleConfigMatcher(), new AccountActivityDefaultRuleConfigMatcher()); diff --git a/src/main/java/org/prebid/server/spring/config/ActivityInfrastructureConfiguration.java b/src/main/java/org/prebid/server/spring/config/ActivityInfrastructureConfiguration.java index f8ed4445bc6..c79e6eb7b7d 100644 --- a/src/main/java/org/prebid/server/spring/config/ActivityInfrastructureConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/ActivityInfrastructureConfiguration.java @@ -2,8 +2,12 @@ import org.prebid.server.activity.infrastructure.creator.ActivityInfrastructureCreator; import org.prebid.server.activity.infrastructure.creator.ActivityRuleFactory; +import org.prebid.server.activity.infrastructure.creator.privacy.PrivacyModuleCreator; +import org.prebid.server.activity.infrastructure.creator.privacy.usnat.USNatGppReaderFactory; +import org.prebid.server.activity.infrastructure.creator.privacy.usnat.USNatModuleCreator; import org.prebid.server.activity.infrastructure.creator.rule.ComponentRuleCreator; import org.prebid.server.activity.infrastructure.creator.rule.GeoRuleCreator; +import org.prebid.server.activity.infrastructure.creator.rule.PrivacyModulesRuleCreator; import org.prebid.server.activity.infrastructure.creator.rule.RuleCreator; import org.prebid.server.metric.Metrics; import org.springframework.context.annotation.Bean; @@ -14,14 +18,41 @@ @Configuration public class ActivityInfrastructureConfiguration { - @Bean - GeoRuleCreator geoRuleCreator() { - return new GeoRuleCreator(); + @Configuration + static class PrivacyModuleCreatorConfiguration { + + @Configuration + static class USNatModuleCreatorConfiguration { + + @Bean + USNatGppReaderFactory usNatGppReaderFactory() { + return new USNatGppReaderFactory(); + } + + @Bean + USNatModuleCreator usNatModuleCreator(USNatGppReaderFactory gppReaderFactory) { + return new USNatModuleCreator(gppReaderFactory); + } + } } - @Bean - ComponentRuleCreator componentRuleCreator() { - return new ComponentRuleCreator(); + @Configuration + static class RuleCreatorConfiguration { + + @Bean + ComponentRuleCreator componentRuleCreator() { + return new ComponentRuleCreator(); + } + + @Bean + GeoRuleCreator geoRuleCreator() { + return new GeoRuleCreator(); + } + + @Bean + PrivacyModulesRuleCreator privacyModulesRuleCreator(List privacyModuleCreators) { + return new PrivacyModulesRuleCreator(privacyModuleCreators); + } } @Bean diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountGppConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountGppConfig.groovy new file mode 100644 index 00000000000..a98f79cacd1 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountGppConfig.groovy @@ -0,0 +1,14 @@ +package org.prebid.server.functional.model.config + +import groovy.transform.ToString +import org.prebid.server.functional.model.request.GppSectionId +import org.prebid.server.functional.model.request.auction.PrivacyModule + +@ToString(includeNames = true, ignoreNulls = true) +class AccountGppConfig { + + PrivacyModule code + SidsConfig config + Boolean enabled + ModuleConfig moduleConfig +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy index 5e99f23e11a..1fdd4ba6cc9 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy @@ -14,4 +14,5 @@ class AccountPrivacyConfig { AccountCcpaConfig ccpa @JsonProperty("allowactivities") AllowActivities allowActivities + List modules } diff --git a/src/test/groovy/org/prebid/server/functional/model/config/ModuleConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/ModuleConfig.groovy new file mode 100644 index 00000000000..038f9b80a83 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/ModuleConfig.groovy @@ -0,0 +1,19 @@ +package org.prebid.server.functional.model.config + +import groovy.transform.ToString +import org.prebid.server.functional.model.request.GppSectionId + +@ToString(includeNames = true, ignoreNulls = true) +class ModuleConfig { + + List sids + Boolean normalizeFlags + + static ModuleConfig getDefaultModuleConfig(List sids = [GppSectionId.USP_NAT_V1], + Boolean normalizeFlags = true) { + new ModuleConfig().tap { + it.sids = sids + it.normalizeFlags = normalizeFlags + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/SidsConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/SidsConfig.groovy new file mode 100644 index 00000000000..77e63ac99ba --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/SidsConfig.groovy @@ -0,0 +1,10 @@ +package org.prebid.server.functional.model.config + +import groovy.transform.ToString +import org.prebid.server.functional.model.request.GppSectionId + +@ToString(includeNames = true, ignoreNulls = true) +class SidsConfig { + + List skipSids +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/GppSectionId.groovy b/src/test/groovy/org/prebid/server/functional/model/request/GppSectionId.groovy index f6b5b44c7d3..d71d60d8aa5 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/GppSectionId.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/GppSectionId.groovy @@ -1,15 +1,41 @@ package org.prebid.server.functional.model.request +import com.fasterxml.jackson.annotation.JsonValue +import com.iab.gpp.encoder.section.HeaderV1 +import com.iab.gpp.encoder.section.TcfCaV1 +import com.iab.gpp.encoder.section.TcfEuV2 +import com.iab.gpp.encoder.section.UspCaV1 +import com.iab.gpp.encoder.section.UspCoV1 +import com.iab.gpp.encoder.section.UspCtV1 +import com.iab.gpp.encoder.section.UspNatV1 +import com.iab.gpp.encoder.section.UspUtV1 +import com.iab.gpp.encoder.section.UspV1 +import com.iab.gpp.encoder.section.UspVaV1 + enum GppSectionId { - TCF_EU_V2("2"), USP_V1("6") + TCF_EU_V2(TcfEuV2.ID), + HEADER_V1(HeaderV1.ID), + TCF_CA_V1(TcfCaV1.ID), + USP_V1(UspV1.ID), + USP_NAT_V1(UspNatV1.ID), + USP_CA_V1(UspCaV1.ID), + USP_VA_V1(UspVaV1.ID), + USP_CO_V1(UspCoV1.ID), + USP_UT_V1(UspUtV1.ID), + USP_CT_V1(UspCtV1.ID) - final String value + @JsonValue + final Integer value - GppSectionId(String value) { + GppSectionId(Integer value) { this.value = value } + String getValue() { + value as String + } + Integer getIntValue(){ value.toInteger() } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ActivityRule.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ActivityRule.groovy index 00831095278..5f754628f41 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/ActivityRule.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/ActivityRule.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.model.request.auction +import com.fasterxml.jackson.annotation.JsonProperty import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) @@ -7,6 +8,8 @@ class ActivityRule { Condition condition Boolean allow + @JsonProperty("privacyreg") + List privacyRegulation static ActivityRule getDefaultActivityRule(condition = Condition.baseCondition, allow = true) { new ActivityRule().tap { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/PrivacyModule.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrivacyModule.groovy new file mode 100644 index 00000000000..dafcbe6e0b0 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrivacyModule.groovy @@ -0,0 +1,21 @@ +package org.prebid.server.functional.model.request.auction + +import com.fasterxml.jackson.annotation.JsonValue +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +enum PrivacyModule { + + IAB_TFC_EU("iab.tcfeu"), + IAB_US_GENERAL("iab.usgeneral"), + IAB_ALL("iab.*"), + CUSTOM_US_UTAH("custom.usutah"), + ALL("*") + + @JsonValue + final String value + + private PrivacyModule(String value) { + this.value = value + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppFetchBidActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppFetchBidActivitiesSpec.groovy index 65958a8130f..64e841700a5 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppFetchBidActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppFetchBidActivitiesSpec.groovy @@ -1,5 +1,7 @@ package org.prebid.server.functional.tests.privacy +import org.prebid.server.functional.model.config.AccountGppConfig +import org.prebid.server.functional.model.config.SidsConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.auction.Activity @@ -10,15 +12,34 @@ import org.prebid.server.functional.model.request.auction.Condition import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.Device import org.prebid.server.functional.model.request.auction.Geo +import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.gpp.UspCaV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspCoV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspCtV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspNatV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspUtV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspVaV1Consent import java.time.Instant +import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.pricefloors.Country.USA import static org.prebid.server.functional.model.pricefloors.Country.CAN +import static org.prebid.server.functional.model.request.GppSectionId.USP_CA_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_CO_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_CT_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_NAT_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_UT_V1 import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_VA_V1 +import static org.prebid.server.functional.model.request.amp.ConsentType.GPP import static org.prebid.server.functional.model.request.auction.ActivityType.FETCH_BIDS +import static org.prebid.server.functional.model.request.auction.PrivacyModule.ALL +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_ALL +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_TFC_EU +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_US_GENERAL import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE import static org.prebid.server.functional.util.privacy.model.State.ONTARIO import static org.prebid.server.functional.util.privacy.model.State.ALABAMA @@ -30,6 +51,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { private static final String ACTIVITY_RULES_PROCESSED_COUNT = "requests.activity.processedrules.count" private static final String DISALLOWED_COUNT_FOR_ACTIVITY_RULE = "requests.activity.${FETCH_BIDS.metricValue}.disallowed.count" private static final String DISALLOWED_COUNT_FOR_GENERIC_ADAPTER = "adapter.${GENERIC.value}.activity.${FETCH_BIDS.metricValue}.disallowed.count" + private static final String ALERT_GENERAL = "alerts.general" def "PBS auction call when fetch bid activities is allowing should process bid request and update processed metrics"() { given: "Default basic generic BidRequest" @@ -46,7 +68,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Existed account with allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -77,7 +99,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Existed account with allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -105,7 +127,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) and: "Existed account with allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -130,7 +152,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) and: "Existed account with allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -138,8 +160,8 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { then: "Response should contain error" def logs = activityPbsService.getLogsByTime(startTime) - assert getLogsByText(logs, "Activity configuration for account ${accountId} " + - "contains conditional rule with empty array").size() == 1 + assert getLogsByText(logs, "Activity configuration for account ${accountId} contains conditional rule with empty array").size() == 1 + where: conditions | isAllowed new Condition(componentType: []) | true @@ -164,7 +186,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) and: "Existed account with allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -190,7 +212,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) and: "Existed account with allow activities setup" - Account account = getAccountWithAllowActivities(accountId, activities) + Account account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -200,226 +222,6 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { assert bidder.getBidderRequests(generalBidRequest.id).size() == 0 } - def "PBS amp call when bidder allowed in activities should process bid request and proper metrics and update processed metrics"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = BidRequest.defaultBidRequest.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Allow activities setup" - def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.defaultActivity) - - and: "Flush metrics" - flushMetrics(activityPbsService) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should be present" - assert bidder.getBidderRequest(ampStoredRequest.id) - - and: "Metrics processed across activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 - } - - def "PBS amp call when bidder rejected in activities should skip call to restricted bidders and update disallowed metrics"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = BidRequest.defaultBidRequest.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Reject activities setup" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)]) - AllowActivities allowSetup = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) - - and: "Flush metrics" - flushMetrics(activityPbsService) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, allowSetup) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should not contain bidRequest from amp request" - assert bidder.getBidderRequests(ampStoredRequest.id).size() == 0 - - and: "Metrics for disallowed activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 - assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 - } - - def "PBS amp call when default activity setting set to false should skip call to restricted bidder"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = BidRequest.defaultBidRequest.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Activities set for fetch bids with default action set to false" - def activity = new Activity(defaultAction: false) - def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - defaultPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should not contain bidRequest from amp request" - assert bidder.getBidderRequests(ampStoredRequest.id).size() == 0 - } - - def "PBS amp call when bidder allowed activities have invalid condition type should skip this rule and emit an error"() { - given: "Test start time" - def startTime = Instant.now() - - and: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = BidRequest.defaultBidRequest.tap { - setAccountId(accountId) - } - - and: "Activities set for enrich ufpd with invalid input" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) - def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) - - then: "Response should contain error" - def logs = activityPbsService.getLogsByTime(startTime) - assert getLogsByText(logs, "Activity configuration for account ${accountId} " + - "contains conditional rule with empty array").size() == 1 - - where: - conditions | isAllowed - new Condition(componentType: []) | true - new Condition(componentType: []) | false - new Condition(componentName: []) | true - new Condition(componentName: []) | false - } - - def "PBS auction call when first rule allowing in activities should call each bid adapter"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = BidRequest.defaultBidRequest.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Activity rules with same priority" - def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) - def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) - - and: "Activities set for bidder allowed by hierarchy structure" - def activity = Activity.getDefaultActivity([allowActivity, disallowActivity]) - def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) - - and: "Existed account with allow activities setup" - Account account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - defaultPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should be present" - assert bidder.getBidderRequest(ampStoredRequest.id) - } - - def "PBS amp call with specific reject hierarchy in activities should skip call to restricted bidder"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = BidRequest.defaultBidRequest.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Activities set for actions with Generic bidder rejected by hierarchy setup" - def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) - def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) - - and: "Activities set for bidder disallowing by hierarchy structure" - def activity = Activity.getDefaultActivity([disallowActivity, allowActivity]) - def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) - - and: "Existed account with allow activities setup" - Account account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - defaultPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should not contain bidRequest from amp request" - assert bidder.getBidderRequests(ampStoredRequest.id).size() == 0 - } - def "PBS auction should process rule when gppSid doesn't intersection"() { given: "Generic bid request with account connection" def accountId = PBSUtils.randomNumber as String @@ -442,7 +244,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -484,7 +286,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -524,7 +326,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -570,7 +372,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -615,7 +417,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -653,7 +455,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -689,7 +491,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests with headers" @@ -729,7 +531,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests with headers" @@ -747,67 +549,426 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { gpcHeader << [1, "1"] } - def "PBS amp should process rule when header gpc doesn't intersection with condition.gpc"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + def "PBS auction call when privacy regulation match should call bid adapter"() { + given: "Default Generic BidRequests with gppConsent and account id" def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = bidRequestWithGeo.tap { - setAccountId(accountId) + def bidRequest = BidRequest.defaultBidRequest.tap { + it.setAccountId(accountId) + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = new UspNatV1Consent.Builder().build() } - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId + and: "Activities set for fetchBid with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [privacyAllowRegulations] } - and: "Allow activities setup" - def condition = Condition.baseCondition.tap { - it.componentType = null - it.componentName = null - it.gpc = PBSUtils.randomNumber as String - } - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def allowSetup = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.getDefaultActivity([rule])) - and: "Flush metrics" - flushMetrics(activityPbsService) + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, allowSetup) + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request with header" - activityPbsService.sendAmpRequest(ampRequest, ["Sec-GPC": PBSUtils.randomNumber as String]) - - then: "Bidder request should contain not rounded geo data for device and user" - def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) - verifyAll { - bidderRequests.device.ip == ampStoredRequest.device.ip - bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" - bidderRequests.device.geo.lat == ampStoredRequest.device.geo.lat - bidderRequests.device.geo.lon == ampStoredRequest.device.geo.lon - bidderRequests.user.geo.lat == ampStoredRequest.user.geo.lat - bidderRequests.user.geo.lon == ampStoredRequest.user.geo.lon - } + then: "Generic bidder should be called" + assert bidder.getBidderRequest(bidRequest.id) - and: "Metrics processed across activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + where: + privacyAllowRegulations << [IAB_US_GENERAL, IAB_ALL, ALL] } - def "PBS amp should disallow rule when header gpc intersection with condition.gpc"() { - given: "Default amp stored request" + def "PBS auction call when request have different gpp consent should call bid adapter"() { + given: "Default Generic BidRequests with gppConsent and account id" def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = bidRequestWithGeo.tap { - setAccountId(accountId) + def bidRequest = BidRequest.defaultBidRequest.tap { + it.setAccountId(accountId) + regs.gppSid = [gppSid.intValue] + regs.gpp = gppConsent } - and: "Amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { + and: "Activities set for fetchBid with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) + + then: "Generic bidder should be called" + assert bidder.getBidderRequest(bidRequest.id) + + where: + gppConsent | gppSid + new UspNatV1Consent.Builder().build() | USP_NAT_V1 + new UspCaV1Consent.Builder().build() | USP_CA_V1 + new UspVaV1Consent.Builder().build() | USP_VA_V1 + new UspCoV1Consent.Builder().build() | USP_CO_V1 + new UspUtV1Consent.Builder().build() | USP_UT_V1 + new UspCtV1Consent.Builder().build() | USP_CT_V1 + } + + def "PBS auction call when privacy regulation have duplicate should process request and update alerts metrics"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def genericBidRequest = BidRequest.defaultBidRequest.tap { + it.setAccountId(accountId) + ext.prebid.trace = VERBOSE + regs.gppSid = [USP_NAT_V1.intValue] + } + + and: "Activities set for fetchBid with privacy regulation" + def ruleUsGeneric = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.getDefaultActivity([ruleUsGeneric])) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Account gpp privacy regulation configs with conflict" + def accountGppUsNatAllowConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: false) + def accountGppUsNatRejectConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: []), enabled: true) + + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppUsNatAllowConfig, accountGppUsNatRejectConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) + + then: "Generic bidder should be called due to positive allow in activities" + assert bidder.getBidderRequest(genericBidRequest.id) + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ALERT_GENERAL] == 1 + } + + def "PBS auction call when privacy module contain invalid property should respond with an error"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def genericBidRequest = BidRequest.defaultBidRequest.tap { + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = new UspNatV1Consent.Builder().build() + it.setAccountId(accountId) + } + + and: "Activities set for transmitUfpd with rejecting privacy regulation" + def ruleIabAll = new ActivityRule().tap { + it.privacyRegulation = [IAB_ALL] + } + + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.getDefaultActivity([ruleIabAll])) + + and: "Multiple account gpp privacy regulation config" + def accountGppTfcEuConfig = new AccountGppConfig(code: IAB_TFC_EU, enabled: true) + + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppTfcEuConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) + + then: "Response should contain error" + def error = thrown(PrebidServerException) + assert error.statusCode == UNAUTHORIZED.code() + assert error.responseBody == "Unauthorized account id: ${accountId}" + } + + def "PBS amp call when bidder allowed in activities should process bid request and proper metrics and update processed metrics"() { + given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.defaultActivity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should be present" + assert bidder.getBidderRequest(ampStoredRequest.id) + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + } + + def "PBS amp call when bidder rejected in activities should skip call to restricted bidders and update disallowed metrics"() { + given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Reject activities setup" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)]) + AllowActivities allowSetup = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, allowSetup) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should not contain bidRequest from amp request" + assert bidder.getBidderRequests(ampStoredRequest.id).size() == 0 + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 + assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 + } + + def "PBS amp call when default activity setting set to false should skip call to restricted bidder"() { + given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Activities set for fetch bids with default action set to false" + def activity = new Activity(defaultAction: false) + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should not contain bidRequest from amp request" + assert bidder.getBidderRequests(ampStoredRequest.id).size() == 0 + } + + def "PBS amp call when bidder allowed activities have invalid condition type should skip this rule and emit an error"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + } + + and: "Activities set for invalid input" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "Activity configuration for account ${accountId} contains conditional rule with empty array").size() == 1 + + where: + conditions | isAllowed + new Condition(componentType: []) | true + new Condition(componentType: []) | false + new Condition(componentName: []) | true + new Condition(componentName: []) | false + } + + def "PBS amp call when first rule allowing in activities should call each bid adapter"() { + given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Activity rules with same priority" + def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) + def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) + + and: "Activities set for bidder allowed by hierarchy structure" + def activity = Activity.getDefaultActivity([allowActivity, disallowActivity]) + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) + + and: "Existed account with allow activities setup" + Account account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should be present" + assert bidder.getBidderRequest(ampStoredRequest.id) + } + + def "PBS amp call with specific reject hierarchy in activities should skip call to restricted bidder"() { + given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Activities set for actions with Generic bidder rejected by hierarchy setup" + def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) + def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) + + and: "Activities set for bidder disallowing by hierarchy structure" + def activity = Activity.getDefaultActivity([disallowActivity, allowActivity]) + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) + + and: "Existed account with allow activities setup" + Account account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should not contain bidRequest from amp request" + assert bidder.getBidderRequests(ampStoredRequest.id).size() == 0 + } + + def "PBS amp should process rule when header gpc doesn't intersection with condition.gpc"() { + given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" + def condition = Condition.baseCondition.tap { + it.componentType = null + it.componentName = null + it.gpc = PBSUtils.randomNumber as String + } + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) + def allowSetup = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, activity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, allowSetup) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request with header" + activityPbsService.sendAmpRequest(ampRequest, ["Sec-GPC": PBSUtils.randomNumber as String]) + + then: "Bidder request should contain not rounded geo data for device and user" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == ampStoredRequest.device.ip + bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" + bidderRequests.device.geo.lat == ampStoredRequest.device.geo.lat + bidderRequests.device.geo.lon == ampStoredRequest.device.geo.lon + bidderRequests.user.geo.lat == ampStoredRequest.user.geo.lat + bidderRequests.user.geo.lon == ampStoredRequest.user.geo.lon + } + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + } + + def "PBS amp should disallow rule when header gpc intersection with condition.gpc"() { + given: "Default amp stored request" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo.tap { + setAccountId(accountId) + } + + and: "Amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -824,7 +985,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) and: "Save storedRequest into DB" @@ -842,4 +1003,180 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 } + + def "PBS amp call when privacy regulation match should call bid adapter"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = new UspNatV1Consent.Builder().build() + it.consentType = GPP + } + + and: "Activities set for fetchBid with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [privacyAllowRegulations] + } + + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder should be called" + assert bidder.getBidderRequest(ampStoredRequest.id) + + where: + privacyAllowRegulations << [IAB_US_GENERAL, IAB_ALL, ALL] + } + + def "PBS amp call when request have different gpp consent but match should call bid adapter"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = gppSid.value + it.consentString = gppConsent + it.consentType = GPP + } + + and: "Activities set for fetchBid with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder should be called" + assert bidder.getBidderRequest(ampStoredRequest.id) + + where: + gppConsent | gppSid + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_NAT_V1 + new UspCaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CA_V1 + new UspVaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_VA_V1 + new UspCoV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CO_V1 + new UspUtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_UT_V1 + new UspCtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CT_V1 + } + + def "PBS amp call when privacy regulation have duplicate should process request and update alerts metrics"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.trace = VERBOSE + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + } + + and: "Activities set for fetchBid with privacy regulation" + def ruleUsGeneric = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.getDefaultActivity([ruleUsGeneric])) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Account gpp privacy regulation configs with conflict" + def accountGppUsNatAllowConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: false) + def accountGppUsNatRejectConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: []), enabled: true) + + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppUsNatAllowConfig, accountGppUsNatRejectConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder should be called" + assert bidder.getBidderRequest(ampStoredRequest.id) + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ALERT_GENERAL] == 1 + } + + def "PBS amp call when privacy module contain invalid property should respond with an error"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = BidRequest.defaultBidRequest + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = new UspNatV1Consent.Builder().build() + it.consentType = GPP + } + + def ruleIabAll = new ActivityRule().tap { + it.privacyRegulation = [IAB_ALL] + } + + def activities = AllowActivities.getDefaultAllowActivities(FETCH_BIDS, Activity.getDefaultActivity([ruleIabAll])) + + and: "Multiple account gpp privacy regulation config" + def accountGppTfcEuConfig = new AccountGppConfig(code: IAB_TFC_EU, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppTfcEuConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain error" + def error = thrown(PrebidServerException) + assert error.statusCode == UNAUTHORIZED.code() + assert error.responseBody == "Unauthorized account id: ${accountId}" + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy index 717c0529d72..64cd50b332f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy @@ -1,6 +1,8 @@ package org.prebid.server.functional.tests.privacy import org.prebid.server.functional.model.UidsCookie +import org.prebid.server.functional.model.config.AccountGppConfig +import org.prebid.server.functional.model.config.SidsConfig import org.prebid.server.functional.model.request.auction.Activity import org.prebid.server.functional.model.request.auction.ActivityRule import org.prebid.server.functional.model.request.auction.AllowActivities @@ -9,23 +11,40 @@ import org.prebid.server.functional.model.request.cookiesync.CookieSyncRequest import org.prebid.server.functional.model.request.setuid.SetuidRequest import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.gpp.UspCaV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspCoV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspCtV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspNatV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspUtV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspVaV1Consent import java.time.Instant import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.pricefloors.Country.CAN import static org.prebid.server.functional.model.pricefloors.Country.USA +import static org.prebid.server.functional.model.request.GppSectionId.USP_CA_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_CO_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_CT_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_UT_V1 import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_NAT_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_VA_V1 import static org.prebid.server.functional.model.request.auction.ActivityType.SYNC_USER +import static org.prebid.server.functional.model.request.auction.PrivacyModule.ALL +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_ALL import static org.prebid.server.functional.util.privacy.model.State.MANITOBA import static org.prebid.server.functional.util.privacy.model.State.ALABAMA import static org.prebid.server.functional.util.privacy.model.State.ALASKA +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_TFC_EU +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_US_GENERAL class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { private static final String ACTIVITY_RULES_PROCESSED_COUNT = 'requests.activity.processedrules.count' private static final String DISALLOWED_COUNT_FOR_ACTIVITY_RULE = "requests.activity.${SYNC_USER.metricValue}.disallowed.count" private static final String DISALLOWED_COUNT_FOR_GENERIC_ADAPTER = "adapter.${GENERIC.value}.activity.${SYNC_USER.metricValue}.disallowed.count" + private static final String ALERT_GENERAL = "alerts.general" private final static int INVALID_STATUS_CODE = 451 private final static String INVALID_STATUS_MESSAGE = "Unavailable For Legal Reasons." @@ -48,7 +67,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes cookie sync request" @@ -77,7 +96,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes request without cookies" @@ -104,7 +123,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes request without cookies" @@ -124,12 +143,12 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { it.account = accountId } - and: "Activities set for enrich ufpd with invalid input" + and: "Activities set for invalid input" def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes cookie sync request" @@ -137,8 +156,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { then: "Response should contain error" def logs = activityPbsService.getLogsByTime(startTime) - assert getLogsByText(logs, "Activity configuration for account ${accountId} " + - "contains conditional rule with empty array").size() == 1 + assert getLogsByText(logs, "Activity configuration for account ${accountId} " + "contains conditional rule with empty array").size() == 1 where: conditions | isAllowed @@ -164,7 +182,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes cookie sync request" @@ -190,7 +208,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes cookie sync request" @@ -200,6 +218,344 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { assert !response.bidderStatus.userSync.url } + def "PBS cookie sync should allow rule when gppSid not intersect"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.account = accountId + it.gppSid = gppSid + } + + and: "Setup condition" + def condition = Condition.baseCondition.tap { + it.componentType = null + it.componentName = [PBSUtils.randomString] + it.gppSid = conditionGppSid + } + + and: "Setup activities" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Set up account for allow activities" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should contain bidders userSync.urls" + assert response.getBidderUserSync(GENERIC).userSync.url + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + + where: + gppSid | conditionGppSid + null | [USP_V1.intValue] + USP_V1.value | null + } + + def "PBS cookie sync should disallowed rule when gppSid intersect"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.account = accountId + it.gppSid = USP_V1.value + } + + and: "Setup activity" + def condition = Condition.baseCondition.tap { + componentType = null + componentName = null + gppSid = [USP_V1.intValue] + } + + and: "Setup activities" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Set up account for allow activities" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + when: "PBS processes request without cookies" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should not contain any URLs for bidders" + assert !response.bidderStatus.userSync.url + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 + assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 + } + + def "PBS cookie sync call when privacy regulation match and rejecting should exclude bidders URLs"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = USP_NAT_V1.value + it.account = accountId + it.gpp = SIMPLE_GPC_DISALLOW_LOGIC + } + + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [privacyAllowRegulations] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with cookie sync and privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should not contain any URLs for bidders" + assert !response.bidderStatus.userSync.url + + where: + privacyAllowRegulations << [IAB_US_GENERAL, IAB_ALL, ALL] + } + + def "PBS cookie sync call when privacy module contain some part of disallow logic should exclude bidders URLs"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = USP_NAT_V1.value + it.account = accountId + it.gpp = disallowGppLogic + } + + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with cookie sync and privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should not contain any URLs for bidders" + assert !response.bidderStatus.userSync.url + + where: + disallowGppLogic << [ + SIMPLE_GPC_DISALLOW_LOGIC, + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build(), + new UspNatV1Consent.Builder().setSaleOptOut(1).build(), + new UspNatV1Consent.Builder().setSaleOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSaleOptOutNotice(0).setSaleOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingNotice(2).build(), + new UspNatV1Consent.Builder().setSharingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSharingOptOutNotice(0).setSharingOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingNotice(0).setSharingOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingOptOut(1).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOut(1).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOutNotice(0).setTargetedAdvertisingOptOut(2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 1).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(1, 0).build(), + new UspNatV1Consent.Builder().setPersonalDataConsents(2).build() + ] + } + + def "PBS cookie sync call when request have different gpp consent but match and rejecting should exclude bidders URLs"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = gppSid.value + it.account = accountId + it.gpp = gppConsent + } + + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with cookie sync and privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should not contain any URLs for bidders" + assert !response.bidderStatus.userSync.url + + where: + gppConsent | gppSid + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_NAT_V1 + new UspCaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CA_V1 + new UspVaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_VA_V1 + new UspCoV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CO_V1 + new UspUtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_UT_V1 + new UspCtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CT_V1 + } + + def "PBS cookie sync call when privacy modules contain allowing settings should include proper responded with bidders URLs"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = USP_NAT_V1.value + it.account = accountId + it.gpp = SIMPLE_GPC_DISALLOW_LOGIC + } + + and: "Activities set for cookie sync with all bidders allowed" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + and: "Account gpp configuration" + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Existed account with cookie sync and empty privacy regulations settings" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should contain bidders userSync.urls" + assert response.getBidderUserSync(GENERIC).userSync.url + + where: + accountGppConfig << [ + new AccountGppConfig(code: IAB_US_GENERAL, enabled: false), + new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: true) + ] + } + + def "PBS cookie sync call when regs.gpp in request is allowing should include proper responded with bidders URLs"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = USP_NAT_V1.value + it.account = accountId + it.gpp = regsGpp + } + + and: "Activities set for cookie sync with all bidders allowed" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + and: "Account gpp configuration" + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with cookie sync and empty privacy regulations settings" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should contain bidders userSync.urls" + assert response.getBidderUserSync(GENERIC).userSync.url + + where: + regsGpp << ["", new UspNatV1Consent.Builder().build(), new UspNatV1Consent.Builder().setGpc(false).build()] + } + + def "PBS cookie sync call when privacy regulation have duplicate should include proper responded with bidders URLs"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = USP_NAT_V1.value + it.account = accountId + } + + and: "Activities set for cookie sync with privacy regulation" + def ruleUsGeneric = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([ruleUsGeneric])) + + and: "Account gpp privacy regulation configs with conflict" + def accountGppUsNatAllowConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: false) + def accountGppUsNatRejectConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: []), enabled: true) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Existed account with cookie sync and privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppUsNatAllowConfig, accountGppUsNatRejectConfig]) + accountDao.save(account) + + when: "PBS processes cookie sync request without cookies" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should contain bidders userSync.urls" + assert response.getBidderUserSync(GENERIC).userSync.url + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ALERT_GENERAL] == 1 + } + + def "PBS cookie sync call when privacy module contain invalid code should include proper responded with bidders URLs"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = USP_NAT_V1.value + it.gpp = SIMPLE_GPC_DISALLOW_LOGIC + it.account = accountId + } + + and: "Activities set for cookie sync with privacy regulation" + def ruleIabAll = new ActivityRule().tap { + it.privacyRegulation = [IAB_ALL] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([ruleIabAll])) + + and: "Invalid account gpp privacy regulation config" + def accountGppTfcEuConfig = new AccountGppConfig(code: IAB_TFC_EU, enabled: true) + + and: "Existed account with cookie sync and privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppTfcEuConfig]) + accountDao.save(account) + + when: "PBS processes cookie sync request without cookies" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should contain bidders userSync.urls" + assert response.getBidderUserSync(GENERIC).userSync.url + } + def "PBS setuid request when bidder allowed in activities should respond with valid bidders UIDs cookies and update processed metrics"() { given: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString @@ -217,7 +573,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes cookie sync request" @@ -249,7 +605,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes setuid request" @@ -281,7 +637,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes setuid request" @@ -297,209 +653,425 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { given: "Test start time" def startTime = Instant.now() - and: "Cookie sync SetuidRequest with accountId" + and: "Cookie sync SetuidRequest with accountId" + def accountId = PBSUtils.randomString + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = accountId + } + + and: "UIDS Cookie" + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Activities set for cookie sync with bidder rejection" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + + and: "Existed account with cookie sync and allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + when: "PBS processes setuid request" + def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Response should contain uids cookie" + assert response.responseBody + + and: "Response should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "Activity configuration for account ${accountId} " + "contains conditional rule with empty array").size() == 1 + + where: + conditions | isAllowed + new Condition(componentType: []) | true + new Condition(componentType: []) | false + new Condition(componentName: []) | true + new Condition(componentName: []) | false + } + + def "PBS setuid request when first rule allowing in activities should respond with required UIDs cookies"() { + given: "Cookie sync SetuidRequest with accountId" + def accountId = PBSUtils.randomString + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = accountId + } + + and: "UIDS Cookie" + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Activity rules with different priority" + def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) + def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) + + and: "Activities set for bidder allowed by hierarchy structure" + def activity = Activity.getDefaultActivity([allowActivity, disallowActivity]) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + + and: "Existed account with cookie sync and allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + when: "PBS processes request without cookies" + def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Response should contain uids cookie" + assert response.responseBody + } + + def "PBS setuid request when first rule disallowing in activities should reject bidders with status code invalidStatusCode"() { + given: "Cookie sync SetuidRequest with accountId" + def accountId = PBSUtils.randomString + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = accountId + } + + and: "UIDs cookies" + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Activity rules with different priority" + def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) + def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) + + and: "Activities set for bidder disallowing by hierarchy structure" + def activity = Activity.getDefaultActivity([disallowActivity, allowActivity]) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + + and: "Existed account with cookie sync and allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + when: "PBS processes setuid request" + activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == INVALID_STATUS_CODE + assert exception.responseBody == INVALID_STATUS_MESSAGE + } + + def "PBS setuid should allow rule when gppSid not intersect"() { + given: "Default set uid request" + def accountId = PBSUtils.randomString + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = accountId + it.gppSid = USP_V1.value + } + + and: "UIDS Cookie" + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Setup condition" + def condition = Condition.baseCondition.tap { + it.componentType = null + it.componentName = [PBSUtils.randomString] + it.gppSid = conditionGppSid + } + + and: "Setup activities" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Set up account for allow activities" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Response should contain uids cookie" + assert response.uidsCookie + assert response.responseBody + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + + where: + gppSid | conditionGppSid + null | [USP_V1.intValue] + USP_V1.value | null + } + + def "PBS setuid shouldn't allow rule when gppSid intersect"() { + given: "Default set uid request" + def accountId = PBSUtils.randomString + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = accountId + it.gppSid = USP_V1.value + } + + and: "UIDS Cookie" + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Setup condition" + def condition = Condition.baseCondition.tap { + it.componentType = null + it.componentName = null + it.gppSid = [USP_V1.intValue] + } + + and: "Setup activities" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Set up account for allow activities" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + when: "PBS processes cookie sync request" + activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == INVALID_STATUS_CODE + assert exception.responseBody == INVALID_STATUS_MESSAGE + } + + def "PBS setuid request when privacy regulation match and rejecting should reject bidders with status code invalidStatusCode"() { + given: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { it.account = accountId + it.gppSid = USP_NAT_V1.value + it.gpp = SIMPLE_GPC_DISALLOW_LOGIC } and: "UIDS Cookie" def uidsCookie = UidsCookie.defaultUidsCookie - and: "Activities set for cookie sync with bidder rejection" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) - def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [privacyAllowRegulations] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) - when: "PBS processes setuid request" - def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) - - then: "Response should contain uids cookie" - assert response.responseBody + when: "PBS processes cookie sync request" + activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) - and: "Response should contain error" - def logs = activityPbsService.getLogsByTime(startTime) - assert getLogsByText(logs, "Activity configuration for account ${accountId} " + - "contains conditional rule with empty array").size() == 1 + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == INVALID_STATUS_CODE + assert exception.responseBody == INVALID_STATUS_MESSAGE where: - conditions | isAllowed - new Condition(componentType: []) | true - new Condition(componentType: []) | false - new Condition(componentName: []) | true - new Condition(componentName: []) | false + privacyAllowRegulations << [IAB_US_GENERAL, IAB_ALL, ALL] } - def "PBS setuid request when first rule allowing in activities should respond with required UIDs cookies"() { + def "PBS setuid request when privacy module contain some part of disallow logic should reject bidders with status code invalidStatusCode"() { given: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { it.account = accountId + it.gppSid = USP_NAT_V1.value + it.gpp = disallowGppLogic } and: "UIDS Cookie" def uidsCookie = UidsCookie.defaultUidsCookie - and: "Activity rules with different priority" - def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) - def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } - and: "Activities set for bidder allowed by hierarchy structure" - def activity = Activity.getDefaultActivity([allowActivity, disallowActivity]) - def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) - when: "PBS processes request without cookies" - def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + when: "PBS processes cookie sync request" + activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) - then: "Response should contain uids cookie" - assert response.responseBody + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == INVALID_STATUS_CODE + assert exception.responseBody == INVALID_STATUS_MESSAGE + + where: + disallowGppLogic << [ + SIMPLE_GPC_DISALLOW_LOGIC, + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build(), + new UspNatV1Consent.Builder().setSaleOptOut(1).build(), + new UspNatV1Consent.Builder().setSaleOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSaleOptOutNotice(0).setSaleOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingNotice(2).build(), + new UspNatV1Consent.Builder().setSharingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSharingOptOutNotice(0).setSharingOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingNotice(0).setSharingOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingOptOut(1).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOut(1).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOutNotice(0).setTargetedAdvertisingOptOut(2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 1).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(1, 0).build(), + new UspNatV1Consent.Builder().setPersonalDataConsents(2).build() + ] } - def "PBS setuid request when first rule disallowing in activities should reject bidders with status code invalidStatusCode"() { + def "PBS setuid request when request have different gpp consent but match and rejecting should reject bidders with status code invalidStatusCode"() { given: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { it.account = accountId + it.gppSid = gppSid.value + it.gpp = gppConsent } - and: "UIDs cookies" + and: "UIDS Cookie" def uidsCookie = UidsCookie.defaultUidsCookie - and: "Activity rules with different priority" - def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) - def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } - and: "Activities set for bidder disallowing by hierarchy structure" - def activity = Activity.getDefaultActivity([disallowActivity, allowActivity]) - def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) - when: "PBS processes setuid request" + when: "PBS processes cookie sync request" activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) then: "Request should fail with error" def exception = thrown(PrebidServerException) assert exception.statusCode == INVALID_STATUS_CODE assert exception.responseBody == INVALID_STATUS_MESSAGE + + where: + gppConsent | gppSid + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_NAT_V1 + new UspCaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CA_V1 + new UspVaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_VA_V1 + new UspCoV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CO_V1 + new UspUtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_UT_V1 + new UspCtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CT_V1 } - def "PBS cookie sync should allow rule when gppSid not intersect"() { - given: "Cookie sync request with link to account" + + def "PBS setuid request when privacy modules contain allowing settings should respond with valid bidders UIDs cookies"() { + given: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString - def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { it.account = accountId - it.gppSid = gppSid + it.gppSid = USP_NAT_V1.value + it.gpp = SIMPLE_GPC_DISALLOW_LOGIC } - and: "Setup condition" - def condition = Condition.baseCondition.tap { - it.componentType = null - it.componentName = [PBSUtils.randomString] - it.gppSid = conditionGppSid - } + and: "UIDS Cookie" + def uidsCookie = UidsCookie.defaultUidsCookie - and: "Setup activities" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } - and: "Flush metrics" - flushMetrics(activityPbsService) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) - and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Existed account with cookie sync and allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) when: "PBS processes cookie sync request" - def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) - - then: "Response should contain bidders userSync.urls" - assert response.getBidderUserSync(GENERIC).userSync.url + def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) - and: "Metrics processed across activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + then: "Response should contain uids cookie" + assert response.uidsCookie + assert response.responseBody where: - gppSid | conditionGppSid - null | [USP_V1.intValue] - USP_V1.value | null + accountGppConfig << [ + new AccountGppConfig(code: IAB_US_GENERAL, enabled: false), + new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: true), + + ] } - def "PBS cookie sync should disallowed rule when gppSid intersect"() { - given: "Cookie sync request with link to account" + def "PBS setuid request when regs.gpp in request is allowing should respond with valid bidders UIDs cookies"() { + given: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString - def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { it.account = accountId - it.gppSid = USP_V1.value + it.gppSid = USP_NAT_V1.value + it.gpp = regsGpp } - and: "Setup activity" - def condition = Condition.baseCondition.tap { - componentType = null - componentName = null - gppSid = [USP_V1.intValue] + and: "UIDS Cookie" + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] } - and: "Setup activities" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) - and: "Flush metrics" - flushMetrics(activityPbsService) + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) - and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Existed account with cookie sync and allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) - when: "PBS processes request without cookies" - def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + when: "PBS processes cookie sync request" + def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) - then: "Response should not contain any URLs for bidders" - assert !response.bidderStatus.userSync.url + then: "Response should contain uids cookie" + assert response.uidsCookie + assert response.responseBody - and: "Metrics for disallowed activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 - assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 + where: + regsGpp << ["", new UspNatV1Consent.Builder().build(), new UspNatV1Consent.Builder().setGpc(false).build()] } - def "PBS set uid should allow rule when gppSid not intersect"() { - given: "Default set uid request" + def "PBS setuid request when privacy regulation have duplicate should respond with valid bidders UIDs cookies"() { + given: "SetuidRequest with accountId" def accountId = PBSUtils.randomString def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { it.account = accountId - it.gppSid = USP_V1.value + it.gppSid = USP_NAT_V1.value } and: "UIDS Cookie" def uidsCookie = UidsCookie.defaultUidsCookie - and: "Setup condition" - def condition = Condition.baseCondition.tap { - it.componentType = null - it.componentName = [PBSUtils.randomString] - it.gppSid = conditionGppSid + and: "Activities set for cookie sync with privacy regulation" + def ruleUsGeneric = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] } - and: "Setup activities" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([ruleUsGeneric])) + + and: "Account gpp privacy regulation configs with conflict" + def accountGppUsNatAllowConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: false) + def accountGppUsNatRejectConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: []), enabled: true) and: "Flush metrics" flushMetrics(activityPbsService) - and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppUsNatAllowConfig, accountGppUsNatRejectConfig]) accountDao.save(account) when: "PBS processes cookie sync request" @@ -509,71 +1081,62 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { assert response.uidsCookie assert response.responseBody - and: "Metrics processed across activities should be updated" + and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 - - where: - gppSid | conditionGppSid - null | [USP_V1.intValue] - USP_V1.value | null + assert metrics[ALERT_GENERAL] == 1 } - def "PBS set uid shouldn't allow rule when gppSid intersect"() { - given: "Default set uid request" + def "PBS setuid request call when privacy module contain invalid code should respond with valid bidders UIDs cookies"() { + given: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { it.account = accountId - it.gppSid = USP_V1.value + it.gppSid = USP_NAT_V1.value + it.gpp = SIMPLE_GPC_DISALLOW_LOGIC } and: "UIDS Cookie" def uidsCookie = UidsCookie.defaultUidsCookie - and: "Setup condition" - def condition = Condition.baseCondition.tap { - it.componentType = null - it.componentName = null - it.gppSid = [USP_V1.intValue] + and: "Activities set for cookie sync with rejecting privacy regulation" + def ruleIabAll = new ActivityRule().tap { + it.privacyRegulation = [IAB_ALL] } - and: "Setup activities" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, activity) + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([ruleIabAll])) - and: "Flush metrics" - flushMetrics(activityPbsService) + and: "Multiple account gpp privacy regulation config" + def accountGppTfcEuConfig = new AccountGppConfig(code: IAB_TFC_EU, enabled: true) - and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppTfcEuConfig]) accountDao.save(account) when: "PBS processes cookie sync request" - activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) - then: "Request should fail with error" - def exception = thrown(PrebidServerException) - assert exception.statusCode == INVALID_STATUS_CODE - assert exception.responseBody == INVALID_STATUS_MESSAGE + then: "Response should contain uids cookie" + assert response.uidsCookie + assert response.responseBody } def "PBS cookie sync should process rule when geo doesn't intersection"() { given: "Pbs config with geo location" def prebidServerService = pbsServiceFactory.getService(PBS_CONFIG + GEO_LOCATION + - ["geolocation.configurations.[0].geo-info.country": countyConfig, - "geolocation.configurations.[0].geo-info.region" : regionConfig]) + ["geolocation.configurations.geo-info.[0].country": countyConfig, + "geolocation.configurations.geo-info.[0].region" : regionConfig]) and: "Cookie sync request with account connection" def accountId = PBSUtils.randomNumber as String def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { it.account = accountId - it.gdpr = null + it.gppSid = USP_V1.value } and: "Setup condition" def condition = Condition.baseCondition.tap { it.componentType = null - it.componentName = null + it.componentName = [PBSUtils.randomString] + it.gppSid = [USP_V1.intValue] it.geo = conditionGeo } @@ -585,7 +1148,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { flushMetrics(prebidServerService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes cookie sync request with header" @@ -602,30 +1165,33 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { where: countyConfig | regionConfig | conditionGeo null | null | ["$USA.value".toString()] + USA.value | ALABAMA.abbreviation | null CAN.value | ALASKA.abbreviation | [USA.withState(ALABAMA)] null | MANITOBA.abbreviation | [USA.withState(ALABAMA)] CAN.value | null | [USA.withState(ALABAMA)] } - def "PBS cookie sync should disallowed rule when device.geo intersection"() { + def "PBS setuid should process rule when geo doesn't intersection"() { given: "Pbs config with geo location" def prebidServerService = pbsServiceFactory.getService(PBS_CONFIG + GEO_LOCATION + ["geolocation.configurations.[0].geo-info.country": countyConfig, "geolocation.configurations.[0].geo-info.region" : regionConfig]) - and: "Cookie sync request with account connection" - def accountId = PBSUtils.randomNumber as String - def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + and: "Default set uid request" + def accountId = PBSUtils.randomString + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { it.account = accountId - it.gppSid = null + it.gppSid = USP_V1.value it.gdpr = null } + and: "UIDS Cookie" + def uidsCookie = UidsCookie.defaultUidsCookie + and: "Setup condition" def condition = Condition.baseCondition.tap { it.componentType = null it.componentName = null - it.gppSid = null it.geo = conditionGeo } @@ -637,48 +1203,48 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { flushMetrics(prebidServerService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) - when: "PBS processes cookie sync request with header" + when: "PBS processes set uid request with header" def response = prebidServerService - .sendCookieSyncRequest(cookieSyncRequest, ["X-Forwarded-For": "209.232.44.21"]) + .sendSetUidRequest(setuidRequest, uidsCookie, ["X-Forwarded-For": "209.232.44.21"]) - then: "Response should not contain any URLs for bidders" - assert !response.bidderStatus.userSync.url + then: "Response should contain uids cookie" + assert response.uidsCookie + assert response.responseBody - and: "Metrics for disallowed activities should be updated" + and: "Metrics processed across activities should be updated" def metrics = prebidServerService.sendCollectedMetricsRequest() - assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 - assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 + assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 where: - countyConfig | regionConfig | conditionGeo - USA.value | null | [USA.value] - USA.value | ALABAMA.abbreviation | [USA.withState(ALABAMA)] + countyConfig | regionConfig | conditionGeo + null | null | [USA.value] + CAN.value | ALASKA.abbreviation | [USA.withState(ALABAMA)] + null | MANITOBA.abbreviation | [USA.withState(ALABAMA)] + CAN.value | null | [USA.withState(ALABAMA)] } - def "PBS set uid should process rule when geo doesn't intersection"() { + def "PBS cookie sync should disallowed rule when device.geo intersection"() { given: "Pbs config with geo location" def prebidServerService = pbsServiceFactory.getService(PBS_CONFIG + GEO_LOCATION + ["geolocation.configurations.[0].geo-info.country": countyConfig, "geolocation.configurations.[0].geo-info.region" : regionConfig]) - and: "Default set uid request" - def accountId = PBSUtils.randomString - def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + and: "Cookie sync request with account connection" + def accountId = PBSUtils.randomNumber as String + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { it.account = accountId - it.gppSid = USP_V1.value + it.gppSid = null it.gdpr = null } - and: "UIDS Cookie" - def uidsCookie = UidsCookie.defaultUidsCookie - and: "Setup condition" def condition = Condition.baseCondition.tap { it.componentType = null it.componentName = null + it.gppSid = null it.geo = conditionGeo } @@ -690,30 +1256,28 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { flushMetrics(prebidServerService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) - when: "PBS processes set uid request with header" + when: "PBS processes cookie sync request with header" def response = prebidServerService - .sendSetUidRequest(setuidRequest, uidsCookie, ["X-Forwarded-For": "209.232.44.21"]) + .sendCookieSyncRequest(cookieSyncRequest, ["X-Forwarded-For": "209.232.44.21"]) - then: "Response should contain uids cookie" - assert response.uidsCookie - assert response.responseBody + then: "Response should not contain any URLs for bidders" + assert !response.bidderStatus.userSync.url - and: "Metrics processed across activities should be updated" + and: "Metrics for disallowed activities should be updated" def metrics = prebidServerService.sendCollectedMetricsRequest() - assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 + assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 where: - countyConfig | regionConfig | conditionGeo - null | null | [USA.value] - CAN.value | ALASKA.abbreviation | [USA.withState(ALABAMA)] - null | MANITOBA.abbreviation | [USA.withState(ALABAMA)] - CAN.value | null | [USA.withState(ALABAMA)] + countyConfig | regionConfig | conditionGeo + USA.value | null | [USA.value] + USA.value | ALABAMA.abbreviation | [USA.withState(ALABAMA)] } - def "PBS set uid should disallowed rule when device.geo intersection"() { + def "PBS setuid should disallowed rule when device.geo intersection"() { given: "Pbs config with geo location" def prebidServerService = pbsServiceFactory.getService(PBS_CONFIG + GEO_LOCATION + ["geolocation.configurations.[0].geo-info.country": countyConfig, @@ -746,7 +1310,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { flushMetrics(prebidServerService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes set uid request" diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitPreciseGeoActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitPreciseGeoActivitiesSpec.groovy index c505f98b246..938ad026a2f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitPreciseGeoActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitPreciseGeoActivitiesSpec.groovy @@ -1,5 +1,7 @@ package org.prebid.server.functional.tests.privacy +import org.prebid.server.functional.model.config.AccountGppConfig +import org.prebid.server.functional.model.config.SidsConfig import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.Activity @@ -7,15 +9,32 @@ import org.prebid.server.functional.model.request.auction.ActivityRule import org.prebid.server.functional.model.request.auction.AllowActivities import org.prebid.server.functional.model.request.auction.Condition import org.prebid.server.functional.model.request.auction.Geo +import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.gpp.UspCaV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspCoV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspCtV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspNatV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspUtV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspVaV1Consent +import org.prebid.server.functional.util.privacy.gpp.data.UsNationalSensitiveData import java.time.Instant +import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.pricefloors.Country.CAN import static org.prebid.server.functional.model.pricefloors.Country.USA +import static org.prebid.server.functional.model.request.GppSectionId.USP_CA_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_CO_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_CT_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_NAT_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_UT_V1 import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_VA_V1 +import static org.prebid.server.functional.model.request.amp.ConsentType.GPP import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_PRECISE_GEO +import static org.prebid.server.functional.model.request.auction.PrivacyModule.* import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE import static org.prebid.server.functional.util.privacy.model.State.ALABAMA import static org.prebid.server.functional.util.privacy.model.State.ONTARIO @@ -27,6 +46,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { private static final String ACTIVITY_RULES_PROCESSED_COUNT = "requests.activity.processedrules.count" private static final String DISALLOWED_COUNT_FOR_ACTIVITY_RULE = "requests.activity.${TRANSMIT_PRECISE_GEO.metricValue}.disallowed.count" private static final String DISALLOWED_COUNT_FOR_GENERIC_ADAPTER = "adapter.${GENERIC.value}.activity.${TRANSMIT_PRECISE_GEO.metricValue}.disallowed.count" + private static final String ALERT_GENERAL = "alerts.general" def "PBS auction call with bidder allowed in activities should not round lat/lon data and update processed metrics"() { given: "Default basic generic BidRequest" @@ -43,7 +63,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -83,7 +103,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -120,7 +140,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) and: "Flush metrics" @@ -157,7 +177,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -192,7 +212,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -227,7 +247,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) and: "Existed account with cookie sync and allow activities setup" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -246,272 +266,6 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { } } - def "PBS amp call with bidder allowed in activities should not round lat/lon data and update processed metrics"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = bidRequestWithGeo.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Allow activities setup" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, true)]) - def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) - - and: "Flush metrics" - flushMetrics(activityPbsService) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, allowSetup) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should contain not rounded geo data for device and user" - def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) - - verifyAll { - bidderRequests.device.ip == ampStoredRequest.device.ip - bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" - bidderRequests.device.geo.lat == ampStoredRequest.device.geo.lat - bidderRequests.device.geo.lon == ampStoredRequest.device.geo.lon - bidderRequests.user.geo.lat == ampStoredRequest.user.geo.lat - bidderRequests.user.geo.lon == ampStoredRequest.user.geo.lon - } - - and: "Metrics processed across activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 - } - - def "PBS amp call with bidder rejected in activities should round lat/lon data to 2 digits and update disallowed metrics"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = bidRequestWithGeo.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Allow activities setup" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)]) - def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) - - and: "Flush metrics" - flushMetrics(activityPbsService) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, allowSetup) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should contain rounded geo data for device and user to 2 digits" - def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) - - verifyAll { - bidderRequests.device.ip == "43.77.114.0" - bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" - ampStoredRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat - ampStoredRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon - ampStoredRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat - ampStoredRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon - } - - and: "Metrics for disallowed activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 - assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 - } - - def "PBS amp call when default activity setting set to false should round lat/lon data to 2 digits"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = bidRequestWithGeo.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Allow activities setup" - def activity = new Activity(defaultAction: false) - def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, allowSetup) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should contain rounded geo data for device and user to 2 digits" - def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) - - verifyAll { - bidderRequests.device.ip == "43.77.114.0" - bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" - ampStoredRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat - ampStoredRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon - ampStoredRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat - ampStoredRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon - } - } - - def "PBS amp call when bidder allowed activities have invalid condition type should skip this rule and emit an error"() { - given: "Test start time" - def startTime = Instant.now() - - and: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = bidRequestWithGeo.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Allow activities setup" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) - def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, allowSetup) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) - - then: "Response should contain error" - def logs = activityPbsService.getLogsByTime(startTime) - assert getLogsByText(logs, "Activity configuration for account ${accountId} " + - "contains conditional rule with empty array").size() == 1 - - where: - conditions | isAllowed - new Condition(componentType: []) | true - new Condition(componentType: []) | false - new Condition(componentName: []) | true - new Condition(componentName: []) | false - } - - def "PBS amp call when first rule allowing in activities should not round lat/lon data"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = bidRequestWithGeo.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Activity rules with same priority" - def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) - def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) - - and: "Activities set for bidder allowed by hierarchy structure" - def activity = Activity.getDefaultActivity([allowActivity, disallowActivity]) - def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should contain not rounded geo data for device and user" - def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) - - verifyAll { - bidderRequests.device.ip == ampStoredRequest.device.ip - bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" - bidderRequests.device.geo.lat == ampStoredRequest.device.geo.lat - bidderRequests.device.geo.lon == ampStoredRequest.device.geo.lon - bidderRequests.user.geo.lat == ampStoredRequest.user.geo.lat - bidderRequests.user.geo.lon == ampStoredRequest.user.geo.lon - } - } - - def "PBS amp call when first rule disallowing in activities should round lat/lon data to 2 digits"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = bidRequestWithGeo.tap { - setAccountId(accountId) - } - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Activities set for actions with Generic bidder rejected by hierarchy setup" - def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) - def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) - - and: "Activities set for bidder disallowing by hierarchy structure" - def activity = Activity.getDefaultActivity([disallowActivity, allowActivity]) - def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, allowSetup) - accountDao.save(account) - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should contain rounded geo data for device and user to 2 digits" - def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) - - verifyAll { - bidderRequests.device.ip == "43.77.114.0" - bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" - ampStoredRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat - ampStoredRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon - ampStoredRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat - ampStoredRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon - } - } - def "PBS auction should allow rule when gppSid not intersect"() { given: "Default basic generic BidRequest" def accountId = PBSUtils.randomNumber as String @@ -536,7 +290,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -589,7 +343,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -643,7 +397,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -668,7 +422,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { where: deviceGeo | conditionGeo - new Geo(country: USA) | null + new Geo(country: USA,) | null new Geo(region: ALABAMA.abbreviation) | [USA.withState(ALABAMA)] new Geo(country: CAN, region: ALABAMA.abbreviation) | [USA.withState(ALABAMA)] } @@ -699,7 +453,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -752,7 +506,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -807,7 +561,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -857,7 +611,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -906,7 +660,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -954,7 +708,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction request" @@ -979,10 +733,592 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 } - def "PBS amp should process rule when header gpc doesn't intersection with condition.gpc"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + def "PBS auction call when privacy regulation match and rejecting should round lat/lon data to 2 digits"() { + given: "Default Generic BidRequests with gppConsent and account id" def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = bidRequestWithGeo.tap { + def bidRequest = bidRequestWithGeo.tap { + it.setAccountId(accountId) + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + } + + and: "Activities set for transmitPreciseGeINTERNAL_SERVER_ERRORo with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [privacyAllowRegulations] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain rounded geo data for device and user to 2 digits" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + + verifyAll { + bidderRequests.device.ip == "43.77.114.0" + bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" + bidRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat + bidRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon + bidRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat + bidRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon + } + + where: + privacyAllowRegulations << [IAB_US_GENERAL, IAB_ALL, ALL] + } + + def "PBS auction call when privacy module contain some part of disallow logic should round lat/lon data to 2 digits"() { + given: "Default Generic BidRequests with gppConsent and account id" + def accountId = PBSUtils.randomNumber as String + def bidRequest = bidRequestWithGeo.tap { + it.setAccountId(accountId) + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = disallowGppLogic + } + + and: "Activities set for transmitPreciseGeo with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain rounded geo data for device and user to 2 digits" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + + verifyAll { + bidderRequests.device.ip == "43.77.114.0" + bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" + bidRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat + bidRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon + bidRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat + bidRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon + } + + where: + disallowGppLogic << [ + SIMPLE_GPC_DISALLOW_LOGIC, + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSensitiveDataLimitUseNotice(2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 1).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(1, 0).build(), + new UspNatV1Consent.Builder().setPersonalDataConsents(2).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataLimitUseNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + geolocation: 2 + )).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataProcessingOptOutNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + geolocation: 2 + )).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataProcessingOptOutNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + geolocation: 1 + )).build() + ] + } + + def "PBS auction call when request have different gpp consent but match and rejecting should round lat/lon data to 2 digits"() { + given: "Default Generic BidRequests with gppConsent and account id" + def accountId = PBSUtils.randomNumber as String + def bidRequest = bidRequestWithGeo.tap { + it.setAccountId(accountId) + regs.gppSid = [gppSid.intValue] + regs.gpp = gppConsent + } + + and: "Activities set for transmitPreciseGeo with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain rounded geo data for device and user to 2 digits" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + + verifyAll { + bidderRequests.device.ip == "43.77.114.0" + bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" + bidRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat + bidRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon + bidRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat + bidRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon + } + + where: + gppConsent | gppSid + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_NAT_V1 + new UspCaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CA_V1 + new UspVaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_VA_V1 + new UspCoV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CO_V1 + new UspUtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_UT_V1 + new UspCtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CT_V1 + } + + def "PBS auction call when privacy modules contain allowing settings should not round lat/lon data"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def bidRequest = bidRequestWithGeo.tap { + it.setAccountId(accountId) + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + } + + and: "Activities set for transmitPreciseGeo with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain not rounded geo data for device and user" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + + verifyAll { + bidderRequests.device.ip == bidRequest.device.ip + bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" + bidderRequests.device.geo.lat == bidRequest.device.geo.lat + bidderRequests.device.geo.lon == bidRequest.device.geo.lon + bidderRequests.user.geo.lat == bidRequest.user.geo.lat + bidderRequests.user.geo.lon == bidRequest.user.geo.lon + } + + where: + accountGppConfig << [ + new AccountGppConfig(code: IAB_US_GENERAL, enabled: false), + new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: true) + ] + } + + def "PBS auction call when regs.gpp in request is allowing should not round lat/lon data"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def bidRequest = bidRequestWithGeo.tap { + it.setAccountId(accountId) + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = regsGpp + } + + and: "Activities set for transmitPreciseGeo with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain not rounded geo data for device and user" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + + verifyAll { + bidderRequests.device.ip == bidRequest.device.ip + bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" + bidderRequests.device.geo.lat == bidRequest.device.geo.lat + bidderRequests.device.geo.lon == bidRequest.device.geo.lon + bidderRequests.user.geo.lat == bidRequest.user.geo.lat + bidderRequests.user.geo.lon == bidRequest.user.geo.lon + } + + where: + regsGpp << ["", new UspNatV1Consent.Builder().build(), new UspNatV1Consent.Builder().setGpc(false).build()] + } + + def "PBS auction call when privacy regulation have duplicate should process request and update alerts metrics"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def bidRequest = bidRequestWithGeo.tap { + it.setAccountId(accountId) + regs.gppSid = [USP_NAT_V1.intValue] + } + + and: "Activities set for transmitPreciseGeo with privacy regulation" + def ruleUsGeneric = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([ruleUsGeneric])) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Account gpp privacy regulation configs with conflict" + def accountGppUsNatAllowConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: false) + def accountGppUsNatRejectConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: []), enabled: true) + + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppUsNatAllowConfig, accountGppUsNatRejectConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain not rounded geo data for device and user" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + + verifyAll { + bidderRequests.device.ip == bidRequest.device.ip + bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" + bidderRequests.device.geo.lat == bidRequest.device.geo.lat + bidderRequests.device.geo.lon == bidRequest.device.geo.lon + bidderRequests.user.geo.lat == bidRequest.user.geo.lat + bidderRequests.user.geo.lon == bidRequest.user.geo.lon + } + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ALERT_GENERAL] == 1 + } + + def "PBS auction call when privacy module contain invalid code should respond with an error"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def bidRequest = bidRequestWithGeo.tap { + it.setAccountId(accountId) + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + } + + and: "Activities set for transmitPreciseGeo with rejecting privacy regulation" + def ruleIabAll = new ActivityRule().tap { + it.privacyRegulation = [IAB_ALL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([ruleIabAll])) + + and: "Invalid account gpp privacy regulation config" + def accountGppTfcEuConfig = new AccountGppConfig(code: IAB_TFC_EU, enabled: true) + + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppTfcEuConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain error" + def error = thrown(PrebidServerException) + assert error.statusCode == UNAUTHORIZED.code() + assert error.responseBody == "Unauthorized account id: ${accountId}" + } + + def "PBS amp call with bidder allowed in activities should not round lat/lon data and update processed metrics"() { + given: "Default bid request with allow activities settings that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, true)]) + def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, allowSetup) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain not rounded geo data for device and user" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == ampStoredRequest.device.ip + bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" + bidderRequests.device.geo.lat == ampStoredRequest.device.geo.lat + bidderRequests.device.geo.lon == ampStoredRequest.device.geo.lon + bidderRequests.user.geo.lat == ampStoredRequest.user.geo.lat + bidderRequests.user.geo.lon == ampStoredRequest.user.geo.lon + } + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + } + + def "PBS amp call with bidder rejected in activities should round lat/lon data to 2 digits and update disallowed metrics"() { + given: "Default bid request with allow activities settings that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)]) + def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, allowSetup) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain rounded geo data for device and user to 2 digits" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == "43.77.114.0" + bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" + ampStoredRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat + ampStoredRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon + ampStoredRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat + ampStoredRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon + } + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 + assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 + } + + def "PBS amp call when default activity setting set to false should round lat/lon data to 2 digits"() { + given: "Default bid request with allow activities settings that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" + def activity = new Activity(defaultAction: false) + def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, allowSetup) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain rounded geo data for device and user to 2 digits" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == "43.77.114.0" + bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" + ampStoredRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat + ampStoredRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon + ampStoredRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat + ampStoredRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon + } + } + + def "PBS amp call when bidder allowed activities have invalid condition type should skip this rule and emit an error"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Default bid request with allow activities settings that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) + def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, allowSetup) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "Activity configuration for account ${accountId} " + + "contains conditional rule with empty array").size() == 1 + + where: + conditions | isAllowed + new Condition(componentType: []) | true + new Condition(componentType: []) | false + new Condition(componentName: []) | true + new Condition(componentName: []) | false + } + + def "PBS amp call when first rule allowing in activities should not round lat/lon data"() { + given: "Default bid request with allow activities settings that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Activity rules with same priority" + def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) + def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) + + and: "Activities set for bidder allowed by hierarchy structure" + def activity = Activity.getDefaultActivity([allowActivity, disallowActivity]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain not rounded geo data for device and user" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == ampStoredRequest.device.ip + bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" + bidderRequests.device.geo.lat == ampStoredRequest.device.geo.lat + bidderRequests.device.geo.lon == ampStoredRequest.device.geo.lon + bidderRequests.user.geo.lat == ampStoredRequest.user.geo.lat + bidderRequests.user.geo.lon == ampStoredRequest.user.geo.lon + } + } + + def "PBS amp call when first rule disallowing in activities should round lat/lon data to 2 digits"() { + given: "Default bid request with allow activities settings that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo.tap { + setAccountId(accountId) + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Activities set for actions with Generic bidder rejected by hierarchy setup" + def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) + def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) + + and: "Activities set for bidder disallowing by hierarchy structure" + def activity = Activity.getDefaultActivity([disallowActivity, allowActivity]) + def allowSetup = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, activity) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, allowSetup) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain rounded geo data for device and user to 2 digits" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == "43.77.114.0" + bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" + ampStoredRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat + ampStoredRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon + ampStoredRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat + ampStoredRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon + } + } + + def "PBS amp should process rule when header gpc doesn't intersection with condition.gpc"() { + given: "Default bid request with allow activities settings that decline bidders in selection" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo.tap { setAccountId(accountId) } @@ -1004,7 +1340,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, allowSetup) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, allowSetup) accountDao.save(account) and: "Save storedRequest into DB" @@ -1032,7 +1368,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { } def "PBS amp should disallow rule when header gpc intersection with condition.gpc"() { - given: "Default bid request with allow activities settings for fetch bid that decline bidders in selection" + given: "Default bid request with allow activities settings" def accountId = PBSUtils.randomNumber as String def ampStoredRequest = bidRequestWithGeo.tap { setAccountId(accountId) @@ -1057,7 +1393,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, allowSetup) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, allowSetup) accountDao.save(account) and: "Save storedRequest into DB" @@ -1084,4 +1420,376 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 } + + def "PBS amp call when privacy regulation match and rejecting should round lat/lon data to 2 digits"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = SIMPLE_GPC_DISALLOW_LOGIC + it.consentType = GPP + } + + and: "Activities set for transmitPreciseGeo with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [privacyAllowRegulations] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain rounded geo data for device and user to 2 digits" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == "43.77.114.0" + bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" + ampStoredRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat + ampStoredRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon + ampStoredRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat + ampStoredRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon + } + + where: + privacyAllowRegulations << [IAB_US_GENERAL, IAB_ALL, ALL] + } + + def "PBS amp call when privacy module contain some part of disallow logic should round lat/lon data to 2 digits"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = disallowGppLogic + it.consentType = GPP + } + + and: "Activities set for transmitPreciseGeo with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain rounded geo data for device and user to 2 digits" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == "43.77.114.0" + bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" + ampStoredRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat + ampStoredRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon + ampStoredRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat + ampStoredRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon + } + + where: + disallowGppLogic << [ + SIMPLE_GPC_DISALLOW_LOGIC, + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSensitiveDataLimitUseNotice(2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 1).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(1, 0).build(), + new UspNatV1Consent.Builder().setPersonalDataConsents(2).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataLimitUseNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + geolocation: 2 + )).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataProcessingOptOutNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + geolocation: 2 + )).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataProcessingOptOutNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + geolocation: 1 + )).build() + ] + } + + def "PBS amp call when request have different gpp consent but match and rejecting should round lat/lon data to 2 digits"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = gppSid.value + it.consentString = gppConsent + it.consentType = GPP + } + + and: "Activities set for transmitPreciseGeo with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain rounded geo data for device and user to 2 digits" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == "43.77.114.0" + bidderRequests.device.ipv6 == "af47:892b:3e98:b400::" + ampStoredRequest.device.geo.lat.round(2) == bidderRequests.device.geo.lat + ampStoredRequest.device.geo.lon.round(2) == bidderRequests.device.geo.lon + ampStoredRequest.user.geo.lat.round(2) == bidderRequests.user.geo.lat + ampStoredRequest.user.geo.lon.round(2) == bidderRequests.user.geo.lon + } + + where: + gppConsent | gppSid + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_NAT_V1 + new UspCaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CA_V1 + new UspVaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_VA_V1 + new UspCoV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CO_V1 + new UspUtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_UT_V1 + new UspCtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CT_V1 + } + + def "PBS amp call when privacy modules contain allowing settings should not round lat/lon data"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = SIMPLE_GPC_DISALLOW_LOGIC + it.consentType = GPP + } + + and: "Activities set for transmitPreciseGeo with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain not rounded geo data for device and user" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == ampStoredRequest.device.ip + bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" + bidderRequests.device.geo.lat == ampStoredRequest.device.geo.lat + bidderRequests.device.geo.lon == ampStoredRequest.device.geo.lon + bidderRequests.user.geo.lat == ampStoredRequest.user.geo.lat + bidderRequests.user.geo.lon == ampStoredRequest.user.geo.lon + } + + where: + accountGppConfig << [ + new AccountGppConfig(code: IAB_US_GENERAL, enabled: false), + new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: true) + ] + } + + def "PBS amp call when regs.gpp in request is allowing should not round lat/lon data"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = regsGpp + it.consentType = GPP + } + + and: "Activities set for transmitPreciseGeo with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain not rounded geo data for device and user" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == ampStoredRequest.device.ip + bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" + bidderRequests.device.geo.lat == ampStoredRequest.device.geo.lat + bidderRequests.device.geo.lon == ampStoredRequest.device.geo.lon + bidderRequests.user.geo.lat == ampStoredRequest.user.geo.lat + bidderRequests.user.geo.lon == ampStoredRequest.user.geo.lon + } + + where: + regsGpp << ["", new UspNatV1Consent.Builder().build(), new UspNatV1Consent.Builder().setGpc(false).build()] + } + + def "PBS amp call when privacy regulation have duplicate should process request and update alerts metrics"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + } + + and: "Activities set for transmitPreciseGeo with privacy regulation" + def ruleUsGeneric = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([ruleUsGeneric])) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Account gpp privacy regulation configs with conflict" + def accountGppUsNatAllowConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: false) + def accountGppUsNatRejectConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: []), enabled: true) + + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppUsNatAllowConfig, accountGppUsNatRejectConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain not rounded geo data for device and user" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequests.device.ip == ampStoredRequest.device.ip + bidderRequests.device.ipv6 == "af47:892b:3e98:b49a::" + bidderRequests.device.geo.lat == ampStoredRequest.device.geo.lat + bidderRequests.device.geo.lon == ampStoredRequest.device.geo.lon + bidderRequests.user.geo.lat == ampStoredRequest.user.geo.lat + bidderRequests.user.geo.lon == ampStoredRequest.user.geo.lon + } + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ALERT_GENERAL] == 1 + } + + def "PBS amp call when privacy module contain invalid code should respond with an error"() { + given: "Default Generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = bidRequestWithGeo + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = SIMPLE_GPC_DISALLOW_LOGIC + it.consentType = GPP + } + + and: "Activities set for transmitPreciseGeo with privacy regulation" + def ruleIabAll = new ActivityRule().tap { + it.privacyRegulation = [IAB_ALL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_PRECISE_GEO, Activity.getDefaultActivity([ruleIabAll])) + + and: "Invalid account gpp privacy regulation config" + def accountGppTfcEuConfig = new AccountGppConfig(code: IAB_TFC_EU, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppTfcEuConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain error" + def error = thrown(PrebidServerException) + assert error.statusCode == UNAUTHORIZED.code() + assert error.responseBody == "Unauthorized account id: ${accountId}" + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy index 51f4b822ed5..423894b4bde 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy @@ -1,5 +1,7 @@ package org.prebid.server.functional.tests.privacy +import org.prebid.server.functional.model.config.AccountGppConfig +import org.prebid.server.functional.model.config.SidsConfig import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.auction.Activity import org.prebid.server.functional.model.request.auction.ActivityRule @@ -14,15 +16,35 @@ import org.prebid.server.functional.model.request.auction.User import org.prebid.server.functional.model.request.auction.UserExt import org.prebid.server.functional.model.request.auction.UserExtData import org.prebid.server.functional.model.request.amp.AmpRequest +import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.gpp.UspCaV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspCoV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspCtV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspNatV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspUtV1Consent +import org.prebid.server.functional.util.privacy.gpp.UspVaV1Consent +import org.prebid.server.functional.util.privacy.gpp.data.UsNationalSensitiveData import java.time.Instant +import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.pricefloors.Country.CAN import static org.prebid.server.functional.model.pricefloors.Country.USA +import static org.prebid.server.functional.model.request.GppSectionId.USP_CA_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_CO_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_CT_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_UT_V1 import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_NAT_V1 +import static org.prebid.server.functional.model.request.GppSectionId.USP_VA_V1 +import static org.prebid.server.functional.model.request.amp.ConsentType.GPP import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_UFPD +import static org.prebid.server.functional.model.request.auction.PrivacyModule.ALL +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_ALL +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_TFC_EU +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_US_GENERAL import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE import static org.prebid.server.functional.util.privacy.model.State.ALABAMA import static org.prebid.server.functional.util.privacy.model.State.ONTARIO @@ -34,6 +56,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { private static final String ACTIVITY_RULES_PROCESSED_COUNT = "requests.activity.processedrules.count" private static final String DISALLOWED_COUNT_FOR_ACTIVITY_RULE = "requests.activity.${TRANSMIT_UFPD.metricValue}.disallowed.count" private static final String DISALLOWED_COUNT_FOR_GENERIC_ADAPTER = "adapter.${GENERIC.value}.activity.${TRANSMIT_UFPD.metricValue}.disallowed.count" + private static final String ALERT_GENERAL = "alerts.general" def "PBS auction call when transmit UFPD activities is allowing requests should leave UFPD fields in request and update proper metrics"() { given: "Default Generic BidRequests with UFPD fields and account id" @@ -47,7 +70,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Save account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -92,7 +115,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Save account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -134,7 +157,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) and: "Save account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -173,8 +196,8 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) - and: "Save account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Save account config with allow мяactivities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -207,7 +230,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) and: "Save account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -248,7 +271,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) and: "Save account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -274,88 +297,97 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { } } - def "PBS amp call when transmit UFPD activities is allowing request should leave UFPD fields field in active request and update proper metrics"() { - given: "Default Generic BidRequest with UFPD fields field and account id" + def "PBS auction shouldn't allow rule when gppSid not intersect"() { + given: "Default Generic BidRequests with UFPD fields and account id" def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.gppSid = regsGppSid + } - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId + and: "Setup condition" + def condition = Condition.baseCondition.tap { + it.componentType = null + it.componentName = [PBSUtils.randomString] + it.gppSid = conditionGppSid } and: "Activities set with bidder allowed" - def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.defaultActivity) + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) and: "Flush metrics" flushMetrics(activityPbsService) - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Set up account for allow activities" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) - and: "Stored request in DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) then: "Generic bidder request should have data in UFPD fields" - def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) verifyAll { - genericBidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 - genericBidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 - genericBidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 - genericBidderRequest.device.ifa == ampStoredRequest.device.ifa - genericBidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 - genericBidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 - genericBidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 - genericBidderRequest.user.id == ampStoredRequest.user.id - genericBidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid - genericBidderRequest.user.yob == ampStoredRequest.user.yob - genericBidderRequest.user.gender == ampStoredRequest.user.gender - genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source - genericBidderRequest.user.data == ampStoredRequest.user.data - genericBidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid + genericBidderRequest.device.didsha1 == genericBidRequest.device.didsha1 + genericBidderRequest.device.didmd5 == genericBidRequest.device.didmd5 + genericBidderRequest.device.dpidsha1 == genericBidRequest.device.dpidsha1 + genericBidderRequest.device.ifa == genericBidRequest.device.ifa + genericBidderRequest.device.macsha1 == genericBidRequest.device.macsha1 + genericBidderRequest.device.macmd5 == genericBidRequest.device.macmd5 + genericBidderRequest.device.dpidmd5 == genericBidRequest.device.dpidmd5 + genericBidderRequest.user.id == genericBidRequest.user.id + genericBidderRequest.user.buyeruid == genericBidRequest.user.buyeruid + genericBidderRequest.user.yob == genericBidRequest.user.yob + genericBidderRequest.user.gender == genericBidRequest.user.gender + genericBidderRequest.user.eids[0].source == genericBidRequest.user.eids[0].source + genericBidderRequest.user.data == genericBidRequest.user.data + genericBidderRequest.user.ext.data.buyeruid == genericBidRequest.user.ext.data.buyeruid } and: "Metrics processed across activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + assert metrics[ACTIVITY_PROCESSED_RULES_FOR_ACCOUNT.formatted(accountId)] == 1 + + where: + regsGppSid | conditionGppSid + null | [USP_V1.intValue] + [USP_V1.intValue] | null } - def "PBS amp call when transmit UFPD activities is rejecting request should remove UFPD fields field in active request and update disallowed metrics"() { - given: "Default Generic BidRequest with UFPD fields field and account id" + def "PBS auction should allow rule when gppSid intersect"() { + given: "Default Generic BidRequests with UFPD fields and account id" def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.gppSid = [USP_V1.intValue] + } - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId + and: "Setup condition" + def condition = Condition.baseCondition.tap { + it.componentType = null + it.componentName = null + it.gppSid = [USP_V1.intValue] } - and: "Allow activities setup" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)]) + and: "Activities set with bidder allowed" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) + and: "Flush metrics" flushMetrics(activityPbsService) - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Set up account for allow activities" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) - and: "Stored request in DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) then: "Generic bidder request should have empty UFPD fields" - def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) + verifyAll { !genericBidderRequest.device.didsha1 !genericBidderRequest.device.didmd5 @@ -370,330 +402,155 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { !genericBidderRequest.user.gender !genericBidderRequest.user.eids !genericBidderRequest.user.data - !genericBidderRequest.user.ext } and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 + assert metrics[DISALLOWED_COUNT_FOR_ACCOUNT.formatted(accountId)] == 1 assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 } - def "PBS amp call when default activity setting set to false should remove UFPD fields from request"() { - given: "Default Generic BidRequest with UFPD fields field and account id" + def "PBS auction should process rule when device.geo doesn't intersection"() { + given: "Generic bid request with account connection" def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + def bidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + it.regs.gppSid = [USP_V1.intValue] + it.ext.prebid.trace = VERBOSE + it.device = new Device(geo: deviceGeo) + } - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId + and: "Setup condition" + def condition = Condition.baseCondition.tap { + it.componentType = null + it.componentName = [PBSUtils.randomString] + it.gppSid = [USP_V1.intValue] + it.geo = conditionGeo } - and: "Allow activities setup" - def activity = new Activity(defaultAction: false) + and: "Setup activities" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) + and: "Flush metrics" + flushMetrics(activityPbsService) - and: "Stored request in DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) + and: "Set up account for allow activities" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) - when: "PBS processes amp request" - defaultPbsService.sendAmpRequest(ampRequest) + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) - then: "Generic bidder request should have empty UFPD fields" - def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + then: "Generic bidder request should have data in UFPD fields" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) verifyAll { - !genericBidderRequest.device.didsha1 - !genericBidderRequest.device.didmd5 - !genericBidderRequest.device.dpidsha1 - !genericBidderRequest.device.ifa - !genericBidderRequest.device.macsha1 - !genericBidderRequest.device.macmd5 - !genericBidderRequest.device.dpidmd5 - !genericBidderRequest.user.id - !genericBidderRequest.user.buyeruid - !genericBidderRequest.user.yob - !genericBidderRequest.user.gender - !genericBidderRequest.user.eids - !genericBidderRequest.user.data - !genericBidderRequest.user.ext + bidderRequest.device.didsha1 == bidRequest.device.didsha1 + bidderRequest.device.didmd5 == bidRequest.device.didmd5 + bidderRequest.device.dpidsha1 == bidRequest.device.dpidsha1 + bidderRequest.device.ifa == bidRequest.device.ifa + bidderRequest.device.macsha1 == bidRequest.device.macsha1 + bidderRequest.device.macmd5 == bidRequest.device.macmd5 + bidderRequest.device.dpidmd5 == bidRequest.device.dpidmd5 + bidderRequest.user.id == bidRequest.user.id + bidderRequest.user.buyeruid == bidRequest.user.buyeruid + bidderRequest.user.yob == bidRequest.user.yob + bidderRequest.user.gender == bidRequest.user.gender + bidderRequest.user.eids[0].source == bidRequest.user.eids[0].source + bidderRequest.user.data == bidRequest.user.data + bidderRequest.user.ext.data.buyeruid == bidRequest.user.ext.data.buyeruid } - } - def "PBS amp call when bidder allowed activities have empty condition type should skip this rule and emit an error"() { - given: "Test start time" - def startTime = Instant.now() + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + assert metrics[ACTIVITY_PROCESSED_RULES_FOR_ACCOUNT.formatted(accountId)] == 1 - and: "Default Generic BidRequest with UFPD fields field and account id" + where: + deviceGeo | conditionGeo + null | [USA.value] + new Geo(country: USA) | null + new Geo(region: ALABAMA.abbreviation) | [USA.withState(ALABAMA)] + new Geo(country: CAN, region: ALABAMA.abbreviation) | [USA.withState(ALABAMA)] + } + + def "PBS auction should disallowed rule when device.geo intersection"() { + given: "Generic bid request with account connection" def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + def bidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + it.setAccountId(accountId) + it.ext.prebid.trace = VERBOSE + it.device = new Device(geo: deviceGeo) + } - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId + and: "Setup activity" + def condition = Condition.baseCondition.tap { + it.componentType = null + it.componentName = null + it.gppSid = null + it.geo = conditionGeo } - and: "Activities set with have empty condition type" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) + and: "Setup activities" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Set up account for allow activities" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) - and: "Stored request in DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(bidRequest) - when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) + then: "Generic bidder request should have empty UFPD fields" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) - then: "Response should contain error" - def logs = activityPbsService.getLogsByTime(startTime) - assert getLogsByText(logs, "Activity configuration for account ${accountId} " + - "contains conditional rule with empty array").size() == 1 + verifyAll { + !bidderRequest.device.didsha1 + !bidderRequest.device.didmd5 + !bidderRequest.device.dpidsha1 + !bidderRequest.device.ifa + !bidderRequest.device.macsha1 + !bidderRequest.device.macmd5 + !bidderRequest.device.dpidmd5 + !bidderRequest.user.id + !bidderRequest.user.buyeruid + !bidderRequest.user.yob + !bidderRequest.user.gender + !bidderRequest.user.eids + !bidderRequest.user.data + } + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 + assert metrics[DISALLOWED_COUNT_FOR_ACCOUNT.formatted(accountId)] == 1 + assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 where: - conditions | isAllowed - new Condition(componentType: []) | true - new Condition(componentType: []) | false - new Condition(componentName: []) | true - new Condition(componentName: []) | false + deviceGeo | conditionGeo + new Geo(country: USA) | [USA.value] + new Geo(country: USA, region: ALABAMA.abbreviation) | [USA.withState(ALABAMA)] + new Geo(country: USA, region: ALABAMA.abbreviation) | [CAN.withState(ONTARIO), USA.withState(ALABAMA)] } - def "PBS amp call when first rule allowing in activities should leave UFPD fields in request"() { - given: "Default Generic BidRequest with UFPD fields field and account id" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Activity rules with same priority" - def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) - def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) - - and: "Activities set for bidder allowed by hierarchy structure" - def activity = Activity.getDefaultActivity([allowActivity, disallowActivity]) - def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) - - and: "Save account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - and: "Stored request in DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - defaultPbsService.sendAmpRequest(ampRequest) - - then: "Generic bidder request should have data in UFPD fields" - def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) - - verifyAll { - genericBidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 - genericBidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 - genericBidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 - genericBidderRequest.device.ifa == ampStoredRequest.device.ifa - genericBidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 - genericBidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 - genericBidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 - genericBidderRequest.user.id == ampStoredRequest.user.id - genericBidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid - genericBidderRequest.user.yob == ampStoredRequest.user.yob - genericBidderRequest.user.gender == ampStoredRequest.user.gender - genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source - genericBidderRequest.user.data == ampStoredRequest.user.data - genericBidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid - } - } - - def "PBS amp call when first rule disallowing in activities should remove UFPD fields in request"() { - given: "Default Generic BidRequest with UFPD fields field and account id" - def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) - - and: "amp request with link to account" - def ampRequest = AmpRequest.defaultAmpRequest.tap { - it.account = accountId - } - - and: "Activities set for actions with Generic bidder rejected by hierarchy setup" - def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) - def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) - - and: "Activities set for bidder disallowing by hierarchy structure" - def activity = Activity.getDefaultActivity([disallowActivity, allowActivity]) - def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) - - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - and: "Stored request in DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - when: "PBS processes amp request" - defaultPbsService.sendAmpRequest(ampRequest) - - then: "Generic bidder request should have empty UFPD fields" - def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) - verifyAll { - !genericBidderRequest.device.didsha1 - !genericBidderRequest.device.didmd5 - !genericBidderRequest.device.dpidsha1 - !genericBidderRequest.device.ifa - !genericBidderRequest.device.macsha1 - !genericBidderRequest.device.macmd5 - !genericBidderRequest.device.dpidmd5 - !genericBidderRequest.user.id - !genericBidderRequest.user.buyeruid - !genericBidderRequest.user.yob - !genericBidderRequest.user.gender - !genericBidderRequest.user.eids - !genericBidderRequest.user.data - !genericBidderRequest.user.ext - } - } - - def "PBS auction shouldn't allow rule when gppSid not intersect"() { - given: "Default Generic BidRequests with UFPD fields and account id" - def accountId = PBSUtils.randomNumber as String - def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { - regs.gppSid = regsGppSid - } - - and: "Setup condition" - def condition = Condition.baseCondition.tap { - it.componentType = null - it.componentName = [PBSUtils.randomString] - it.gppSid = conditionGppSid - } - - and: "Activities set with bidder allowed" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) - - and: "Flush metrics" - flushMetrics(activityPbsService) - - and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - when: "PBS processes auction requests" - activityPbsService.sendAuctionRequest(genericBidRequest) - - then: "Generic bidder request should have data in UFPD fields" - def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) - - verifyAll { - genericBidderRequest.device.didsha1 == genericBidRequest.device.didsha1 - genericBidderRequest.device.didmd5 == genericBidRequest.device.didmd5 - genericBidderRequest.device.dpidsha1 == genericBidRequest.device.dpidsha1 - genericBidderRequest.device.ifa == genericBidRequest.device.ifa - genericBidderRequest.device.macsha1 == genericBidRequest.device.macsha1 - genericBidderRequest.device.macmd5 == genericBidRequest.device.macmd5 - genericBidderRequest.device.dpidmd5 == genericBidRequest.device.dpidmd5 - genericBidderRequest.user.id == genericBidRequest.user.id - genericBidderRequest.user.buyeruid == genericBidRequest.user.buyeruid - genericBidderRequest.user.yob == genericBidRequest.user.yob - genericBidderRequest.user.gender == genericBidRequest.user.gender - genericBidderRequest.user.eids[0].source == genericBidRequest.user.eids[0].source - genericBidderRequest.user.data == genericBidRequest.user.data - genericBidderRequest.user.ext.data.buyeruid == genericBidRequest.user.ext.data.buyeruid - } - - and: "Metrics processed across activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 - assert metrics[ACTIVITY_PROCESSED_RULES_FOR_ACCOUNT.formatted(accountId)] == 1 - - where: - regsGppSid | conditionGppSid - null | [USP_V1.intValue] - [USP_V1.intValue] | null - } - - def "PBS auction should allow rule when gppSid intersect"() { - given: "Default Generic BidRequests with UFPD fields and account id" - def accountId = PBSUtils.randomNumber as String - def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { - regs.gppSid = [USP_V1.intValue] - } - - and: "Setup condition" - def condition = Condition.baseCondition.tap { - it.componentType = null - it.componentName = null - it.gppSid = [USP_V1.intValue] - } - - and: "Activities set with bidder allowed" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) - - - and: "Flush metrics" - flushMetrics(activityPbsService) - - and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) - accountDao.save(account) - - when: "PBS processes auction requests" - activityPbsService.sendAuctionRequest(genericBidRequest) - - then: "Generic bidder request should have empty UFPD fields" - def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) - - verifyAll { - !genericBidderRequest.device.didsha1 - !genericBidderRequest.device.didmd5 - !genericBidderRequest.device.dpidsha1 - !genericBidderRequest.device.ifa - !genericBidderRequest.device.macsha1 - !genericBidderRequest.device.macmd5 - !genericBidderRequest.device.dpidmd5 - !genericBidderRequest.user.id - !genericBidderRequest.user.buyeruid - !genericBidderRequest.user.yob - !genericBidderRequest.user.gender - !genericBidderRequest.user.eids - !genericBidderRequest.user.data - } - - and: "Metrics for disallowed activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 - assert metrics[DISALLOWED_COUNT_FOR_ACCOUNT.formatted(accountId)] == 1 - assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 - } - - def "PBS auction should process rule when device.geo doesn't intersection"() { + def "PBS auction should process rule when regs.ext.gpc doesn't intersection with condition.gpc"() { given: "Generic bid request with account connection" def accountId = PBSUtils.randomNumber as String def bidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { - it.regs.gppSid = [USP_V1.intValue] it.ext.prebid.trace = VERBOSE - it.device = new Device(geo: deviceGeo) + it.regs.ext.gpc = PBSUtils.randomNumber as String } and: "Setup condition" def condition = Condition.baseCondition.tap { it.componentType = null - it.componentName = [PBSUtils.randomString] - it.gppSid = [USP_V1.intValue] - it.geo = conditionGeo + it.componentName = null + it.gpc = PBSUtils.randomNumber as String } and: "Setup activities" @@ -704,7 +561,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -734,30 +591,23 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def metrics = activityPbsService.sendCollectedMetricsRequest() assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 assert metrics[ACTIVITY_PROCESSED_RULES_FOR_ACCOUNT.formatted(accountId)] == 1 - - where: - deviceGeo | conditionGeo - null | [USA.value] - new Geo(country: USA) | null - new Geo(region: ALABAMA.abbreviation) | [USA.withState(ALABAMA)] - new Geo(country: CAN, region: ALABAMA.abbreviation) | [USA.withState(ALABAMA)] } - def "PBS auction should disallowed rule when device.geo intersection"() { + def "PBS auction should disallowed rule when regs.ext.gpc intersection with condition.gpc"() { given: "Generic bid request with account connection" def accountId = PBSUtils.randomNumber as String + def gpc = PBSUtils.randomNumber as String def bidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { it.setAccountId(accountId) it.ext.prebid.trace = VERBOSE - it.device = new Device(geo: deviceGeo) + it.regs.ext.gpc = gpc } and: "Setup activity" def condition = Condition.baseCondition.tap { it.componentType = null it.componentName = null - it.gppSid = null - it.geo = conditionGeo + it.gpc = gpc } and: "Setup activities" @@ -768,7 +618,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" @@ -798,15 +648,9 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 assert metrics[DISALLOWED_COUNT_FOR_ACCOUNT.formatted(accountId)] == 1 assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 - - where: - deviceGeo | conditionGeo - new Geo(country: USA) | [USA.value] - new Geo(country: USA, region: ALABAMA.abbreviation) | [USA.withState(ALABAMA)] - new Geo(country: USA, region: ALABAMA.abbreviation) | [CAN.withState(ONTARIO), USA.withState(ALABAMA)] } - def "PBS auction should process rule when regs.ext.gpc doesn't intersection with condition.gpc"() { + def "PBS auction should process rule when header gpc doesn't intersection with condition.gpc"() { given: "Generic bid request with account connection" def accountId = PBSUtils.randomNumber as String def bidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { @@ -818,6 +662,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def condition = Condition.baseCondition.tap { it.componentType = null it.componentName = null + it.gppSid = null it.gpc = PBSUtils.randomNumber as String } @@ -829,11 +674,11 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) when: "PBS processes auction requests" - activityPbsService.sendAuctionRequest(bidRequest) + activityPbsService.sendAuctionRequest(bidRequest, ["Sec-GPC": "1"]) then: "Generic bidder request should have data in UFPD fields" def bidderRequest = bidder.getBidderRequest(bidRequest.id) @@ -861,21 +706,20 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { assert metrics[ACTIVITY_PROCESSED_RULES_FOR_ACCOUNT.formatted(accountId)] == 1 } - def "PBS auction should disallowed rule when regs.ext.gpc intersection with condition.gpc"() { + def "PBS auction should disallowed rule when header gpc intersection with condition.gpc"() { given: "Generic bid request with account connection" def accountId = PBSUtils.randomNumber as String - def gpc = PBSUtils.randomNumber as String def bidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { it.setAccountId(accountId) it.ext.prebid.trace = VERBOSE - it.regs.ext.gpc = gpc + it.regs.ext.gpc = null } and: "Setup activity" def condition = Condition.baseCondition.tap { it.componentType = null it.componentName = null - it.gpc = gpc + it.gpc = VALID_VALUE_FOR_GPC_HEADER } and: "Setup activities" @@ -886,11 +730,11 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { flushMetrics(activityPbsService) and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) - when: "PBS processes auction requests" - activityPbsService.sendAuctionRequest(bidRequest) + when: "PBS processes auction requests with header" + activityPbsService.sendAuctionRequest(bidRequest, ["Sec-GPC": VALID_VALUE_FOR_GPC_HEADER]) then: "Generic bidder request should have empty UFPD fields" def bidderRequest = bidder.getBidderRequest(bidRequest.id) @@ -918,144 +762,1031 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 } - def "PBS auction should process rule when header gpc doesn't intersection with condition.gpc"() { - given: "Generic bid request with account connection" + def "PBS auction call when privacy regulation match and rejecting should remove UFPD fields in request"() { + given: "Default Generic BidRequests with UFPD fields and account id" def accountId = PBSUtils.randomNumber as String - def bidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { - it.ext.prebid.trace = VERBOSE - it.regs.ext.gpc = PBSUtils.randomNumber as String + def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + } + + and: "Activities set for transmitUfpd with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [privacyAllowRegulations] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) + + then: "Generic bidder request should have empty UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) + verifyAll { + !genericBidderRequest.device.didsha1 + !genericBidderRequest.device.didmd5 + !genericBidderRequest.device.dpidsha1 + !genericBidderRequest.device.ifa + !genericBidderRequest.device.macsha1 + !genericBidderRequest.device.macmd5 + !genericBidderRequest.device.dpidmd5 + !genericBidderRequest.user.id + !genericBidderRequest.user.buyeruid + !genericBidderRequest.user.yob + !genericBidderRequest.user.gender + !genericBidderRequest.user.eids + !genericBidderRequest.user.data + !genericBidderRequest.user.ext + } + + where: + privacyAllowRegulations << [IAB_US_GENERAL, IAB_ALL, ALL] + } + + def "PBS auction call when privacy module contain some part of disallow logic should remove UFPD fields in request"() { + given: "Default Generic BidRequests with UFPD fields and account id" + def accountId = PBSUtils.randomNumber as String + def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = disallowGppLogic + } + + and: "Activities set for transmitUfpd with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) + + then: "Generic bidder request should have empty UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) + verifyAll { + !genericBidderRequest.device.didsha1 + !genericBidderRequest.device.didmd5 + !genericBidderRequest.device.dpidsha1 + !genericBidderRequest.device.ifa + !genericBidderRequest.device.macsha1 + !genericBidderRequest.device.macmd5 + !genericBidderRequest.device.dpidmd5 + !genericBidderRequest.user.id + !genericBidderRequest.user.buyeruid + !genericBidderRequest.user.yob + !genericBidderRequest.user.gender + !genericBidderRequest.user.eids + !genericBidderRequest.user.data + !genericBidderRequest.user.ext + } + + where: + disallowGppLogic << [ + SIMPLE_GPC_DISALLOW_LOGIC, + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build(), + new UspNatV1Consent.Builder().setSaleOptOut(1).build(), + new UspNatV1Consent.Builder().setSaleOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSharingNotice(2).build(), + new UspNatV1Consent.Builder().setSaleOptOutNotice(0).setSaleOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSharingOptOut(1).build(), + new UspNatV1Consent.Builder().setSharingOptOutNotice(0).setSharingOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingNotice(0).setSharingOptOut(2).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOut(1).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOutNotice(0).setTargetedAdvertisingOptOut(2).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSensitiveDataLimitUseNotice(2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 1).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 1).build(), + new UspNatV1Consent.Builder().setPersonalDataConsents(2).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessing(new UsNationalSensitiveData( + racialEthnicOrigin: 1, + religiousBeliefs: 1, + healthInfo: 1, + orientation: 1, + citizenshipStatus: 1, + unionMembership: 1, + )).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataLimitUseNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + racialEthnicOrigin: 2, + religiousBeliefs: 2, + healthInfo: 2, + orientation: 2, + citizenshipStatus: 2, + geneticId: 2, + biometricId: 2, + idNumbers: 2, + accountInfo: 2, + unionMembership: 2, + communicationContents: 2 + )).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataProcessingOptOutNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + racialEthnicOrigin: 2, + religiousBeliefs: 2, + healthInfo: 2, + orientation: 2, + citizenshipStatus: 2, + geneticId: 2, + biometricId: 2, + idNumbers: 2, + accountInfo: 2, + unionMembership: 2, + communicationContents: 2 + )).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessing(new UsNationalSensitiveData( + geneticId: 1, + biometricId: 1, + idNumbers: 1, + accountInfo: 1, + communicationContents: 1 + )).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessing(new UsNationalSensitiveData( + geneticId: 2, + biometricId: 2, + idNumbers: 2, + accountInfo: 2, + communicationContents: 2 + )).build() + ] + } + + def "PBS auction call when request have different gpp consent but match and rejecting should remove UFPD fields in request"() { + given: "Default Generic BidRequests with UFPD fields and account id" + def accountId = PBSUtils.randomNumber as String + def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.gppSid = [gppSid.intValue] + regs.gpp = gppConsent + } + + and: "Activities set for transmitUfpd with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) + + then: "Generic bidder request should have empty UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) + verifyAll { + !genericBidderRequest.device.didsha1 + !genericBidderRequest.device.didmd5 + !genericBidderRequest.device.dpidsha1 + !genericBidderRequest.device.ifa + !genericBidderRequest.device.macsha1 + !genericBidderRequest.device.macmd5 + !genericBidderRequest.device.dpidmd5 + !genericBidderRequest.user.id + !genericBidderRequest.user.buyeruid + !genericBidderRequest.user.yob + !genericBidderRequest.user.gender + !genericBidderRequest.user.eids + !genericBidderRequest.user.data + !genericBidderRequest.user.ext + } + + where: + gppConsent | gppSid + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_NAT_V1 + new UspCaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CA_V1 + new UspVaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_VA_V1 + new UspCoV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CO_V1 + new UspUtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_UT_V1 + new UspCtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CT_V1 + } + + def "PBS auction call when privacy modules contain allowing settings should leave UFPD fields in request"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + } + + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) + + then: "Generic bidder request should have data in UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) + + and: "Generic bidder should be called due to positive allow in activities" + verifyAll { + genericBidderRequest.device.didsha1 == genericBidRequest.device.didsha1 + genericBidderRequest.device.didmd5 == genericBidRequest.device.didmd5 + genericBidderRequest.device.dpidsha1 == genericBidRequest.device.dpidsha1 + genericBidderRequest.device.ifa == genericBidRequest.device.ifa + genericBidderRequest.device.macsha1 == genericBidRequest.device.macsha1 + genericBidderRequest.device.macmd5 == genericBidRequest.device.macmd5 + genericBidderRequest.device.dpidmd5 == genericBidRequest.device.dpidmd5 + genericBidderRequest.user.id == genericBidRequest.user.id + genericBidderRequest.user.buyeruid == genericBidRequest.user.buyeruid + genericBidderRequest.user.yob == genericBidRequest.user.yob + genericBidderRequest.user.gender == genericBidRequest.user.gender + genericBidderRequest.user.eids[0].source == genericBidRequest.user.eids[0].source + genericBidderRequest.user.data == genericBidRequest.user.data + genericBidderRequest.user.ext.data.buyeruid == genericBidRequest.user.ext.data.buyeruid + } + + where: + accountGppConfig << [ + new AccountGppConfig(code: IAB_US_GENERAL, enabled: false), + new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: true) + ] + } + + def "PBS auction call when regs.gpp in request is allowing should leave UFPD fields in request"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = regsGpp + } + + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) + + then: "Generic bidder request should have data in UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) + + and: "Generic bidder should be called due to positive allow in activities" + verifyAll { + genericBidderRequest.device.didsha1 == genericBidRequest.device.didsha1 + genericBidderRequest.device.didmd5 == genericBidRequest.device.didmd5 + genericBidderRequest.device.dpidsha1 == genericBidRequest.device.dpidsha1 + genericBidderRequest.device.ifa == genericBidRequest.device.ifa + genericBidderRequest.device.macsha1 == genericBidRequest.device.macsha1 + genericBidderRequest.device.macmd5 == genericBidRequest.device.macmd5 + genericBidderRequest.device.dpidmd5 == genericBidRequest.device.dpidmd5 + genericBidderRequest.user.id == genericBidRequest.user.id + genericBidderRequest.user.buyeruid == genericBidRequest.user.buyeruid + genericBidderRequest.user.yob == genericBidRequest.user.yob + genericBidderRequest.user.gender == genericBidRequest.user.gender + genericBidderRequest.user.eids[0].source == genericBidRequest.user.eids[0].source + genericBidderRequest.user.data == genericBidRequest.user.data + genericBidderRequest.user.ext.data.buyeruid == genericBidRequest.user.ext.data.buyeruid + } + + where: + regsGpp << ["", new UspNatV1Consent.Builder().build(), new UspNatV1Consent.Builder().setGpc(false).build()] + } + + def "PBS auction call when privacy regulation have duplicate should leave UFPD fields in request and update alerts metrics"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.gppSid = [USP_NAT_V1.intValue] + } + + and: "Activities set for transmitUfpd with privacy regulation" + def ruleUsGeneric = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([ruleUsGeneric])) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Account gpp privacy regulation configs with conflict" + def accountGppUsNatAllowConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: false) + def accountGppUsNatRejectConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: []), enabled: true) + + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppUsNatAllowConfig, accountGppUsNatRejectConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) + + then: "Generic bidder request should have data in UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(genericBidRequest.id) + + and: "Generic bidder should be called due to positive allow in activities" + verifyAll { + genericBidderRequest.device.didsha1 == genericBidRequest.device.didsha1 + genericBidderRequest.device.didmd5 == genericBidRequest.device.didmd5 + genericBidderRequest.device.dpidsha1 == genericBidRequest.device.dpidsha1 + genericBidderRequest.device.ifa == genericBidRequest.device.ifa + genericBidderRequest.device.macsha1 == genericBidRequest.device.macsha1 + genericBidderRequest.device.macmd5 == genericBidRequest.device.macmd5 + genericBidderRequest.device.dpidmd5 == genericBidRequest.device.dpidmd5 + genericBidderRequest.user.id == genericBidRequest.user.id + genericBidderRequest.user.buyeruid == genericBidRequest.user.buyeruid + genericBidderRequest.user.yob == genericBidRequest.user.yob + genericBidderRequest.user.gender == genericBidRequest.user.gender + genericBidderRequest.user.eids[0].source == genericBidRequest.user.eids[0].source + genericBidderRequest.user.data == genericBidRequest.user.data + genericBidderRequest.user.ext.data.buyeruid == genericBidRequest.user.ext.data.buyeruid + } + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ALERT_GENERAL] == 1 + } + + def "PBS auction call when privacy module contain invalid property should respond with an error"() { + given: "Default basic generic BidRequest" + def accountId = PBSUtils.randomNumber as String + def genericBidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.gppSid = [USP_NAT_V1.intValue] + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + } + + and: "Activities set for transmitUfpd with rejecting privacy regulation" + def ruleIabAll = new ActivityRule().tap { + it.privacyRegulation = [IAB_ALL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([ruleIabAll])) + + and: "Multiple account gpp privacy regulation config" + def accountGppTfcEuConfig = new AccountGppConfig(code: IAB_TFC_EU, enabled: true) + + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppTfcEuConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + activityPbsService.sendAuctionRequest(genericBidRequest) + + then: "Response should contain error" + def error = thrown(PrebidServerException) + assert error.statusCode == UNAUTHORIZED.code() + assert error.responseBody == "Unauthorized account id: ${accountId}" + } + + def "PBS amp call when transmit UFPD activities is allowing request should leave UFPD fields field in active request and update proper metrics"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Activities set with bidder allowed" + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.defaultActivity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have data in UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + genericBidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 + genericBidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 + genericBidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 + genericBidderRequest.device.ifa == ampStoredRequest.device.ifa + genericBidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 + genericBidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 + genericBidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 + genericBidderRequest.user.id == ampStoredRequest.user.id + genericBidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid + genericBidderRequest.user.yob == ampStoredRequest.user.yob + genericBidderRequest.user.gender == ampStoredRequest.user.gender + genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source + genericBidderRequest.user.data == ampStoredRequest.user.data + genericBidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid + } + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + } + + def "PBS amp call when transmit UFPD activities is rejecting request should remove UFPD fields field in active request and update disallowed metrics"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have empty UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + verifyAll { + !genericBidderRequest.device.didsha1 + !genericBidderRequest.device.didmd5 + !genericBidderRequest.device.dpidsha1 + !genericBidderRequest.device.ifa + !genericBidderRequest.device.macsha1 + !genericBidderRequest.device.macmd5 + !genericBidderRequest.device.dpidmd5 + !genericBidderRequest.user.id + !genericBidderRequest.user.buyeruid + !genericBidderRequest.user.yob + !genericBidderRequest.user.gender + !genericBidderRequest.user.eids + !genericBidderRequest.user.data + !genericBidderRequest.user.ext + } + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 + assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 + } + + def "PBS amp call when default activity setting set to false should remove UFPD fields from request"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" + def activity = new Activity(defaultAction: false) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have empty UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + !genericBidderRequest.device.didsha1 + !genericBidderRequest.device.didmd5 + !genericBidderRequest.device.dpidsha1 + !genericBidderRequest.device.ifa + !genericBidderRequest.device.macsha1 + !genericBidderRequest.device.macmd5 + !genericBidderRequest.device.dpidmd5 + !genericBidderRequest.user.id + !genericBidderRequest.user.buyeruid + !genericBidderRequest.user.yob + !genericBidderRequest.user.gender + !genericBidderRequest.user.eids + !genericBidderRequest.user.data + !genericBidderRequest.user.ext + } + } + + def "PBS amp call when bidder allowed activities have empty condition type should skip this rule and emit an error"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId } - and: "Setup condition" + and: "Activities set with have empty condition type" + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(conditions, isAllowed)]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "Activity configuration for account ${accountId} " + + "contains conditional rule with empty array").size() == 1 + + where: + conditions | isAllowed + new Condition(componentType: []) | true + new Condition(componentType: []) | false + new Condition(componentName: []) | true + new Condition(componentName: []) | false + } + + def "PBS amp call when first rule allowing in activities should leave UFPD fields in request"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Activity rules with same priority" + def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) + def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) + + and: "Activities set for bidder allowed by hierarchy structure" + def activity = Activity.getDefaultActivity([allowActivity, disallowActivity]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) + + and: "Save account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have data in UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + genericBidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 + genericBidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 + genericBidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 + genericBidderRequest.device.ifa == ampStoredRequest.device.ifa + genericBidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 + genericBidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 + genericBidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 + genericBidderRequest.user.id == ampStoredRequest.user.id + genericBidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid + genericBidderRequest.user.yob == ampStoredRequest.user.yob + genericBidderRequest.user.gender == ampStoredRequest.user.gender + genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source + genericBidderRequest.user.data == ampStoredRequest.user.data + genericBidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid + } + } + + def "PBS amp call when first rule disallowing in activities should remove UFPD fields in request"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Activities set for actions with Generic bidder rejected by hierarchy setup" + def disallowActivity = new ActivityRule(condition: Condition.baseCondition, allow: false) + def allowActivity = new ActivityRule(condition: Condition.baseCondition, allow: true) + + and: "Activities set for bidder disallowing by hierarchy structure" + def activity = Activity.getDefaultActivity([disallowActivity, allowActivity]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have empty UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + verifyAll { + !genericBidderRequest.device.didsha1 + !genericBidderRequest.device.didmd5 + !genericBidderRequest.device.dpidsha1 + !genericBidderRequest.device.ifa + !genericBidderRequest.device.macsha1 + !genericBidderRequest.device.macmd5 + !genericBidderRequest.device.dpidmd5 + !genericBidderRequest.user.id + !genericBidderRequest.user.buyeruid + !genericBidderRequest.user.yob + !genericBidderRequest.user.gender + !genericBidderRequest.user.eids + !genericBidderRequest.user.data + !genericBidderRequest.user.ext + } + } + + def "PBS amp should disallowed rule when header.gpc intersection with condition.gpc"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { + regs.ext.gpc = null + } + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" def condition = Condition.baseCondition.tap { it.componentType = null it.componentName = null - it.gppSid = null - it.gpc = PBSUtils.randomNumber as String + it.gpc = VALID_VALUE_FOR_GPC_HEADER } + def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) - and: "Setup activities" + and: "Flush metrics" + flushMetrics(activityPbsService) + + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest, ["Sec-GPC": VALID_VALUE_FOR_GPC_HEADER]) + + then: "Generic bidder request should have empty UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + verifyAll { + !genericBidderRequest.device.didsha1 + !genericBidderRequest.device.didmd5 + !genericBidderRequest.device.dpidsha1 + !genericBidderRequest.device.ifa + !genericBidderRequest.device.macsha1 + !genericBidderRequest.device.macmd5 + !genericBidderRequest.device.dpidmd5 + !genericBidderRequest.user.id + !genericBidderRequest.user.buyeruid + !genericBidderRequest.user.yob + !genericBidderRequest.user.gender + !genericBidderRequest.user.eids + !genericBidderRequest.user.data + !genericBidderRequest.user.ext + } + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 + assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 + } + + def "PBS amp should allowed rule when gpc header doesn't intersection with condition.gpc"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + } + + and: "Allow activities setup" + def condition = Condition.baseCondition.tap { + it.componentType = null + it.componentName = null + it.gpc = PBSUtils.randomNumber as String + } def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) and: "Flush metrics" flushMetrics(activityPbsService) - and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Saved account config with allow activities into DB" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities) accountDao.save(account) - when: "PBS processes auction requests" - activityPbsService.sendAuctionRequest(bidRequest, ["Sec-GPC": "1"]) + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest, ["Sec-GPC": VALID_VALUE_FOR_GPC_HEADER]) then: "Generic bidder request should have data in UFPD fields" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + genericBidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 + genericBidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 + genericBidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 + genericBidderRequest.device.ifa == ampStoredRequest.device.ifa + genericBidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 + genericBidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 + genericBidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 + genericBidderRequest.user.id == ampStoredRequest.user.id + genericBidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid + genericBidderRequest.user.yob == ampStoredRequest.user.yob + genericBidderRequest.user.gender == ampStoredRequest.user.gender + genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source + genericBidderRequest.user.data == ampStoredRequest.user.data + genericBidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid + } + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + } + + def "PBS amp call when privacy regulation match and rejecting should remove UFPD fields in request"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = SIMPLE_GPC_DISALLOW_LOGIC + it.consentType = GPP + } + + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [privacyAllowRegulations] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + then: "Generic bidder request should have empty UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) verifyAll { - bidderRequest.device.didsha1 == bidRequest.device.didsha1 - bidderRequest.device.didmd5 == bidRequest.device.didmd5 - bidderRequest.device.dpidsha1 == bidRequest.device.dpidsha1 - bidderRequest.device.ifa == bidRequest.device.ifa - bidderRequest.device.macsha1 == bidRequest.device.macsha1 - bidderRequest.device.macmd5 == bidRequest.device.macmd5 - bidderRequest.device.dpidmd5 == bidRequest.device.dpidmd5 - bidderRequest.user.id == bidRequest.user.id - bidderRequest.user.buyeruid == bidRequest.user.buyeruid - bidderRequest.user.yob == bidRequest.user.yob - bidderRequest.user.gender == bidRequest.user.gender - bidderRequest.user.eids[0].source == bidRequest.user.eids[0].source - bidderRequest.user.data == bidRequest.user.data - bidderRequest.user.ext.data.buyeruid == bidRequest.user.ext.data.buyeruid + !genericBidderRequest.device.didsha1 + !genericBidderRequest.device.didmd5 + !genericBidderRequest.device.dpidsha1 + !genericBidderRequest.device.ifa + !genericBidderRequest.device.macsha1 + !genericBidderRequest.device.macmd5 + !genericBidderRequest.device.dpidmd5 + !genericBidderRequest.user.id + !genericBidderRequest.user.buyeruid + !genericBidderRequest.user.yob + !genericBidderRequest.user.gender + !genericBidderRequest.user.eids + !genericBidderRequest.user.data + !genericBidderRequest.user.ext } - and: "Metrics processed across activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 - assert metrics[ACTIVITY_PROCESSED_RULES_FOR_ACCOUNT.formatted(accountId)] == 1 + where: + privacyAllowRegulations << [IAB_US_GENERAL, IAB_ALL, ALL] } - def "PBS auction should disallowed rule when header gpc intersection with condition.gpc"() { - given: "Generic bid request with account connection" + def "PBS amp call when privacy module contain some part of disallow logic should remove UFPD fields in request"() { + given: "Default Generic BidRequest with UFPD fields field and account id" def accountId = PBSUtils.randomNumber as String - def bidRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { - it.setAccountId(accountId) - it.ext.prebid.trace = VERBOSE - it.regs.ext.gpc = null + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = disallowGppLogic + it.consentType = GPP } - and: "Setup activity" - def condition = Condition.baseCondition.tap { - it.componentType = null - it.componentName = null - it.gpc = VALID_VALUE_FOR_GPC_HEADER + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] } - and: "Setup activities" - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) - and: "Flush metrics" - flushMetrics(activityPbsService) + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) - and: "Set up account for allow activities" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) - when: "PBS processes auction requests with header" - activityPbsService.sendAuctionRequest(bidRequest, ["Sec-GPC": VALID_VALUE_FOR_GPC_HEADER]) + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) - then: "Generic bidder request should have empty UFPD fields" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + then: "Generic bidder request should have empty UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) verifyAll { - !bidderRequest.device.didsha1 - !bidderRequest.device.didmd5 - !bidderRequest.device.dpidsha1 - !bidderRequest.device.ifa - !bidderRequest.device.macsha1 - !bidderRequest.device.macmd5 - !bidderRequest.device.dpidmd5 - !bidderRequest.user.id - !bidderRequest.user.buyeruid - !bidderRequest.user.yob - !bidderRequest.user.gender - !bidderRequest.user.eids - !bidderRequest.user.data + !genericBidderRequest.device.didsha1 + !genericBidderRequest.device.didmd5 + !genericBidderRequest.device.dpidsha1 + !genericBidderRequest.device.ifa + !genericBidderRequest.device.macsha1 + !genericBidderRequest.device.macmd5 + !genericBidderRequest.device.dpidmd5 + !genericBidderRequest.user.id + !genericBidderRequest.user.buyeruid + !genericBidderRequest.user.yob + !genericBidderRequest.user.gender + !genericBidderRequest.user.eids + !genericBidderRequest.user.data + !genericBidderRequest.user.ext } - and: "Metrics for disallowed activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 - assert metrics[DISALLOWED_COUNT_FOR_ACCOUNT.formatted(accountId)] == 1 - assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 + where: + disallowGppLogic << [ + SIMPLE_GPC_DISALLOW_LOGIC, + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build(), + new UspNatV1Consent.Builder().setSaleOptOut(1).build(), + new UspNatV1Consent.Builder().setSaleOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSharingNotice(2).build(), + new UspNatV1Consent.Builder().setSaleOptOutNotice(0).setSaleOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSharingOptOut(1).build(), + new UspNatV1Consent.Builder().setSharingOptOutNotice(0).setSharingOptOut(2).build(), + new UspNatV1Consent.Builder().setSharingNotice(0).setSharingOptOut(2).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOut(1).build(), + new UspNatV1Consent.Builder().setTargetedAdvertisingOptOutNotice(0).setTargetedAdvertisingOptOut(2).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessingOptOutNotice(2).build(), + new UspNatV1Consent.Builder().setSensitiveDataLimitUseNotice(2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 1).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 2).build(), + new UspNatV1Consent.Builder().setKnownChildSensitiveDataConsents(0, 1).build(), + new UspNatV1Consent.Builder().setPersonalDataConsents(2).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessing(new UsNationalSensitiveData( + racialEthnicOrigin: 1, + religiousBeliefs: 1, + healthInfo: 1, + orientation: 1, + citizenshipStatus: 1, + unionMembership: 1, + )).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataLimitUseNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + racialEthnicOrigin: 2, + religiousBeliefs: 2, + healthInfo: 2, + orientation: 2, + citizenshipStatus: 2, + geneticId: 2, + biometricId: 2, + idNumbers: 2, + accountInfo: 2, + unionMembership: 2, + communicationContents: 2 + )).build(), + new UspNatV1Consent.Builder() + .setSensitiveDataProcessingOptOutNotice(0) + .setSensitiveDataProcessing(new UsNationalSensitiveData( + racialEthnicOrigin: 2, + religiousBeliefs: 2, + healthInfo: 2, + orientation: 2, + citizenshipStatus: 2, + geneticId: 2, + biometricId: 2, + idNumbers: 2, + accountInfo: 2, + unionMembership: 2, + communicationContents: 2 + )).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessing(new UsNationalSensitiveData( + geneticId: 1, + biometricId: 1, + idNumbers: 1, + accountInfo: 1, + communicationContents: 1 + )).build(), + new UspNatV1Consent.Builder().setSensitiveDataProcessing(new UsNationalSensitiveData( + geneticId: 2, + biometricId: 2, + idNumbers: 2, + accountInfo: 2, + communicationContents: 2 + )).build() + ] } - def "PBS amp should disallowed rule when header.gpc intersection with condition.gpc"() { + def "PBS amp call when request have different gpp consent but match and rejecting should remove UFPD fields in request"() { given: "Default Generic BidRequest with UFPD fields field and account id" def accountId = PBSUtils.randomNumber as String - def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId).tap { - regs.ext.gpc = null - } + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) and: "amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId + it.gppSid = gppSid.value + it.consentString = gppConsent + it.consentType = GPP } - and: "Allow activities setup" - def condition = Condition.baseCondition.tap { - it.componentType = null - it.componentName = null - it.gpc = VALID_VALUE_FOR_GPC_HEADER + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] } - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) - and: "Flush metrics" - flushMetrics(activityPbsService) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) and: "Stored request in DB" @@ -1063,7 +1794,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { storedRequestDao.save(storedRequest) when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest, ["Sec-GPC": VALID_VALUE_FOR_GPC_HEADER]) + activityPbsService.sendAmpRequest(ampRequest) then: "Generic bidder request should have empty UFPD fields" def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) @@ -1084,13 +1815,17 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { !genericBidderRequest.user.ext } - and: "Metrics for disallowed activities should be updated" - def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[DISALLOWED_COUNT_FOR_ACTIVITY_RULE] == 1 - assert metrics[DISALLOWED_COUNT_FOR_GENERIC_ADAPTER] == 1 + where: + gppConsent | gppSid + new UspNatV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_NAT_V1 + new UspCaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CA_V1 + new UspVaV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_VA_V1 + new UspCoV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CO_V1 + new UspUtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_UT_V1 + new UspCtV1Consent.Builder().setMspaServiceProviderMode(1).build() | USP_CT_V1 } - def "PBS amp should allowed rule when gpc header doesn't intersection with condition.gpc"() { + def "PBS amp call when privacy modules contain allowing settings should leave UFPD fields in request"() { given: "Default Generic BidRequest with UFPD fields field and account id" def accountId = PBSUtils.randomNumber as String def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) @@ -1098,22 +1833,142 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { and: "amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = SIMPLE_GPC_DISALLOW_LOGIC + it.consentType = GPP } - and: "Allow activities setup" - def condition = Condition.baseCondition.tap { - it.componentType = null - it.componentName = null - it.gpc = PBSUtils.randomNumber as String + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] } - def activity = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(condition, false)]) - def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, activity) + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have data in UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + genericBidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 + genericBidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 + genericBidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 + genericBidderRequest.device.ifa == ampStoredRequest.device.ifa + genericBidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 + genericBidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 + genericBidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 + genericBidderRequest.user.id == ampStoredRequest.user.id + genericBidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid + genericBidderRequest.user.yob == ampStoredRequest.user.yob + genericBidderRequest.user.gender == ampStoredRequest.user.gender + genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source + genericBidderRequest.user.data == ampStoredRequest.user.data + genericBidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid + } + + where: + accountGppConfig << [ + new AccountGppConfig(code: IAB_US_GENERAL, enabled: false), + new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: true) + ] + } + + def "PBS amp call when regs.gpp in request is allowing should leave UFPD fields in request"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = regsGpp + it.consentType = GPP + } + + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have data in UFPD fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + genericBidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 + genericBidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 + genericBidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 + genericBidderRequest.device.ifa == ampStoredRequest.device.ifa + genericBidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 + genericBidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 + genericBidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 + genericBidderRequest.user.id == ampStoredRequest.user.id + genericBidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid + genericBidderRequest.user.yob == ampStoredRequest.user.yob + genericBidderRequest.user.gender == ampStoredRequest.user.gender + genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source + genericBidderRequest.user.data == ampStoredRequest.user.data + genericBidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid + } + + where: + regsGpp << ["", new UspNatV1Consent.Builder().build(), new UspNatV1Consent.Builder().setGpc(false).build()] + } + + def "PBS amp call when privacy regulation have duplicate should leave UFPD fields in request and update alerts metrics"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = "" + it.consentType = GPP + } + + and: "Activities set for transmitUfpd with privacy regulation" + def ruleUsGeneric = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([ruleUsGeneric])) and: "Flush metrics" flushMetrics(activityPbsService) - and: "Saved account config with allow activities into DB" - def account = getAccountWithAllowActivities(accountId, activities) + and: "Account gpp privacy regulation configs with conflict" + def accountGppUsNatAllowConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: [USP_NAT_V1]), enabled: false) + def accountGppUsNatRejectConfig = new AccountGppConfig(code: IAB_US_GENERAL, config: new SidsConfig(skipSids: []), enabled: true) + + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppUsNatAllowConfig, accountGppUsNatRejectConfig]) accountDao.save(account) and: "Stored request in DB" @@ -1121,7 +1976,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { storedRequestDao.save(storedRequest) when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest, ["Sec-GPC": VALID_VALUE_FOR_GPC_HEADER]) + activityPbsService.sendAmpRequest(ampRequest) then: "Generic bidder request should have data in UFPD fields" def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) @@ -1143,9 +1998,48 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { genericBidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid } - and: "Metrics processed across activities should be updated" + and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ACTIVITY_RULES_PROCESSED_COUNT] == 1 + assert metrics[ALERT_GENERAL] == 1 + } + + def "PBS amp call when privacy module contain invalid property should respond with an error"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = givenBidRequestWithAccountAndUfpdData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = USP_NAT_V1.value + it.consentString = SIMPLE_GPC_DISALLOW_LOGIC + it.consentType = GPP + } + + def ruleIabAll = new ActivityRule().tap { + it.privacyRegulation = [IAB_ALL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([ruleIabAll])) + + and: "Multiple account gpp privacy regulation config" + def accountGppTfcEuConfig = new AccountGppConfig(code: IAB_TFC_EU, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppTfcEuConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + activityPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain error" + def error = thrown(PrebidServerException) + assert error.statusCode == UNAUTHORIZED.code() + assert error.responseBody == "Unauthorized account id: ${accountId}" } private BidRequest givenBidRequestWithAccountAndUfpdData(String accountId) { diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy index d515cd1939d..96bc2361af5 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy @@ -5,6 +5,7 @@ import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountCookieSyncConfig import org.prebid.server.functional.model.config.AccountCoopSyncConfig import org.prebid.server.functional.model.config.AccountGdprConfig +import org.prebid.server.functional.model.config.AccountGppConfig import org.prebid.server.functional.model.config.AccountPrivacyConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.request.amp.AmpRequest @@ -24,6 +25,8 @@ import org.prebid.server.functional.tests.BaseSpec import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.ConsentString import org.prebid.server.functional.util.privacy.TcfConsent +import org.prebid.server.functional.util.privacy.gpp.GppConsent +import org.prebid.server.functional.util.privacy.gpp.UspNatV1Consent import spock.lang.Shared import static org.prebid.server.functional.model.bidder.BidderName.GENERIC @@ -52,11 +55,13 @@ abstract class PrivacyBaseSpec extends BaseSpec { "adapters.${OPENX.value}.enabled" : 'true'] private static final Map GDPR_VENDOR_LIST_CONFIG = ["gdpr.vendorlist.v2.http-endpoint-template": "$networkServiceContainer.rootUri/v2/vendor-list.json".toString(), "gdpr.vendorlist.v3.http-endpoint-template": "$networkServiceContainer.rootUri/v3/vendor-list.json".toString()] + private static final Map SETTING_CONFIG = ["settings.enforce-valid-account": 'true'] private static final PbsPgConfig pgConfig = new PbsPgConfig(networkServiceContainer) protected static final Map PBS_CONFIG = OPENX_CONFIG + OPENX_COOKIE_SYNC_CONFIG + - GENERIC_COOKIE_SYNC_CONFIG + pgConfig.properties + GDPR_VENDOR_LIST_CONFIG + GENERIC_COOKIE_SYNC_CONFIG + pgConfig.properties + GDPR_VENDOR_LIST_CONFIG + SETTING_CONFIG protected static final String VALID_VALUE_FOR_GPC_HEADER = "1" + protected static final GppConsent SIMPLE_GPC_DISALLOW_LOGIC = new UspNatV1Consent.Builder().setGpc(true).build() protected static final VendorList vendorListResponse = new VendorList(networkServiceContainer) @Shared @@ -159,8 +164,11 @@ abstract class PrivacyBaseSpec extends BaseSpec { new Account(uuid: accountId, config: new AccountConfig(privacy: privacy)) } - protected static Account getAccountWithAllowActivities(String accountId, AllowActivities activities) { - def privacy = new AccountPrivacyConfig(ccpa: new AccountCcpaConfig(enabled: true), allowActivities: activities) + protected static Account getAccountWithAllowActivitiesAndPrivacyModule(String accountId, + AllowActivities activities, + List gppConfigs = []) { + + def privacy = new AccountPrivacyConfig(ccpa: new AccountCcpaConfig(enabled: true), allowActivities: activities, modules: gppConfigs) def cookieSyncConfig = new AccountCookieSyncConfig(coopSync: new AccountCoopSyncConfig(enabled: false)) def accountConfig = new AccountConfig(cookieSync: cookieSyncConfig, privacy: privacy) new Account(uuid: accountId, config: accountConfig) diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/GppConsent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/GppConsent.groovy index 49aa2354394..09ce5fdb277 100644 --- a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/GppConsent.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/GppConsent.groovy @@ -2,7 +2,13 @@ package org.prebid.server.functional.util.privacy.gpp import com.iab.gpp.encoder.GppModel import com.iab.gpp.encoder.section.TcfEuV2 +import com.iab.gpp.encoder.section.UspCaV1 +import com.iab.gpp.encoder.section.UspCoV1 +import com.iab.gpp.encoder.section.UspCtV1 +import com.iab.gpp.encoder.section.UspNatV1 +import com.iab.gpp.encoder.section.UspUtV1 import com.iab.gpp.encoder.section.UspV1 +import com.iab.gpp.encoder.section.UspVaV1 import org.prebid.server.functional.util.privacy.ConsentString abstract class GppConsent implements ConsentString { @@ -56,8 +62,14 @@ abstract class GppConsent implements ConsentString { enum Section { - TCF_EU_V2(TcfEuV2.NAME, TcfEuV2.VERSION), - USP_V1(UspV1.NAME, UspV1.VERSION) + TCF_EU_V2(TcfEuV2.NAME, TcfEuV2.VERSION), //2 + USP_V1(UspV1.NAME, UspV1.VERSION), //6 + USP_NAT_V1(UspNatV1.NAME, UspNatV1.VERSION), //7 + USP_CA_V1(UspCaV1.NAME, UspCaV1.VERSION), //8 + USP_VA_V1(UspVaV1.NAME, UspVaV1.VERSION), //9 + USP_CO_V1(UspCoV1.NAME, UspCoV1.VERSION), //10 + USP_UT_V1(UspUtV1.NAME, UspUtV1.VERSION), //11 + USP_CT_V1(UspCtV1.NAME, UspCtV1.VERSION), // 12 final String name final int version diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspCaV1Consent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspCaV1Consent.groovy new file mode 100644 index 00000000000..d5ab56be602 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspCaV1Consent.groovy @@ -0,0 +1,105 @@ +package org.prebid.server.functional.util.privacy.gpp + +import com.iab.gpp.encoder.field.UspCaV1Field +import org.prebid.server.functional.util.privacy.gpp.data.UsCaliforniaSensitiveData + +class UspCaV1Consent extends GppConsent { + + private static final Section SECTION = Section.USP_CA_V1 + + protected UspCaV1Consent(Section section, Map fieldValues) { + super(section, fieldValues) + } + + @Override + protected String encodeSection() { + gppModel.encodeSection(SECTION.name) + } + + static class Builder extends GppConsent.Builder { + + Builder() { + super(SECTION) + } + + Builder setVersion(Integer version) { + fieldValue(UspCaV1Field.VERSION, version) + this + } + + Builder setSaleOptOutNotice(Integer saleOptOutNotice) { + fieldValue(UspCaV1Field.SALE_OPT_OUT_NOTICE, saleOptOutNotice) + this + } + + Builder setSharingOptOutNotice(Integer sharingOptOutNotice) { + fieldValue(UspCaV1Field.SHARING_OPT_OUT_NOTICE, sharingOptOutNotice) + this + } + + Builder setSensitiveDataLimitUseNotice(Integer sensitiveDataLimitUseNotice) { + fieldValue(UspCaV1Field.SENSITIVE_DATA_LIMIT_USE_NOTICE, sensitiveDataLimitUseNotice) + this + } + + Builder setSaleOptOut(Integer saleOptOut) { + fieldValue(UspCaV1Field.SALE_OPT_OUT, saleOptOut) + this + } + + Builder setSharingOptOut(Integer sharingOptOut) { + fieldValue(UspCaV1Field.SHARING_OPT_OUT, sharingOptOut) + this + } + + Builder setSensitiveDataProcessing(UsCaliforniaSensitiveData sensitiveDataProcessing) { + fieldValue(UspCaV1Field.SENSITIVE_DATA_PROCESSING, sensitiveDataProcessing.contentList) + this + } + + Builder setKnownChildSensitiveDataConsents(Integer childFrom13to16, Integer childBlow13) { + fieldValue(UspCaV1Field.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, [childFrom13to16, childBlow13]) + this + } + + Builder setPersonalDataConsents(Integer personalDataConsents) { + fieldValue(UspCaV1Field.PERSONAL_DATA_CONSENTS, personalDataConsents) + this + } + + Builder setMspaCoveredTransaction(Integer mspaCoveredTransaction) { + fieldValue(UspCaV1Field.MSPA_COVERED_TRANSACTION, mspaCoveredTransaction) + this + } + + Builder setMspaOptOutOptionMode(Integer mspaOptOutOptionMode) { + fieldValue(UspCaV1Field.MSPA_OPT_OUT_OPTION_MODE, mspaOptOutOptionMode) + this + } + + Builder setMspaServiceProviderMode(Integer mspaServiceProviderMode) { + fieldValue(UspCaV1Field.MSPA_SERVICE_PROVIDER_MODE, mspaServiceProviderMode) + this + } + + Builder setGpcSegmentType(Integer gpcSegmentType) { + fieldValue(UspCaV1Field.GPC_SEGMENT_TYPE, gpcSegmentType) + this + } + + Builder setGpcSegmentIncluded(Boolean gpcSegmentIncluded) { + fieldValue(UspCaV1Field.GPC_SEGMENT_INCLUDED, gpcSegmentIncluded) + this + } + + Builder setGpc(Boolean gpc) { + fieldValue(UspCaV1Field.GPC, gpc) + this + } + + @Override + GppConsent build() { + return new UspCaV1Consent(section, fieldValues) + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspCoV1Consent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspCoV1Consent.groovy new file mode 100644 index 00000000000..1f4c2b29744 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspCoV1Consent.groovy @@ -0,0 +1,100 @@ +package org.prebid.server.functional.util.privacy.gpp + +import com.iab.gpp.encoder.field.UspCoV1Field +import org.prebid.server.functional.util.privacy.gpp.data.UsColoradoSensitiveData + +class UspCoV1Consent extends GppConsent { + + private static final Section SECTION = Section.USP_CO_V1 + + protected UspCoV1Consent(Section section, Map fieldValues) { + super(section, fieldValues) + } + + @Override + protected String encodeSection() { + gppModel.encodeSection(SECTION.name) + } + + static class Builder extends GppConsent.Builder { + + Builder() { + super(SECTION) + } + + Builder setVersion(Integer version) { + fieldValue(UspCoV1Field.VERSION, version) + this + } + + Builder setSharingNotice(Integer sharingNotice) { + fieldValue(UspCoV1Field.SHARING_NOTICE, sharingNotice) + this + } + + Builder setSaleOptOutNotice(Integer saleOptOutNotice) { + fieldValue(UspCoV1Field.SALE_OPT_OUT_NOTICE, saleOptOutNotice) + this + } + + Builder setTargetedAdvertisingOptOutNotice(Integer targetedAdvertisingOptOutNotice) { + fieldValue(UspCoV1Field.TARGETED_ADVERTISING_OPT_OUT_NOTICE, targetedAdvertisingOptOutNotice) + this + } + + Builder setSaleOptOut(Integer saleOptOut) { + fieldValue(UspCoV1Field.SALE_OPT_OUT, saleOptOut) + this + } + + Builder setTargetedAdvertisingOptOut(Integer targetedAdvertisingOptOut) { + fieldValue(UspCoV1Field.TARGETED_ADVERTISING_OPT_OUT, targetedAdvertisingOptOut) + this + } + + Builder setSensitiveDataProcessing(UsColoradoSensitiveData sensitiveDataProcessing) { + fieldValue(UspCoV1Field.SENSITIVE_DATA_PROCESSING, sensitiveDataProcessing.contentList) + this + } + + Builder setKnownChildSensitiveDataConsents(Integer knownChildSensitiveDataConsents) { + fieldValue(UspCoV1Field.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, knownChildSensitiveDataConsents) + this + } + + Builder setMspaCoveredTransaction(Integer mspaCoveredTransaction) { + fieldValue(UspCoV1Field.MSPA_COVERED_TRANSACTION, mspaCoveredTransaction) + this + } + + Builder setMspaOptOutOptionMode(Integer mspaOptOutOptionMode) { + fieldValue(UspCoV1Field.MSPA_OPT_OUT_OPTION_MODE, mspaOptOutOptionMode) + this + } + + Builder setMspaServiceProviderMode(Integer mspaServiceProviderMode) { + fieldValue(UspCoV1Field.MSPA_SERVICE_PROVIDER_MODE, mspaServiceProviderMode) + this + } + + Builder setGpcSegmentType(Integer gpcSegmentType) { + fieldValue(UspCoV1Field.GPC_SEGMENT_TYPE, gpcSegmentType) + this + } + + Builder setGpcSegmentIncluded(Integer gpcSegmentIncluded) { + fieldValue(UspCoV1Field.GPC_SEGMENT_INCLUDED, gpcSegmentIncluded) + this + } + + Builder setGpc(Boolean gpc) { + fieldValue(UspCoV1Field.GPC, gpc) + this + } + + @Override + GppConsent build() { + new UspCoV1Consent(section, fieldValues) + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspCtV1Consent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspCtV1Consent.groovy new file mode 100644 index 00000000000..a7fe10c4443 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspCtV1Consent.groovy @@ -0,0 +1,100 @@ +package org.prebid.server.functional.util.privacy.gpp + +import com.iab.gpp.encoder.field.UspCtV1Field +import org.prebid.server.functional.util.privacy.gpp.data.UsConnecticutSensitiveData + +class UspCtV1Consent extends GppConsent { + + private static final Section SECTION = Section.USP_CT_V1 + + protected UspCtV1Consent(Section section, Map fieldValues) { + super(section, fieldValues) + } + + @Override + protected String encodeSection() { + gppModel.encodeSection(SECTION.name) + } + + static class Builder extends GppConsent.Builder { + + Builder() { + super(SECTION) + } + + Builder setVersion(Integer version) { + fieldValue(UspCtV1Field.VERSION, version) + this + } + + Builder setSharingNotice(Integer sharingNotice) { + fieldValue(UspCtV1Field.SHARING_NOTICE, sharingNotice) + this + } + + Builder setSaleOptOutNotice(Integer saleOptOutNotice) { + fieldValue(UspCtV1Field.SALE_OPT_OUT_NOTICE, saleOptOutNotice) + this + } + + Builder setTargetedAdvertisingOptOutNotice(Integer targetedAdvertisingOptOutNotice) { + fieldValue(UspCtV1Field.TARGETED_ADVERTISING_OPT_OUT_NOTICE, targetedAdvertisingOptOutNotice) + this + } + + Builder setSaleOptOut(Integer saleOptOut) { + fieldValue(UspCtV1Field.SALE_OPT_OUT, saleOptOut) + this + } + + Builder setTargetedAdvertisingOptOut(Integer targetedAdvertisingOptOut) { + fieldValue(UspCtV1Field.TARGETED_ADVERTISING_OPT_OUT, targetedAdvertisingOptOut) + this + } + + Builder setSensitiveDataProcessing(UsConnecticutSensitiveData sensitiveDataProcessing) { + fieldValue(UspCtV1Field.SENSITIVE_DATA_PROCESSING, sensitiveDataProcessing.contentList) + this + } + + Builder setKnownChildSensitiveDataConsents(Integer childFrom13to16, Integer childBlow13, Integer childFrom16to18) { + fieldValue(UspCtV1Field.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, [childFrom13to16, childBlow13, childFrom16to18]) + this + } + + Builder setMspaCoveredTransaction(Integer mspaCoveredTransaction) { + fieldValue(UspCtV1Field.MSPA_COVERED_TRANSACTION, mspaCoveredTransaction) + this + } + + Builder setMspaOptOutOptionMode(Integer mspaOptOutOptionMode) { + fieldValue(UspCtV1Field.MSPA_OPT_OUT_OPTION_MODE, mspaOptOutOptionMode) + this + } + + Builder setMspaServiceProviderMode(Integer mspaServiceProviderMode) { + fieldValue(UspCtV1Field.MSPA_SERVICE_PROVIDER_MODE, mspaServiceProviderMode) + this + } + + Builder setGpcSegmentType(Integer gpcSegmentType) { + fieldValue(UspCtV1Field.GPC_SEGMENT_TYPE, gpcSegmentType) + this + } + + Builder setGpcSegmentIncluded(Integer gpcSegmentIncluded) { + fieldValue(UspCtV1Field.GPC_SEGMENT_INCLUDED, gpcSegmentIncluded) + this + } + + Builder setGpc(Boolean gpc) { + fieldValue(UspCtV1Field.GPC, gpc) + this + } + + @Override + GppConsent build() { + return new UspCtV1Consent(section, fieldValues) + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspNatV1Consent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspNatV1Consent.groovy new file mode 100644 index 00000000000..850586a9348 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspNatV1Consent.groovy @@ -0,0 +1,123 @@ +package org.prebid.server.functional.util.privacy.gpp + +import com.iab.gpp.encoder.field.UspNatV1Field +import org.prebid.server.functional.util.privacy.gpp.data.UsNationalSensitiveData + +class UspNatV1Consent extends GppConsent { + + protected UspNatV1Consent(Section section, Map fieldValues) { + super(section, fieldValues) + } + + @Override + protected String encodeSection() { + gppModel.encodeSection(Section.USP_NAT_V1.name) + } + + static class Builder extends GppConsent.Builder { + + Builder() { + super(GppConsent.Section.USP_NAT_V1) + } + + Builder setVersion(Integer version) { + fieldValue(UspNatV1Field.VERSION, version) + this + } + + Builder setSharingNotice(Integer sharingNotice) { + fieldValue(UspNatV1Field.SHARING_NOTICE, sharingNotice) + this + } + + Builder setSaleOptOutNotice(Integer saleOptOutNotice) { + fieldValue(UspNatV1Field.SALE_OPT_OUT_NOTICE, saleOptOutNotice) + this + } + + Builder setSharingOptOutNotice(Integer sharingOptOutNotice) { + fieldValue(UspNatV1Field.SHARING_OPT_OUT_NOTICE, sharingOptOutNotice) + this + } + + Builder setTargetedAdvertisingOptOutNotice(Integer targetedAdvertisingOptOutNotice) { + fieldValue(UspNatV1Field.TARGETED_ADVERTISING_OPT_OUT_NOTICE, targetedAdvertisingOptOutNotice) + this + } + + Builder setSensitiveDataProcessingOptOutNotice(Integer sensitiveDataProcessingOptOutNotice) { + fieldValue(UspNatV1Field.SENSITIVE_DATA_PROCESSING_OPT_OUT_NOTICE, sensitiveDataProcessingOptOutNotice) + this + } + + Builder setSensitiveDataLimitUseNotice(Integer sensitiveDataLimitUseNotice) { + fieldValue(UspNatV1Field.SENSITIVE_DATA_LIMIT_USE_NOTICE, sensitiveDataLimitUseNotice) + this + } + + Builder setSaleOptOut(Integer saleOptOut) { + fieldValue(UspNatV1Field.SALE_OPT_OUT, saleOptOut) + this + } + + Builder setSharingOptOut(Integer sharingOptOut) { + fieldValue(UspNatV1Field.SHARING_OPT_OUT, sharingOptOut) + this + } + + Builder setTargetedAdvertisingOptOut(Integer targetedAdvertisingOptOut) { + fieldValue(UspNatV1Field.TARGETED_ADVERTISING_OPT_OUT, targetedAdvertisingOptOut) + this + } + + Builder setSensitiveDataProcessing(UsNationalSensitiveData sensitiveDataProcessing) { + fieldValue(UspNatV1Field.SENSITIVE_DATA_PROCESSING, sensitiveDataProcessing.contentList) + this + } + + Builder setPersonalDataConsents(Integer personalDataConsents) { + fieldValue(UspNatV1Field.PERSONAL_DATA_CONSENTS, personalDataConsents) + this + } + + Builder setKnownChildSensitiveDataConsents(Integer childFrom13to16, Integer childBlow13) { + fieldValue(UspNatV1Field.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, [childFrom13to16, childBlow13]) + this + } + + Builder setMspaCoveredTransaction(Integer mspaCoveredTransaction) { + fieldValue(UspNatV1Field.MSPA_COVERED_TRANSACTION, mspaCoveredTransaction) + this + } + + Builder setMspaOptOutOptionMode(Integer mspaOptOutOptionMode) { + fieldValue(UspNatV1Field.MSPA_OPT_OUT_OPTION_MODE, mspaOptOutOptionMode) + this + } + + Builder setMspaServiceProviderMode(Integer mspaServiceProviderMode) { + fieldValue(UspNatV1Field.MSPA_SERVICE_PROVIDER_MODE, mspaServiceProviderMode) + this + } + + Builder setGpcSegmentType(Integer gpcSegmentType) { + fieldValue(UspNatV1Field.GPC_SEGMENT_TYPE, gpcSegmentType) + this + } + + Builder setGpcSegmentIncluded(Integer gpcSegmentIncluded) { + fieldValue(UspNatV1Field.GPC_SEGMENT_INCLUDED, gpcSegmentIncluded) + this + } + + Builder setGpc(Boolean gpc) { + fieldValue(UspNatV1Field.GPC, gpc) + this + } + + @Override + GppConsent build() { + new UspNatV1Consent(section, fieldValues) + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspUtV1Consent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspUtV1Consent.groovy new file mode 100644 index 00000000000..cb9101db214 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspUtV1Consent.groovy @@ -0,0 +1,90 @@ +package org.prebid.server.functional.util.privacy.gpp + +import com.iab.gpp.encoder.field.UspUtV1Field +import org.prebid.server.functional.util.privacy.gpp.data.UsUtahSensitiveData + +class UspUtV1Consent extends GppConsent { + + private static final Section SECTION = Section.USP_UT_V1 + + protected UspUtV1Consent(Section section, Map fieldValues) { + super(section, fieldValues) + } + + @Override + protected String encodeSection() { + gppModel.encodeSection(SECTION.name) + } + + static class Builder extends GppConsent.Builder { + + Builder() { + super(GppConsent.Section.USP_UT_V1) + } + + Builder setVersion(Integer version) { + fieldValue(UspUtV1Field.VERSION, version) + this + } + + Builder setSharingNotice(Integer sharingNotice) { + fieldValue(UspUtV1Field.SHARING_NOTICE, sharingNotice) + this + } + + Builder setSaleOptOutNotice(Integer saleOptOutNotice) { + fieldValue(UspUtV1Field.SALE_OPT_OUT_NOTICE, saleOptOutNotice) + this + } + + Builder setTargetedAdvertisingOptOutNotice(Integer targetedAdvertisingOptOutNotice) { + fieldValue(UspUtV1Field.TARGETED_ADVERTISING_OPT_OUT_NOTICE, targetedAdvertisingOptOutNotice) + this + } + + Builder setSensitiveDataProcessingOptOutNotice(Integer sensitiveDataProcessingOptOutNotice) { + fieldValue(UspUtV1Field.SENSITIVE_DATA_PROCESSING_OPT_OUT_NOTICE, sensitiveDataProcessingOptOutNotice) + this + } + + Builder setSaleOptOut(Integer saleOptOut) { + fieldValue(UspUtV1Field.SALE_OPT_OUT, saleOptOut) + this + } + + Builder setTargetedAdvertisingOptOut(Integer targetedAdvertisingOptOut) { + fieldValue(UspUtV1Field.TARGETED_ADVERTISING_OPT_OUT, targetedAdvertisingOptOut) + this + } + + Builder setSensitiveDataProcessing(UsUtahSensitiveData sensitiveDataProcessing) { + fieldValue(UspUtV1Field.SENSITIVE_DATA_PROCESSING, sensitiveDataProcessing.contentList) + this + } + + Builder setKnownChildSensitiveDataConsents(Integer knownChildSensitiveDataConsents) { + fieldValue(UspUtV1Field.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, knownChildSensitiveDataConsents) + this + } + + Builder setMspaCoveredTransaction(Integer mspaCoveredTransaction) { + fieldValue(UspUtV1Field.MSPA_COVERED_TRANSACTION, mspaCoveredTransaction) + this + } + + Builder setMspaOptOutOptionMode(Integer mspaOptOutOptionMode) { + fieldValue(UspUtV1Field.MSPA_OPT_OUT_OPTION_MODE, mspaOptOutOptionMode) + this + } + + Builder setMspaServiceProviderMode(Integer mspaServiceProviderMode) { + fieldValue(UspUtV1Field.MSPA_SERVICE_PROVIDER_MODE, mspaServiceProviderMode) + this + } + + @Override + GppConsent build() { + return new UspUtV1Consent(section, fieldValues) + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspVaV1Consent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspVaV1Consent.groovy new file mode 100644 index 00000000000..b33cf2d9603 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/UspVaV1Consent.groovy @@ -0,0 +1,85 @@ +package org.prebid.server.functional.util.privacy.gpp + +import com.iab.gpp.encoder.field.UspVaV1Field +import org.prebid.server.functional.util.privacy.gpp.data.UsVirginiaSensitiveData + +class UspVaV1Consent extends GppConsent { + + private static final Section SECTION = Section.USP_VA_V1 + + protected UspVaV1Consent(Section section, Map fieldValues) { + super(section, fieldValues) + } + + @Override + protected String encodeSection() { + gppModel.encodeSection(SECTION.name) + } + + static class Builder extends GppConsent.Builder { + + Builder() { + super(SECTION) + } + + Builder setVersion(Integer version) { + fieldValue(UspVaV1Field.VERSION, version) + this + } + + Builder setSharingNotice(Integer sharingNotice) { + fieldValue(UspVaV1Field.SHARING_NOTICE, sharingNotice) + this + } + + Builder setSaleOptOutNotice(Integer saleOptOutNotice) { + fieldValue(UspVaV1Field.SALE_OPT_OUT_NOTICE, saleOptOutNotice) + this + } + + Builder setTargetedAdvertisingOptOutNotice(Integer targetedAdvertisingOptOutNotice) { + fieldValue(UspVaV1Field.TARGETED_ADVERTISING_OPT_OUT_NOTICE, targetedAdvertisingOptOutNotice) + this + } + + Builder setSaleOptOut(Integer saleOptOut) { + fieldValue(UspVaV1Field.SALE_OPT_OUT, saleOptOut) + this + } + + Builder setTargetedAdvertisingOptOut(Integer targetedAdvertisingOptOut) { + fieldValue(UspVaV1Field.TARGETED_ADVERTISING_OPT_OUT, targetedAdvertisingOptOut) + this + } + + Builder setSensitiveDataProcessing(UsVirginiaSensitiveData sensitiveDataProcessing) { + fieldValue(UspVaV1Field.SENSITIVE_DATA_PROCESSING, sensitiveDataProcessing.contentList) + this + } + + Builder setKnownChildSensitiveDataConsents(Integer childSensitiveDataConsents) { + fieldValue(UspVaV1Field.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, childSensitiveDataConsents) + this + } + + Builder setMspaCoveredTransaction(Integer mspaCoveredTransaction) { + fieldValue(UspVaV1Field.MSPA_COVERED_TRANSACTION, mspaCoveredTransaction) + this + } + + Builder setMspaOptOutOptionMode(Integer mspaOptOutOptionMode) { + fieldValue(UspVaV1Field.MSPA_OPT_OUT_OPTION_MODE, mspaOptOutOptionMode) + this + } + + Builder setMspaServiceProviderMode(Integer mspaServiceProviderMode) { + fieldValue(UspVaV1Field.MSPA_SERVICE_PROVIDER_MODE, mspaServiceProviderMode) + this + } + + @Override + GppConsent build() { + return new UspVaV1Consent(section, fieldValues) + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsCaliforniaSensitiveData.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsCaliforniaSensitiveData.groovy new file mode 100644 index 00000000000..7a6fba408e7 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsCaliforniaSensitiveData.groovy @@ -0,0 +1,52 @@ +package org.prebid.server.functional.util.privacy.gpp.data + +import org.prebid.server.functional.util.PBSUtils + +class UsCaliforniaSensitiveData { + + int idNumbers + int accountInfo + int geolocation + int racialEthnicOrigin + int communicationContents + int geneticId + int biometricId + int healthInfo + int orientation + + static UsCaliforniaSensitiveData generateRandomSensitiveData() { + new UsCaliforniaSensitiveData().tap { + idNumbers = PBSUtils.getRandomNumber(0, 3) + accountInfo = PBSUtils.getRandomNumber(0, 3) + geolocation = PBSUtils.getRandomNumber(0, 3) + racialEthnicOrigin = PBSUtils.getRandomNumber(0, 3) + communicationContents = PBSUtils.getRandomNumber(0, 3) + geneticId = PBSUtils.getRandomNumber(0, 3) + biometricId = PBSUtils.getRandomNumber(0, 3) + healthInfo = PBSUtils.getRandomNumber(0, 3) + orientation = PBSUtils.getRandomNumber(0, 3) + } + } + + static UsCaliforniaSensitiveData fromList(List sensitiveData) { + if (sensitiveData.size() != 9) { + throw new IllegalArgumentException("Invalid data size. Expected 9 values.") + } + new UsCaliforniaSensitiveData().tap { + idNumbers = sensitiveData[0] + accountInfo = sensitiveData[1] + geolocation = sensitiveData[2] + racialEthnicOrigin = sensitiveData[3] + communicationContents = sensitiveData[4] + geneticId = sensitiveData[5] + biometricId = sensitiveData[6] + healthInfo = sensitiveData[7] + orientation = sensitiveData[8] + } + } + + List getContentList() { + [idNumbers, accountInfo, geolocation, racialEthnicOrigin, + communicationContents, geneticId, biometricId, healthInfo, orientation] + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsColoradoSensitiveData.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsColoradoSensitiveData.groovy new file mode 100644 index 00000000000..ae7f045ca8c --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsColoradoSensitiveData.groovy @@ -0,0 +1,46 @@ +package org.prebid.server.functional.util.privacy.gpp.data + +import org.prebid.server.functional.util.PBSUtils + +class UsColoradoSensitiveData { + + int racialEthnicOrigin + int religiousBeliefs + int healthInfo + int orientation + int citizenshipStatus + int geneticId + int biometricId + + static UsColoradoSensitiveData generateRandomSensitiveData() { + new UsColoradoSensitiveData().tap { + racialEthnicOrigin = PBSUtils.getRandomNumber(0, 3) + religiousBeliefs = PBSUtils.getRandomNumber(0, 3) + healthInfo = PBSUtils.getRandomNumber(0, 3) + orientation = PBSUtils.getRandomNumber(0, 3) + citizenshipStatus = PBSUtils.getRandomNumber(0, 3) + geneticId = PBSUtils.getRandomNumber(0, 3) + biometricId = PBSUtils.getRandomNumber(0, 3) + } + } + + static UsColoradoSensitiveData fromList(List data) { + if (data.size() != 7) { + throw new IllegalArgumentException("Invalid data size. Expected 7 values.") + } + new UsColoradoSensitiveData().tap { + racialEthnicOrigin = data[0] + religiousBeliefs = data[1] + healthInfo = data[2] + orientation = data[3] + citizenshipStatus = data[4] + geneticId = data[5] + biometricId = data[6] + } + } + + List getContentList() { + [racialEthnicOrigin, religiousBeliefs, healthInfo, orientation, + citizenshipStatus, geneticId, biometricId] + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsConnecticutSensitiveData.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsConnecticutSensitiveData.groovy new file mode 100644 index 00000000000..93198225573 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsConnecticutSensitiveData.groovy @@ -0,0 +1,52 @@ +package org.prebid.server.functional.util.privacy.gpp.data + +import org.prebid.server.functional.util.PBSUtils + +class UsConnecticutSensitiveData { + + int racialEthnicOrigin + int religiousBeliefs + int healthInfo + int orientation + int citizenshipStatus + int geneticId + int biometricId + int geolocation + int idNumbers + + static UsConnecticutSensitiveData generateRandomSensitiveData() { + new UsConnecticutSensitiveData().tap { + racialEthnicOrigin = PBSUtils.getRandomNumber(0, 3) + religiousBeliefs = PBSUtils.getRandomNumber(0, 3) + healthInfo = PBSUtils.getRandomNumber(0, 3) + orientation = PBSUtils.getRandomNumber(0, 3) + citizenshipStatus = PBSUtils.getRandomNumber(0, 3) + geneticId = PBSUtils.getRandomNumber(0, 3) + biometricId = PBSUtils.getRandomNumber(0, 3) + geolocation = PBSUtils.getRandomNumber(0, 3) + idNumbers = PBSUtils.getRandomNumber(0, 3) + } + } + + static UsConnecticutSensitiveData fromList(List data) { + if (data.size() != 9) { + throw new IllegalArgumentException("Invalid data size. Expected 9 values.") + } + new UsConnecticutSensitiveData().tap { + racialEthnicOrigin = data[0] + religiousBeliefs = data[1] + healthInfo = data[2] + orientation = data[3] + citizenshipStatus = data[4] + geneticId = data[5] + biometricId = data[6] + geolocation = data[7] + idNumbers = data[8] + } + } + + List getContentList() { + [racialEthnicOrigin, religiousBeliefs, healthInfo, orientation, + citizenshipStatus, geneticId, biometricId, geolocation, idNumbers] + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsNationalSensitiveData.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsNationalSensitiveData.groovy new file mode 100644 index 00000000000..cf59bc29e59 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsNationalSensitiveData.groovy @@ -0,0 +1,62 @@ +package org.prebid.server.functional.util.privacy.gpp.data + +import org.prebid.server.functional.util.PBSUtils + +class UsNationalSensitiveData { + + int racialEthnicOrigin + int religiousBeliefs + int healthInfo + int orientation + int citizenshipStatus + int geneticId + int biometricId + int geolocation + int idNumbers + int accountInfo + int unionMembership + int communicationContents + + static UsNationalSensitiveData generateRandomSensitiveData() { + new UsNationalSensitiveData().tap { + racialEthnicOrigin = PBSUtils.getRandomNumber(0, 3) + religiousBeliefs = PBSUtils.getRandomNumber(0, 3) + healthInfo = PBSUtils.getRandomNumber(0, 3) + orientation = PBSUtils.getRandomNumber(0, 3) + citizenshipStatus = PBSUtils.getRandomNumber(0, 3) + geneticId = PBSUtils.getRandomNumber(0, 3) + biometricId = PBSUtils.getRandomNumber(0, 3) + geolocation = PBSUtils.getRandomNumber(0, 3) + idNumbers = PBSUtils.getRandomNumber(0, 3) + accountInfo = PBSUtils.getRandomNumber(0, 3) + unionMembership = PBSUtils.getRandomNumber(0, 3) + communicationContents = PBSUtils.getRandomNumber(0, 3) + } + } + + static UsNationalSensitiveData fromList(List data) { + if (data.size() != 12) { + throw new IllegalArgumentException("Invalid data size. Expected 12 values.") + } + new UsNationalSensitiveData().tap { + racialEthnicOrigin = data[0] + religiousBeliefs = data[1] + healthInfo = data[2] + orientation = data[3] + citizenshipStatus = data[4] + geneticId = data[5] + biometricId = data[6] + geolocation = data[7] + idNumbers = data[8] + accountInfo = data[9] + unionMembership = data[10] + communicationContents = data[11] + } + } + + List getContentList() { + [racialEthnicOrigin, religiousBeliefs, healthInfo, orientation, + citizenshipStatus, geneticId, biometricId, geolocation, + idNumbers, accountInfo, unionMembership, communicationContents] + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsUtahSensitiveData.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsUtahSensitiveData.groovy new file mode 100644 index 00000000000..17605612779 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsUtahSensitiveData.groovy @@ -0,0 +1,49 @@ +package org.prebid.server.functional.util.privacy.gpp.data + +import org.prebid.server.functional.util.PBSUtils + +class UsUtahSensitiveData { + + int racialEthnicOrigin + int religiousBeliefs + int orientation + int citizenshipStatus + int healthInfo + int geneticId + int biometricId + int geolocation + + static UsUtahSensitiveData generateRandomSensitiveData() { + new UsUtahSensitiveData().tap { + racialEthnicOrigin = PBSUtils.getRandomNumber(0, 3) + religiousBeliefs = PBSUtils.getRandomNumber(0, 3) + orientation = PBSUtils.getRandomNumber(0, 3) + citizenshipStatus = PBSUtils.getRandomNumber(0, 3) + healthInfo = PBSUtils.getRandomNumber(0, 3) + geneticId = PBSUtils.getRandomNumber(0, 3) + biometricId = PBSUtils.getRandomNumber(0, 3) + geolocation = PBSUtils.getRandomNumber(0, 3) + } + } + + static UsUtahSensitiveData fromList(List data) { + if (data.size() != 8) { + throw new IllegalArgumentException("Invalid data size. Expected 8 values.") + } + new UsUtahSensitiveData().tap { + racialEthnicOrigin = data[0] + religiousBeliefs = data[1] + orientation = data[2] + citizenshipStatus = data[3] + healthInfo = data[4] + geneticId = data[5] + biometricId = data[6] + geolocation = data[7] + } + } + + List getContentList() { + [racialEthnicOrigin, religiousBeliefs, orientation, citizenshipStatus, + healthInfo, geneticId, biometricId, geolocation] + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsVirginiaSensitiveData.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsVirginiaSensitiveData.groovy new file mode 100644 index 00000000000..f4828461bd3 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/gpp/data/UsVirginiaSensitiveData.groovy @@ -0,0 +1,49 @@ +package org.prebid.server.functional.util.privacy.gpp.data + +import org.prebid.server.functional.util.PBSUtils + +class UsVirginiaSensitiveData { + + int racialEthnicOrigin + int religiousBeliefs + int healthInfo + int orientation + int citizenshipStatus + int geneticId + int biometricId + int geolocation + + static UsVirginiaSensitiveData generateRandomSensitiveData() { + new UsVirginiaSensitiveData().tap { + racialEthnicOrigin = PBSUtils.getRandomNumber(0, 3) + religiousBeliefs = PBSUtils.getRandomNumber(0, 3) + healthInfo = PBSUtils.getRandomNumber(0, 3) + orientation = PBSUtils.getRandomNumber(0, 3) + citizenshipStatus = PBSUtils.getRandomNumber(0, 3) + geneticId = PBSUtils.getRandomNumber(0, 3) + biometricId = PBSUtils.getRandomNumber(0, 3) + geolocation = PBSUtils.getRandomNumber(0, 3) + } + } + + static UsVirginiaSensitiveData fromList(List data) { + if (data.size() != 8) { + throw new IllegalArgumentException("Invalid data size. Expected 8 values.") + } + new UsVirginiaSensitiveData().tap { + racialEthnicOrigin = data[0] + religiousBeliefs = data[1] + healthInfo = data[2] + orientation = data[3] + citizenshipStatus = data[4] + geneticId = data[5] + biometricId = data[6] + geolocation = data[7] + } + } + + List getContentList() { + [racialEthnicOrigin, religiousBeliefs, healthInfo, orientation, + citizenshipStatus, geneticId, biometricId, geolocation] + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/ActivityConfigurationTest.java b/src/test/java/org/prebid/server/activity/infrastructure/ActivityControllerTest.java similarity index 74% rename from src/test/java/org/prebid/server/activity/infrastructure/ActivityConfigurationTest.java rename to src/test/java/org/prebid/server/activity/infrastructure/ActivityControllerTest.java index bfa8c7c47ae..c50d254330e 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/ActivityConfigurationTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/ActivityControllerTest.java @@ -6,19 +6,19 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; -public class ActivityConfigurationTest { +public class ActivityControllerTest { @Test public void isAllowedShouldReturnExpectedResultIfNoRulesMatched() { // given - final ActivityConfiguration activityConfiguration = ActivityConfiguration.of( + final ActivityController activityController = ActivityController.of( true, asList( TestRule.allowIfMatches(payload -> false), TestRule.disallowIfMatches(payload -> false))); // when - final ActivityCallResult result = activityConfiguration.isAllowed(null); + final ActivityCallResult result = activityController.isAllowed(null); // then assertThat(result).isEqualTo(ActivityCallResult.of(true, 2)); @@ -27,7 +27,7 @@ public void isAllowedShouldReturnExpectedResultIfNoRulesMatched() { @Test public void isAllowedShouldReturnExpectedResultIfSomeRuleMatched() { // given - final ActivityConfiguration activityConfiguration = ActivityConfiguration.of( + final ActivityController activityController = ActivityController.of( true, asList( TestRule.allowIfMatches(payload -> false), @@ -35,7 +35,7 @@ public void isAllowedShouldReturnExpectedResultIfSomeRuleMatched() { TestRule.disallowIfMatches(payload -> false))); // when - final ActivityCallResult result = activityConfiguration.isAllowed(null); + final ActivityCallResult result = activityController.isAllowed(null); // then assertThat(result).isEqualTo(ActivityCallResult.of(false, 2)); diff --git a/src/test/java/org/prebid/server/activity/infrastructure/ActivityInfrastructureTest.java b/src/test/java/org/prebid/server/activity/infrastructure/ActivityInfrastructureTest.java index f6dc5a6e967..fa1ad5beec4 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/ActivityInfrastructureTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/ActivityInfrastructureTest.java @@ -33,7 +33,7 @@ public class ActivityInfrastructureTest { public final MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock - private ActivityConfiguration activityConfiguration; + private ActivityController activityController; @Mock private Metrics metrics; @@ -44,7 +44,7 @@ public void creationShouldFailOnInvalidConfiguration() { assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> new ActivityInfrastructure( "accountId", - Map.of(Activity.CALL_BIDDER, activityConfiguration), + Map.of(Activity.CALL_BIDDER, activityController), TraceLevel.basic, metrics)); } @@ -52,7 +52,7 @@ public void creationShouldFailOnInvalidConfiguration() { @Test public void isAllowedShouldReturnTrueAndUpdateMetrics() { // given - given(activityConfiguration.isAllowed(argThat(arg -> arg.componentType().equals(ComponentType.BIDDER)))) + given(activityController.isAllowed(argThat(arg -> arg.componentType().equals(ComponentType.BIDDER)))) .willReturn(ActivityCallResult.of(true, 3)); final ActivityInfrastructure infrastructure = activityInfrastructure(TraceLevel.verbose); @@ -74,7 +74,7 @@ public void isAllowedShouldReturnTrueAndUpdateMetrics() { @Test public void isAllowedShouldNotUpdateMetricsIfAllowedAndZeroProcessedRules() { // given - given(activityConfiguration.isAllowed(any())) + given(activityController.isAllowed(any())) .willReturn(ActivityCallResult.of(true, 0)); final ActivityInfrastructure infrastructure = activityInfrastructure(TraceLevel.basic); @@ -94,7 +94,7 @@ public void isAllowedShouldNotUpdateMetricsIfAllowedAndZeroProcessedRules() { @Test public void isAllowedShouldUpdateExpectedMetricsIfDisallowedAndTraceLevelIsBasic() { // given - given(activityConfiguration.isAllowed(any())) + given(activityController.isAllowed(any())) .willReturn(ActivityCallResult.of(false, 1)); final ActivityInfrastructure infrastructure = activityInfrastructure(TraceLevel.basic); @@ -114,7 +114,7 @@ public void isAllowedShouldUpdateExpectedMetricsIfDisallowedAndTraceLevelIsBasic @Test public void isAllowedShouldUpdateExpectedMetricsIfDisallowedAndTraceLevelIsVerbose() { // given - given(activityConfiguration.isAllowed(any())) + given(activityController.isAllowed(any())) .willReturn(ActivityCallResult.of(false, 1)); final ActivityInfrastructure infrastructure = activityInfrastructure(TraceLevel.verbose); @@ -137,7 +137,7 @@ private ActivityInfrastructure activityInfrastructure(TraceLevel traceLevel) { Arrays.stream(Activity.values()) .collect(Collectors.toMap( UnaryOperator.identity(), - key -> activityConfiguration)), + key -> activityController)), traceLevel, metrics); } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreatorTest.java index f2d6c9b3945..0ef53a2e377 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreatorTest.java @@ -8,23 +8,30 @@ import org.mockito.junit.MockitoRule; import org.prebid.server.activity.Activity; import org.prebid.server.activity.infrastructure.ActivityCallResult; -import org.prebid.server.activity.infrastructure.ActivityConfiguration; +import org.prebid.server.activity.infrastructure.ActivityController; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; import org.prebid.server.activity.infrastructure.rule.TestRule; import org.prebid.server.auction.gpp.model.GppContext; import org.prebid.server.auction.gpp.model.GppContextCreator; +import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; import org.prebid.server.settings.model.Account; import org.prebid.server.settings.model.AccountPrivacyConfig; import org.prebid.server.settings.model.activity.AccountActivityConfiguration; +import org.prebid.server.settings.model.activity.privacy.AccountUSNatModuleConfig; import org.prebid.server.settings.model.activity.rule.AccountActivityComponentRuleConfig; import java.util.Map; +import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; public class ActivityInfrastructureCreatorTest { @@ -45,72 +52,91 @@ public void setUp() { } @Test - public void parseShouldReturnExpectedResultIfAccountNull() { + public void parseShouldReturnExpectedResultIfAccountPrivacyNull() { + // given + final Account account = Account.builder().build(); + // when - final Map configuration = creator.parse(null, null); + final Map controllers = creator.parse(account, null); // then - assertThat(configuration.keySet()).containsExactlyInAnyOrder(Activity.values()); + assertThat(controllers.keySet()).containsExactlyInAnyOrder(Activity.values()); } @Test - public void parseShouldReturnExpectedResultIfAccountPrivacyNull() { + public void parseShouldReturnExpectedResultIfAccountPrivacyActivitiesNull() { // given - final Account account = Account.builder().build(); + final Account account = Account.builder().privacy(AccountPrivacyConfig.of(null, null, null, null)).build(); // when - final Map configuration = creator.parse(account, null); + final Map controllers = creator.parse(account, null); // then - assertThat(configuration.keySet()).containsExactlyInAnyOrder(Activity.values()); + assertThat(controllers.keySet()).containsExactlyInAnyOrder(Activity.values()); } @Test - public void parseShouldReturnExpectedResultIfAccountPrivacyActivitiesNull() { + public void parseShouldSkipPrivacyModulesDuplicatesAndEmitWarnings() { // given - final Account account = Account.builder().privacy(AccountPrivacyConfig.of(null, null, null)).build(); + final Account account = Account.builder() + .privacy(AccountPrivacyConfig.of( + null, + null, + Map.of(Activity.SYNC_USER, AccountActivityConfiguration.of( + null, singletonList(AccountActivityComponentRuleConfig.of(null, null)))), + asList( + AccountUSNatModuleConfig.of(null, null), + AccountUSNatModuleConfig.of(null, null)))) + .build(); // when - final Map configuration = creator.parse(account, null); + creator.parse(account, null); // then - assertThat(configuration.keySet()).containsExactlyInAnyOrder(Activity.values()); + verify(activityRuleFactory).from( + any(), + argThat(creationContext -> creationContext.getPrivacyModulesConfigs().size() == 1)); + verify(metrics).updateAlertsMetrics(eq(MetricName.general)); } @Test public void parseShouldReturnExpectedResult() { // given final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(null, null, Map.of( - Activity.SYNC_USER, AccountActivityConfiguration.of(null, null), - Activity.CALL_BIDDER, AccountActivityConfiguration.of(false, null), - Activity.MODIFY_UFDP, AccountActivityConfiguration.of(true, null), - Activity.TRANSMIT_UFPD, AccountActivityConfiguration.of(true, singletonList( - AccountActivityComponentRuleConfig.of(null, null)))))) + .privacy(AccountPrivacyConfig.of( + null, + null, + Map.of( + Activity.SYNC_USER, AccountActivityConfiguration.of(null, null), + Activity.CALL_BIDDER, AccountActivityConfiguration.of(false, null), + Activity.MODIFY_UFDP, AccountActivityConfiguration.of(true, null), + Activity.TRANSMIT_UFPD, AccountActivityConfiguration.of(true, singletonList( + AccountActivityComponentRuleConfig.of(null, null)))), + null)) .build(); final GppContext gppContext = GppContextCreator.from(null, null).build().getGppContext(); given(activityRuleFactory.from( same(account.getPrivacy().getActivities().get(Activity.TRANSMIT_UFPD).getRules().get(0)), - same(gppContext))) + argThat(arg -> arg.getGppContext() == gppContext))) .willReturn(TestRule.disallowIfMatches(payload -> true)); // when - final Map configuration = creator.parse(account, gppContext); + final Map controllers = creator.parse(account, gppContext); // then - assertThat(configuration.keySet()).containsExactlyInAnyOrder(Activity.values()); + assertThat(controllers.keySet()).containsExactlyInAnyOrder(Activity.values()); - assertThat(configuration.get(Activity.SYNC_USER).isAllowed(null)) + assertThat(controllers.get(Activity.SYNC_USER).isAllowed(null)) .isEqualTo(ActivityCallResult.of(ActivityInfrastructure.ALLOW_ACTIVITY_BY_DEFAULT, 0)); - assertThat(configuration.get(Activity.CALL_BIDDER).isAllowed(null)) + assertThat(controllers.get(Activity.CALL_BIDDER).isAllowed(null)) .isEqualTo(ActivityCallResult.of(false, 0)); - assertThat(configuration.get(Activity.MODIFY_UFDP).isAllowed(null)) + assertThat(controllers.get(Activity.MODIFY_UFDP).isAllowed(null)) .isEqualTo(ActivityCallResult.of(true, 0)); - assertThat(configuration.get(Activity.TRANSMIT_UFPD).isAllowed(null)) + assertThat(controllers.get(Activity.TRANSMIT_UFPD).isAllowed(null)) .isEqualTo(ActivityCallResult.of(false, 1)); } } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatGppReaderFactoryTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatGppReaderFactoryTest.java new file mode 100644 index 00000000000..6bbb43e5420 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatGppReaderFactoryTest.java @@ -0,0 +1,58 @@ +package org.prebid.server.activity.infrastructure.creator.privacy.usnat; + +import org.junit.Test; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USCaliforniaGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USColoradoGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USConnecticutGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USNationalGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USUtahGppReader; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USVirginiaGppReader; + +import static org.assertj.core.api.Assertions.assertThat; + +public class USNatGppReaderFactoryTest { + + private final USNatGppReaderFactory target = new USNatGppReaderFactory(); + + @Test + public void fromShouldReturnNationalGppReader() { + // when and then + assertThat(target.forSection(USNatSection.NATIONAL.sectionId(), null)) + .isInstanceOf(USNationalGppReader.class); + } + + @Test + public void fromShouldReturnCaliforniaGppReader() { + // when and then + assertThat(target.forSection(USNatSection.CALIFORNIA.sectionId(), null)) + .isInstanceOf(USCaliforniaGppReader.class); + } + + @Test + public void fromShouldReturnVirginiaGppReader() { + // when and then + assertThat(target.forSection(USNatSection.VIRGINIA.sectionId(), null)) + .isInstanceOf(USVirginiaGppReader.class); + } + + @Test + public void fromShouldReturnColoradoGppReader() { + // when and then + assertThat(target.forSection(USNatSection.COLORADO.sectionId(), null)) + .isInstanceOf(USColoradoGppReader.class); + } + + @Test + public void fromShouldReturnUtahGppReader() { + // when and then + assertThat(target.forSection(USNatSection.UTAH.sectionId(), null)) + .isInstanceOf(USUtahGppReader.class); + } + + @Test + public void fromShouldReturnConnecticutGppReader() { + // when and then + assertThat(target.forSection(USNatSection.CONNECTICUT.sectionId(), null)) + .isInstanceOf(USConnecticutGppReader.class); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java new file mode 100644 index 00000000000..685dd36b8d9 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java @@ -0,0 +1,132 @@ +package org.prebid.server.activity.infrastructure.creator.privacy.usnat; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.prebid.server.activity.Activity; +import org.prebid.server.activity.infrastructure.creator.PrivacyModuleCreationContext; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; +import org.prebid.server.activity.infrastructure.privacy.usnat.reader.USNationalGppReader; +import org.prebid.server.activity.infrastructure.rule.Rule; +import org.prebid.server.auction.gpp.model.GppContextCreator; +import org.prebid.server.settings.model.activity.privacy.AccountUSNatModuleConfig; + +import java.util.List; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +public class USNatModuleCreatorTest { + + @org.junit.Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private USNatGppReaderFactory gppReaderFactory; + + private USNatModuleCreator target; + + @Before + public void setUp() { + given(gppReaderFactory.forSection(any(), any())).willReturn(new USNationalGppReader(null)); + + target = new USNatModuleCreator(gppReaderFactory); + } + + @Test + public void qualifierShouldReturnExpectedResult() { + // when and then + assertThat(target.qualifier()).isEqualTo(PrivacyModuleQualifier.US_NAT); + } + + @Test + public void fromShouldCreateProperPrivacyModuleIfSectionsIdsIsNull() { + // given + final PrivacyModuleCreationContext creationContext = givenCreationContext(null, null); + + // when + final PrivacyModule privacyModule = target.from(creationContext); + + // then + assertThat(privacyModule.proceed(null)).isEqualTo(Rule.Result.ABSTAIN); + verifyNoInteractions(gppReaderFactory); + } + + @Test + public void fromShouldCreateProperPrivacyModuleIfSectionsIdsIsEmpty() { + // given + final PrivacyModuleCreationContext creationContext = givenCreationContext(emptyList(), null); + + // when + final PrivacyModule privacyModule = target.from(creationContext); + + // then + assertThat(privacyModule.proceed(null)).isEqualTo(Rule.Result.ABSTAIN); + verifyNoInteractions(gppReaderFactory); + } + + @Test + public void fromShouldCreateProperPrivacyModuleIfAllSectionsIdsSkipped() { + // given + final PrivacyModuleCreationContext creationContext = givenCreationContext(singletonList(1), null); + + // when + final PrivacyModule privacyModule = target.from(creationContext); + + // then + assertThat(privacyModule.proceed(null)).isEqualTo(Rule.Result.ABSTAIN); + verifyNoInteractions(gppReaderFactory); + } + + @Test + public void fromShouldShouldSkipNotSupportedSectionsIds() { + // given + final PrivacyModuleCreationContext creationContext = givenCreationContext( + asList(6, 7, 8, 9, 10, 11, 12, 13), null); + + // when + target.from(creationContext); + + // then + verify(gppReaderFactory).forSection(eq(7), any()); + verify(gppReaderFactory).forSection(eq(8), any()); + verify(gppReaderFactory).forSection(eq(9), any()); + verify(gppReaderFactory).forSection(eq(10), any()); + verify(gppReaderFactory).forSection(eq(11), any()); + verify(gppReaderFactory).forSection(eq(12), any()); + verifyNoMoreInteractions(gppReaderFactory); + } + + @Test + public void fromShouldShouldSkipConfiguredSectionsIds() { + // given + final PrivacyModuleCreationContext creationContext = givenCreationContext(asList(7, 8, 9), asList(8, 9)); + + // when + target.from(creationContext); + + // then + verify(gppReaderFactory).forSection(eq(7), any()); + verifyNoMoreInteractions(gppReaderFactory); + } + + private static PrivacyModuleCreationContext givenCreationContext(List sectionsIds, + List skipSectionsIds) { + + return PrivacyModuleCreationContext.of( + Activity.CALL_BIDDER, + AccountUSNatModuleConfig.of(true, AccountUSNatModuleConfig.Config.of(skipSectionsIds)), + GppContextCreator.from(null, sectionsIds).build().getGppContext()); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/ComponentRuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/ComponentRuleCreatorTest.java index 0f685fc3b01..b2ecdb0fa28 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/ComponentRuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/ComponentRuleCreatorTest.java @@ -2,7 +2,6 @@ import org.junit.Test; import org.prebid.server.activity.ComponentType; -import org.prebid.server.activity.infrastructure.ActivityInfrastructure; import org.prebid.server.activity.infrastructure.payload.impl.ActivityCallPayloadImpl; import org.prebid.server.activity.infrastructure.rule.Rule; import org.prebid.server.settings.model.activity.rule.AccountActivityComponentRuleConfig; @@ -23,8 +22,7 @@ public void fromShouldCreateDefaultRule() { final Rule rule = target.from(config, null); // then - assertThat(rule.matches(null)).isTrue(); - assertThat(rule.allowed()).isEqualTo(ActivityInfrastructure.ALLOW_ACTIVITY_BY_DEFAULT); + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ALLOW); } @Test @@ -40,7 +38,7 @@ public void fromShouldCreateExpectedRule() { final Rule rule = target.from(config, null); // then - assertThat(rule.matches(ActivityCallPayloadImpl.of(ComponentType.BIDDER, "name"))).isTrue(); - assertThat(rule.allowed()).isFalse(); + assertThat(rule.proceed(ActivityCallPayloadImpl.of(ComponentType.BIDDER, "name"))) + .isEqualTo(Rule.Result.DISALLOW); } } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/GeoRuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/GeoRuleCreatorTest.java index ea3b4da8698..7088ccb9b1c 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/GeoRuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/GeoRuleCreatorTest.java @@ -6,7 +6,7 @@ import com.iab.openrtb.request.Regs; import org.junit.Test; import org.prebid.server.activity.ComponentType; -import org.prebid.server.activity.infrastructure.ActivityInfrastructure; +import org.prebid.server.activity.infrastructure.creator.ActivityControllerCreationContext; import org.prebid.server.activity.infrastructure.payload.ActivityCallPayload; import org.prebid.server.activity.infrastructure.payload.impl.ActivityCallPayloadImpl; import org.prebid.server.activity.infrastructure.payload.impl.BidRequestActivityCallPayload; @@ -29,13 +29,13 @@ public void fromShouldCreateDefaultRule() { // given final AccountActivityGeoRuleConfig config = AccountActivityGeoRuleConfig.of(null, null); final GppContext gppContext = GppContextCreator.from(null, null).build().getGppContext(); + final ActivityControllerCreationContext creationContext = creationContext(gppContext); // when - final Rule rule = target.from(config, gppContext); + final Rule rule = target.from(config, creationContext); // then - assertThat(rule.matches(null)).isTrue(); - assertThat(rule.allowed()).isEqualTo(ActivityInfrastructure.ALLOW_ACTIVITY_BY_DEFAULT); + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ALLOW); } @Test @@ -50,32 +50,31 @@ public void fromShouldCreateExpectedRule() { "2"), false); final GppContext gppContext = GppContextCreator.from(null, asList(2, 3)).build().getGppContext(); + final ActivityControllerCreationContext creationContext = creationContext(gppContext); // when - final Rule rule = target.from(config, gppContext); + final Rule rule = target.from(config, creationContext); // then final ActivityCallPayload payload1 = BidRequestActivityCallPayload.of( ActivityCallPayloadImpl.of(ComponentType.BIDDER, "name"), givenBidRequest("country1", "region", "2")); - assertThat(rule.matches(payload1)).isTrue(); + assertThat(rule.proceed(payload1)).isEqualTo(Rule.Result.DISALLOW); final ActivityCallPayload payload2 = BidRequestActivityCallPayload.of( ActivityCallPayloadImpl.of(ComponentType.BIDDER, "name"), givenBidRequest("country2", "region", "2")); - assertThat(rule.matches(payload2)).isTrue(); + assertThat(rule.proceed(payload2)).isEqualTo(Rule.Result.DISALLOW); final ActivityCallPayload payload3 = BidRequestActivityCallPayload.of( ActivityCallPayloadImpl.of(ComponentType.BIDDER, "name"), givenBidRequest("country3", "region", "2")); - assertThat(rule.matches(payload3)).isTrue(); + assertThat(rule.proceed(payload3)).isEqualTo(Rule.Result.DISALLOW); final ActivityCallPayload payload4 = BidRequestActivityCallPayload.of( ActivityCallPayloadImpl.of(ComponentType.BIDDER, "name"), givenBidRequest("country1", null, "2")); - assertThat(rule.matches(payload4)).isTrue(); - - assertThat(rule.allowed()).isFalse(); + assertThat(rule.proceed(payload4)).isEqualTo(Rule.Result.DISALLOW); } private static BidRequest givenBidRequest(String country, String region, String gpc) { @@ -84,4 +83,8 @@ private static BidRequest givenBidRequest(String country, String region, String .regs(Regs.builder().ext(ExtRegs.of(null, null, gpc)).build()) .build(); } + + private static ActivityControllerCreationContext creationContext(GppContext gppContext) { + return ActivityControllerCreationContext.of(null, null, gppContext); + } } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java new file mode 100644 index 00000000000..8f13ac9804a --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java @@ -0,0 +1,181 @@ +package org.prebid.server.activity.infrastructure.creator.rule; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.prebid.server.activity.infrastructure.creator.ActivityControllerCreationContext; +import org.prebid.server.activity.infrastructure.creator.PrivacyModuleCreationContext; +import org.prebid.server.activity.infrastructure.creator.privacy.PrivacyModuleCreator; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; +import org.prebid.server.activity.infrastructure.privacy.TestPrivacyModule; +import org.prebid.server.activity.infrastructure.rule.Rule; +import org.prebid.server.settings.model.activity.privacy.AccountPrivacyModuleConfig; +import org.prebid.server.settings.model.activity.privacy.AccountUSNatModuleConfig; +import org.prebid.server.settings.model.activity.rule.AccountActivityPrivacyModulesRuleConfig; + +import java.util.Map; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; + +public class PrivacyModulesRuleCreatorTest { + + @org.junit.Rule + public final MockitoRule rule = MockitoJUnit.rule(); + + @Mock + private PrivacyModuleCreator privacyModuleCreator; + + private PrivacyModulesRuleCreator target; + + @Before + public void setUp() { + given(privacyModuleCreator.qualifier()).willReturn(PrivacyModuleQualifier.US_NAT); + + target = new PrivacyModulesRuleCreator(singletonList(privacyModuleCreator)); + } + + @Test + public void fromShouldCreateDefaultRule() { + // given + final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of(null); + + // when + final Rule rule = target.from(config, creationContext(null)); + + // then + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ABSTAIN); + } + + @Test + public void fromShouldCreateDefaultRuleIfNoneOfConfiguredPrivacyModulesMatches() { + // given + final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( + singletonList("not_configured")); + final ActivityControllerCreationContext creationContext = creationContext(Map.of( + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, null))); + + // when + final Rule rule = target.from(config, creationContext); + + // then + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ABSTAIN); + } + + @Test + public void fromShouldCreateRuleWithAllConfiguredPrivacyModules() { + // given + final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( + singletonList("*")); + final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null); + final ActivityControllerCreationContext creationContext = creationContext( + Map.of(PrivacyModuleQualifier.US_NAT, moduleConfig)); + + given(privacyModuleCreator.from(eq(PrivacyModuleCreationContext.of(null, moduleConfig, null)))) + .willReturn(TestPrivacyModule.of(Rule.Result.ALLOW)); + + // when + final Rule rule = target.from(config, creationContext); + + // then + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ALLOW); + } + + @Test + public void fromShouldCreateRuleWithAllConfiguredPrivacyModulesThatMatches() { + // given + final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( + singletonList("iab.*")); + final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null); + final ActivityControllerCreationContext creationContext = creationContext( + Map.of(PrivacyModuleQualifier.US_NAT, moduleConfig)); + + given(privacyModuleCreator.from(eq(PrivacyModuleCreationContext.of(null, moduleConfig, null)))) + .willReturn(TestPrivacyModule.of(Rule.Result.ALLOW)); + + // when + final Rule rule = target.from(config, creationContext); + + // then + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ALLOW); + } + + @Test + public void fromShouldCreateRuleAndModifyContextWithUsedPrivacyModules() { + // given + final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( + singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); + final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null); + final ActivityControllerCreationContext creationContext = creationContext( + Map.of(PrivacyModuleQualifier.US_NAT, moduleConfig)); + + given(privacyModuleCreator.from(eq(PrivacyModuleCreationContext.of(null, moduleConfig, null)))) + .willReturn(TestPrivacyModule.of(Rule.Result.ALLOW)); + + // when + final Rule rule = target.from(config, creationContext); + + // then + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ALLOW); + assertThat(creationContext.isUsed(PrivacyModuleQualifier.US_NAT)).isTrue(); + } + + @Test + public void fromShouldSkipAlreadyUsedPrivacyModule() { + // given + final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( + singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); + final ActivityControllerCreationContext creationContext = creationContext(Map.of( + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, null))); + creationContext.use(PrivacyModuleQualifier.US_NAT); + + // when + final Rule rule = target.from(config, creationContext); + + // then + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ABSTAIN); + } + + @Test + public void fromShouldSkipDisabledPrivacyModule() { + // given + final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( + singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); + final ActivityControllerCreationContext creationContext = creationContext(Map.of( + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(false, null))); + + // when + final Rule rule = target.from(config, creationContext); + + // then + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ABSTAIN); + } + + @Test + public void fromShouldSkipPrivacyModuleWithoutCreator() { + // given + target = new PrivacyModulesRuleCreator(emptyList()); + + final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( + singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); + final ActivityControllerCreationContext creationContext = creationContext(Map.of( + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, null))); + + // when + final Rule rule = target.from(config, creationContext); + + // then + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ABSTAIN); + } + + private static ActivityControllerCreationContext creationContext( + Map modulesConfigs) { + + return ActivityControllerCreationContext.of(null, modulesConfigs, null); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/TestPrivacyModule.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/TestPrivacyModule.java new file mode 100644 index 00000000000..7e8979cbdf0 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/TestPrivacyModule.java @@ -0,0 +1,15 @@ +package org.prebid.server.activity.infrastructure.privacy; + +import lombok.Value; +import org.prebid.server.activity.infrastructure.payload.ActivityCallPayload; + +@Value(staticConstructor = "of") +public class TestPrivacyModule implements PrivacyModule { + + Result result; + + @Override + public Result proceed(ActivityCallPayload activityCallPayload) { + return result; + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatDefaultTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatDefaultTest.java new file mode 100644 index 00000000000..12aba32b16d --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatDefaultTest.java @@ -0,0 +1,18 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner; + +import org.junit.Test; +import org.prebid.server.activity.infrastructure.rule.Rule; + +import static org.assertj.core.api.Assertions.assertThat; + +public class USNatDefaultTest { + + @Test + public void proceedShouldAlwaysReturnAbstain() { + // when + final Rule.Result result = new USNatDefault().proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.ABSTAIN); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatSyncUserTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatSyncUserTest.java new file mode 100644 index 00000000000..f34b1e9685c --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatSyncUserTest.java @@ -0,0 +1,260 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner; + +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.activity.infrastructure.rule.Rule; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +public class USNatSyncUserTest { + + @org.junit.Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private USNatGppReader gppReader; + + @Test + public void proceedShouldDisallowIfMspaServiceProviderModeEquals1() { + // given + given(gppReader.getMspaServiceProviderMode()).willReturn(1); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfGpcEqualsTrue() { + // given + given(gppReader.getGpc()).willReturn(true); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSaleOptOutEquals1() { + // given + given(gppReader.getSaleOptOut()).willReturn(1); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSaleOptOutNoticeEquals2() { + // given + given(gppReader.getSaleOptOutNotice()).willReturn(2); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSaleOptOutNoticeEquals0AndSaleOptOutEquals2() { + // given + given(gppReader.getSaleOptOut()).willReturn(2); + given(gppReader.getSaleOptOutNotice()).willReturn(0); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSharingNoticeEquals2() { + // given + given(gppReader.getSharingNotice()).willReturn(2); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSharingOptOutEquals1() { + // given + given(gppReader.getSharingOptOut()).willReturn(1); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSharingOptOutNoticeEquals2() { + // given + given(gppReader.getSharingOptOutNotice()).willReturn(2); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSharingNoticeEquals0AndSharingOptOutEquals2() { + // given + given(gppReader.getSharingOptOut()).willReturn(2); + given(gppReader.getSharingNotice()).willReturn(0); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSharingOptOutNoticeEquals0AndSharingOptOutEquals2() { + // given + given(gppReader.getSharingOptOut()).willReturn(2); + given(gppReader.getSharingOptOutNotice()).willReturn(0); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfTargetedAdvertisingOptOutEquals1() { + // given + given(gppReader.getTargetedAdvertisingOptOut()).willReturn(1); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfTargetedAdvertisingOptOutNoticeEquals2() { + // given + given(gppReader.getTargetedAdvertisingOptOutNotice()).willReturn(2); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfTargetedAdvertisingOptOutNoticeEquals0AndTargetedAdvertisingOptOutEquals2() { + // given + given(gppReader.getTargetedAdvertisingOptOut()).willReturn(2); + given(gppReader.getTargetedAdvertisingOptOutNotice()).willReturn(0); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfKnownChildSensitiveDataConsents1Equals1() { + // given + given(gppReader.getKnownChildSensitiveDataConsents()).willReturn(singletonList(1)); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfKnownChildSensitiveDataConsents2Equals1() { + // given + given(gppReader.getKnownChildSensitiveDataConsents()).willReturn(asList(1, 1)); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfKnownChildSensitiveDataConsents2Equals2() { + // given + given(gppReader.getKnownChildSensitiveDataConsents()).willReturn(asList(1, 2)); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfPersonalDataConsentsEquals2() { + // given + given(gppReader.getPersonalDataConsents()).willReturn(2); + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldAllow() { + // given + final PrivacyModule target = new USNatSyncUser(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.ALLOW); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitGeoTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitGeoTest.java new file mode 100644 index 00000000000..4f62d5f395c --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitGeoTest.java @@ -0,0 +1,182 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner; + +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.activity.infrastructure.rule.Rule; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +public class USNatTransmitGeoTest { + + @org.junit.Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private USNatGppReader gppReader; + + @Test + public void proceedShouldDisallowIfMspaServiceProviderModeEquals1() { + // given + given(gppReader.getMspaServiceProviderMode()).willReturn(1); + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfGpcEqualsTrue() { + // given + given(gppReader.getGpc()).willReturn(true); + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSensitiveDataLimitUseNoticeEquals2() { + // given + given(gppReader.getSensitiveDataLimitUseNotice()).willReturn(2); + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSensitiveDataProcessingOptOutNoticeEquals2() { + // given + given(gppReader.getSensitiveDataProcessingOptOutNotice()).willReturn(2); + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSensitiveDataProcessingOptOutNoticeIs0AndIfSensitiveDataProcessing8Is2() { + // given + given(gppReader.getSensitiveDataProcessingOptOutNotice()).willReturn(0); + given(gppReader.getSensitiveDataProcessing()).willReturn(asList(0, 0, 0, 0, 0, 0, 0, 2)); + + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSensitiveDataLimitUseNoticeEquals0AndIfSensitiveDataProcessing8Equals2() { + // given + given(gppReader.getSensitiveDataLimitUseNotice()).willReturn(0); + given(gppReader.getSensitiveDataProcessing()).willReturn(asList(0, 0, 0, 0, 0, 0, 0, 2)); + + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSensitiveDataProcessing8Equals1() { + // given + given(gppReader.getSensitiveDataProcessing()).willReturn(asList(0, 0, 0, 0, 0, 0, 0, 1)); + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfKnownChildSensitiveDataConsents1Equals1() { + // given + given(gppReader.getKnownChildSensitiveDataConsents()).willReturn(singletonList(1)); + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfKnownChildSensitiveDataConsents2Equals1() { + // given + given(gppReader.getKnownChildSensitiveDataConsents()).willReturn(asList(1, 1)); + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfKnownChildSensitiveDataConsents2Equals2() { + // given + given(gppReader.getKnownChildSensitiveDataConsents()).willReturn(asList(1, 2)); + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfPersonalDataConsentsEquals2() { + // given + given(gppReader.getPersonalDataConsents()).willReturn(2); + final PrivacyModule target = new USNatTransmitGeo(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldAllow() { + // given + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.ALLOW); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitUfpdTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitUfpdTest.java new file mode 100644 index 00000000000..277d7958831 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/inner/USNatTransmitUfpdTest.java @@ -0,0 +1,342 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.inner; + +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.usnat.USNatGppReader; +import org.prebid.server.activity.infrastructure.rule.Rule; + +import java.util.ArrayList; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +public class USNatTransmitUfpdTest { + + @org.junit.Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private USNatGppReader gppReader; + + @Test + public void proceedShouldDisallowIfMspaServiceProviderModeEquals1() { + // given + given(gppReader.getMspaServiceProviderMode()).willReturn(1); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfGpcEqualsTrue() { + // given + given(gppReader.getGpc()).willReturn(true); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSaleOptOutEquals1() { + // given + given(gppReader.getSaleOptOut()).willReturn(1); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSaleOptOutNoticeEquals2() { + // given + given(gppReader.getSaleOptOutNotice()).willReturn(2); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSaleOptOutNoticeEquals0AndSaleOptOutEquals2() { + // given + given(gppReader.getSaleOptOut()).willReturn(2); + given(gppReader.getSaleOptOutNotice()).willReturn(0); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSharingNoticeEquals2() { + // given + given(gppReader.getSharingNotice()).willReturn(2); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSharingOptOutEquals1() { + // given + given(gppReader.getSharingOptOut()).willReturn(1); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSharingOptOutNoticeEquals2() { + // given + given(gppReader.getSharingOptOutNotice()).willReturn(2); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSharingOptOutNoticeEquals0AndSharingOptOutEquals2() { + // given + given(gppReader.getSharingOptOut()).willReturn(2); + given(gppReader.getSharingOptOutNotice()).willReturn(0); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfTargetedAdvertisingOptOutEquals1() { + // given + given(gppReader.getTargetedAdvertisingOptOut()).willReturn(1); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfTargetedAdvertisingOptOutNoticeEquals2() { + // given + given(gppReader.getTargetedAdvertisingOptOutNotice()).willReturn(2); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfTargetedAdvertisingOptOutNoticeEquals0AndTargetedAdvertisingOptOutEquals2() { + // given + given(gppReader.getTargetedAdvertisingOptOut()).willReturn(2); + given(gppReader.getTargetedAdvertisingOptOutNotice()).willReturn(0); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSensitiveDataLimitUseNoticeEquals2() { + // given + given(gppReader.getSensitiveDataLimitUseNotice()).willReturn(2); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSensitiveDataProcessingOptOutNoticeEquals2() { + // given + given(gppReader.getSensitiveDataProcessingOptOutNotice()).willReturn(2); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfSensitiveDataProcessingOptOutNoticeEquals0AndOnCertainSensitiveDataProcessing() { + for (int i : Set.of(0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11)) { + // given + given(gppReader.getSensitiveDataProcessingOptOutNotice()).willReturn(0); + + final ArrayList data = new ArrayList<>(asList(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + data.remove(i); + data.add(i, 2); + given(gppReader.getSensitiveDataProcessing()).willReturn(data); + + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + } + + @Test + public void proceedShouldDisallowIfSensitiveDataLimitUseNoticeEquals0AndOnCertainSensitiveDataProcessing() { + for (int i : Set.of(0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11)) { + // given + given(gppReader.getSensitiveDataLimitUseNotice()).willReturn(0); + + final ArrayList data = new ArrayList<>(asList(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + data.remove(i); + data.add(i, 2); + given(gppReader.getSensitiveDataProcessing()).willReturn(data); + + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + } + + @Test + public void proceedShouldDisallowIfSensitiveDataProcessingAtCertainIndicesEquals1() { + for (int i : Set.of(0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11)) { + // given + final ArrayList data = new ArrayList<>(asList(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + data.remove(i); + data.add(i, 1); + given(gppReader.getSensitiveDataProcessing()).willReturn(data); + + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + } + + @Test + public void proceedShouldDisallowIfSensitiveDataProcessingAtCertainIndicesEquals2() { + for (int i : Set.of(5, 6, 8, 9, 11)) { + // given + final ArrayList data = new ArrayList<>(asList(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + data.remove(i); + data.add(i, 2); + given(gppReader.getSensitiveDataProcessing()).willReturn(data); + + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + } + + @Test + public void proceedShouldDisallowIfKnownChildSensitiveDataConsents1Equals1() { + // given + given(gppReader.getKnownChildSensitiveDataConsents()).willReturn(singletonList(1)); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfKnownChildSensitiveDataConsents2NotEquals0() { + // given + given(gppReader.getKnownChildSensitiveDataConsents()).willReturn(asList(1, 1)); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldDisallowIfPersonalDataConsentsEquals2() { + // given + given(gppReader.getPersonalDataConsents()).willReturn(2); + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + } + + @Test + public void proceedShouldAllow() { + // given + final PrivacyModule target = new USNatTransmitUfpd(gppReader); + + // when + final Rule.Result result = target.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.ALLOW); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USCaliforniaGppReaderTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USCaliforniaGppReaderTest.java new file mode 100644 index 00000000000..eca386000c8 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USCaliforniaGppReaderTest.java @@ -0,0 +1,188 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspCaV1; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Collections; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verifyNoInteractions; + +public class USCaliforniaGppReaderTest { + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private GppModel gppModel; + + @Mock + private UspCaV1 uspCaV1; + + private USCaliforniaGppReader gppReader; + + @Before + public void setUp() { + given(gppModel.getUspCaV1Section()).willReturn(uspCaV1); + + gppReader = new USCaliforniaGppReader(gppModel); + } + + @Test + public void getMspaServiceProviderModeShouldReturnExpectedResult() { + // given + given(uspCaV1.getMspaServiceProviderMode()).willReturn(1); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isEqualTo(1); + } + + @Test + public void getGpcShouldReturnExpectedResult() { + // given + given(uspCaV1.getGpc()).willReturn(true); + + // when and then + assertThat(gppReader.getGpc()).isTrue(); + } + + @Test + public void getSaleOptOutShouldReturnExpectedResult() { + // given + given(uspCaV1.getSaleOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOut()).isEqualTo(1); + } + + @Test + public void getSaleOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspCaV1.getSaleOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSharingNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSharingNotice()).isNull(); + verifyNoInteractions(uspCaV1); + } + + @Test + public void getSharingOptOutShouldReturnExpectedResult() { + // given + given(uspCaV1.getSharingOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getSharingOptOut()).isEqualTo(1); + } + + @Test + public void getSharingOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspCaV1.getSharingOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSharingOptOutNotice()).isEqualTo(1); + } + + @Test + public void getTargetedAdvertisingOptOutShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOut()).isNull(); + verifyNoInteractions(uspCaV1); + } + + @Test + public void getTargetedAdvertisingOptOutNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isNull(); + verifyNoInteractions(uspCaV1); + } + + @Test + public void getSensitiveDataLimitUseNoticeShouldReturnExpectedResult() { + // given + given(uspCaV1.getSensitiveDataLimitUseNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isEqualTo(1); + } + + @Test + public void getSensitiveDataProcessingOptOutNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + verifyNoInteractions(uspCaV1); + } + + @Test + public void getSensitiveDataProcessingShouldReturnExpectedResult() { + // given + given(uspCaV1.getSensitiveDataProcessing()).willReturn(asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); + + // when and then + assertThat(gppReader.getSensitiveDataProcessing()) + .containsExactly(3, 3, 7, 8, null, 5, 6, 2, 0, 1, null, 4); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnNonChildResult() { + // given + given(uspCaV1.getKnownChildSensitiveDataConsents()).willReturn(asList(0, 0)); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(0, 0); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnChildResult() { + // given + given(uspCaV1.getKnownChildSensitiveDataConsents()).willReturn(asList(0, 2)); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(1, 1); + } + + @Test + public void getPersonalDataConsentsShouldReturnExpectedResult() { + // given + given(uspCaV1.getPersonalDataConsents()).willReturn(1); + + // when and then + assertThat(gppReader.getPersonalDataConsents()).isEqualTo(1); + } + + @Test + public void gppReaderShouldReturnExpectedResultsIfSectionAbsent() { + // given + gppReader = new USCaliforniaGppReader(null); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isNull(); + assertThat(gppReader.getGpc()).isNull(); + assertThat(gppReader.getSaleOptOut()).isNull(); + assertThat(gppReader.getSaleOptOutNotice()).isNull(); + assertThat(gppReader.getSharingNotice()).isNull(); + assertThat(gppReader.getSharingOptOut()).isNull(); + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOut()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessing()).isEqualTo(Collections.nCopies(12, null)); + assertThat(gppReader.getKnownChildSensitiveDataConsents()).isEqualTo(asList(1, 1)); + assertThat(gppReader.getPersonalDataConsents()).isNull(); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USColoradoGppReaderTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USColoradoGppReaderTest.java new file mode 100644 index 00000000000..deb11d05ce3 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USColoradoGppReaderTest.java @@ -0,0 +1,195 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspCoV1; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verifyNoInteractions; + +public class USColoradoGppReaderTest { + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private GppModel gppModel; + + @Mock + private UspCoV1 uspCoV1; + + private USColoradoGppReader gppReader; + + @Before + public void setUp() { + given(gppModel.getUspCoV1Section()).willReturn(uspCoV1); + + gppReader = new USColoradoGppReader(gppModel); + } + + @Test + public void getMspaServiceProviderModeShouldReturnExpectedResult() { + // given + given(uspCoV1.getMspaServiceProviderMode()).willReturn(1); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isEqualTo(1); + } + + @Test + public void getGpcShouldReturnExpectedResult() { + // given + given(uspCoV1.getGpc()).willReturn(true); + + // when and then + assertThat(gppReader.getGpc()).isTrue(); + } + + @Test + public void getSaleOptOutShouldReturnExpectedResult() { + // given + given(uspCoV1.getSaleOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOut()).isEqualTo(1); + } + + @Test + public void getSaleOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspCoV1.getSaleOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSharingNoticeShouldReturnExpectedResult() { + // given + given(uspCoV1.getSharingNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSharingNotice()).isEqualTo(1); + } + + @Test + public void getSharingOptOutShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSharingOptOut()).isNull(); + verifyNoInteractions(uspCoV1); + } + + @Test + public void getSharingOptOutNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + verifyNoInteractions(uspCoV1); + } + + @Test + public void getTargetedAdvertisingOptOutShouldReturnExpectedResult() { + // given + given(uspCoV1.getTargetedAdvertisingOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOut()).isEqualTo(1); + } + + @Test + public void getTargetedAdvertisingOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspCoV1.getTargetedAdvertisingOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSensitiveDataLimitUseNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + verifyNoInteractions(uspCoV1); + } + + @Test + public void getSensitiveDataProcessingOptOutNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + verifyNoInteractions(uspCoV1); + } + + @Test + public void getSensitiveDataProcessingShouldReturnExpectedResult() { + // given + final List data = Collections.emptyList(); + given(uspCoV1.getSensitiveDataProcessing()).willReturn(data); + + // when and then + assertThat(gppReader.getSensitiveDataProcessing()).isSameAs(data); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnChildResultOn1() { + // given + given(uspCoV1.getKnownChildSensitiveDataConsents()).willReturn(1); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(1, 1); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnChildResultOn2() { + // given + given(uspCoV1.getKnownChildSensitiveDataConsents()).willReturn(2); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(1, 1); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnNonChildResultOn0() { + // given + given(uspCoV1.getKnownChildSensitiveDataConsents()).willReturn(0); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(0, 0); + } + + @Test + public void getPersonalDataConsentsShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getPersonalDataConsents()).isNull(); + verifyNoInteractions(uspCoV1); + } + + @Test + public void gppReaderShouldReturnExpectedResultsIfSectionAbsent() { + // given + gppReader = new USColoradoGppReader(null); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isNull(); + assertThat(gppReader.getGpc()).isNull(); + assertThat(gppReader.getSaleOptOut()).isNull(); + assertThat(gppReader.getSaleOptOutNotice()).isNull(); + assertThat(gppReader.getSharingNotice()).isNull(); + assertThat(gppReader.getSharingOptOut()).isNull(); + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOut()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessing()).isNull(); + assertThat(gppReader.getKnownChildSensitiveDataConsents()).isNull(); + assertThat(gppReader.getPersonalDataConsents()).isNull(); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USConnecticutGppReaderTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USConnecticutGppReaderTest.java new file mode 100644 index 00000000000..5d7c0725e62 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USConnecticutGppReaderTest.java @@ -0,0 +1,196 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspCtV1; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Collections; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verifyNoInteractions; + +public class USConnecticutGppReaderTest { + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private GppModel gppModel; + + @Mock + private UspCtV1 uspCtV1; + + private USConnecticutGppReader gppReader; + + @Before + public void setUp() { + given(gppModel.getUspCtV1Section()).willReturn(uspCtV1); + + gppReader = new USConnecticutGppReader(gppModel); + } + + @Test + public void getMspaServiceProviderModeShouldReturnExpectedResult() { + // given + given(uspCtV1.getMspaServiceProviderMode()).willReturn(1); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isEqualTo(1); + } + + @Test + public void getGpcShouldReturnExpectedResult() { + // given + given(uspCtV1.getGpc()).willReturn(true); + + // when and then + assertThat(gppReader.getGpc()).isTrue(); + } + + @Test + public void getSaleOptOutShouldReturnExpectedResult() { + // given + given(uspCtV1.getSaleOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOut()).isEqualTo(1); + } + + @Test + public void getSaleOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspCtV1.getSaleOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSharingNoticeShouldReturnExpectedResult() { + // given + given(uspCtV1.getSharingNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSharingNotice()).isEqualTo(1); + } + + @Test + public void getSharingOptOutShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSharingOptOut()).isNull(); + verifyNoInteractions(uspCtV1); + } + + @Test + public void getSharingOptOutNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + verifyNoInteractions(uspCtV1); + } + + @Test + public void getTargetedAdvertisingOptOutShouldReturnExpectedResult() { + // given + given(uspCtV1.getTargetedAdvertisingOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOut()).isEqualTo(1); + } + + @Test + public void getTargetedAdvertisingOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspCtV1.getTargetedAdvertisingOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSensitiveDataLimitUseNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + verifyNoInteractions(uspCtV1); + } + + @Test + public void getSensitiveDataProcessingOptOutNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + verifyNoInteractions(uspCtV1); + } + + @Test + public void getSensitiveDataProcessingShouldReturnExpectedResult() { + // given + final List data = Collections.emptyList(); + given(uspCtV1.getSensitiveDataProcessing()).willReturn(data); + + // when and then + assertThat(gppReader.getSensitiveDataProcessing()).isSameAs(data); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnNonChildResult() { + // given + given(uspCtV1.getKnownChildSensitiveDataConsents()).willReturn(asList(0, 0, 0)); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(0, 0); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnMixedChildResult() { + // given + given(uspCtV1.getKnownChildSensitiveDataConsents()).willReturn(asList(null, 2, 2)); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(2, 1); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnChildResult() { + // given + given(uspCtV1.getKnownChildSensitiveDataConsents()).willReturn(asList(null, null, null)); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(1, 1); + } + + @Test + public void getPersonalDataConsentsShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getPersonalDataConsents()).isNull(); + verifyNoInteractions(uspCtV1); + } + + @Test + public void gppReaderShouldReturnExpectedResultsIfSectionAbsent() { + // given + gppReader = new USConnecticutGppReader(null); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isNull(); + assertThat(gppReader.getGpc()).isNull(); + assertThat(gppReader.getSaleOptOut()).isNull(); + assertThat(gppReader.getSaleOptOutNotice()).isNull(); + assertThat(gppReader.getSharingNotice()).isNull(); + assertThat(gppReader.getSharingOptOut()).isNull(); + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOut()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessing()).isNull(); + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(1, 1); + assertThat(gppReader.getPersonalDataConsents()).isNull(); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USNationalGppReaderTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USNationalGppReaderTest.java new file mode 100644 index 00000000000..723bd08d5e1 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USNationalGppReaderTest.java @@ -0,0 +1,187 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspNatV1; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +public class USNationalGppReaderTest { + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private GppModel gppModel; + + @Mock + private UspNatV1 uspNatV1; + + private USNationalGppReader gppReader; + + @Before + public void setUp() { + given(gppModel.getUspNatV1Section()).willReturn(uspNatV1); + + gppReader = new USNationalGppReader(gppModel); + } + + @Test + public void getMspaServiceProviderModeShouldReturnExpectedResult() { + // given + given(uspNatV1.getMspaServiceProviderMode()).willReturn(1); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isEqualTo(1); + } + + @Test + public void getGpcShouldReturnExpectedResult() { + // given + given(uspNatV1.getGpc()).willReturn(true); + + // when and then + assertThat(gppReader.getGpc()).isTrue(); + } + + @Test + public void getSaleOptOutShouldReturnExpectedResult() { + // given + given(uspNatV1.getSaleOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOut()).isEqualTo(1); + } + + @Test + public void getSaleOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspNatV1.getSaleOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSharingNoticeShouldReturnExpectedResult() { + // given + given(uspNatV1.getSharingNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSharingNotice()).isEqualTo(1); + } + + @Test + public void getSharingOptOutShouldReturnExpectedResult() { + // given + given(uspNatV1.getSharingOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getSharingOptOut()).isEqualTo(1); + } + + @Test + public void getSharingOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspNatV1.getSharingOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSharingOptOutNotice()).isEqualTo(1); + } + + @Test + public void getTargetedAdvertisingOptOutShouldReturnExpectedResult() { + // given + given(uspNatV1.getTargetedAdvertisingOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOut()).isEqualTo(1); + } + + @Test + public void getTargetedAdvertisingOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspNatV1.getTargetedAdvertisingOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSensitiveDataLimitUseNoticeShouldReturnExpectedResult() { + // given + given(uspNatV1.getSensitiveDataLimitUseNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isEqualTo(1); + } + + @Test + public void getSensitiveDataProcessingOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspNatV1.getSensitiveDataProcessingOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSensitiveDataProcessingShouldReturnExpectedResult() { + // given + final List data = Collections.emptyList(); + given(uspNatV1.getSensitiveDataProcessing()).willReturn(data); + + // when and then + assertThat(gppReader.getSensitiveDataProcessing()).isSameAs(data); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnExpectedResult() { + // given + final List data = Collections.emptyList(); + given(uspNatV1.getKnownChildSensitiveDataConsents()).willReturn(data); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).isSameAs(data); + } + + @Test + public void getPersonalDataConsentsShouldReturnExpectedResult() { + // given + given(uspNatV1.getPersonalDataConsents()).willReturn(1); + + // when and then + assertThat(gppReader.getPersonalDataConsents()).isEqualTo(1); + } + + @Test + public void gppReaderShouldReturnExpectedResultsIfSectionAbsent() { + // given + gppReader = new USNationalGppReader(null); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isNull(); + assertThat(gppReader.getGpc()).isNull(); + assertThat(gppReader.getSaleOptOut()).isNull(); + assertThat(gppReader.getSaleOptOutNotice()).isNull(); + assertThat(gppReader.getSharingNotice()).isNull(); + assertThat(gppReader.getSharingOptOut()).isNull(); + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOut()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessing()).isNull(); + assertThat(gppReader.getKnownChildSensitiveDataConsents()).isNull(); + assertThat(gppReader.getPersonalDataConsents()).isNull(); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USUtahGppReaderTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USUtahGppReaderTest.java new file mode 100644 index 00000000000..66124b38877 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USUtahGppReaderTest.java @@ -0,0 +1,195 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspUtV1; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Collections; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verifyNoInteractions; + +public class USUtahGppReaderTest { + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private GppModel gppModel; + + @Mock + private UspUtV1 uspUtV1; + + private USUtahGppReader gppReader; + + @Before + public void setUp() { + given(gppModel.getUspUtV1Section()).willReturn(uspUtV1); + + gppReader = new USUtahGppReader(gppModel); + } + + @Test + public void getMspaServiceProviderModeShouldReturnExpectedResult() { + // given + given(uspUtV1.getMspaServiceProviderMode()).willReturn(1); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isEqualTo(1); + } + + @Test + public void getGpcShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getGpc()).isNull(); + verifyNoInteractions(uspUtV1); + } + + @Test + public void getSaleOptOutShouldReturnExpectedResult() { + // given + given(uspUtV1.getSaleOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOut()).isEqualTo(1); + } + + @Test + public void getSaleOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspUtV1.getSaleOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSharingNoticeShouldReturnExpectedResult() { + // given + given(uspUtV1.getSharingNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSharingNotice()).isEqualTo(1); + } + + @Test + public void getSharingOptOutShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSharingOptOut()).isNull(); + verifyNoInteractions(uspUtV1); + } + + @Test + public void getSharingOptOutNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + verifyNoInteractions(uspUtV1); + } + + @Test + public void getTargetedAdvertisingOptOutShouldReturnExpectedResult() { + // given + given(uspUtV1.getTargetedAdvertisingOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOut()).isEqualTo(1); + } + + @Test + public void getTargetedAdvertisingOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspUtV1.getTargetedAdvertisingOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSensitiveDataLimitUseNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + verifyNoInteractions(uspUtV1); + } + + @Test + public void getSensitiveDataProcessingOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspUtV1.getSensitiveDataProcessingOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSensitiveDataProcessingShouldReturnExpectedResult() { + // given + given(uspUtV1.getSensitiveDataProcessing()).willReturn(asList(0, 1, 2, 3, 4, 5, 6, 7)); + + // when and then + assertThat(gppReader.getSensitiveDataProcessing()) + .containsExactly(0, 1, 4, 2, 3, 5, 6, 7); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnChildResultOn1() { + // given + given(uspUtV1.getKnownChildSensitiveDataConsents()).willReturn(1); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(1, 1); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnChildResultOn2() { + // given + given(uspUtV1.getKnownChildSensitiveDataConsents()).willReturn(2); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(1, 1); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnNonChildResultOn0() { + // given + given(uspUtV1.getKnownChildSensitiveDataConsents()).willReturn(0); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(0, 0); + } + + @Test + public void getPersonalDataConsentsShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getPersonalDataConsents()).isNull(); + verifyNoInteractions(uspUtV1); + } + + @Test + public void gppReaderShouldReturnExpectedResultsIfSectionAbsent() { + // given + gppReader = new USUtahGppReader(null); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isNull(); + assertThat(gppReader.getGpc()).isNull(); + assertThat(gppReader.getSaleOptOut()).isNull(); + assertThat(gppReader.getSaleOptOutNotice()).isNull(); + assertThat(gppReader.getSharingNotice()).isNull(); + assertThat(gppReader.getSharingOptOut()).isNull(); + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOut()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessing()).isEqualTo(Collections.nCopies(8, null)); + assertThat(gppReader.getKnownChildSensitiveDataConsents()).isNull(); + assertThat(gppReader.getPersonalDataConsents()).isNull(); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USVirginiaGppReaderTest.java b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USVirginiaGppReaderTest.java new file mode 100644 index 00000000000..d1acd419287 --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/privacy/usnat/reader/USVirginiaGppReaderTest.java @@ -0,0 +1,193 @@ +package org.prebid.server.activity.infrastructure.privacy.usnat.reader; + +import com.iab.gpp.encoder.GppModel; +import com.iab.gpp.encoder.section.UspVaV1; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verifyNoInteractions; + +public class USVirginiaGppReaderTest { + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private GppModel gppModel; + + @Mock + private UspVaV1 uspVaV1; + + private USVirginiaGppReader gppReader; + + @Before + public void setUp() { + given(gppModel.getUspVaV1Section()).willReturn(uspVaV1); + + gppReader = new USVirginiaGppReader(gppModel); + } + + @Test + public void getMspaServiceProviderModeShouldReturnExpectedResult() { + // given + given(uspVaV1.getMspaServiceProviderMode()).willReturn(1); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isEqualTo(1); + } + + @Test + public void getGpcShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getGpc()).isNull(); + verifyNoInteractions(uspVaV1); + } + + @Test + public void getSaleOptOutShouldReturnExpectedResult() { + // given + given(uspVaV1.getSaleOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOut()).isEqualTo(1); + } + + @Test + public void getSaleOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspVaV1.getSaleOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSaleOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSharingNoticeShouldReturnExpectedResult() { + // given + given(uspVaV1.getSharingNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getSharingNotice()).isEqualTo(1); + } + + @Test + public void getSharingOptOutShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSharingOptOut()).isNull(); + verifyNoInteractions(uspVaV1); + } + + @Test + public void getSharingOptOutNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + verifyNoInteractions(uspVaV1); + } + + @Test + public void getTargetedAdvertisingOptOutShouldReturnExpectedResult() { + // given + given(uspVaV1.getTargetedAdvertisingOptOut()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOut()).isEqualTo(1); + } + + @Test + public void getTargetedAdvertisingOptOutNoticeShouldReturnExpectedResult() { + // given + given(uspVaV1.getTargetedAdvertisingOptOutNotice()).willReturn(1); + + // when and then + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isEqualTo(1); + } + + @Test + public void getSensitiveDataLimitUseNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + verifyNoInteractions(uspVaV1); + } + + @Test + public void getSensitiveDataProcessingOptOutNoticeShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + verifyNoInteractions(uspVaV1); + } + + @Test + public void getSensitiveDataProcessingShouldReturnExpectedResult() { + // given + final List data = Collections.emptyList(); + given(uspVaV1.getSensitiveDataProcessing()).willReturn(data); + + // when and then + assertThat(gppReader.getSensitiveDataProcessing()).isSameAs(data); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnChildResultOn1() { + // given + given(uspVaV1.getKnownChildSensitiveDataConsents()).willReturn(1); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(1, 1); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnChildResultOn2() { + // given + given(uspVaV1.getKnownChildSensitiveDataConsents()).willReturn(2); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(1, 1); + } + + @Test + public void getKnownChildSensitiveDataConsentsShouldReturnNonChildResultOn0() { + // given + given(uspVaV1.getKnownChildSensitiveDataConsents()).willReturn(0); + + // when and then + assertThat(gppReader.getKnownChildSensitiveDataConsents()).containsExactly(0, 0); + } + + @Test + public void getPersonalDataConsentsShouldReturnExpectedResult() { + // when and then + assertThat(gppReader.getPersonalDataConsents()).isNull(); + verifyNoInteractions(uspVaV1); + } + + @Test + public void gppReaderShouldReturnExpectedResultsIfSectionAbsent() { + // given + gppReader = new USVirginiaGppReader(null); + + // when and then + assertThat(gppReader.getMspaServiceProviderMode()).isNull(); + assertThat(gppReader.getGpc()).isNull(); + assertThat(gppReader.getSaleOptOut()).isNull(); + assertThat(gppReader.getSaleOptOutNotice()).isNull(); + assertThat(gppReader.getSharingNotice()).isNull(); + assertThat(gppReader.getSharingOptOut()).isNull(); + assertThat(gppReader.getSharingOptOutNotice()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOut()).isNull(); + assertThat(gppReader.getTargetedAdvertisingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataLimitUseNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessingOptOutNotice()).isNull(); + assertThat(gppReader.getSensitiveDataProcessing()).isNull(); + assertThat(gppReader.getKnownChildSensitiveDataConsents()).isNull(); + assertThat(gppReader.getPersonalDataConsents()).isNull(); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/rule/AndRuleTest.java b/src/test/java/org/prebid/server/activity/infrastructure/rule/AndRuleTest.java new file mode 100644 index 00000000000..77ec804f20e --- /dev/null +++ b/src/test/java/org/prebid/server/activity/infrastructure/rule/AndRuleTest.java @@ -0,0 +1,78 @@ +package org.prebid.server.activity.infrastructure.rule; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +public class AndRuleTest { + + @org.junit.Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private Rule allowRule; + + @Mock + private Rule disallowRule; + + @Mock + private Rule abstainRule; + + @Before + public void setUp() { + given(allowRule.proceed(any())).willReturn(Rule.Result.ALLOW); + given(disallowRule.proceed(any())).willReturn(Rule.Result.DISALLOW); + given(abstainRule.proceed(any())).willReturn(Rule.Result.ABSTAIN); + } + + @Test + public void proceedShouldReturnDisallowOnFirstOccurrence() { + // given + final Rule rule = new AndRule(asList(allowRule, disallowRule, abstainRule)); + + // when + final Rule.Result result = rule.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.DISALLOW); + verifyNoInteractions(abstainRule); + } + + @Test + public void proceedShouldReturnAllowAndProceedAllRules() { + // given + final Rule rule = new AndRule(asList(allowRule, abstainRule, allowRule)); + + // when + final Rule.Result result = rule.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.ALLOW); + verify(allowRule, times(2)).proceed(any()); + verify(abstainRule).proceed(any()); + } + + @Test + public void proceedShouldReturnAbstain() { + // given + final Rule rule = new AndRule(singletonList(abstainRule)); + + // when + final Rule.Result result = rule.proceed(null); + + // then + assertThat(result).isEqualTo(Rule.Result.ABSTAIN); + verify(abstainRule).proceed(any()); + } +} diff --git a/src/test/java/org/prebid/server/activity/infrastructure/rule/ComponentRuleTest.java b/src/test/java/org/prebid/server/activity/infrastructure/rule/ComponentRuleTest.java index f141292d3d4..7ee19315238 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/rule/ComponentRuleTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/rule/ComponentRuleTest.java @@ -12,7 +12,7 @@ public class ComponentRuleTest { @Test public void allowedShouldReturnExpectedResult() { // given - final Rule rule = new ComponentRule(null, null, true); + final ComponentRule rule = new ComponentRule(null, null, true); // when final boolean allowed = rule.allowed(); @@ -24,7 +24,7 @@ public void allowedShouldReturnExpectedResult() { @Test public void matchesShouldReturnTrueIfComponentTypesIsNull() { // given - final Rule rule = new ComponentRule(null, null, true); + final ComponentRule rule = new ComponentRule(null, null, true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.BIDDER, null)); @@ -36,7 +36,7 @@ public void matchesShouldReturnTrueIfComponentTypesIsNull() { @Test public void matchesShouldReturnFalseIfComponentTypesDoesNotContainsArgument() { // given - final Rule rule = new ComponentRule(singleton(ComponentType.ANALYTICS), null, true); + final ComponentRule rule = new ComponentRule(singleton(ComponentType.ANALYTICS), null, true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.BIDDER, null)); @@ -48,7 +48,7 @@ public void matchesShouldReturnFalseIfComponentTypesDoesNotContainsArgument() { @Test public void matchesShouldReturnTrueIfComponentNamesIsNull() { // given - final Rule rule = new ComponentRule(null, null, true); + final ComponentRule rule = new ComponentRule(null, null, true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.ANALYTICS, "componentName")); @@ -60,7 +60,7 @@ public void matchesShouldReturnTrueIfComponentNamesIsNull() { @Test public void matchesShouldReturnFalseIfComponentNamesDoesNotContainsArgument() { // given - final Rule rule = new ComponentRule(null, singleton("other"), true); + final ComponentRule rule = new ComponentRule(null, singleton("other"), true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.ANALYTICS, "componentName")); @@ -72,7 +72,7 @@ public void matchesShouldReturnFalseIfComponentNamesDoesNotContainsArgument() { @Test public void matchesShouldReturnExpectedResult() { // given - final Rule rule = new ComponentRule(singleton(ComponentType.BIDDER), singleton("bidder"), true); + final ComponentRule rule = new ComponentRule(singleton(ComponentType.BIDDER), singleton("bidder"), true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.BIDDER, "bidder")); diff --git a/src/test/java/org/prebid/server/activity/infrastructure/rule/GeoRuleTest.java b/src/test/java/org/prebid/server/activity/infrastructure/rule/GeoRuleTest.java index b458b89bf8f..c98f6d0b38e 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/rule/GeoRuleTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/rule/GeoRuleTest.java @@ -21,7 +21,7 @@ public class GeoRuleTest { @Test public void allowedShouldReturnExpectedResult() { // given - final Rule rule = new GeoRule(null, null, true, null, null, true); + final GeoRule rule = new GeoRule(null, null, true, null, null, true); // when final boolean allowed = rule.allowed(); @@ -33,7 +33,7 @@ public void allowedShouldReturnExpectedResult() { @Test public void matchesShouldReturnTrueIfComponentTypesIsNull() { // given - final Rule rule = new GeoRule(null, null, true, null, null, true); + final GeoRule rule = new GeoRule(null, null, true, null, null, true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.BIDDER, null)); @@ -45,7 +45,7 @@ public void matchesShouldReturnTrueIfComponentTypesIsNull() { @Test public void matchesShouldReturnFalseIfComponentTypesDoesNotContainsArgument() { // given - final Rule rule = new GeoRule(singleton(ComponentType.ANALYTICS), null, true, null, null, true); + final GeoRule rule = new GeoRule(singleton(ComponentType.ANALYTICS), null, true, null, null, true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.BIDDER, null)); @@ -57,7 +57,7 @@ public void matchesShouldReturnFalseIfComponentTypesDoesNotContainsArgument() { @Test public void matchesShouldReturnTrueIfComponentNamesIsNull() { // given - final Rule rule = new GeoRule(null, null, true, null, null, true); + final GeoRule rule = new GeoRule(null, null, true, null, null, true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.ANALYTICS, "componentName")); @@ -69,7 +69,7 @@ public void matchesShouldReturnTrueIfComponentNamesIsNull() { @Test public void matchesShouldReturnFalseIfComponentNamesDoesNotContainsArgument() { // given - final Rule rule = new GeoRule(null, singleton("other"), true, null, null, true); + final GeoRule rule = new GeoRule(null, singleton("other"), true, null, null, true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.ANALYTICS, "componentName")); @@ -81,7 +81,7 @@ public void matchesShouldReturnFalseIfComponentNamesDoesNotContainsArgument() { @Test public void matchesShouldReturnFalseIfSidsDoesNotMatched() { // given - final Rule rule = new GeoRule(null, null, false, null, null, true); + final GeoRule rule = new GeoRule(null, null, false, null, null, true); // when final boolean matches = rule.matches(ActivityCallPayloadImpl.of(ComponentType.ANALYTICS, "componentName")); @@ -93,7 +93,13 @@ public void matchesShouldReturnFalseIfSidsDoesNotMatched() { @Test public void matchesShouldReturnTrueIfGeoCodeWithoutRegionMatched() { // given - final Rule rule = new GeoRule(null, null, true, singletonList(GeoRule.GeoCode.of("Country", null)), null, true); + final GeoRule rule = new GeoRule( + null, + null, + true, + singletonList(GeoRule.GeoCode.of("Country", null)), + null, + true); final ActivityCallPayload payload = PrivacyEnforcementServiceActivityCallPayload.of( ActivityCallPayloadImpl.of(ComponentType.BIDDER, "bidder"), "country", @@ -110,7 +116,13 @@ public void matchesShouldReturnTrueIfGeoCodeWithoutRegionMatched() { @Test public void matchesShouldReturnFalseIfCountryDoesNotMatched() { // given - final Rule rule = new GeoRule(null, null, true, singletonList(GeoRule.GeoCode.of("Country", null)), null, true); + final GeoRule rule = new GeoRule( + null, + null, + true, + singletonList(GeoRule.GeoCode.of("Country", null)), + null, + true); final ActivityCallPayload payload = PrivacyEnforcementServiceActivityCallPayload.of( ActivityCallPayloadImpl.of(ComponentType.BIDDER, "bidder"), "otherCountry", @@ -127,7 +139,7 @@ public void matchesShouldReturnFalseIfCountryDoesNotMatched() { @Test public void matchesShouldReturnTrueIfGeoCodeMatched() { // given - final Rule rule = new GeoRule( + final GeoRule rule = new GeoRule( null, null, true, @@ -150,7 +162,7 @@ public void matchesShouldReturnTrueIfGeoCodeMatched() { @Test public void matchesShouldReturnFalseIfRegionDoesNotMatched() { // given - final Rule rule = new GeoRule( + final GeoRule rule = new GeoRule( null, null, true, @@ -173,7 +185,7 @@ public void matchesShouldReturnFalseIfRegionDoesNotMatched() { @Test public void matchesShouldReturnTrueIfGpcMatched() { // given - final Rule rule = new GeoRule(null, null, true, null, "2", true); + final GeoRule rule = new GeoRule(null, null, true, null, "2", true); final ActivityCallPayload payload = BidRequestActivityCallPayload.of( null, BidRequest.builder() @@ -190,7 +202,7 @@ public void matchesShouldReturnTrueIfGpcMatched() { @Test public void matchesShouldReturnFalseIfGpcNotMatched() { // given - final Rule rule = new GeoRule(null, null, true, null, "2", true); + final GeoRule rule = new GeoRule(null, null, true, null, "2", true); final ActivityCallPayload payload = BidRequestActivityCallPayload.of( null, BidRequest.builder() @@ -207,7 +219,7 @@ public void matchesShouldReturnFalseIfGpcNotMatched() { @Test public void matchesShouldReturnExpectedResult() { // given - final Rule rule = new GeoRule( + final GeoRule rule = new GeoRule( singleton(ComponentType.BIDDER), singleton("bidder"), true, diff --git a/src/test/java/org/prebid/server/activity/infrastructure/rule/TestRule.java b/src/test/java/org/prebid/server/activity/infrastructure/rule/TestRule.java index 2b0ff17b9f4..c63a4e87a4a 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/rule/TestRule.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/rule/TestRule.java @@ -5,7 +5,7 @@ import java.util.Objects; import java.util.function.Predicate; -public class TestRule implements Rule { +public class TestRule extends AbstractMatchRule { private final Predicate predicate; private final boolean allowed; diff --git a/src/test/java/org/prebid/server/activity/utils/AccountActivitiesConfigurationUtilsTest.java b/src/test/java/org/prebid/server/activity/utils/AccountActivitiesConfigurationUtilsTest.java index f740618fb78..020524a3b0f 100644 --- a/src/test/java/org/prebid/server/activity/utils/AccountActivitiesConfigurationUtilsTest.java +++ b/src/test/java/org/prebid/server/activity/utils/AccountActivitiesConfigurationUtilsTest.java @@ -42,7 +42,7 @@ public void isInvalidActivitiesConfigurationShouldReturnFalseIfAccountPrivacyNul @Test public void isInvalidActivitiesConfigurationShouldReturnFalseIfAccountPrivacyActivitiesNull() { // given - final Account account = Account.builder().privacy(AccountPrivacyConfig.of(null, null, null)).build(); + final Account account = Account.builder().privacy(AccountPrivacyConfig.of(null, null, null, null)).build(); // when final boolean result = AccountActivitiesConfigurationUtils.isInvalidActivitiesConfiguration(account); @@ -55,31 +55,35 @@ public void isInvalidActivitiesConfigurationShouldReturnFalseIfAccountPrivacyAct public void isInvalidActivitiesConfigurationShouldReturnFalseIfConfigurationValid() { // given final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(null, null, Map.of( - Activity.SYNC_USER, AccountActivityConfiguration.of(null, null), - Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, asList( - null, - AccountActivityComponentRuleConfig.of(null, null), - AccountActivityComponentRuleConfig.of( - AccountActivityComponentRuleConfig.Condition.of(null, null), - null), - AccountActivityComponentRuleConfig.of( - AccountActivityComponentRuleConfig.Condition.of( - singletonList(ComponentType.BIDDER), singletonList("bidder")), - null))), - Activity.MODIFY_UFDP, AccountActivityConfiguration.of(null, asList( - AccountActivityGeoRuleConfig.of(null, null), - AccountActivityGeoRuleConfig.of( - AccountActivityGeoRuleConfig.Condition.of(null, null, null, null, null), - null), - AccountActivityGeoRuleConfig.of( - AccountActivityGeoRuleConfig.Condition.of( - singletonList(ComponentType.BIDDER), - singletonList("bidder"), - null, - null, + .privacy(AccountPrivacyConfig.of( + null, + null, + Map.of( + Activity.SYNC_USER, AccountActivityConfiguration.of(null, null), + Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, asList( + null, + AccountActivityComponentRuleConfig.of(null, null), + AccountActivityComponentRuleConfig.of( + AccountActivityComponentRuleConfig.Condition.of(null, null), + null), + AccountActivityComponentRuleConfig.of( + AccountActivityComponentRuleConfig.Condition.of( + singletonList(ComponentType.BIDDER), singletonList("bidder")), + null))), + Activity.MODIFY_UFDP, AccountActivityConfiguration.of(null, asList( + AccountActivityGeoRuleConfig.of(null, null), + AccountActivityGeoRuleConfig.of( + AccountActivityGeoRuleConfig.Condition.of(null, null, null, null, null), null), - null)))))) + AccountActivityGeoRuleConfig.of( + AccountActivityGeoRuleConfig.Condition.of( + singletonList(ComponentType.BIDDER), + singletonList("bidder"), + null, + null, + null), + null)))), + null)) .build(); // when @@ -93,11 +97,14 @@ public void isInvalidActivitiesConfigurationShouldReturnFalseIfConfigurationVali public void isInvalidActivitiesConfigurationShouldReturnTrueOnInvalidComponentRule() { // given final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(null, null, Map.of( - Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, singletonList( + .privacy(AccountPrivacyConfig.of( + null, + null, + Map.of(Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, singletonList( AccountActivityComponentRuleConfig.of( AccountActivityComponentRuleConfig.Condition.of(emptyList(), emptyList()), - null)))))) + null)))), + null)) .build(); // when @@ -111,12 +118,15 @@ public void isInvalidActivitiesConfigurationShouldReturnTrueOnInvalidComponentRu public void isInvalidActivitiesConfigurationShouldReturnTrueOnInvalidGeoRule() { // given final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(null, null, Map.of( - Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, singletonList( + .privacy(AccountPrivacyConfig.of( + null, + null, + Map.of(Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, singletonList( AccountActivityGeoRuleConfig.of( AccountActivityGeoRuleConfig.Condition.of( emptyList(), emptyList(), null, null, null), - null)))))) + null)))), + null)) .build(); // when diff --git a/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java b/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java index d17ff6c6ba4..08d5895a34b 100644 --- a/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java +++ b/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java @@ -529,6 +529,7 @@ public void shouldMaskForCcpaWhenAccountHasCppaConfigEnabledForRequestType() { AccountCcpaConfig.builder() .enabledForRequestType(EnabledForRequestType.of(false, false, true, false)) .build(), + null, null)) .build()) .requestTypeMetric(MetricName.openrtb2app) @@ -578,7 +579,11 @@ public void shouldMaskForCcpaWhenAccountHasCppaEnforcedTrue() { final AuctionContext context = AuctionContext.builder() .account(Account.builder() - .privacy(AccountPrivacyConfig.of(null, AccountCcpaConfig.builder().enabled(true).build(), null)) + .privacy(AccountPrivacyConfig.of( + null, + AccountCcpaConfig.builder().enabled(true).build(), + null, + null)) .build()) .requestTypeMetric(MetricName.openrtb2app) .bidRequest(bidRequest) @@ -630,6 +635,7 @@ public void shouldMaskForCcpaWhenAccountHasCcpaConfigEnabled() { .privacy(AccountPrivacyConfig.of( null, AccountCcpaConfig.builder().enabled(true).build(), + null, null)) .build()) .requestTypeMetric(null) @@ -1510,7 +1516,7 @@ public void isCcpaEnforcedShouldReturnFalseWhenEnforcedPropertyIsTrueInConfigura final Ccpa ccpa = Ccpa.of("1YYY"); final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(null, AccountCcpaConfig.builder().enabled(false).build(), null)) + .privacy(AccountPrivacyConfig.of(null, AccountCcpaConfig.builder().enabled(false).build(), null, null)) .build(); // when and then @@ -1543,6 +1549,7 @@ public void isCcpaEnforcedShouldReturnFalseWhenAccountCcpaConfigHasEnabledTrue() .privacy(AccountPrivacyConfig.of( null, AccountCcpaConfig.builder().enabled(true).build(), + null, null)) .build(); diff --git a/src/test/java/org/prebid/server/handler/CookieSyncHandlerTest.java b/src/test/java/org/prebid/server/handler/CookieSyncHandlerTest.java index 4a870d618de..36ecdfaff25 100644 --- a/src/test/java/org/prebid/server/handler/CookieSyncHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/CookieSyncHandlerTest.java @@ -292,7 +292,7 @@ public void shouldPassAccountToPrivacyEnforcementServiceWhenAccountIsFound() { final AccountGdprConfig accountGdprConfig = AccountGdprConfig.builder() .enabledForRequestType(EnabledForRequestType.of(true, true, true, true)).build(); final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(accountGdprConfig, null, null)) + .privacy(AccountPrivacyConfig.of(accountGdprConfig, null, null, null)) .build(); given(applicationSettings.getAccountById(any(), any())).willReturn(Future.succeededFuture(account)); diff --git a/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java b/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java index 82ede828a97..e4478164fc9 100644 --- a/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java @@ -370,7 +370,7 @@ public void shouldPassAccountToPrivacyEnforcementServiceWhenAccountIsFound() { .enabledForRequestType(EnabledForRequestType.of(true, true, true, true)) .build(); final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(accountGdprConfig, null, null)) + .privacy(AccountPrivacyConfig.of(accountGdprConfig, null, null, null)) .build(); final Future accountFuture = Future.succeededFuture(account); given(applicationSettings.getAccountById(any(), any())).willReturn(accountFuture); diff --git a/src/test/java/org/prebid/server/metric/MetricsTest.java b/src/test/java/org/prebid/server/metric/MetricsTest.java index 77792935049..ab70dff788a 100644 --- a/src/test/java/org/prebid/server/metric/MetricsTest.java +++ b/src/test/java/org/prebid/server/metric/MetricsTest.java @@ -473,6 +473,18 @@ public void updatePriceFloorGeneralErrorsShouldCreateMetricsAsExpected() { assertThat(metricRegistry.counter("price-floors.general.err").getCount()).isOne(); } + @Test + public void updateAlertsMetricsShouldCreateMetricsAsExpected() { + // when + metrics.updateAlertsMetrics(MetricName.general); + metrics.updateAlertsMetrics(MetricName.failed); + metrics.updateAlertsMetrics(MetricName.general); + + // then + assertThat(metricRegistry.counter("alerts.general").getCount()).isEqualTo(2); + assertThat(metricRegistry.counter("alerts.failed").getCount()).isOne(); + } + @Test public void updateAlertsConfigMetricsShouldCreateMetricsAsExpected() { // when diff --git a/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java index c1107c7d157..688a853a910 100644 --- a/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java +++ b/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java @@ -123,6 +123,7 @@ public void getAccountByIdShouldMergeAccountWithDefaultAccount() { .enabledForRequestType(EnabledForRequestType.of(true, null, null, null)) .build(), null, + null, null)) .build())); @@ -142,6 +143,7 @@ public void getAccountByIdShouldMergeAccountWithDefaultAccount() { .enabledForRequestType(EnabledForRequestType.of(true, null, null, null)) .build(), null, + null, null)) .build()); } @@ -227,20 +229,26 @@ public void getAccountByIdShouldRemoveInvalidRulesFromAccountActivitiesConfigura jacksonMapper); given(delegate.getAccountById(anyString(), any())).willReturn(Future.succeededFuture(Account.builder() - .privacy(AccountPrivacyConfig.of(null, null, Map.of( - Activity.SYNC_USER, AccountActivityConfiguration.of(null, null), - Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, asList( - AccountActivityComponentRuleConfig.of(null, null), - AccountActivityComponentRuleConfig.of( - AccountActivityComponentRuleConfig.Condition.of(null, null), - null), - AccountActivityComponentRuleConfig.of( - AccountActivityComponentRuleConfig.Condition.of(emptyList(), emptyList()), - null), - AccountActivityComponentRuleConfig.of( - AccountActivityComponentRuleConfig.Condition.of( - singletonList(ComponentType.BIDDER), singletonList("bidder")), - null)))))) + .privacy(AccountPrivacyConfig.of( + null, + null, + Map.of( + Activity.SYNC_USER, AccountActivityConfiguration.of(null, null), + Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, asList( + AccountActivityComponentRuleConfig.of(null, null), + AccountActivityComponentRuleConfig.of( + AccountActivityComponentRuleConfig.Condition.of(null, null), + null), + AccountActivityComponentRuleConfig.of( + AccountActivityComponentRuleConfig.Condition.of( + emptyList(), + emptyList()), + null), + AccountActivityComponentRuleConfig.of( + AccountActivityComponentRuleConfig.Condition.of( + singletonList(ComponentType.BIDDER), singletonList("bidder")), + null)))), + null)) .build())); // when @@ -248,17 +256,21 @@ public void getAccountByIdShouldRemoveInvalidRulesFromAccountActivitiesConfigura // then assertThat(accountFuture).succeededWith(Account.builder() - .privacy(AccountPrivacyConfig.of(null, null, Map.of( - Activity.SYNC_USER, AccountActivityConfiguration.of(null, null), - Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, asList( - AccountActivityComponentRuleConfig.of(null, null), - AccountActivityComponentRuleConfig.of( - AccountActivityComponentRuleConfig.Condition.of(null, null), - null), - AccountActivityComponentRuleConfig.of( - AccountActivityComponentRuleConfig.Condition.of( - singletonList(ComponentType.BIDDER), singletonList("bidder")), - null)))))) + .privacy(AccountPrivacyConfig.of( + null, + null, + Map.of( + Activity.SYNC_USER, AccountActivityConfiguration.of(null, null), + Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, asList( + AccountActivityComponentRuleConfig.of(null, null), + AccountActivityComponentRuleConfig.of( + AccountActivityComponentRuleConfig.Condition.of(null, null), + null), + AccountActivityComponentRuleConfig.of( + AccountActivityComponentRuleConfig.Condition.of( + singletonList(ComponentType.BIDDER), singletonList("bidder")), + null)))), + null)) .build()); } } diff --git a/src/test/java/org/prebid/server/settings/FileApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/FileApplicationSettingsTest.java index 93b761a2fe7..ea1eda95d99 100644 --- a/src/test/java/org/prebid/server/settings/FileApplicationSettingsTest.java +++ b/src/test/java/org/prebid/server/settings/FileApplicationSettingsTest.java @@ -171,6 +171,7 @@ public void getAccountByIdShouldReturnPresentAccount() { .purposeOneTreatmentInterpretation(PurposeOneTreatmentInterpretation.accessAllowed) .build(), null, + null, null)) .analytics(AccountAnalyticsConfig.of( expectedEventsConfig, diff --git a/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java index 6bfb8e2aff6..628b4feb936 100644 --- a/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java +++ b/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java @@ -107,7 +107,7 @@ public void getAccountByIdShouldReturnFetchedAccount() throws JsonProcessingExce .auction(AccountAuctionConfig.builder() .priceGranularity("testPriceGranularity") .build()) - .privacy(AccountPrivacyConfig.of(null, null, null)) + .privacy(AccountPrivacyConfig.of(null, null, null, null)) .build(); final HttpAccountsResponse response = HttpAccountsResponse.of(Collections.singletonMap("someId", account)); givenHttpClientReturnsResponse(200, mapper.writeValueAsString(response)); diff --git a/src/test/java/org/prebid/server/settings/JdbcApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/JdbcApplicationSettingsTest.java index cf2374d27df..876e6555d4d 100644 --- a/src/test/java/org/prebid/server/settings/JdbcApplicationSettingsTest.java +++ b/src/test/java/org/prebid/server/settings/JdbcApplicationSettingsTest.java @@ -250,6 +250,7 @@ public void getAccountByIdShouldReturnAccountWithAllFieldsPopulated(TestContext .enabledForRequestType(EnabledForRequestType.of(true, true, true, true)) .build(), null, + null, null)) .analytics(AccountAnalyticsConfig.of( expectedEventsConfig, diff --git a/src/test/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityPrivacyModulesRuleConfigMatcherTest.java b/src/test/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityPrivacyModulesRuleConfigMatcherTest.java new file mode 100644 index 00000000000..1f16f1ae816 --- /dev/null +++ b/src/test/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityPrivacyModulesRuleConfigMatcherTest.java @@ -0,0 +1,68 @@ +package org.prebid.server.settings.model.activity.rule.resolver; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import org.junit.Test; +import org.prebid.server.json.ObjectMapperProvider; +import org.prebid.server.settings.model.activity.rule.AccountActivityPrivacyModulesRuleConfig; +import org.prebid.server.settings.model.activity.rule.AccountActivityRuleConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AccountActivityPrivacyModulesRuleConfigMatcherTest { + + private final ObjectMapper mapper = ObjectMapperProvider.mapper(); + + private final AccountActivityPrivacyModulesRuleConfigMatcher target = + new AccountActivityPrivacyModulesRuleConfigMatcher(); + + @Test + public void matchesShouldReturnFalseOnNull() { + // when + final boolean result = target.matches(null); + + // then + assertThat(result).isFalse(); + } + + @Test + public void matchesShouldReturnFalseOnWrongNodeType() { + // when + final boolean result = target.matches(TextNode.valueOf("")); + + // then + assertThat(result).isFalse(); + } + + @Test + public void matchesShouldReturnFalseIfExpectedPropertyNotFound() { + // when + final boolean result = target.matches(mapper.createObjectNode()); + + // then + assertThat(result).isFalse(); + } + + @Test + public void matchesShouldReturnTrueOnConfigWithExpectedProperty() { + // given + final ObjectNode config = mapper.createObjectNode(); + config.put("privacyreg", 1); + + // when + final boolean result = target.matches(config); + + // then + assertThat(result).isTrue(); + } + + @Test + public void typeShouldReturnExpectedResult() { + // when + final Class result = target.type(); + + // then + assertThat(result).isEqualTo(AccountActivityPrivacyModulesRuleConfig.class); + } +} diff --git a/src/test/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityRuleConfigResolverTest.java b/src/test/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityRuleConfigResolverTest.java index 690e0497342..50e280e3438 100644 --- a/src/test/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityRuleConfigResolverTest.java +++ b/src/test/java/org/prebid/server/settings/model/activity/rule/resolver/AccountActivityRuleConfigResolverTest.java @@ -6,6 +6,7 @@ import org.prebid.server.json.ObjectMapperProvider; import org.prebid.server.settings.model.activity.rule.AccountActivityComponentRuleConfig; import org.prebid.server.settings.model.activity.rule.AccountActivityGeoRuleConfig; +import org.prebid.server.settings.model.activity.rule.AccountActivityPrivacyModulesRuleConfig; import org.prebid.server.settings.model.activity.rule.AccountActivityRuleConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -14,6 +15,19 @@ public class AccountActivityRuleConfigResolverTest { private final ObjectMapper mapper = ObjectMapperProvider.mapper(); + @Test + public void matchesShouldReturnPrivacyModulesRuleTypeForCertainConfig() { + //given + final ObjectNode config = mapper.createObjectNode(); + config.put("privacyreg", 1); + + // when + final Class result = AccountActivityRuleConfigResolver.resolve(config); + + // then + assertThat(result).isEqualTo(AccountActivityPrivacyModulesRuleConfig.class); + } + @Test public void matchesShouldReturnGeoRuleTypeForCertainConfig() { //given