Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@
@NoArgsConstructor( access = AccessLevel.PRIVATE )
public final class ApacheHttpClient5Accessor
{
/**
* Internal request-header marker that instructs the CSRF token interceptor to skip fetching a CSRF token for the
* request it is attached to. The interceptor strips this header before the request is sent, so it never reaches the
* target system.
* <p>
* This is an implementation detail used by the OData VDM layer to preserve the legacy {@code withoutCsrfToken()}
* opt-out behavior and is not intended for direct use by applications.
*/
@Nonnull
public static final String SKIP_CSRF_TOKEN_HEADER = "x-sap-sdk-skip-csrf-token";

/**
* Configures the {@code HttpClient5Cache} that is used by the {@code #getHttpClient(String)} and
* {@code #getHttpClient(Destination)} methods.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ class CsrfTokenInterceptor implements HttpRequestInterceptor
throws HttpException,
IOException
{
if( request.containsHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER) ) {
request.removeHeaders(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER);
log.debug("CSRF token retrieval explicitly disabled for this request, skipping.");
return;
}

if( !MUTATING_METHODS.contains(request.getMethod().toUpperCase()) ) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,33 @@ void tokenIsNotFetchedWhenAlreadyPresent()
.isEqualTo("existing-token");
}

@Test
@SneakyThrows
void tokenIsNotFetchedAndMarkerIsStrippedWhenSkipHeaderPresent()
{
final HttpPost request = new HttpPost(REQUEST_PATH);
request.addHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true");

sut.process(request, null, null);

verify(mockHttpClient, never()).execute(any(), ArgumentMatchers.<HttpClientResponseHandler<String>> any());
assertThat(request.getFirstHeader(CsrfTokenInterceptor.X_CSRF_TOKEN_HEADER_KEY)).isNull();
assertThat(request.containsHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER)).isFalse();
}

@Test
@SneakyThrows
void skipMarkerIsStrippedEvenOnGetRequest()
{
final HttpGet request = new HttpGet(REQUEST_PATH);
request.addHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true");

sut.process(request, null, null);

verify(mockHttpClient, never()).execute(any(), ArgumentMatchers.<HttpClientResponseHandler<String>> any());
assertThat(request.containsHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER)).isFalse();
}

@Test
@SneakyThrows
void requestProceedsWithoutTokenWhenServerReturnsNoHeader( final WireMockRuntimeInfo wm )
Expand Down
126 changes: 126 additions & 0 deletions datamodel/odata-core-apache-httpclient5/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<artifactId>datamodel-parent</artifactId>
<version>5.35.0-SNAPSHOT</version>
</parent>
<artifactId>odata-core-apache-httpclient5</artifactId>
<packaging>jar</packaging>
<name>Data Model - OData Services - Core (HttpClient 5)</name>
<description>OData Services data model (VDM) - core classes using Apache HttpClient 5.</description>
<url>https://sap.github.io/cloud-sdk/docs/java/getting-started</url>
<organization>
<name>SAP SE</name>
<url>https://www.sap.com</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<name>SAP</name>
<email>cloudsdk@sap.com</email>
<organization>SAP SE</organization>
<organizationUrl>https://www.sap.com</organizationUrl>
</developer>
</developers>
<properties>
<jsr305.optional>false</jsr305.optional>
</properties>
<dependencies>
<dependency>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<artifactId>odata-client-apache-httpclient5</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sap.cloud.sdk.cloudplatform</groupId>
<artifactId>cloudplatform-core</artifactId>
</dependency>
<dependency>
<groupId>com.sap.cloud.sdk.cloudplatform</groupId>
<artifactId>cloudplatform-connectivity</artifactId>
</dependency>
<dependency>
<groupId>com.sap.cloud.sdk.cloudplatform</groupId>
<artifactId>connectivity-apache-httpclient5</artifactId>
</dependency>
<dependency>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<artifactId>fluent-result</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
</dependency>
<!-- scope "provided" -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- scope "test" -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.sap.cloud.sdk.datamodel.odata.adapter;

import java.lang.reflect.Type;
import java.math.BigDecimal;

import javax.annotation.Nonnull;

import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

/**
* GSON serializer that transforms numbers to their JSON representation according to the OData V2 standard. Meant for
* internal use only.
*/
public class ODataNumberSerializer implements JsonSerializer<Number>
{
@Override
@Nonnull
public JsonElement serialize(
@Nonnull final Number src,
@Nonnull final Type typeOfSrc,
@Nonnull final JsonSerializationContext context )
{
/*
Short is used both for Edm.Byte and Edm.Int16.
Edm.Byte should be a string but Edm.Int16 should be a number.
But we can't differentiate between that here because we only know it's a Short.
So we serialize to the plain number because this worked in the past.
*/
if( typeOfSrc == Integer.class || typeOfSrc == Short.class ) {
return new JsonPrimitive(src);
} else if( typeOfSrc == BigDecimal.class ) {
return new JsonPrimitive(((BigDecimal) src).toPlainString());
} else {
return new JsonPrimitive(src.toString());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.sap.cloud.sdk.datamodel.odata.helper;

import javax.annotation.Nonnull;

/**
* Representation of any OData function import as a fluent class for further configuring the request and
* {@link #executeRequest(Destination) executing} it.This is specifically for functions that return either a collection
* of primitive values or entities
*
* @param <FluentHelperT>
* The fluent helper type.
* @param <ObjectT>
* The type of the object this OData request operates on, if any.
* @param <ResultT>
* The type of the result entity, if any.
*/
public abstract class CollectionValuedFluentHelperFunction<FluentHelperT, ObjectT, ResultT>
extends
FluentHelperFunction<FluentHelperT, ObjectT, ResultT>
{

/**
* Instantiates this fluent helper using the given service path to send the requests.
*
* @param servicePath
* The service path to direct the requests to.
*/
public CollectionValuedFluentHelperFunction( @Nonnull final String servicePath )
{
super(servicePath);
}
}
Loading
Loading