Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DSA fields and validation #2931

Merged
merged 7 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/main/java/org/prebid/server/auction/ExchangeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.iab.openrtb.request.Dooh;
import com.iab.openrtb.request.Eid;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Regs;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.request.Source;
import com.iab.openrtb.request.SupplyChain;
Expand Down Expand Up @@ -95,6 +96,8 @@
import org.prebid.server.proto.openrtb.ext.request.ExtDooh;
import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebidFloors;
import org.prebid.server.proto.openrtb.ext.request.ExtRegs;
import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa;
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestBidAdjustmentFactors;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
Expand Down Expand Up @@ -167,6 +170,7 @@ public class ExchangeService {
private static final Integer DEFAULT_MULTIBID_LIMIT_MAX = 9;
private static final String EID_ALLOWED_FOR_ALL_BIDDERS = "*";
private static final BigDecimal THOUSAND = BigDecimal.valueOf(1000);
private static final Set<Integer> DSA_REQUIRED = Set.of(2, 3);

private final double logSamplingRate;
private final int timeoutAdjustmentFactor;
Expand Down Expand Up @@ -1517,8 +1521,12 @@ private AuctionParticipation validBidderResponse(AuctionParticipation auctionPar
final String lineItemId = LineItemUtil.lineItemIdFrom(bid.getBid(), bidRequest.getImp(), mapper);
maybeRecordInTxnLog(lineItemId, () -> txnLog.lineItemsReceivedFromBidder().get(bidder));

final ValidationResult validationResult =
responseBidValidator.validate(bid, bidderResponse.getBidder(), auctionContext, aliases);
final ValidationResult validationResult = responseBidValidator.validate(
bid,
bidderResponse.getBidder(),
auctionContext,
aliases,
isDsaValidationRequired(bidRequest));

if (validationResult.hasWarnings() || validationResult.hasErrors()) {
errors.add(makeValidationBidderError(bid.getBid(), validationResult));
Expand All @@ -1545,6 +1553,15 @@ private AuctionParticipation validBidderResponse(AuctionParticipation auctionPar
return auctionParticipation.with(resultBidderResponse);
}

private static boolean isDsaValidationRequired(BidRequest bidRequest) {
return Optional.ofNullable(bidRequest.getRegs())
.map(Regs::getExt)
.map(ExtRegs::getDsa)
.map(ExtRegsDsa::getDsaRequired)
.map(DSA_REQUIRED::contains)
.orElse(false);
}

private BidderError makeValidationBidderError(Bid bid, ValidationResult validationResult) {
final String validationErrors = Stream.concat(
validationResult.getErrors().stream().map(message -> "Error: " + message),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ private static Regs createRegs(ConsentParam consentParam,
.usPrivacy(usPrivacy)
.gppSid(gppSid)
.gpp(gpp)
.ext(gpc != null ? ExtRegs.of(null, null, gpc) : null)
.ext(gpc != null ? ExtRegs.of(null, null, gpc, null) : null)
.build()
: null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ private Regs fillRegsWithValuesFromHttpRequest(Regs regs, HttpRequestContext htt
.ext(ExtRegs.of(
extRegs != null ? extRegs.getGdpr() : null,
extRegs != null ? extRegs.getUsPrivacy() : null,
gpc))
gpc,
extRegs != null ? extRegs.getDsa() : null))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import com.iab.openrtb.request.Dooh;
import com.iab.openrtb.request.Geo;
import com.iab.openrtb.request.Publisher;
import com.iab.openrtb.request.Regs;
import com.iab.openrtb.request.Site;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.web.RoutingContext;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.activity.infrastructure.ActivityInfrastructure;
Expand Down Expand Up @@ -52,15 +54,21 @@
import org.prebid.server.proto.openrtb.ext.FlexibleExtension;
import org.prebid.server.proto.openrtb.ext.request.ExtPublisher;
import org.prebid.server.proto.openrtb.ext.request.ExtPublisherPrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtRegs;
import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa;
import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsaTransparency;
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting;
import org.prebid.server.proto.openrtb.ext.request.TraceLevel;
import org.prebid.server.settings.ApplicationSettings;
import org.prebid.server.settings.model.Account;
import org.prebid.server.settings.model.AccountAuctionConfig;
import org.prebid.server.settings.model.AccountDsaConfig;
import org.prebid.server.settings.model.AccountPrivacyConfig;
import org.prebid.server.settings.model.AccountStatus;
import org.prebid.server.settings.model.AccountTargetingConfig;
import org.prebid.server.settings.model.DefaultDsa;
import org.prebid.server.util.HttpUtil;
import org.prebid.server.util.ObjectUtil;
import org.prebid.server.validation.RequestValidator;
Expand Down Expand Up @@ -217,16 +225,69 @@ public BidRequest enrichBidRequestWithAccountAndPrivacyData(AuctionContext aucti
final Device device = bidRequest.getDevice();
final Device enrichedDevice = enrichDevice(device, privacyContext);

if (enrichedRequestExt != null || enrichedDevice != null) {
final Regs regs = bidRequest.getRegs();
final Regs enrichedRegs = enrichRegs(regs, privacyContext, account);

if (enrichedRequestExt != null || enrichedDevice != null || enrichedRegs != null) {
return bidRequest.toBuilder()
.ext(ObjectUtils.defaultIfNull(enrichedRequestExt, requestExt))
.device(ObjectUtils.defaultIfNull(enrichedDevice, device))
.regs(ObjectUtils.defaultIfNull(enrichedRegs, regs))
.build();
}

return bidRequest;
}

private static Regs enrichRegs(Regs regs, PrivacyContext privacyContext, Account account) {
final ExtRegs regsExt = regs != null ? regs.getExt() : null;
final ExtRegsDsa regsExtDsa = regsExt != null ? regsExt.getDsa() : null;
if (regsExtDsa != null) {
return null;
}

final AccountDsaConfig accountDsaConfig = Optional.ofNullable(account)
.map(Account::getPrivacy)
.map(AccountPrivacyConfig::getDsa)
.orElse(null);
final DefaultDsa defaultDsa = accountDsaConfig != null ? accountDsaConfig.getDefaultDsa() : null;
if (defaultDsa == null) {
return null;
}

final boolean isGdprOnly = BooleanUtils.isTrue(accountDsaConfig.getGdprOnly());
if (isGdprOnly && !privacyContext.getTcfContext().isInGdprScope()) {
return null;
}

return Optional.ofNullable(regs)
.map(Regs::toBuilder)
.orElseGet(Regs::builder)
.ext(mapRegsExtDsa(defaultDsa, regsExt))
.build();
}

private static ExtRegs mapRegsExtDsa(DefaultDsa defaultDsa, ExtRegs regsExt) {
final List<ExtRegsDsaTransparency> enrichedDsaTransparencies = defaultDsa.getTransparency()
.stream()
.map(dsaTransparency -> ExtRegsDsaTransparency.of(
dsaTransparency.getDomain(), dsaTransparency.getDsaParams()))
.toList();

final ExtRegsDsa enrichedRegsExtDsa = ExtRegsDsa.of(
defaultDsa.getDsaRequired(),
defaultDsa.getPubRender(),
defaultDsa.getDataToPub(),
enrichedDsaTransparencies);

final boolean isRegsExtPresent = regsExt != null;
return ExtRegs.of(
isRegsExtPresent ? regsExt.getGdpr() : null,
isRegsExtPresent ? regsExt.getUsPrivacy() : null,
isRegsExtPresent ? regsExt.getGpc() : null,
enrichedRegsExtDsa);
}

public Future<HttpRequestContext> executeEntrypointHooks(RoutingContext routingContext,
String body,
AuctionContext auctionContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.proto.openrtb.ext.FlexibleExtension;
import org.prebid.server.proto.openrtb.ext.request.ExtRegs;
import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa;
import org.prebid.server.proto.openrtb.ext.request.ExtSource;
import org.prebid.server.proto.openrtb.ext.request.ExtUser;

Expand Down Expand Up @@ -422,7 +423,8 @@ private static Regs modifyRegs(Regs regs) {

final ExtRegs originalExtRegs = regs.getExt();
final String gpc = originalExtRegs != null ? originalExtRegs.getGpc() : null;
final ExtRegs extRegs = ExtRegs.of(gdpr, usPrivacy, gpc);
final ExtRegsDsa dsa = originalExtRegs != null ? originalExtRegs.getDsa() : null;
final ExtRegs extRegs = ExtRegs.of(gdpr, usPrivacy, gpc, dsa);
copyProperties(originalExtRegs, extRegs);

return regs.toBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ private static ExtRegs resolveRegsExt(ExtRegs extRegs) {
return null;
}

final ExtRegs modifiedExtRegs = ExtRegs.of(null, null, extRegs.getGpc());
final ExtRegs modifiedExtRegs = ExtRegs.of(null, null, extRegs.getGpc(), extRegs.getDsa());
copyProperties(extRegs, modifiedExtRegs);

return modifiedExtRegs;
Expand Down Expand Up @@ -259,7 +259,8 @@ private static ExtUser nullIfEmpty(ExtUser ext) {
}

private static ExtRegs nullIfEmpty(ExtRegs ext) {
return allNull(ext.getGdpr(), ext.getUsPrivacy(), ext.getGpc()) && MapUtils.isEmpty(ext.getProperties())
return allNull(ext.getGdpr(), ext.getUsPrivacy(), ext.getGpc(), ext.getDsa())
&& MapUtils.isEmpty(ext.getProperties())
? null
: ext;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebidFloors;
import org.prebid.server.proto.openrtb.ext.request.ExtPublisher;
import org.prebid.server.proto.openrtb.ext.request.ExtRegs;
import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa;
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidMultiBid;
Expand Down Expand Up @@ -1514,7 +1515,8 @@ private static Regs makeRegs(Regs regs) {

final ExtRegs originalExtRegs = regs.getExt();
final String gpc = originalExtRegs != null ? originalExtRegs.getGpc() : null;
final ExtRegs extRegs = copyProperties(originalExtRegs, ExtRegs.of(gdpr, usPrivacy, gpc));
final ExtRegsDsa dsa = originalExtRegs != null ? originalExtRegs.getDsa() : null;
final ExtRegs extRegs = copyProperties(originalExtRegs, ExtRegs.of(gdpr, usPrivacy, gpc, dsa));

return regs.toBuilder()
.gdpr(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
import org.prebid.server.proto.openrtb.ext.FlexibleExtension;
import org.prebid.server.proto.openrtb.ext.request.ExtRegs;
import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa;
import org.prebid.server.proto.openrtb.ext.request.yahooads.ExtImpYahooAds;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.HttpUtil;
Expand Down Expand Up @@ -184,7 +185,10 @@ private ExtRegs resolveExtRegs(Regs regs) {
final String gpc = Optional.ofNullable(regs.getExt())
.map(ExtRegs::getGpc)
.orElse(null);
final ExtRegs extRegs = ExtRegs.of(gdpr, usPrivacy, gpc);
final ExtRegsDsa dsa = Optional.ofNullable(regs.getExt())
.map(ExtRegs::getDsa)
.orElse(null);
final ExtRegs extRegs = ExtRegs.of(gdpr, usPrivacy, gpc, dsa);
extRegs.addProperty("gpp", TextNode.valueOf(gpp));
if (!CollectionUtils.isEmpty(gppSid)) {
final ArrayNode gppArrayNode = mapper.mapper().createArrayNode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ public class ExtRegs extends FlexibleExtension {
* should be set to 1 and where it is not present this property should not be present.
*/
String gpc;

/**
* Allows for publishers to indicate that a transaction is subject to Digital Services Act (DSA)
* and whether they will render the required transparency information themselves
*/
ExtRegsDsa dsa;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.prebid.server.proto.openrtb.ext.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;

import java.util.List;

/**
* Defines the contract for bidrequest.regs.ext.dsa
*/
@Value(staticConstructor = "of")
public class ExtRegsDsa {

/**
* Defines the contract for bidrequest.regs.ext.dsa.dsarequired
*/
@JsonProperty("dsarequired")
Integer dsaRequired;

/**
* Defines the contract for bidrequest.regs.ext.dsa.pubrender
*/
@JsonProperty("pubrender")
Integer pubRender;

/**
* Defines the contract for bidrequest.regs.ext.dsa.datatopub
*/
@JsonProperty("datatopub")
Integer dataToPub;

/**
* Defines the contract for bidrequest.regs.ext.dsa.transparency[]
*/
List<ExtRegsDsaTransparency> transparency;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.prebid.server.proto.openrtb.ext.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;

import java.util.List;

/**
* Defines the contract for bidrequest.regs.ext.dsa.transparency[i]
*/
@Value(staticConstructor = "of")
public class ExtRegsDsaTransparency {

/**
* Defines the contract for bidrequest.regs.ext.dsa.transparency[i].domain
*/
String domain;

/**
* Defines the contract for bidrequest.regs.ext.dsa.transparency[i].dsaparams[]
*/
@JsonProperty("dsaparams")
List<Integer> dsaParams;
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ private Account validateAndModifyAccount(Account account) {
.privacy(AccountPrivacyConfig.of(
accountPrivacyConfig.getGdpr(),
accountPrivacyConfig.getCcpa(),
accountPrivacyConfig.getDsa(),
AccountActivitiesConfigurationUtils
.removeInvalidRules(accountPrivacyConfig.getActivities()),
accountPrivacyConfig.getModules()))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.prebid.server.settings.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;

@Value(staticConstructor = "of")
public class AccountDsaConfig {

@JsonProperty("default")
DefaultDsa defaultDsa;

@JsonProperty("gdpr-only")
Boolean gdprOnly;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public class AccountPrivacyConfig {

AccountCcpaConfig ccpa;

AccountDsaConfig dsa;

@JsonProperty("allowactivities")
Map<Activity, AccountActivityConfiguration> activities;

Expand Down
21 changes: 21 additions & 0 deletions src/main/java/org/prebid/server/settings/model/DefaultDsa.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.prebid.server.settings.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;

import java.util.List;

@Value(staticConstructor = "of")
public class DefaultDsa {

@JsonProperty("dsarequired")
Integer dsaRequired;

@JsonProperty("pubrender")
Integer pubRender;

@JsonProperty("datatopub")
Integer dataToPub;

List<DsaTransparency> transparency;
}
Loading
Loading