Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public class AmpRequestFactory {
private final DebugResolver debugResolver;
private final JacksonMapper mapper;
private final GeoLocationServiceWrapper geoLocationServiceWrapper;
private final TcfDefinerService tcfDefinerService;

public AmpRequestFactory(Ortb2RequestFactory ortb2RequestFactory,
StoredRequestProcessor storedRequestProcessor,
Expand All @@ -115,7 +116,8 @@ public AmpRequestFactory(Ortb2RequestFactory ortb2RequestFactory,
AmpPrivacyContextFactory ampPrivacyContextFactory,
DebugResolver debugResolver,
JacksonMapper mapper,
GeoLocationServiceWrapper geoLocationServiceWrapper) {
GeoLocationServiceWrapper geoLocationServiceWrapper,
TcfDefinerService tcfDefinerService) {

this.ortb2RequestFactory = Objects.requireNonNull(ortb2RequestFactory);
this.storedRequestProcessor = Objects.requireNonNull(storedRequestProcessor);
Expand All @@ -130,6 +132,7 @@ public AmpRequestFactory(Ortb2RequestFactory ortb2RequestFactory,
this.ampPrivacyContextFactory = Objects.requireNonNull(ampPrivacyContextFactory);
this.mapper = Objects.requireNonNull(mapper);
this.geoLocationServiceWrapper = Objects.requireNonNull(geoLocationServiceWrapper);
this.tcfDefinerService = Objects.requireNonNull(tcfDefinerService);
}

/**
Expand Down Expand Up @@ -217,7 +220,7 @@ private Future<BidRequest> parseBidRequest(AuctionContext auctionContext, HttpRe
return Future.succeededFuture(bidRequest);
}

private static ConsentParam consentParamFromQueryStringParams(HttpRequestContext httpRequest) {
private ConsentParam consentParamFromQueryStringParams(HttpRequestContext httpRequest) {
final ConsentType specifiedConsentType = ConsentType.from(httpRequest.getQueryParams().get(CONSENT_TYPE_PARAM));
final CaseInsensitiveMultiMap queryParams = httpRequest.getQueryParams();

Expand All @@ -229,12 +232,12 @@ private static ConsentParam consentParamFromQueryStringParams(HttpRequestContext
: toConsentParam(gdprConsentParam, GDPR_CONSENT_PARAM, specifiedConsentType);
}

private static ConsentParam toConsentParam(String consent, String fromParam, ConsentType specifiedConsentType) {
private ConsentParam toConsentParam(String consent, String fromParam, ConsentType specifiedConsentType) {
return ConsentParam.of(
consent,
fromParam,
specifiedConsentType,
TcfDefinerService.isConsentStringValid(consent),
tcfDefinerService.isConsentStringValid(consent),
Ccpa.isValid(consent));
}

Expand All @@ -259,10 +262,10 @@ private static Site createSite(HttpRequestContext httpRequest) {

return !StringUtils.isAllBlank(accountId, canonicalUrl, domain)
? Site.builder()
.publisher(Publisher.builder().id(accountId).build())
.page(canonicalUrl)
.domain(domain)
.build()
.publisher(Publisher.builder().id(accountId).build())
.page(canonicalUrl)
.domain(domain)
.build()
: null;
}

Expand All @@ -278,9 +281,9 @@ private static User createUser(ConsentParam consentParam, String addtlConsent) {

final ExtUser extUser = consentedProvidersSettings != null
? ExtUser.builder()
.deprecatedConsentedProvidersSettings(consentedProvidersSettings)
.consentedProvidersSettings(consentedProvidersSettings)
.build()
.deprecatedConsentedProvidersSettings(consentedProvidersSettings)
.consentedProvidersSettings(consentedProvidersSettings)
.build()
: null;

return User.builder().consent(consent).ext(extUser).build();
Expand All @@ -301,12 +304,12 @@ private static Regs createRegs(ConsentParam consentParam,

return gdpr != null || usPrivacy != null || gppSid != null || gpp != null || gpc != null
? Regs.builder()
.gdpr(gdpr)
.usPrivacy(usPrivacy)
.gppSid(gppSid)
.gpp(gpp)
.ext(gpc != null ? ExtRegs.of(null, null, gpc, null) : null)
.build()
.gdpr(gdpr)
.usPrivacy(usPrivacy)
.gppSid(gppSid)
.gpp(gpp)
.ext(gpc != null ? ExtRegs.of(null, null, gpc, null) : null)
.build()
: null;
}

Expand Down Expand Up @@ -359,8 +362,8 @@ private GppSidExtraction gppSidFromQueryStringParams(HttpRequestContext httpRequ
try {
final List<Integer> gppSid = StringUtils.isNotBlank(gppSidParam)
? Arrays.stream(gppSidParam.split(","))
.map(Integer::valueOf)
.toList()
.map(Integer::valueOf)
.toList()
: null;

return GppSidExtraction.success(gppSid);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/prebid/server/metric/MetricName.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public enum MetricName {
specified,
opt_out("opt-out"),
invalid,
no_disclosed_vendors("no-disclosed-vendors"),
in_geo("in-geo"),
out_geo("out-geo"),
unknown_geo("unknown-geo"),
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/prebid/server/metric/Metrics.java
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,10 @@ public void updatePrivacyTcfInvalidMetric() {
privacy().tcf().incCounter(MetricName.invalid);
}

public void updatePrivacyTcfNoDisclosedVendorsMetric() {
privacy().tcf().incCounter(MetricName.no_disclosed_vendors);
}

public void updatePrivacyTcfRequestsMetric(int version) {
final UpdatableMetrics versionMetrics = privacy().tcf().fromVersion(version);
versionMetrics.incCounter(MetricName.requests);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.prebid.server.privacy.gdpr;

import com.iabtcf.decoder.TCString;
import org.prebid.server.settings.model.GdprConfig;

import java.time.Instant;
import java.time.Month;
import java.time.Year;
import java.time.ZoneOffset;

public class DisclosedVendorsStrictness {

private static final Instant TCF_2_3_ENFORCEMENT_CUTOFF_DATE = Year.of(2026)
.atMonth(Month.MARCH)
.atDay(1)
.atStartOfDay()
.toInstant(ZoneOffset.UTC);

private final boolean strictnessEnabled;

public DisclosedVendorsStrictness(GdprConfig gdprConfig) {
this.strictnessEnabled = gdprConfig == null || gdprConfig.isStrictDisclosedVendorsTreatment();
}

public boolean isValid(TCString consent) {
return !strictnessEnabled
|| isCreatedBeforeTcfV2M3EnforcementCutoff(consent)
|| !consent.getDisclosedVendors().isEmpty();
}

private boolean isCreatedBeforeTcfV2M3EnforcementCutoff(TCString consent) {
final Instant created = consent.getCreated();
final Instant lastUpdated = consent.getLastUpdated();
final Instant latest = lastUpdated.isAfter(created) ? lastUpdated : created;

return latest.isBefore(TCF_2_3_ENFORCEMENT_CUTOFF_DATE);
}

public boolean isVendorDisclosed(TCString consent, Integer vendorId) {
return !strictnessEnabled
|| (vendorId != null
&& (isCreatedBeforeTcfV2M3EnforcementCutoff(consent)
|| consent.getDisclosedVendors().contains(vendorId)));
}
}
28 changes: 14 additions & 14 deletions src/main/java/org/prebid/server/privacy/gdpr/Tcf2Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -358,17 +358,17 @@ private Purposes mergeAccountPurposes(AccountGdprConfig accountGdprConfig) {

return accountPurposes != null
? Purposes.builder()
.p1(mergeItem(accountPurposes.getP1(), defaultPurposes.getP1()))
.p2(mergeItem(accountPurposes.getP2(), defaultPurposes.getP2()))
.p3(mergeItem(accountPurposes.getP3(), defaultPurposes.getP3()))
.p4(mergeItem(accountPurposes.getP4(), defaultPurposes.getP4()))
.p5(mergeItem(accountPurposes.getP5(), defaultPurposes.getP5()))
.p6(mergeItem(accountPurposes.getP6(), defaultPurposes.getP6()))
.p7(mergeItem(accountPurposes.getP7(), defaultPurposes.getP7()))
.p8(mergeItem(accountPurposes.getP8(), defaultPurposes.getP8()))
.p9(mergeItem(accountPurposes.getP9(), defaultPurposes.getP9()))
.p10(mergeItem(accountPurposes.getP10(), defaultPurposes.getP10()))
.build()
.p1(mergeItem(accountPurposes.getP1(), defaultPurposes.getP1()))
.p2(mergeItem(accountPurposes.getP2(), defaultPurposes.getP2()))
.p3(mergeItem(accountPurposes.getP3(), defaultPurposes.getP3()))
.p4(mergeItem(accountPurposes.getP4(), defaultPurposes.getP4()))
.p5(mergeItem(accountPurposes.getP5(), defaultPurposes.getP5()))
.p6(mergeItem(accountPurposes.getP6(), defaultPurposes.getP6()))
.p7(mergeItem(accountPurposes.getP7(), defaultPurposes.getP7()))
.p8(mergeItem(accountPurposes.getP8(), defaultPurposes.getP8()))
.p9(mergeItem(accountPurposes.getP9(), defaultPurposes.getP9()))
.p10(mergeItem(accountPurposes.getP10(), defaultPurposes.getP10()))
.build()
: defaultPurposes;
}

Expand All @@ -379,9 +379,9 @@ private SpecialFeatures mergeAccountSpecialFeatures(AccountGdprConfig accountGdp

return accountSpecialFeatures != null
? SpecialFeatures.builder()
.sf1(mergeItem(accountSpecialFeatures.getSf1(), defaultSpecialFeatures.getSf1()))
.sf2(mergeItem(accountSpecialFeatures.getSf2(), defaultSpecialFeatures.getSf2()))
.build()
.sf1(mergeItem(accountSpecialFeatures.getSf1(), defaultSpecialFeatures.getSf1()))
.sf2(mergeItem(accountSpecialFeatures.getSf2(), defaultSpecialFeatures.getSf2()))
.build()
: defaultSpecialFeatures;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class TcfDefinerService {
private final boolean gdprEnabled;
private final String gdprDefaultValue;
private final boolean consentStringMeansInScope;
private final DisclosedVendorsStrictness disclosedVendorsStrictness;
private final Tcf2Service tcf2Service;
private final Set<String> eeaCountries;
private final GeoLocationServiceWrapper geoLocationServiceWrapper;
Expand All @@ -70,6 +71,7 @@ public class TcfDefinerService {

public TcfDefinerService(GdprConfig gdprConfig,
Set<String> eeaCountries,
DisclosedVendorsStrictness disclosedVendorsStrictness,
Tcf2Service tcf2Service,
GeoLocationServiceWrapper geoLocationServiceWrapper,
BidderCatalog bidderCatalog,
Expand All @@ -81,6 +83,7 @@ public TcfDefinerService(GdprConfig gdprConfig,
this.gdprDefaultValue = gdprConfig != null ? gdprConfig.getDefaultValue() : null;
this.consentStringMeansInScope = gdprConfig != null
&& BooleanUtils.isTrue(gdprConfig.getConsentStringMeansInScope());
this.disclosedVendorsStrictness = Objects.requireNonNull(disclosedVendorsStrictness);
this.tcf2Service = Objects.requireNonNull(tcf2Service);
this.eeaCountries = Objects.requireNonNull(eeaCountries);
this.geoLocationServiceWrapper = Objects.requireNonNull(geoLocationServiceWrapper);
Expand Down Expand Up @@ -345,6 +348,15 @@ private TCStringParsingResult parseConsentString(String consentString, RequestLo
return TCStringParsingResult.of(TCStringEmpty.create(), warnings);
}

if (!disclosedVendorsStrictness.isValid(tcString)) {
final String message = "Invalid TCF string: `disclosedVendors` list is empty.";
warnings.add(message);
logWarn(consentString, message, requestLogInfo);
metrics.updatePrivacyTcfNoDisclosedVendorsMetric();

return TCStringParsingResult.of(TCStringEmpty.create(), warnings);
}

return toValidResult(consentString, TCStringParsingResult.of(tcString, warnings));
}

Expand Down Expand Up @@ -417,10 +429,9 @@ private static boolean isConsentValid(TCString consent) {
return consent != null && !(consent instanceof TCStringEmpty);
}

public static boolean isConsentStringValid(String consentString) {
public boolean isConsentStringValid(String consentString) {
try {
TCString.decode(consentString);
return true;
return disclosedVendorsStrictness.isValid(TCString.decode(consentString));
} catch (RuntimeException e) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public static VendorIdResolver of(BidderCatalog bidderCatalog) {
}

public Integer resolve(String aliasOrBidder) {
return aliases != null ? aliases.resolveAliasVendorId(aliasOrBidder) : null;
return aliases.resolveAliasVendorId(aliasOrBidder);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ public int getVersion() {

@Override
public Instant getCreated() {
return null;
return Instant.MAX;
}

@Override
public Instant getLastUpdated() {
return null;
return Instant.MAX;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.prebid.server.privacy.gdpr.tcfstrategies.purpose;

import org.prebid.server.privacy.gdpr.DisclosedVendorsStrictness;
import org.prebid.server.privacy.gdpr.model.PrivacyEnforcementAction;
import org.prebid.server.privacy.gdpr.tcfstrategies.purpose.typestrategies.BasicEnforcePurposeStrategy;
import org.prebid.server.privacy.gdpr.tcfstrategies.purpose.typestrategies.FullEnforcePurposeStrategy;
Expand All @@ -8,11 +9,16 @@

public class Purpose01Strategy extends PurposeStrategy {

public Purpose01Strategy(FullEnforcePurposeStrategy fullEnforcePurposeStrategy,
public Purpose01Strategy(DisclosedVendorsStrictness disclosedVendorsStrictness,
FullEnforcePurposeStrategy fullEnforcePurposeStrategy,
BasicEnforcePurposeStrategy basicEnforcePurposeStrategy,
NoEnforcePurposeStrategy noEnforcePurposeStrategy) {

super(fullEnforcePurposeStrategy, basicEnforcePurposeStrategy, noEnforcePurposeStrategy);
super(
disclosedVendorsStrictness,
fullEnforcePurposeStrategy,
basicEnforcePurposeStrategy,
noEnforcePurposeStrategy);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.prebid.server.privacy.gdpr.tcfstrategies.purpose;

import org.prebid.server.privacy.gdpr.DisclosedVendorsStrictness;
import org.prebid.server.privacy.gdpr.model.PrivacyEnforcementAction;
import org.prebid.server.privacy.gdpr.tcfstrategies.purpose.typestrategies.BasicEnforcePurposeStrategy;
import org.prebid.server.privacy.gdpr.tcfstrategies.purpose.typestrategies.FullEnforcePurposeStrategy;
Expand All @@ -8,11 +9,16 @@

public class Purpose02Strategy extends PurposeStrategy {

public Purpose02Strategy(FullEnforcePurposeStrategy fullEnforcePurposeStrategy,
public Purpose02Strategy(DisclosedVendorsStrictness disclosedVendorsStrictness,
FullEnforcePurposeStrategy fullEnforcePurposeStrategy,
BasicEnforcePurposeStrategy basicEnforcePurposeStrategy,
NoEnforcePurposeStrategy noEnforcePurposeStrategy) {

super(fullEnforcePurposeStrategy, basicEnforcePurposeStrategy, noEnforcePurposeStrategy);
super(
disclosedVendorsStrictness,
fullEnforcePurposeStrategy,
basicEnforcePurposeStrategy,
noEnforcePurposeStrategy);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.prebid.server.privacy.gdpr.tcfstrategies.purpose;

import org.prebid.server.privacy.gdpr.DisclosedVendorsStrictness;
import org.prebid.server.privacy.gdpr.model.PrivacyEnforcementAction;
import org.prebid.server.privacy.gdpr.tcfstrategies.purpose.typestrategies.BasicEnforcePurposeStrategy;
import org.prebid.server.privacy.gdpr.tcfstrategies.purpose.typestrategies.FullEnforcePurposeStrategy;
Expand All @@ -8,11 +9,16 @@

public class Purpose03Strategy extends PurposeStrategy {

public Purpose03Strategy(FullEnforcePurposeStrategy fullEnforcePurposeStrategy,
public Purpose03Strategy(DisclosedVendorsStrictness disclosedVendorsStrictness,
FullEnforcePurposeStrategy fullEnforcePurposeStrategy,
BasicEnforcePurposeStrategy basicEnforcePurposeStrategy,
NoEnforcePurposeStrategy noEnforcePurposeStrategy) {

super(fullEnforcePurposeStrategy, basicEnforcePurposeStrategy, noEnforcePurposeStrategy);
super(
disclosedVendorsStrictness,
fullEnforcePurposeStrategy,
basicEnforcePurposeStrategy,
noEnforcePurposeStrategy);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.prebid.server.privacy.gdpr.tcfstrategies.purpose;

import org.prebid.server.privacy.gdpr.DisclosedVendorsStrictness;
import org.prebid.server.privacy.gdpr.model.PrivacyEnforcementAction;
import org.prebid.server.privacy.gdpr.tcfstrategies.purpose.typestrategies.BasicEnforcePurposeStrategy;
import org.prebid.server.privacy.gdpr.tcfstrategies.purpose.typestrategies.FullEnforcePurposeStrategy;
Expand All @@ -8,11 +9,16 @@

public class Purpose04Strategy extends PurposeStrategy {

public Purpose04Strategy(FullEnforcePurposeStrategy fullEnforcePurposeStrategy,
public Purpose04Strategy(DisclosedVendorsStrictness disclosedVendorsStrictness,
FullEnforcePurposeStrategy fullEnforcePurposeStrategy,
BasicEnforcePurposeStrategy basicEnforcePurposeStrategy,
NoEnforcePurposeStrategy noEnforcePurposeStrategy) {

super(fullEnforcePurposeStrategy, basicEnforcePurposeStrategy, noEnforcePurposeStrategy);
super(
disclosedVendorsStrictness,
fullEnforcePurposeStrategy,
basicEnforcePurposeStrategy,
noEnforcePurposeStrategy);
}

@Override
Expand Down
Loading
Loading