Skip to content
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 @@ -22,6 +22,7 @@
import org.prebid.cache.model.ResponseObject;
import org.prebid.cache.repository.CacheConfig;
import org.prebid.cache.repository.ReactiveRepository;
import org.prebid.cache.routers.ApiConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -49,6 +50,7 @@ public class PostCacheHandler extends CacheHandler {

private static final String UUID_KEY = "uuid";
private static final String SECONDARY_CACHE_KEY = "secondaryCache";
private static final String API_KEY_HEADER = "x-pbc-api-key";

private final ReactiveRepository<PayloadWrapper, String> repository;
private final CacheConfig config;
Expand All @@ -57,14 +59,16 @@ public class PostCacheHandler extends CacheHandler {
private final Map<String, WebClient> webClients = new HashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
private final CircuitBreaker circuitBreaker;
private final ApiConfig apiConfig;

@Autowired
public PostCacheHandler(final ReactiveRepository<PayloadWrapper, String> repository,
final CacheConfig config,
final MetricsRecorder metricsRecorder,
final PrebidServerResponseBuilder builder,
final CircuitBreaker webClientCircuitBreaker,
@Value("${sampling.rate:0.01}") final Double samplingRate) {
@Value("${sampling.rate:0.01}") final Double samplingRate,
final ApiConfig apiConfig) {

super(samplingRate);
this.metricsRecorder = metricsRecorder;
Expand All @@ -77,9 +81,14 @@ public PostCacheHandler(final ReactiveRepository<PayloadWrapper, String> reposit
this.builder = builder;
this.metricTagPrefix = "write";
this.circuitBreaker = webClientCircuitBreaker;
this.apiConfig = apiConfig;
}

public Mono<ServerResponse> save(final ServerRequest request) {
if (!isWriteAllowed(request)) {
return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
}

metricsRecorder.markMeterForTag(this.metricTagPrefix, MetricsRecorder.MeasurementTag.REQUEST);
final var timerContext = metricsRecorder.createRequestTimerForServiceType(type);

Expand Down Expand Up @@ -119,6 +128,11 @@ public Mono<ServerResponse> save(final ServerRequest request) {
return finalizeResult(responseMono, request, timerContext);
}

private boolean isWriteAllowed(final ServerRequest request) {
return !apiConfig.isCacheWriteSecured()
|| StringUtils.equals(request.headers().firstHeader(API_KEY_HEADER), apiConfig.getApiKey());
}

private Function<PayloadTransfer, PayloadWrapper> payloadWrapperTransformer() {
return transfer -> PayloadWrapper.builder()
.id(RandomUUID.extractUUID(transfer))
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/prebid/cache/routers/ApiConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class ApiConfig {
@NotEmpty
private String cachePath;

private boolean cacheWriteSecured;

@NotEmpty
private String storagePath;

Expand Down
52 changes: 40 additions & 12 deletions src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class PostCacheHandlerTests extends CacheHandlerTests {
@MockBean
ReactiveRepository<PayloadWrapper, String> repository;

@MockBean
ApiConfig apiConfig;

@Value("${sampling.rate:2.0}")
Double samplingRate;

Expand All @@ -92,7 +95,8 @@ void testVerifyError() {
metricsRecorder,
builder,
webClientCircuitBreaker,
samplingRate);
samplingRate,
apiConfig);
verifyJacksonError(handler);
verifyRepositoryError(handler);
}
Expand All @@ -116,7 +120,7 @@ void testVerifySave() {
given(repository.save(PAYLOAD_WRAPPER)).willReturn(Mono.just(PAYLOAD_WRAPPER));

final PostCacheHandler handler = new PostCacheHandler(repository, cacheConfig, metricsRecorder, builder,
webClientCircuitBreaker, samplingRate);
webClientCircuitBreaker, samplingRate, apiConfig);

final Mono<RequestObject> request = Mono.just(RequestObject.of(Collections.singletonList(PAYLOAD_TRANSFER)));
final MockServerRequest requestMono = MockServerRequest.builder()
Expand Down Expand Up @@ -144,7 +148,7 @@ void testSecondaryCacheSuccess() {
.willReturn(aResponse().withBody("{\"responses\":[{\"uuid\":\"2be04ba5-8f9b-4a1e-8100-d573c40312f8\"}]}")));

final PostCacheHandler handler = new PostCacheHandler(repository, cacheConfig, metricsRecorder, builder,
webClientCircuitBreaker, samplingRate);
webClientCircuitBreaker, samplingRate, apiConfig);

final Mono<RequestObject> request = Mono.just(RequestObject.of(Collections.singletonList(PAYLOAD_TRANSFER)));
final MockServerRequest requestMono = MockServerRequest.builder()
Expand Down Expand Up @@ -173,11 +177,11 @@ void testSecondaryCacheSuccess() {
void testExternalUUIDInvalid() {
//given
final var cacheConfigLocal = new CacheConfig(cacheConfig.getPrefix(), cacheConfig.getExpirySec(),
cacheConfig.getTimeoutMs(),
cacheConfig.getMinExpiry(), cacheConfig.getMaxExpiry(),
false, Collections.emptyList(), cacheConfig.getSecondaryCachePath(), 100, 100, "example.com", "http");
cacheConfig.getTimeoutMs(),
cacheConfig.getMinExpiry(), cacheConfig.getMaxExpiry(),
false, Collections.emptyList(), cacheConfig.getSecondaryCachePath(), 100, 100, "example.com", "http");
final var handler = new PostCacheHandler(repository, cacheConfigLocal, metricsRecorder, builder,
webClientCircuitBreaker, samplingRate);
webClientCircuitBreaker, samplingRate, apiConfig);

final Mono<RequestObject> request = Mono.just(RequestObject.of(Collections.singletonList(PAYLOAD_TRANSFER)));
final MockServerRequest requestMono = MockServerRequest.builder()
Expand Down Expand Up @@ -208,7 +212,7 @@ void testUUIDDuplication() {
5, cacheConfig.getMaxExpiry(), cacheConfig.isAllowExternalUUID(),
Collections.emptyList(), cacheConfig.getSecondaryCachePath(), 100, 100, "example.com", "http");
final PostCacheHandler handler = new PostCacheHandler(repository, cacheConfigLocal, metricsRecorder, builder,
webClientCircuitBreaker, samplingRate);
webClientCircuitBreaker, samplingRate, apiConfig);

final Mono<RequestObject> request = Mono.just(RequestObject.of(Collections.singletonList(PAYLOAD_TRANSFER)));
final MockServerRequest requestMono = MockServerRequest.builder()
Expand All @@ -229,11 +233,35 @@ void testUUIDDuplication() {
final Mono<ServerResponse> responseMonoSecond = handler.save(requestMono);

final Consumer<ServerResponse> consumerSecond = serverResponse ->
assertEquals(400, serverResponse.statusCode().value());
assertEquals(400, serverResponse.statusCode().value());

StepVerifier.create(responseMonoSecond)
.consumeNextWith(consumerSecond)
.expectComplete()
.verify();
.consumeNextWith(consumerSecond)
.expectComplete()
.verify();
}

@Test
void testAuthorization() {
given(apiConfig.isCacheWriteSecured()).willReturn(true);
given(apiConfig.getApiKey()).willReturn("api-key");

final var handler = new PostCacheHandler(
repository,
cacheConfig,
metricsRecorder,
builder,
webClientCircuitBreaker,
samplingRate,
apiConfig);

final var request = MockServerRequest.builder()
.method(HttpMethod.POST)
.build();

StepVerifier.create(handler.save(request))
.consumeNextWith(serverResponse -> assertEquals(401, serverResponse.statusCode().value()))
.expectComplete()
.verify();
}
}
26 changes: 13 additions & 13 deletions src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class AerospikeCacheSpec : ShouldSpec({

should("throw an exception when cache record is absent in Aerospike repository") {
// given: Prebid cache config
val config = prebidCacheConfig.getBaseAerospikeConfig("true")
val config = prebidCacheConfig.getBaseAerospikeConfig(true)
val cachePrefix = config["cache.prefix"]

// when: GET cache endpoint with random UUID is called
Expand All @@ -42,7 +42,7 @@ class AerospikeCacheSpec : ShouldSpec({
should("rethrow an exception from Aerospike cache server when such happens") {
// given: Prebid Cache with not matched to Aerospike server namespace
val unmatchedNamespace = getRandomString()
val config = prebidCacheConfig.getBaseAerospikeConfig("true", unmatchedNamespace)
val config = prebidCacheConfig.getBaseAerospikeConfig(true, unmatchedNamespace)
val prebidCacheApi = BaseSpec.getPrebidCacheApi(config)

// and: Default request object
Expand All @@ -64,19 +64,19 @@ class AerospikeCacheSpec : ShouldSpec({
should("throw an exception when aerospike.prevent_UUID_duplication=true and request with already existing UUID is send") {
// given: Prebid Cache with aerospike.prevent_UUID_duplication=true property
val prebidCacheApi = BaseSpec.getPrebidCacheApi(
prebidCacheConfig.getBaseAerospikeConfig("true") +
prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("true")
prebidCacheConfig.getBaseAerospikeConfig(true) +
prebidCacheConfig.getAerospikePreventUuidDuplicationConfig(true)
)

// and: First request object with set UUID
val uuid = getRandomUuid()
val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = uuid }
val requestObject = RequestObject.of(xmlPayloadTransfer)

// and: First request object is saved to Aerospike cache
// and: The first request object is saved to Aerospike cache
prebidCacheApi.postCache(requestObject)

// and: Second request object with already existing UUID is prepared
// and: A second request object with already existing UUID is prepared
val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = uuid }
val secondRequestObject = RequestObject.of(jsonPayloadTransfer)

Expand All @@ -93,8 +93,8 @@ class AerospikeCacheSpec : ShouldSpec({
should("return back two request UUIDs when allow_external_UUID=true and 2 payload transfers were successfully cached in Aerospike") {
// given: Prebid Cache with allow_external_UUID=true property
val prebidCacheApi = BaseSpec.getPrebidCacheApi(
prebidCacheConfig.getBaseAerospikeConfig("true") +
prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false")
prebidCacheConfig.getBaseAerospikeConfig(true) +
prebidCacheConfig.getAerospikePreventUuidDuplicationConfig(false)
)

// and: Request object with 2 payload transfers and set UUIDs is prepared
Expand All @@ -105,7 +105,7 @@ class AerospikeCacheSpec : ShouldSpec({
// when: POST cache endpoint is called
val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject)

// then: UUIDs from request object are returned
// then: UUIDs from a request object are returned
responseObject.responses.isEmpty() shouldBe false
responseObject.responses.size shouldBe 2

Expand All @@ -117,19 +117,19 @@ class AerospikeCacheSpec : ShouldSpec({
should("update existing cache record when aerospike.prevent_UUID_duplication=false and request with already existing UUID is send") {
// given: Prebid Cache with aerospike.prevent_UUID_duplication=false
val prebidCacheApi = BaseSpec.getPrebidCacheApi(
prebidCacheConfig.getBaseAerospikeConfig("true") +
prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false")
prebidCacheConfig.getBaseAerospikeConfig(true) +
prebidCacheConfig.getAerospikePreventUuidDuplicationConfig(false)
)

// and: First request object
val uuid = getRandomUuid()
val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = uuid }
val requestObject = RequestObject.of(xmlPayloadTransfer)

// and: First request object is saved to Aerospike cache
// and: The first request object is saved to Aerospike cache
prebidCacheApi.postCache(requestObject)

// and: Second request object with already existing UUID is prepared
// and: A second request object with already existing UUID is prepared
val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = uuid }
val secondRequestObject = RequestObject.of(jsonPayloadTransfer)
val requestTransferValue = objectMapper.readValue(secondRequestObject.puts[0].value, TransferValue::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({

should("throw an exception when cache record is absent in Apache ignite repository") {
// given: Prebid cache config
val config = prebidCacheConfig.getBaseApacheIgniteConfig("true")
val config = prebidCacheConfig.getBaseApacheIgniteConfig(true)
val cachePrefix = config["cache.prefix"]

// when: GET cache endpoint with random UUID is called
Expand All @@ -42,7 +42,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({

should("throw an exception when cache is absent in Apache ignite repository due to expiration") {
// given: Prebid Cache with set min and max expiry as 0
val config = prebidCacheConfig.getBaseApacheIgniteConfig("false") +
val config = prebidCacheConfig.getBaseApacheIgniteConfig(false) +
prebidCacheConfig.getCacheExpiryConfig("0", "0")
val prebidCacheApi = BaseSpec.getPrebidCacheApi(config)

Expand All @@ -65,7 +65,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({

should("rethrow an exception from Apache ignite cache server when such happens") {
// given: Prebid Cache with not matched to Apache ignite server namespace
val config = prebidCacheConfig.getBaseApacheIgniteConfig("true", getRandomString())
val config = prebidCacheConfig.getBaseApacheIgniteConfig(true, getRandomString())
val prebidCacheApi = BaseSpec.getPrebidCacheApi(config)

// and: Default request object
Expand All @@ -86,7 +86,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({

should("return back PBC UUID when allow_external_UUID=false and request object was successfully cached in Apache ignite") {
// given: Prebid Cache with disabled allow_external_UUID property
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("false"))
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(false))

// and: Request object
val requestObject = RequestObject.getDefaultJsonRequestObject()
Expand All @@ -102,7 +102,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({

should("return back two random UUIDs when allow_external_UUID=false and 2 payload transfers were successfully cached") {
// given: Prebid Cache with disabled allow_external_UUID property
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("false"))
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(false))

// given: Request object
val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = null }
Expand All @@ -121,7 +121,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({

should("return back a request UUID when allow_external_UUID=true and request object was successfully cached") {
// given: Prebid Cache with enabled allow_external_UUID property
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("true"))
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(true))

// and: Request object with set payload transfer UUID key
val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = getRandomUuid() }
Expand All @@ -137,7 +137,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({

should("return back two request UUIDs when allow_external_UUID=true and 2 payload transfers were successfully cached in Apache ignite") {
// given: Prebid Cache with allow_external_UUID=true property
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("true"))
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(true))

// and: Request object with 2 payload transfers and set UUIDs is prepared
val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = getRandomUuid() }
Expand All @@ -158,18 +158,18 @@ class ApacheIgniteCacheSpec : ShouldSpec({

should("shouldn't update existing cache record when request with already existing UUID is send") {
// given: Prebid Cache with allow_external_UUID=true property
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("true"))
val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(true))

// and: First request object
val uuid = getRandomUuid()
val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = uuid }
val requestObject = RequestObject.of(xmlPayloadTransfer)
val requestTransferValue = objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java)

// and: First request object is saved to Aerospike cache
// and: The first request object is saved to Apache ignite cache
prebidCacheApi.postCache(requestObject)

// and: Second request object with already existing UUID is prepared
// and: A second request object with already existing UUID is prepared
val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = uuid }
val secondRequestObject = RequestObject.of(jsonPayloadTransfer)

Expand All @@ -181,7 +181,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
responseObject.responses.size shouldBe 1
responseObject.responses[0].uuid shouldBe secondRequestObject.puts[0].key

// and: Cache record was updated in Aerospike with a second request object payload
// and: Cache record was updated in Apache ignite with a second request object payload
val getCacheResponse = prebidCacheApi.getCache(responseObject.responses[0].uuid)
val responseTransferValue = objectMapper.readValue(getCacheResponse.bodyAsText(), TransferValue::class.java)

Expand Down
Loading