diff --git a/env/src/main/java/com/sap/cloud/security/config/ServiceBindingMapper.java b/env/src/main/java/com/sap/cloud/security/config/ServiceBindingMapper.java index c3be5cbcac..7f6d8b4b17 100644 --- a/env/src/main/java/com/sap/cloud/security/config/ServiceBindingMapper.java +++ b/env/src/main/java/com/sap/cloud/security/config/ServiceBindingMapper.java @@ -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; @@ -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 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 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[]{})); + } } \ No newline at end of file diff --git a/env/src/test/java/com/sap/cloud/security/config/ServiceBindingMapperDomainsTest.java b/env/src/test/java/com/sap/cloud/security/config/ServiceBindingMapperDomainsTest.java new file mode 100644 index 0000000000..11659cdb84 --- /dev/null +++ b/env/src/test/java/com/sap/cloud/security/config/ServiceBindingMapperDomainsTest.java @@ -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."); + } +} \ No newline at end of file diff --git a/env/src/test/resources/vcapIasServiceDomainsMissing.json b/env/src/test/resources/vcapIasServiceDomainsMissing.json new file mode 100644 index 0000000000..5f815ad6a7 --- /dev/null +++ b/env/src/test/resources/vcapIasServiceDomainsMissing.json @@ -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": [] + } + ] +} \ No newline at end of file diff --git a/env/src/test/resources/vcapIasServiceSingleDomain.json b/env/src/test/resources/vcapIasServiceSingleDomain.json new file mode 100644 index 0000000000..1ad73039d1 --- /dev/null +++ b/env/src/test/resources/vcapIasServiceSingleDomain.json @@ -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": [] + } + ] +} \ No newline at end of file diff --git a/java-api/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java b/java-api/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java index 74d3043093..f0d7b11237 100644 --- a/java-api/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java +++ b/java-api/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java @@ -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"; }