Skip to content

Commit fafb819

Browse files
Merge pull request #251 from cap-java/deleteissue
SDMEXT:1871:Delete Flow with authorities(to support user email logged in audit log)
2 parents 36d4ded + eb44cd5 commit fafb819

13 files changed

Lines changed: 328 additions & 31 deletions

sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ private SDMConstants() {
99
}
1010

1111
public static final String REPOSITORY_ID = System.getenv("REPOSITORY_ID");
12+
public static final String SYSTEM_USER = "system-internal";
13+
public static final String DESTINATION_EXCEPTION =
14+
"Unable to get the destination for sdm service binding";
15+
1216
public static final String SDM_ANNOTATION_ADDITIONALPROPERTY_NAME =
1317
"SDM.Attachments.AdditionalProperty.name";
1418
public static final String SDM_ANNOTATION_ADDITIONALPROPERTY =

sdm/src/main/java/com/sap/cds/sdm/handler/TokenHandler.java

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
package com.sap.cds.sdm.handler;
22

33
import static com.sap.cds.sdm.constants.SDMConstants.NAMED_USER_FLOW;
4+
import static com.sap.cloud.sdk.cloudplatform.connectivity.OnBehalfOf.TECHNICAL_USER_CURRENT_TENANT;
45
import static java.util.Objects.requireNonNull;
56

67
import com.sap.cds.sdm.constants.SDMConstants;
78
import com.sap.cds.sdm.model.SDMCredentials;
9+
import com.sap.cds.sdm.service.SDMPropertySupplier;
10+
import com.sap.cds.sdm.service.SDMUser;
811
import com.sap.cds.services.environment.CdsProperties;
912
import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor;
1013
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
11-
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpClientFactory;
12-
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
13-
import com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2DestinationBuilder;
14-
import com.sap.cloud.sdk.cloudplatform.connectivity.OnBehalfOf;
14+
import com.sap.cloud.environment.servicebinding.api.ServiceIdentifier;
15+
import com.sap.cloud.sdk.cloudplatform.connectivity.*;
1516
import com.sap.cloud.security.config.ClientCredentials;
1617
import java.nio.charset.StandardCharsets;
1718
import java.time.Duration;
1819
import java.util.List;
1920
import java.util.Map;
21+
import java.util.Optional;
2022
import org.apache.http.client.HttpClient;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
2125

2226
public class TokenHandler {
2327
private static final String SDM_TOKEN_ENDPOINT = "url";
@@ -29,13 +33,20 @@ public class TokenHandler {
2933

3034
private TokenHandler() {}
3135

36+
private static final Logger logger = LoggerFactory.getLogger(TokenHandler.class);
37+
3238
public static TokenHandler getTokenHandlerInstance() {
3339
if (instance == null) {
3440
instance = new TokenHandler();
3541
}
3642
return instance;
3743
}
3844

45+
static {
46+
OAuth2ServiceBindingDestinationLoader.registerPropertySupplier(
47+
ServiceIdentifier.of("sdm"), SDMPropertySupplier::new);
48+
}
49+
3950
public byte[] toBytes(String str) {
4051
return requireNonNull(str).getBytes(StandardCharsets.UTF_8);
4152
}
@@ -127,4 +138,48 @@ public HttpClient getHttpClient(
127138

128139
return builder.build().createHttpClient(destination);
129140
}
141+
142+
public HttpClient getHttpClientForAuthoritiesFlow(
143+
CdsProperties.ConnectionPool connectionPoolConfig, String user) {
144+
145+
Optional<HttpDestination> destinations = getHttpDestination(user);
146+
if (destinations.isPresent()) {
147+
DefaultHttpClientFactory.DefaultHttpClientFactoryBuilder builder =
148+
DefaultHttpClientFactory.builder();
149+
150+
if (connectionPoolConfig == null) {
151+
Duration timeout = Duration.ofSeconds(SDMConstants.CONNECTION_TIMEOUT);
152+
builder.timeoutMilliseconds((int) timeout.toMillis());
153+
builder.maxConnectionsPerRoute(SDMConstants.MAX_CONNECTIONS);
154+
builder.maxConnectionsTotal(SDMConstants.MAX_CONNECTIONS);
155+
} else {
156+
builder.timeoutMilliseconds((int) connectionPoolConfig.getTimeout().toMillis());
157+
builder.maxConnectionsPerRoute(connectionPoolConfig.getMaxConnectionsPerRoute());
158+
builder.maxConnectionsTotal(connectionPoolConfig.getMaxConnections());
159+
}
160+
161+
return builder.build().createHttpClient(destinations.get());
162+
}
163+
return null;
164+
}
165+
166+
private Optional<HttpDestination> getHttpDestination(String userName) {
167+
HttpDestination httpDestination;
168+
try {
169+
httpDestination =
170+
ServiceBindingDestinationLoader.defaultLoaderChain()
171+
.getDestination(getSDMDestinationOptions(userName));
172+
} catch (Exception exception) {
173+
logger.error("Error with fetching httpdestination " + exception.getCause());
174+
httpDestination = null;
175+
}
176+
return Optional.ofNullable(httpDestination);
177+
}
178+
179+
public static ServiceBindingDestinationOptions getSDMDestinationOptions(String userName) {
180+
return ServiceBindingDestinationOptions.forService(ServiceIdentifier.of("sdm"))
181+
.onBehalfOf(TECHNICAL_USER_CURRENT_TENANT)
182+
.withOption(SDMUser.of(userName))
183+
.build();
184+
}
130185
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.sap.cds.sdm.service;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultOAuth2PropertySupplier;
5+
import com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2Options;
6+
import com.sap.cloud.sdk.cloudplatform.connectivity.ServiceBindingDestinationOptions;
7+
import io.reactivex.annotations.NonNull;
8+
import java.net.URI;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
public class SDMPropertySupplier extends DefaultOAuth2PropertySupplier {
12+
13+
public SDMPropertySupplier(ServiceBindingDestinationOptions options) {
14+
super(options);
15+
}
16+
17+
@NonNull
18+
@Override
19+
public URI getServiceUri() {
20+
return getCredentialOrThrow(URI.class, "endpoints", "ecmservice", "url");
21+
}
22+
23+
@NotNull
24+
@Override
25+
public OAuth2Options getOAuth2Options() {
26+
var builder = OAuth2Options.builder();
27+
var user = this.options.getOption(SDMUser.class);
28+
if (!user.isEmpty()) {
29+
var objectMapper = new ObjectMapper();
30+
var azAttrNode = objectMapper.createObjectNode();
31+
// add X-EcmUserEnc attribute
32+
azAttrNode.put("X-EcmUserEnc", user.get());
33+
34+
// add X-EcmAddPrincipals attribute
35+
azAttrNode.put("X-EcmAddPrincipals", user.get());
36+
var authoritiesNode = objectMapper.createObjectNode();
37+
authoritiesNode.set("az_attr", azAttrNode);
38+
builder.withTokenRetrievalParameter("authorities", authoritiesNode.toString());
39+
}
40+
return builder.build();
41+
}
42+
}

sdm/src/main/java/com/sap/cds/sdm/service/SDMService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public String getFolderIdByPath(
3333

3434
public Boolean isRepositoryVersioned(JSONObject repoInfo, String repositoryId) throws IOException;
3535

36-
public int deleteDocument(String cmisaction, String objectId) throws IOException;
36+
public int deleteDocument(String cmisaction, String objectId, String user) throws IOException;
3737

3838
public void readDocument(
3939
String objectId, SDMCredentials sdmCredentials, AttachmentReadEventContext context)

sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,12 @@
1818
import com.sap.cds.services.environment.CdsProperties;
1919
import com.sap.cds.services.persistence.PersistenceService;
2020
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
21+
import com.sap.cloud.sdk.cloudplatform.connectivity.*;
2122
import java.io.ByteArrayInputStream;
2223
import java.io.IOException;
2324
import java.io.InputStream;
2425
import java.nio.charset.StandardCharsets;
25-
import java.util.ArrayList;
26-
import java.util.HashMap;
27-
import java.util.Iterator;
28-
import java.util.List;
29-
import java.util.Map;
30-
import java.util.Set;
26+
import java.util.*;
3127
import java.util.stream.Collectors;
3228
import org.apache.http.HttpEntity;
3329
import org.apache.http.client.HttpClient;
@@ -496,9 +492,14 @@ public Boolean isRepositoryVersioned(JSONObject repoInfo, String repositoryId) {
496492
}
497493

498494
@Override
499-
public int deleteDocument(String cmisaction, String objectId) throws IOException {
495+
public int deleteDocument(String cmisaction, String objectId, String user) {
500496
SDMCredentials sdmCredentials = tokenHandler.getSDMCredentials();
501-
var httpClient = tokenHandler.getHttpClient(binding, connectionPool, null, TECHNICAL_USER_FLOW);
497+
HttpClient httpClient;
498+
if (user.equals(SDMConstants.SYSTEM_USER)) {
499+
httpClient = tokenHandler.getHttpClient(binding, connectionPool, null, TECHNICAL_USER_FLOW);
500+
} else {
501+
httpClient = tokenHandler.getHttpClientForAuthoritiesFlow(connectionPool, user);
502+
}
502503

503504
String sdmUrl = sdmCredentials.getUrl() + "browser/" + SDMConstants.REPOSITORY_ID + "/root";
504505
HttpPost deleteDocumentRequest = new HttpPost(sdmUrl);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.sap.cds.sdm.service;
2+
3+
import com.sap.cloud.sdk.cloudplatform.connectivity.ServiceBindingDestinationOptions;
4+
5+
public class SDMUser implements ServiceBindingDestinationOptions.OptionsEnhancer<String> {
6+
7+
private final String value;
8+
9+
/** Set the user. */
10+
public static SDMUser of(String value) {
11+
return new SDMUser(value);
12+
}
13+
14+
private SDMUser(String value) {
15+
this.value = value;
16+
}
17+
18+
@Override
19+
public String getValue() {
20+
return value;
21+
}
22+
}

sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ public void markAttachmentAsDeleted(AttachmentMarkAsDeletedEventContext context)
7979
dbQuery.getAttachmentsForFolder(entity, persistenceService, folderId, context);
8080
if (cmisDocuments.isEmpty()) {
8181
// deleteFolder API
82-
sdmService.deleteDocument("deleteTree", folderId);
82+
sdmService.deleteDocument("deleteTree", folderId, context.getDeletionUserInfo().getName());
8383
} else {
8484
if (!isObjectIdPresent(cmisDocuments, objectId)) {
85-
sdmService.deleteDocument("delete", objectId);
85+
sdmService.deleteDocument("delete", objectId, context.getDeletionUserInfo().getName());
8686
}
8787
}
8888
}

sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMCustomServiceHandler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,13 @@ public void copyAttachments(AttachmentCopyEventContext context) throws IOExcepti
7474
} catch (ServiceException e) {
7575
if (!folderExists) {
7676
// deleteFolder
77-
sdmService.deleteDocument("deleteTree", folderId);
77+
sdmService.deleteDocument("deleteTree", folderId, context.getUserInfo().getName());
7878
throw new ServiceException(e.getMessage());
7979
} else {
8080
for (List<String> attachmentMetadata : attachmentsMetadata) {
8181
// delete the copied attachments
82-
sdmService.deleteDocument("delete", attachmentMetadata.get(2));
82+
sdmService.deleteDocument(
83+
"delete", attachmentMetadata.get(2), context.getUserInfo().getName());
8384
}
8485
throw new ServiceException(e.getMessage());
8586
}

sdm/src/test/java/unit/com/sap/cds/sdm/handler/TokenHandlerTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package unit.com.sap.cds.sdm.handler;
22

33
import static org.junit.Assert.assertNotNull;
4+
import static org.junit.Assert.assertNull;
45
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
56
import static org.junit.jupiter.api.Assertions.assertEquals;
67
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -94,6 +95,14 @@ public void testGetHttpClientForTechnicalUser() {
9495
assertNotNull(client);
9596
}
9697

98+
@Test
99+
public void testGetHttpClientForAuthorities() {
100+
HttpClient client =
101+
TokenHandler.getTokenHandlerInstance()
102+
.getHttpClientForAuthoritiesFlow(connectionPoolConfig, "testUser");
103+
assertNull(client);
104+
}
105+
97106
@Test
98107
public void testGetHttpClientWithNullSubdomain() {
99108
HttpClient client =
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package unit.com.sap.cds.sdm.service;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.when;
6+
7+
import com.sap.cds.sdm.service.SDMPropertySupplier;
8+
import com.sap.cds.sdm.service.SDMUser;
9+
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
10+
import com.sap.cloud.sdk.cloudplatform.connectivity.ServiceBindingDestinationOptions;
11+
import java.net.URI;
12+
import java.util.Map;
13+
import org.junit.jupiter.api.Test;
14+
15+
public class SDMPropertySupplierTest {
16+
@Test
17+
void userNameIsSetAsAdditionalAttribute() {
18+
var options =
19+
ServiceBindingDestinationOptions.forService(mock(ServiceBinding.class))
20+
.withOption(SDMUser.of("my_ods_user"))
21+
.build();
22+
23+
var supplier = new SDMPropertySupplier(options);
24+
25+
var attributes = supplier.getOAuth2Options().getAdditionalTokenRetrievalParameters();
26+
assertThat(attributes)
27+
.containsEntry(
28+
"authorities",
29+
"{\"az_attr\":{\"X-EcmUserEnc\":\"my_ods_user\",\"X-EcmAddPrincipals\":\"my_ods_user\"}}");
30+
}
31+
32+
@Test
33+
void specialCharactersInUserNameAreEscaped() {
34+
var options =
35+
ServiceBindingDestinationOptions.forService(mock(ServiceBinding.class))
36+
.withOption(SDMUser.of("my_attack_user\\abc\"def"))
37+
.build();
38+
39+
var supplier = new SDMPropertySupplier(options);
40+
41+
var attributes = supplier.getOAuth2Options().getAdditionalTokenRetrievalParameters();
42+
assertThat(attributes)
43+
.containsEntry(
44+
"authorities",
45+
"{\"az_attr\":{\"X-EcmUserEnc\":\"my_attack_user\\\\abc\\\"def\",\"X-EcmAddPrincipals\":\"my_attack_user\\\\abc\\\"def\"}}");
46+
}
47+
48+
@Test
49+
void sdmEndpointIsReturnedAsServiceUri() {
50+
var binding = mock(ServiceBinding.class);
51+
Map<String, Object> credentials =
52+
Map.of(
53+
"endpoints",
54+
Map.of("ecmservice", Map.of("url", "https://buslog-write-host/", "timeout", 900000)));
55+
when(binding.getCredentials()).thenReturn(credentials);
56+
var options = ServiceBindingDestinationOptions.forService(binding).build();
57+
58+
var supplier = new SDMPropertySupplier(options);
59+
60+
assertThat(supplier.getServiceUri()).isEqualTo(URI.create("https://buslog-write-host/"));
61+
}
62+
}

0 commit comments

Comments
 (0)