Skip to content

Commit

Permalink
Fix ServiceBindingMapper for IAS configuration without domains (#1153)
Browse files Browse the repository at this point in the history
* Fix ServiceBindingMapper throwing exception when IAS configuration has no 'domains' key in credentials.
For backward compatibility, it also accepts single String values under key 'domain'.
  • Loading branch information
finkmanAtSap authored Apr 14, 2023
1 parent e09f7e3 commit a91328e
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;

import static com.sap.cloud.security.config.Service.IAS;
import static com.sap.cloud.security.config.cf.CFConstants.IAS.DOMAIN;
import static com.sap.cloud.security.config.cf.CFConstants.IAS.DOMAINS;
import static com.sap.cloud.security.config.cf.CFConstants.SERVICE_PLAN;

Expand All @@ -33,17 +35,36 @@ public static OAuth2ServiceConfigurationBuilder mapToOAuth2ServiceConfigurationB
return null;
}

TypedMapView credentials = TypedMapView.ofCredentials(b);
OAuth2ServiceConfigurationBuilder builder = OAuth2ServiceConfigurationBuilder.forService(service)
.withProperties(TypedMapView.ofCredentials(b).getEntries(String.class))
.withProperty(SERVICE_PLAN,
b.getServicePlan().orElse(K8sConstants.Plan.APPLICATION.name()).toUpperCase());
.withProperties(credentials.getEntries(String.class))
.withProperty(SERVICE_PLAN, b.getServicePlan().orElse(K8sConstants.Plan.APPLICATION.name()).toUpperCase());

if (IAS.equals(service)) {
List<String> domains = TypedMapView.ofCredentials(b).getListView(DOMAINS).getItems(String.class);
LOGGER.info("first domain : {}", domains.get(0));
builder.withDomains(domains.toArray(new String[] {}));
parseDomains(builder, credentials);
}

return builder;
}

/**
* Parses the 'domains' key in the credentials of an IAS configuration and configures the given builder with them if present.
* For backward compatibility, domains may also be provided via key 'domain'.
*
* @param credentials value of JSON key 'credentials' in an IAS service configuration
*/
private static void parseDomains(OAuth2ServiceConfigurationBuilder builder, TypedMapView credentials) {
List<String> domains;
if(credentials.getKeys().contains(DOMAINS)) {
domains = credentials.getListView(DOMAINS).getItems(String.class);
} else if (credentials.getKeys().contains(DOMAIN)) {
domains = Collections.singletonList(credentials.getString(DOMAIN));
} else {
LOGGER.warn("Neither 'domains' nor 'domain' found in IAS credentials.");
return;
}

LOGGER.info("Domains {} found in IAS credentials.", domains);
builder.withDomains(domains.toArray(new String[]{}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.sap.cloud.security.config;

import com.sap.cloud.environment.servicebinding.SapVcapServicesServiceBindingAccessor;
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
import com.sap.cloud.environment.servicebinding.api.ServiceBindingAccessor;
import nl.altindag.log.LogCaptor;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests whether ServiceBindingMapper correctly handles 'domains' key in service configurations.
* Asserts that both 'domains' values given as String array or as single String value are accepted.
* Asserts that 'domain' value given as single String value is also accepted as fall-back for backward compatibility.
* Asserts that a warning is printed when no domains are found in an IAS configuration.
*/
class ServiceBindingMapperDomainsTest {
private static ServiceBinding xsuaaBinding;
private static ServiceBinding iasBinding, iasBindingSingleDomain, iasBindingDomainsMissing;
private LogCaptor logCaptor;

@BeforeAll
static void setupClass() throws IOException {
xsuaaBinding = readServiceBindingFromJson(Service.XSUAA, "/vcapXsuaaServiceSingleBinding.json");
iasBinding = readServiceBindingFromJson(Service.IAS, "/vcapIasServiceSingleBinding.json");
iasBindingSingleDomain = readServiceBindingFromJson(Service.IAS, "/vcapIasServiceSingleDomain.json");
iasBindingDomainsMissing = readServiceBindingFromJson(Service.IAS, "/vcapIasServiceDomainsMissing.json");
}

private static ServiceBinding readServiceBindingFromJson(Service service, String jsonPath) throws IOException {
String vcapJson = IOUtils.resourceToString(jsonPath, UTF_8);
ServiceBindingAccessor sba = new SapVcapServicesServiceBindingAccessor(any -> vcapJson);

return sba.getServiceBindings().stream()
.filter(b -> service.equals(Service.from(b.getServiceName().orElse(""))))
.findFirst().get();
}

@BeforeEach
void setup() {
logCaptor = LogCaptor.forClass(ServiceBindingMapper.class);
}

@Test
void getXsuaaConfiguration() {
OAuth2ServiceConfiguration config = ServiceBindingMapper.mapToOAuth2ServiceConfigurationBuilder(xsuaaBinding).build();
assertThat(config.getDomains()).isEmpty();
}

@Test
void getIasConfiguration() {
OAuth2ServiceConfiguration config = ServiceBindingMapper.mapToOAuth2ServiceConfigurationBuilder(iasBinding).build();
assertThat(config.getDomains()).containsExactly("myauth.com", "my.auth.com");
}

@Test
void getIasConfigurationWithSingleDomain() {
OAuth2ServiceConfiguration config = ServiceBindingMapper.mapToOAuth2ServiceConfigurationBuilder(iasBindingSingleDomain).build();
assertThat(config.getDomains()).containsExactly("domain1");
}

@Test
void getIasConfigurationWithDomainsMissing() {
OAuth2ServiceConfiguration config = ServiceBindingMapper.mapToOAuth2ServiceConfigurationBuilder(iasBindingDomainsMissing).build();
assertThat(config.getDomains()).isEmpty();

assertThat(logCaptor.getWarnLogs()).contains("Neither 'domains' nor 'domain' found in IAS credentials.");
}
}
20 changes: 20 additions & 0 deletions env/src/test/resources/vcapIasServiceDomainsMissing.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"identity": [
{
"binding_name": null,
"credentials": {
"clientsecret": "clientsecret",
"clientid": "clientid",
"url": "https://myauth.com"
},
"instance_name": "myservice",
"label": "identity",
"name": "myservice",
"plan": "application",
"provider": null,
"syslog_drain_url": null,
"tags": [],
"volume_mounts": []
}
]
}
21 changes: 21 additions & 0 deletions env/src/test/resources/vcapIasServiceSingleDomain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"identity": [
{
"binding_name": null,
"credentials": {
"clientsecret": "clientsecret",
"clientid": "clientid",
"url": "https://myauth.com",
"domain" : "domain1"
},
"instance_name": "myservice",
"label": "identity",
"name": "myservice",
"plan": "application",
"provider": null,
"syslog_drain_url": null,
"tags": [],
"volume_mounts": []
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public static class IAS {
private IAS() {
}

/**
* @deprecated in favor of {@link IAS#DOMAINS}
*/
public static final String DOMAIN = "domain";
public static final String DOMAINS = "domains";
}

Expand Down

0 comments on commit a91328e

Please sign in to comment.