Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ServiceBindingMapper for IAS configuration without domains #1153

Merged
merged 3 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -2,14 +2,18 @@

import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
import com.sap.cloud.environment.servicebinding.api.TypedMapView;
import com.sap.cloud.environment.servicebinding.api.exception.KeyNotFoundException;
import com.sap.cloud.environment.servicebinding.api.exception.ValueCastException;
import com.sap.cloud.security.config.k8s.K8sConstants;
import org.slf4j.Logger;
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 +37,42 @@ 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, it also accepts single String values instead of String arrays.
* Single String values 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;
try {
try {
domains = credentials.getListView(DOMAINS).getItems(String.class);
finkmanAtSap marked this conversation as resolved.
Show resolved Hide resolved
} catch (ValueCastException e) {
domains = Collections.singletonList(credentials.getString(DOMAINS));
}
} catch (KeyNotFoundException e) {
try {
domains = Collections.singletonList(credentials.getString(DOMAIN));
} catch (KeyNotFoundException e2) {
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,82 @@
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, iasBindingDomainsMissing, iasBindingDomainsAsString, iasBindingDomainAsString;
private LogCaptor logCaptor;

@BeforeAll
static void setupClass() throws IOException {
xsuaaBinding = readServiceBindingFromJson(Service.XSUAA, "/vcapXsuaaServiceSingleBinding.json");
iasBinding = readServiceBindingFromJson(Service.IAS, "/vcapIasServiceSingleBinding.json");
iasBindingDomainsMissing = readServiceBindingFromJson(Service.IAS, "/vcapIasServiceDomainsMissing.json");
iasBindingDomainsAsString = readServiceBindingFromJson(Service.IAS, "/vcapIasServiceDomainsAsString.json");
iasBindingDomainAsString = readServiceBindingFromJson(Service.IAS, "/vcapIasServiceDomainAsString.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()).contains("myauth.com", "my.auth.com");
}

@Test
void getIasConfigurationWithDomainsAsString() {
OAuth2ServiceConfiguration config = ServiceBindingMapper.mapToOAuth2ServiceConfigurationBuilder(iasBindingDomainsAsString).build();
assertThat(config.getDomains()).contains("domain1");
}

@Test
void getIasConfigurationWithDomainAsString() {
OAuth2ServiceConfiguration config = ServiceBindingMapper.mapToOAuth2ServiceConfigurationBuilder(iasBindingDomainAsString).build();
assertThat(config.getDomains()).contains("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.");
}
}
21 changes: 21 additions & 0 deletions env/src/test/resources/vcapIasServiceDomainAsString.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": []
}
]
}
21 changes: 21 additions & 0 deletions env/src/test/resources/vcapIasServiceDomainsAsString.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",
"domains" : "domain1"
},
"instance_name": "myservice",
"label": "identity",
"name": "myservice",
"plan": "application",
"provider": null,
"syslog_drain_url": null,
"tags": [],
"volume_mounts": []
}
]
}
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": []
}
]
}
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