Skip to content

Commit

Permalink
enhance openid-connect-issuers to be able to contain a list of issuer…
Browse files Browse the repository at this point in the history
… urls in the config

Signed-off-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
  • Loading branch information
thjaeckle committed Aug 26, 2022
1 parent c80fd7c commit 297f931
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 65 deletions.
Expand Up @@ -183,7 +183,11 @@ private CompletableFuture<PublicKeyWithParser> loadPublicKeyWithParser(
return CompletableFuture.failedFuture(GatewayJwtIssuerNotSupportedException.newBuilder(issuer).build());
}

final String discoveryEndpoint = getDiscoveryEndpoint(subjectIssuerConfigOpt.get().getIssuer());
final String discoveryEndpoint = getDiscoveryEndpoint(subjectIssuerConfigOpt.get().getIssuers()
.stream()
.filter(configuredIssuer -> configuredIssuer.contains(issuer))
.findAny()
.orElse(issuer));
final CompletionStage<HttpResponse> responseFuture = getPublicKeysFromDiscoveryEndpoint(discoveryEndpoint);
final CompletionStage<JsonArray> publicKeysFuture = responseFuture.thenCompose(this::mapResponseToJsonArray);

Expand Down
Expand Up @@ -15,6 +15,7 @@
import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
Expand All @@ -31,7 +32,7 @@
public final class JwtSubjectIssuerConfig {

private final SubjectIssuer subjectIssuer;
private final String issuer;
private final List<String> issuers;
private final List<String> authSubjectTemplates;

private static final List<String> DEFAULT_AUTH_SUBJECT = Collections.singletonList("{{jwt:sub}}");
Expand All @@ -40,34 +41,36 @@ public final class JwtSubjectIssuerConfig {
* Constructs a new {@code JwtSubjectIssuerConfig}.
*
* @param subjectIssuer the subject issuer.
* @param issuer the issuer.
* @param issuers the issuer.
*
*/
public JwtSubjectIssuerConfig(final SubjectIssuer subjectIssuer, final String issuer) {
this(subjectIssuer, issuer, DEFAULT_AUTH_SUBJECT);
public JwtSubjectIssuerConfig(final SubjectIssuer subjectIssuer, final Collection<String> issuers) {
this(subjectIssuer, issuers, DEFAULT_AUTH_SUBJECT);
}

/**
* Constructs a new {@code JwtSubjectIssuerConfig}.
*
* @param subjectIssuer the subject issuer.
* @param issuer the issuer.
* @param issuers the list of issuers.
* @param authSubjectTemplates the authorization subject templates
*
*/
public JwtSubjectIssuerConfig(final SubjectIssuer subjectIssuer, final String issuer, final List<String> authSubjectTemplates) {
public JwtSubjectIssuerConfig(final SubjectIssuer subjectIssuer,
final Collection<String> issuers,
final Collection<String> authSubjectTemplates) {
this.subjectIssuer = requireNonNull(subjectIssuer);
this.issuer = requireNonNull(issuer);
this.issuers = Collections.unmodifiableList(new ArrayList<>(requireNonNull(issuers)));
this.authSubjectTemplates = Collections.unmodifiableList(new ArrayList<>(requireNonNull(authSubjectTemplates)));
}

/**
* Returns the issuer.
* Returns the issuers.
*
* @return the issuer.
* @return the issuers.
*/
public String getIssuer() {
return issuer;
public List<String> getIssuers() {
return issuers;
}

/**
Expand All @@ -93,21 +96,21 @@ public boolean equals(@Nullable final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final JwtSubjectIssuerConfig that = (JwtSubjectIssuerConfig) o;
return Objects.equals(issuer, that.issuer) &&
return Objects.equals(issuers, that.issuers) &&
Objects.equals(subjectIssuer, that.subjectIssuer) &&
Objects.equals(authSubjectTemplates, that.authSubjectTemplates);
}

@Override
public int hashCode() {
return Objects.hash(issuer, subjectIssuer, authSubjectTemplates);
return Objects.hash(issuers, subjectIssuer, authSubjectTemplates);
}

@Override
public String toString() {
return getClass().getSimpleName() + " [" +
"subjectIssuer=" + subjectIssuer +
", issuer=" + issuer +
", issuers=" + issuers +
", authSubjectTemplates=" + authSubjectTemplates +
"]";
}
Expand Down
Expand Up @@ -66,7 +66,7 @@ public static JwtSubjectIssuersConfig fromOAuthConfig(final OAuthConfig config)
// merge the default and extension config
Stream.concat(config.getOpenIdConnectIssuers().entrySet().stream(),
config.getOpenIdConnectIssuersExtension().entrySet().stream())
.map(entry -> new JwtSubjectIssuerConfig(entry.getKey(), entry.getValue().getIssuer(),
.map(entry -> new JwtSubjectIssuerConfig(entry.getKey(), entry.getValue().getIssuers(),
entry.getValue().getAuthorizationSubjectTemplates()))
.collect(Collectors.toSet());
return new JwtSubjectIssuersConfig(configItems, config.getProtocol());
Expand All @@ -75,8 +75,12 @@ public static JwtSubjectIssuersConfig fromOAuthConfig(final OAuthConfig config)
private static void addConfigToMap(final JwtSubjectIssuerConfig config,
final Map<String, JwtSubjectIssuerConfig> map,
final String protocolPrefix) {
map.put(config.getIssuer(), config);
map.put(protocolPrefix + config.getIssuer(), config);

config.getIssuers()
.forEach(issuer -> {
map.put(issuer, config);
map.put(protocolPrefix + issuer, config);
});
}

public String getProtocolPrefix() {
Expand All @@ -97,7 +101,9 @@ public Optional<JwtSubjectIssuerConfig> getConfigItem(final String issuer) {
private Optional<JwtSubjectIssuerConfig> getConfigItemByIssuer(final String issuer) {
return subjectIssuerConfigMap.values()
.stream()
.filter(jwtSubjectIssuerConfig -> jwtSubjectIssuerConfig.getIssuer().equals(issuer))
.filter(jwtSubjectIssuerConfig -> jwtSubjectIssuerConfig.getIssuers().stream()
.anyMatch(configuredIssuer -> configuredIssuer.equals(issuer))
)
.findFirst();
}

Expand All @@ -122,7 +128,7 @@ public Collection<JwtSubjectIssuerConfig> getConfigItems(final SubjectIssuer sub

return subjectIssuerConfigMap.values().stream()
.filter(jwtSubjectIssuerConfig -> jwtSubjectIssuerConfig.getSubjectIssuer().equals(subjectIssuer))
.collect(Collectors.toList());
.toList();
}

@Override
Expand Down
Expand Up @@ -154,7 +154,7 @@ public Supplier<Map<SubjectIssuer, SubjectIssuerConfig>> supplier() {
@Override
public BiConsumer<Map<SubjectIssuer, SubjectIssuerConfig>, Map.Entry<String, ConfigValue>> accumulator() {
return (map, entry) -> map.put(SubjectIssuer.newInstance(entry.getKey()),
DefaultSubjectIssuerConfig.of(ConfigFactory.empty().withFallback(entry.getValue())));
DefaultSubjectIssuerConfig.of(entry.getKey(), ConfigFactory.empty().withFallback(entry.getValue())));
}

@Override
Expand Down
Expand Up @@ -15,65 +15,86 @@
import static org.eclipse.ditto.base.model.common.ConditionChecker.argumentNotEmpty;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import com.typesafe.config.Config;

import org.eclipse.ditto.internal.utils.config.ConfigWithFallback;
import org.eclipse.ditto.internal.utils.config.DittoConfigError;

import com.typesafe.config.Config;

/**
* This class is the default implementation of the SubjectIssuer config.
* It is instantiated for each {@code openid-connect-issuers} entry containing issuers and auth-subject templates.
*/
public final class DefaultSubjectIssuerConfig implements SubjectIssuerConfig {

private final String issuer;
private final List<String> issuers;

private final List<String> authSubjectTemplates;

private DefaultSubjectIssuerConfig(final ConfigWithFallback configWithFallback) {
issuer = configWithFallback.getString(SubjectIssuerConfigValue.ISSUER.getConfigPath());
private DefaultSubjectIssuerConfig(final String issuerConfigKey, final ConfigWithFallback configWithFallback) {
final List<String> issuersList =
configWithFallback.getStringList(SubjectIssuerConfigValue.ISSUERS.getConfigPath());
if (!issuersList.isEmpty()) {
issuers = issuersList;
} else {
final String singleIssuer = configWithFallback.getString(SubjectIssuerConfigValue.ISSUER.getConfigPath());
if (!singleIssuer.isBlank()) {
issuers = List.of(singleIssuer);
} else {
throw new DittoConfigError("Neither '" + SubjectIssuerConfigValue.ISSUERS.getConfigPath() +
"' nor '" + SubjectIssuerConfigValue.ISSUER.getConfigPath() + "' were configured " +
"for openid-connect issuer: <" + issuerConfigKey + ">");
}
}
authSubjectTemplates = configWithFallback.getStringList(SubjectIssuerConfigValue.AUTH_SUBJECTS.getConfigPath());
}

private DefaultSubjectIssuerConfig(final String issuer, final List<String> authSubjectTemplates) {
this.issuer = issuer;
this.authSubjectTemplates = authSubjectTemplates;
private DefaultSubjectIssuerConfig(final Collection<String> issuers,
final Collection<String> authSubjectTemplates) {
this.issuers = Collections.unmodifiableList(new ArrayList<>(issuers));
this.authSubjectTemplates = Collections.unmodifiableList(new ArrayList<>(authSubjectTemplates));
}

/**
* Returns an instance of {@code DefaultSubjectIssuerConfig} based on the settings of the specified Config.
*
* @param key the key of the open-id-issuer config passed in the {@code config}.
* @param config is supposed to provide the config for the issuer at its current level.
* @return the instance.
* @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid.
*/
public static DefaultSubjectIssuerConfig of(final Config config) {
return new DefaultSubjectIssuerConfig(
public static DefaultSubjectIssuerConfig of(final String key, final Config config) {
return new DefaultSubjectIssuerConfig(key,
ConfigWithFallback.newInstance(config, SubjectIssuerConfigValue.values()));
}

/**
* Returns a new SubjectIssuerConfig based on the provided strings.
*
* @param issuer the issuer's endpoint {@code issuer}.
* @param authSubjectTemplates list of authorizationsubject placeholder strings
* {@code authSubjectTemplates}.
* @param issuers the list of issuers' endpoint {@code issuers}.
* @param authSubjectTemplates list of authorizationsubject placeholder strings.
* @return a new SubjectIssuerConfig.
* @throws NullPointerException if {@code issuer} or {@code authSubjectTemplates} is
* {@code null}.
* @throws IllegalArgumentException if {@code issuer} or {@code authSubjectTemplates} is
* empty.
* @throws NullPointerException if {@code issuers} or {@code authSubjectTemplates} is {@code null}.
* @throws IllegalArgumentException if {@code issuers} or {@code authSubjectTemplates} is empty.
*/
public static DefaultSubjectIssuerConfig of(final String issuer, final List<String> authSubjectTemplates) {
checkNotNull(issuer, "issuer");
public static DefaultSubjectIssuerConfig of(final Collection<String> issuers,
final Collection<String> authSubjectTemplates) {
checkNotNull(issuers, "issuers");
argumentNotEmpty(authSubjectTemplates, "authSubjectTemplates");

return new DefaultSubjectIssuerConfig(issuer, authSubjectTemplates);
return new DefaultSubjectIssuerConfig(issuers, authSubjectTemplates);
}

public final String getIssuer() {
return issuer;
public List<String> getIssuers() {
return issuers;
}

public final List<String> getAuthorizationSubjectTemplates() {
public List<String> getAuthorizationSubjectTemplates() {
return authSubjectTemplates;
}

Expand All @@ -86,18 +107,18 @@ public boolean equals(final Object o) {
return false;
}
final DefaultSubjectIssuerConfig that = (DefaultSubjectIssuerConfig) o;
return Objects.equals(issuer, that.issuer) && authSubjectTemplates.equals(that.authSubjectTemplates);
return Objects.equals(issuers, that.issuers) && authSubjectTemplates.equals(that.authSubjectTemplates);
}

@Override
public int hashCode() {
return Objects.hash(issuer, authSubjectTemplates);
return Objects.hash(issuers, authSubjectTemplates);
}

@Override
public String toString() {
return getClass().getSimpleName() + " [" +
"issuer=" + issuer +
"issuers=" + issuers +
", authSubjectTemplates=" + authSubjectTemplates +
"]";
}
Expand Down
Expand Up @@ -26,11 +26,11 @@
public interface SubjectIssuerConfig {

/**
* Returns the issuer endpoint.
* Returns the issuer endpoints.
*
* @return the token issuer endpoint.
* @return the token issuer endpoints.
*/
String getIssuer();
List<String> getIssuers();

/**
* Returns the authorization subject templates.
Expand All @@ -42,6 +42,7 @@ public interface SubjectIssuerConfig {

enum SubjectIssuerConfigValue implements KnownConfigValue {
ISSUER("issuer", ""),
ISSUERS("issuers", List.of()),
AUTH_SUBJECTS("auth-subjects", List.of("{{jwt:sub}}"));

private final String path;
Expand Down
4 changes: 4 additions & 0 deletions gateway/service/src/main/resources/gateway.conf
Expand Up @@ -243,6 +243,10 @@ ditto {
openid-connect-issuers = {
# auth0 = {
# issuer = "<your-domain>.<region>.auth0.com/"
# issuers = [
# "<your-domain>.<region>.auth0.com/"
# "<your-domain-2>.<region>.auth0.com/"
# ]
#}
google = {
issuer = "accounts.google.com"
Expand Down
Expand Up @@ -300,15 +300,15 @@ private static JwtSubjectIssuersConfig createSubjectIssuersConfig(final String s
final List<String> subjectTemplates) {
final JwtSubjectIssuerConfig subjectIssuerConfig = new JwtSubjectIssuerConfig(
SubjectIssuer.newInstance(subjectIssuer),
JwtTestConstants.ISSUER,
List.of(JwtTestConstants.ISSUER),
subjectTemplates);
return JwtSubjectIssuersConfig.fromJwtSubjectIssuerConfigs(List.of(subjectIssuerConfig));
}

private static JwtSubjectIssuersConfig createSubjectIssuersConfig(final String subjectIssuer) {
final JwtSubjectIssuerConfig subjectIssuerConfig = new JwtSubjectIssuerConfig(
SubjectIssuer.newInstance(subjectIssuer),
JwtTestConstants.ISSUER);
List.of(JwtTestConstants.ISSUER));
return JwtSubjectIssuersConfig.fromJwtSubjectIssuerConfigs(List.of(subjectIssuerConfig));
}

Expand Down
Expand Up @@ -21,6 +21,7 @@

import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -77,7 +78,7 @@ public void setup() {
actorSystem = ActorSystem.create(getClass().getSimpleName());
when(httpClientMock.getActorSystem()).thenReturn(actorSystem);
final JwtSubjectIssuersConfig subjectIssuersConfig = JwtSubjectIssuersConfig.fromJwtSubjectIssuerConfigs(
Collections.singleton(new JwtSubjectIssuerConfig(SubjectIssuer.GOOGLE, "google.com")));
Collections.singleton(new JwtSubjectIssuerConfig(SubjectIssuer.GOOGLE, List.of("google.com"))));
when(oauthConfigMock.getAllowedClockSkew()).thenReturn(Duration.ofSeconds(1));
underTest = new DittoPublicKeyProvider(subjectIssuersConfig, httpClientMock,
oauthConfigMock, DittoPublicKeyProviderTest::thisThreadCache);
Expand Down

0 comments on commit 297f931

Please sign in to comment.