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

Support Testing Label Header #2987

Merged
merged 8 commits into from
Feb 21, 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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.AuctionStoredResult;
import org.prebid.server.auction.versionconverter.BidRequestOrtbVersionConversionManager;
import org.prebid.server.cookie.CookieDeprecationService;
import org.prebid.server.exception.InvalidRequestException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.metric.MetricName;
Expand All @@ -39,6 +40,7 @@ public class AuctionRequestFactory {
private final StoredRequestProcessor storedRequestProcessor;
private final BidRequestOrtbVersionConversionManager ortbVersionConversionManager;
private final AuctionGppService gppService;
private final CookieDeprecationService cookieDeprecationService;
private final ImplicitParametersExtractor paramsExtractor;
private final Ortb2ImplicitParametersResolver paramsResolver;
private final InterstitialProcessor interstitialProcessor;
Expand All @@ -54,6 +56,7 @@ public AuctionRequestFactory(long maxRequestSize,
StoredRequestProcessor storedRequestProcessor,
BidRequestOrtbVersionConversionManager ortbVersionConversionManager,
AuctionGppService gppService,
CookieDeprecationService cookieDeprecationService,
ImplicitParametersExtractor paramsExtractor,
Ortb2ImplicitParametersResolver paramsResolver,
InterstitialProcessor interstitialProcessor,
Expand All @@ -67,6 +70,7 @@ public AuctionRequestFactory(long maxRequestSize,
this.storedRequestProcessor = Objects.requireNonNull(storedRequestProcessor);
this.ortbVersionConversionManager = Objects.requireNonNull(ortbVersionConversionManager);
this.gppService = Objects.requireNonNull(gppService);
this.cookieDeprecationService = Objects.requireNonNull(cookieDeprecationService);
this.paramsExtractor = Objects.requireNonNull(paramsExtractor);
this.paramsResolver = Objects.requireNonNull(paramsResolver);
this.interstitialProcessor = Objects.requireNonNull(interstitialProcessor);
Expand Down Expand Up @@ -231,7 +235,8 @@ private Future<BidRequest> updateBidRequest(AuctionStoredResult auctionStoredRes
return Future.succeededFuture(auctionStoredResult.bidRequest())
.map(ortbVersionConversionManager::convertToAuctionSupportedVersion)
.map(bidRequest -> gppService.updateBidRequest(bidRequest, auctionContext))
.map(bidRequest -> paramsResolver.resolve(bidRequest, auctionContext, ENDPOINT, hasStoredBidRequest));
.map(bidRequest -> paramsResolver.resolve(bidRequest, auctionContext, ENDPOINT, hasStoredBidRequest))
.map(bidRequest -> cookieDeprecationService.updateBidRequestDevice(bidRequest, auctionContext));
}

private static MetricName requestTypeMetric(BidRequest bidRequest) {
Expand Down
111 changes: 111 additions & 0 deletions src/main/java/org/prebid/server/cookie/CookieDeprecationService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.prebid.server.cookie;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
import io.vertx.core.http.Cookie;
import io.vertx.core.http.CookieSameSite;
import io.vertx.ext.web.RoutingContext;
import org.apache.commons.lang3.BooleanUtils;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.cookie.model.PartitionedCookie;
import org.prebid.server.proto.openrtb.ext.request.ExtDevice;
import org.prebid.server.settings.model.Account;
import org.prebid.server.settings.model.AccountAuctionConfig;
import org.prebid.server.settings.model.AccountPrivacySandboxConfig;
import org.prebid.server.settings.model.AccountPrivacySandboxCookieDeprecationConfig;
import org.prebid.server.util.HttpUtil;

import java.util.Objects;
import java.util.Optional;

public class CookieDeprecationService {

private static final String COOKIE_NAME = "receive-cookie-deprecation";
private static final String DEVICE_EXT_COOKIE_DEPRECATION_FIELD_NAME = "cdep";
private static final long DEFAULT_MAX_AGE = 604800L;

private final Account defaultAccount;

public CookieDeprecationService(Account defaultAccount) {
this.defaultAccount = Objects.requireNonNull(defaultAccount);
}

public PartitionedCookie makeCookie(Account account, RoutingContext routingContext) {
final Account resolvedAccount = account.isEmpty() ? defaultAccount : account;

if (hasDeprecationCookieInRequest(routingContext) || isCookieDeprecationDisabled(resolvedAccount)) {
return null;
}

final Long maxAge = getCookieDeprecationConfig(resolvedAccount)
.map(AccountPrivacySandboxCookieDeprecationConfig::getTtlSec)
.orElse(DEFAULT_MAX_AGE);

return PartitionedCookie.of(Cookie.cookie(COOKIE_NAME, "1")
.setPath("/")
.setSameSite(CookieSameSite.NONE)
.setSecure(true)
.setHttpOnly(true)
.setMaxAge(maxAge));
}

private static boolean hasDeprecationCookieInRequest(RoutingContext routingContext) {
return HttpUtil.cookiesAsMap(routingContext).containsKey(COOKIE_NAME);
}

public BidRequest updateBidRequestDevice(BidRequest bidRequest, AuctionContext auctionContext) {
final String secCookieDeprecation = auctionContext.getHttpRequest()
.getHeaders()
.get(HttpUtil.SEC_COOKIE_DEPRECATION);

final Account account = auctionContext.getAccount();
final Account resolvedAccount = account.isEmpty() ? defaultAccount : account;
final Device device = bidRequest.getDevice();

if (secCookieDeprecation == null
|| containsCookieDeprecation(device)
|| isCookieDeprecationDisabled(resolvedAccount)) {

return bidRequest;
}

if (secCookieDeprecation.length() > 100) {
auctionContext.getDebugWarnings().add(HttpUtil.SEC_COOKIE_DEPRECATION + " header has invalid value");
return bidRequest;
}

final ExtDevice extDevice = Optional.ofNullable(device).map(Device::getExt).orElse(ExtDevice.empty());
extDevice.addProperty(DEVICE_EXT_COOKIE_DEPRECATION_FIELD_NAME, TextNode.valueOf(secCookieDeprecation));

final Device resolvedDevice = Optional.ofNullable(device)
.map(Device::toBuilder)
.orElse(Device.builder())
.ext(extDevice)
.build();

return bidRequest.toBuilder().device(resolvedDevice).build();
}

private boolean containsCookieDeprecation(Device device) {
return Optional.ofNullable(device)
.map(Device::getExt)
.map(ext -> ext.getProperty(DEVICE_EXT_COOKIE_DEPRECATION_FIELD_NAME))
.map(JsonNode::asText)
.isPresent();
}

private static Optional<AccountPrivacySandboxCookieDeprecationConfig> getCookieDeprecationConfig(Account account) {
return Optional.ofNullable(account.getAuction())
.map(AccountAuctionConfig::getPrivacySandbox)
.map(AccountPrivacySandboxConfig::getCookieDeprecation);
}

private static boolean isCookieDeprecationDisabled(Account account) {
return getCookieDeprecationConfig(account)
.map(AccountPrivacySandboxCookieDeprecationConfig::getEnabled)
.map(BooleanUtils::isNotTrue)
.orElse(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.prebid.server.cookie.model;

import io.vertx.core.http.Cookie;
import lombok.Value;

/**
* Defines Partitioned Cookie
* todo: remove this class after the Vertx dependencies update
* (assuming the newer version of the Vertx Cookie supports Partitioned Cookies)
*/

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

Cookie cookie;

public String encode() {
return cookie.encode() + "; Partitioned";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import io.vertx.core.logging.LoggerFactory;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.exception.PreBidException;
import org.prebid.server.json.DecodeException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.log.ConditionalLogger;
import org.prebid.server.metric.MetricName;
import org.prebid.server.metric.Metrics;
Expand Down Expand Up @@ -41,34 +39,18 @@ public class PriceFloorsConfigResolver {
private final Metrics metrics;
private final AccountPriceFloorsConfig defaultFloorsConfig;

public PriceFloorsConfigResolver(String defaultAccountConfig, Metrics metrics, JacksonMapper mapper) {
this.defaultAccount = parseAccount(defaultAccountConfig, mapper);
public PriceFloorsConfigResolver(Account defaultAccount, Metrics metrics) {
this.defaultAccount = Objects.requireNonNull(defaultAccount);
this.defaultFloorsConfig = getFloorsConfig(defaultAccount);
this.metrics = Objects.requireNonNull(metrics);
}

private static Account parseAccount(String accountConfig, JacksonMapper mapper) {
try {
final Account account = StringUtils.isNotBlank(accountConfig)
? mapper.decodeValue(accountConfig, Account.class)
: null;

return isNotEmpty(account) ? account : null;
} catch (DecodeException e) {
throw new IllegalArgumentException("Could not parse default account configuration", e);
}
}

private static AccountPriceFloorsConfig getFloorsConfig(Account account) {
final AccountAuctionConfig auctionConfig = ObjectUtil.getIfNotNull(account, Account::getAuction);

return ObjectUtil.getIfNotNull(auctionConfig, AccountAuctionConfig::getPriceFloors);
}

private static boolean isNotEmpty(Account account) {
return account != null && !account.equals(Account.builder().build());
}

public Future<Account> updateFloorsConfig(Account account) {
try {
validatePriceFloorConfig(account, defaultFloorsConfig);
Expand Down Expand Up @@ -167,9 +149,8 @@ private static String invalidPriceFloorsPropertyMessage(String property, Object

private Account fallbackToDefaultConfig(Account account) {
final AccountAuctionConfig auctionConfig = account.getAuction();
final AccountAuctionConfig defaultAuctionConfig = ObjectUtil.getIfNotNull(defaultAccount, Account::getAuction);
final AccountPriceFloorsConfig defaultPriceFloorsConfig =
ObjectUtil.getIfNotNull(defaultAuctionConfig, AccountAuctionConfig::getPriceFloors);
ObjectUtil.getIfNotNull(defaultAccount.getAuction(), AccountAuctionConfig::getPriceFloors);

return account.toBuilder()
.auction(auctionConfig.toBuilder().priceFloors(defaultPriceFloorsConfig).build())
Expand Down
22 changes: 18 additions & 4 deletions src/main/java/org/prebid/server/handler/CookieSyncHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.prebid.server.auction.PrivacyEnforcementService;
import org.prebid.server.auction.gpp.CookieSyncGppService;
import org.prebid.server.bidder.UsersyncMethodChooser;
import org.prebid.server.cookie.CookieDeprecationService;
import org.prebid.server.cookie.CookieSyncService;
import org.prebid.server.cookie.UidsCookie;
import org.prebid.server.cookie.UidsCookieService;
Expand All @@ -24,6 +25,7 @@
import org.prebid.server.cookie.exception.UnauthorizedUidsException;
import org.prebid.server.cookie.model.BiddersContext;
import org.prebid.server.cookie.model.CookieSyncContext;
import org.prebid.server.cookie.model.PartitionedCookie;
import org.prebid.server.exception.InvalidAccountConfigException;
import org.prebid.server.execution.Timeout;
import org.prebid.server.execution.TimeoutFactory;
Expand All @@ -42,6 +44,7 @@

import java.util.ArrayList;
import java.util.Objects;
import java.util.Optional;

public class CookieSyncHandler implements Handler<RoutingContext> {

Expand All @@ -51,6 +54,7 @@ public class CookieSyncHandler implements Handler<RoutingContext> {
private final long defaultTimeout;
private final double logSamplingRate;
private final UidsCookieService uidsCookieService;
private final CookieDeprecationService cookieDeprecationService;
private final CookieSyncGppService gppService;
private final ActivityInfrastructureCreator activityInfrastructureCreator;
private final CookieSyncService cookieSyncService;
Expand All @@ -64,6 +68,7 @@ public class CookieSyncHandler implements Handler<RoutingContext> {
public CookieSyncHandler(long defaultTimeout,
double logSamplingRate,
UidsCookieService uidsCookieService,
CookieDeprecationService cookieDeprecationService,
CookieSyncGppService gppService,
ActivityInfrastructureCreator activityInfrastructureCreator,
CookieSyncService cookieSyncService,
Expand All @@ -77,6 +82,7 @@ public CookieSyncHandler(long defaultTimeout,
this.defaultTimeout = defaultTimeout;
this.logSamplingRate = logSamplingRate;
this.uidsCookieService = Objects.requireNonNull(uidsCookieService);
this.cookieDeprecationService = Objects.requireNonNull(cookieDeprecationService);
this.gppService = Objects.requireNonNull(gppService);
this.activityInfrastructureCreator = Objects.requireNonNull(activityInfrastructureCreator);
this.cookieSyncService = Objects.requireNonNull(cookieSyncService);
Expand Down Expand Up @@ -189,12 +195,20 @@ private Future<CookieSyncContext> fillWithPrivacyContext(CookieSyncContext cooki

private void respondWithResult(CookieSyncContext cookieSyncContext, CookieSyncResponse cookieSyncResponse) {
final HttpResponseStatus status = HttpResponseStatus.OK;
final PartitionedCookie deprecationCookie = cookieDeprecationService.makeCookie(
cookieSyncContext.getAccount(),
cookieSyncContext.getRoutingContext());

HttpUtil.executeSafely(cookieSyncContext.getRoutingContext(), Endpoint.cookie_sync,
response -> response
.setStatusCode(status.code())
.putHeader(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON)
.end(mapper.encodeToString(cookieSyncResponse)));
response -> {
response.setStatusCode(status.code())
.putHeader(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON);

Optional.ofNullable(deprecationCookie)
.ifPresent(cookie -> response.putHeader(HttpUtil.SET_COOKIE_HEADER, cookie.encode()));

response.end(mapper.encodeToString(cookieSyncResponse));
});

final CookieSyncEvent event = CookieSyncEvent.builder()
.status(status.code())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

import io.vertx.core.Future;
import io.vertx.core.logging.LoggerFactory;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.activity.utils.AccountActivitiesConfigurationUtils;
import org.prebid.server.execution.Timeout;
import org.prebid.server.floors.PriceFloorsConfigResolver;
import org.prebid.server.json.DecodeException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.json.JsonMerger;
import org.prebid.server.log.ConditionalLogger;
import org.prebid.server.settings.model.Account;
Expand All @@ -34,35 +31,17 @@ public class EnrichingApplicationSettings implements ApplicationSettings {

public EnrichingApplicationSettings(boolean enforceValidAccount,
double logSamplingRate,
String defaultAccountConfig,
Account defaultAccount,
ApplicationSettings delegate,
PriceFloorsConfigResolver priceFloorsConfigResolver,
JsonMerger jsonMerger,
JacksonMapper mapper) {
JsonMerger jsonMerger) {

this.enforceValidAccount = enforceValidAccount;
this.logSamplingRate = logSamplingRate;
this.delegate = Objects.requireNonNull(delegate);
this.jsonMerger = Objects.requireNonNull(jsonMerger);
this.priceFloorsConfigResolver = Objects.requireNonNull(priceFloorsConfigResolver);

defaultAccount = parseAccount(defaultAccountConfig, mapper);
}

private static Account parseAccount(String accountConfig, JacksonMapper mapper) {
try {
final Account account = StringUtils.isNotBlank(accountConfig)
? mapper.decodeValue(accountConfig, Account.class)
: null;

return isNotEmpty(account) ? account : null;
} catch (DecodeException e) {
throw new IllegalArgumentException("Could not parse default account configuration", e);
}
}

private static boolean isNotEmpty(Account account) {
return account != null && !account.equals(Account.builder().build());
this.defaultAccount = Objects.requireNonNull(defaultAccount);
}

@Override
Expand Down Expand Up @@ -112,9 +91,7 @@ public Future<StoredDataResult> getVideoStoredData(String accountId,
}

private Account mergeAccounts(Account account) {
return defaultAccount != null
? jsonMerger.merge(account, defaultAccount, Account.class)
: account;
return jsonMerger.merge(account, defaultAccount, Account.class);
}

private Account validateAndModifyAccount(Account account) {
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/org/prebid/server/settings/model/Account.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.prebid.server.settings.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Value;
Expand All @@ -26,8 +27,11 @@ public class Account {
AccountHooksConfiguration hooks;

public static Account empty(String id) {
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved
return Account.builder()
.id(id)
.build();
return Account.builder().id(id).build();
}

@JsonIgnore
public boolean isEmpty() {
return this.equals(empty(id));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,7 @@ public class AccountAuctionConfig {

@JsonProperty("preferredmediatype")
Map<String, MediaType> preferredMediaTypes;

@JsonProperty("privacysandbox")
AccountPrivacySandboxConfig privacySandbox;
}
Loading
Loading