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 Validations #3133

Merged
merged 6 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
93 changes: 84 additions & 9 deletions src/main/java/org/prebid/server/auction/DsaEnforcer.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.prebid.server.auction;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Regs;
import com.iab.openrtb.response.Bid;
Expand All @@ -12,8 +12,14 @@
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.BidderSeatBid;
import org.prebid.server.exception.PreBidException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.proto.openrtb.ext.request.DsaPublisherRender;
import org.prebid.server.proto.openrtb.ext.request.DsaRequired;
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.response.DsaAdvertiserRender;
import org.prebid.server.proto.openrtb.ext.response.ExtBidDsa;
import org.prebid.server.util.ObjectUtil;

import java.util.ArrayList;
Expand All @@ -24,7 +30,16 @@
public class DsaEnforcer {

private static final String DSA_EXT = "dsa";
private static final Set<Integer> DSA_REQUIRED = Set.of(2, 3);
private static final Set<Integer> DSA_REQUIRED = Set.of(
DsaRequired.REQUIRED.getValue(),
DsaRequired.REQUIRED_ONLINE_PLATFORM.getValue());
private static final int MAX_DSA_FIELD_LENGTH = 100;

private final JacksonMapper mapper;

public DsaEnforcer(JacksonMapper mapper) {
this.mapper = mapper;
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved
}

public AuctionParticipation enforce(BidRequest bidRequest,
AuctionParticipation auctionParticipation,
Expand All @@ -34,7 +49,7 @@ public AuctionParticipation enforce(BidRequest bidRequest,
final BidderSeatBid seatBid = ObjectUtil.getIfNotNull(bidderResponse, BidderResponse::getSeatBid);
final List<BidderBid> bidderBids = ObjectUtil.getIfNotNull(seatBid, BidderSeatBid::getBids);

if (CollectionUtils.isEmpty(bidderBids) || !isDsaValidationRequired(bidRequest)) {
if (CollectionUtils.isEmpty(bidderBids)) {
return auctionParticipation;
}

Expand All @@ -44,9 +59,20 @@ public AuctionParticipation enforce(BidRequest bidRequest,
for (BidderBid bidderBid : bidderBids) {
final Bid bid = bidderBid.getBid();

if (!isValid(bid)) {
warnings.add(BidderError.invalidBid("Bid \"%s\" missing DSA".formatted(bid.getId())));
rejectionTracker.reject(bid.getImpid(), BidRejectionReason.GENERAL);
final ExtBidDsa dsaResponse = Optional.ofNullable(bid.getExt())
.map(ext -> ext.get(DSA_EXT))
.map(this::getDsaResponse)
.orElse(null);

try {
if (isDsaValidationRequired(bidRequest)) {
validateDsa(bidRequest, dsaResponse);
} else {
validateFieldLength(dsaResponse);
}
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved
} catch (PreBidException e) {
warnings.add(BidderError.invalidBid("Bid \"%s\": %s".formatted(bid.getId(), e.getMessage())));
rejectionTracker.reject(bid.getImpid(), BidRejectionReason.REJECTED_BY_DSA_PRIVACY);
updatedBidderBids.remove(bidderBid);
}
}
Expand All @@ -71,9 +97,58 @@ private static boolean isDsaValidationRequired(BidRequest bidRequest) {
.orElse(false);
}

private boolean isValid(Bid bid) {
final ObjectNode bidExt = bid.getExt();
return bidExt != null && bidExt.hasNonNull(DSA_EXT) && !bidExt.get(DSA_EXT).isEmpty();
private static void validateDsa(BidRequest bidRequest, ExtBidDsa dsaResponse) {
if (dsaResponse == null) {
throw new PreBidException("DSA object missing when required");
}

validateFieldLength(dsaResponse);
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved

final Integer adRender = dsaResponse.getAdRender();
final Integer pubRender = Optional.ofNullable(bidRequest.getRegs())
.map(Regs::getExt)
.map(ExtRegs::getDsa)
.map(ExtRegsDsa::getPubRender)
.orElse(null);
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved

if (pubRender == null) {
return;
}

if (pubRender.equals(DsaPublisherRender.WILL_RENDER.getValue())
&& adRender != null && adRender.equals(DsaAdvertiserRender.WILL_RENDER.getValue())) {
throw new PreBidException("DSA publisher and buyer both signal will render");
}

if (pubRender.equals(DsaPublisherRender.NOT_RENDER.getValue())
&& (adRender == null || adRender.equals(DsaAdvertiserRender.NOT_RENDER.getValue()))) {
throw new PreBidException("DSA publisher and buyer both signal will not render");
}
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved
}

private static void validateFieldLength(ExtBidDsa dsaResponse) {
if (dsaResponse == null) {
return;
}

if (!hasValidLength(dsaResponse.getBehalf())) {
throw new PreBidException("DSA behalf exceeds limit of 100 chars");
}
if (!hasValidLength(dsaResponse.getPaid())) {
throw new PreBidException("DSA paid exceeds limit of 100 chars");
}
}

private static boolean hasValidLength(String value) {
return value == null || value.length() <= MAX_DSA_FIELD_LENGTH;
}

private ExtBidDsa getDsaResponse(JsonNode dsaExt) {
try {
return mapper.mapper().convertValue(dsaExt, ExtBidDsa.class);
} catch (IllegalArgumentException e) {
return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum BidRejectionReason {
REJECTED_BY_MEDIA_TYPE(204),
GENERAL(300),
REJECTED_DUE_TO_PRICE_FLOOR(301),
REJECTED_BY_DSA_PRIVACY(305),
FAILED_TO_REQUEST_BIDS(100),
OTHER_ERROR(100);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
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.DsaTransparency;
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;
Expand Down Expand Up @@ -289,9 +289,9 @@ private static Regs enrichRegs(Regs regs, PrivacyContext privacyContext, Account
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
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.DsaTransparency;
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.ExtUser;
Expand Down Expand Up @@ -271,7 +271,7 @@ private static Map<String, String> extractDsaRequestParamsFromDsaRegsExtension(f
dsaRequestParams.put("dsadatatopub", dsa.getDataToPub().toString());
}

final List<ExtRegsDsaTransparency> dsaTransparency = dsa.getTransparency();
final List<DsaTransparency> dsaTransparency = dsa.getTransparency();
if (CollectionUtils.isNotEmpty(dsaTransparency)) {
final String encodedTransparencies = encodeTransparenciesAsString(dsaTransparency);
if (StringUtils.isNotBlank(encodedTransparencies)) {
Expand All @@ -282,20 +282,20 @@ private static Map<String, String> extractDsaRequestParamsFromDsaRegsExtension(f
return dsaRequestParams;
}

private static String encodeTransparenciesAsString(List<ExtRegsDsaTransparency> transparencies) {
private static String encodeTransparenciesAsString(List<DsaTransparency> transparencies) {
return transparencies.stream()
.filter(YieldlabBidder::isTransparencyValid)
.map(YieldlabBidder::encodeTransparency)
.collect(Collectors.joining(TRANSPARENCY_TEMPLATE_DELIMITER));
}

private static boolean isTransparencyValid(ExtRegsDsaTransparency transparency) {
private static boolean isTransparencyValid(DsaTransparency transparency) {
return StringUtils.isNotBlank(transparency.getDomain())
&& transparency.getDsaParams() != null
&& CollectionUtils.isNotEmpty(transparency.getDsaParams());
}

private static String encodeTransparency(ExtRegsDsaTransparency transparency) {
private static String encodeTransparency(DsaTransparency transparency) {
return TRANSPARENCY_TEMPLATE.formatted(transparency.getDomain(),
encodeTransparencyParams(transparency.getDsaParams()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.prebid.server.proto.openrtb.ext.request;

public enum DsaPublisherRender {

NOT_RENDER(0),
COULD_RENDER(1),
WILL_RENDER(2);

private final Integer value;

DsaPublisherRender(final Integer value) {
this.value = value;
}

public Integer getValue() {
return value;
}
}
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.prebid.server.proto.openrtb.ext.request;

public enum DsaRequired {

NOT_REQUIRED(0),
SUPPORTED(1),
REQUIRED(2),
REQUIRED_ONLINE_PLATFORM(3);

private final Integer value;

DsaRequired(final Integer value) {
this.value = value;
}

public Integer getValue() {
return value;
}
}
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@

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

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

/**
* Defines the contract for bidrequest.regs.ext.dsa.transparency[i].dsaparams[]
* and bidresponse.seatbid[i].bid[i].ext.dsa.transparency[i].dsaparams[]
*/
@JsonProperty("dsaparams")
List<Integer> dsaParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ public class ExtRegsDsa {
/**
* Defines the contract for bidrequest.regs.ext.dsa.transparency[]
*/
List<ExtRegsDsaTransparency> transparency;
List<DsaTransparency> transparency;

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

public enum DsaAdvertiserRender {

NOT_RENDER(0),
WILL_RENDER(1);

private final Integer value;

DsaAdvertiserRender(final Integer value) {
this.value = value;
}

public Integer getValue() {
return value;
}
}
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.prebid.server.proto.openrtb.ext.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import org.prebid.server.proto.openrtb.ext.request.DsaTransparency;

import java.util.List;

/**
* Defines the contract for bidresponse.seatbid[i].bid[i].ext.dsa
*/
@Value(staticConstructor = "of")
public class ExtBidDsa {

/**
* Defines the contract for bidresponse.seatbid[i].bid[i].ext.dsa.behalf
*/
String behalf;

/**
* Defines the contract for bidresponse.seatbid[i].bid[i].ext.dsa.paid
*/
String paid;

/**
* Defines the contract for bidresponse.seatbid[i].bid[i].ext.dsa.transparency[]
*/
List<DsaTransparency> transparency;

/**
* Defines the contract for bidresponse.seatbid[i].bid[i].ext.dsa.adrender
*/
@JsonProperty("adrender")
Integer adRender;

}
Original file line number Diff line number Diff line change
Expand Up @@ -1117,8 +1117,8 @@ LoggerControlKnob loggerControlKnob(Vertx vertx) {
}

@Bean
DsaEnforcer dsaEnforcer() {
return new DsaEnforcer();
DsaEnforcer dsaEnforcer(JacksonMapper mapper) {
return new DsaEnforcer(mapper);
}

private static List<String> splitToList(String listAsString) {
Expand Down
Loading
Loading