Skip to content

Commit

Permalink
Add support for arbitrary claims for authorization subjects
Browse files Browse the repository at this point in the history
Fixes #512

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

read config from file

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

fix test

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

add getter function

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

rename

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

wire up templates

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

more test

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

fix license header

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

add unit test for subjectsprovider

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

Update services/gateway/util/src/main/java/org/eclipse/ditto/services/gateway/util/config/security/DefaultSubjectIssuerConfig.java

Co-authored-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

Update services/gateway/util/src/test/resources/oauth-test.conf

Co-authored-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

Update services/gateway/util/src/main/java/org/eclipse/ditto/services/gateway/util/config/security/DefaultSubjectIssuerConfig.java

Co-authored-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

Update services/gateway/util/src/main/java/org/eclipse/ditto/services/gateway/util/config/security/DefaultOAuthConfig.java

Co-authored-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

Update services/gateway/security/src/main/java/org/eclipse/ditto/services/gateway/security/authentication/jwt/JwtSubjectIssuerConfig.java

Co-authored-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

Update services/gateway/security/src/main/java/org/eclipse/ditto/services/gateway/security/authentication/jwt/DittoJwtAuthorizationSubjectsProvider.java

Co-authored-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

move SubjectIssuerConfig

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

fix devops-test config resource

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

fix Config reading

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

allow json structures in jwt

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

Update services/gateway/util/src/main/java/org/eclipse/ditto/services/gateway/util/config/security/SubjectIssuerConfig.java

Co-authored-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

add test for json in jwt

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

move config parsing into SubjectIssuerConfig interface

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

correctly use config fallback values

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>

fix config syntax

Signed-off-by: Dirk Van Haerenborgh <dirk.vanhaerenborgh@aloxy.io>
  • Loading branch information
vhdirk committed Feb 19, 2021
1 parent dec4900 commit 267ff63
Show file tree
Hide file tree
Showing 17 changed files with 481 additions and 87 deletions.
Expand Up @@ -52,7 +52,7 @@ public Set<SubjectId> getSubjectIds(final DittoHeaders dittoHeaders, final JsonW
PlaceholderFactory.newPlaceholderResolver(JwtPlaceholder.getInstance(), jwt)
);
final String issuerWithSubject = expressionResolver.resolvePartially(subjectTemplate);
return TokenIntegrationSubjectIdFactory.expandJsonArraysInResolvedSubject(issuerWithSubject)
return JwtPlaceholder.expandJsonArraysInResolvedSubject(issuerWithSubject)
.map(SubjectId::newInstance)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
Expand Down
Expand Up @@ -13,12 +13,7 @@
package org.eclipse.ditto.services.gateway.endpoints.routes.policies;

import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.model.base.headers.DittoHeaders;
import org.eclipse.ditto.model.jwt.JsonWebToken;
import org.eclipse.ditto.model.policies.SubjectId;
Expand All @@ -28,12 +23,6 @@
*/
public interface TokenIntegrationSubjectIdFactory {

/**
* Compiled Pattern of a string containing any unresolved non-empty JsonArray-String notations inside.
* All strings matching this pattern are valid JSON arrays. Not all JSON arrays match this pattern.
*/
Pattern JSON_ARRAY_PATTERN = Pattern.compile("(\\[\"(?:\\\\\"|[^\"])*+\"(?:,\"(?:\\\\\"|[^\"])*+\")*+])");

/**
* Compute the token integration subject IDs from headers and JWT.
*
Expand All @@ -42,36 +31,4 @@ public interface TokenIntegrationSubjectIdFactory {
* @return the computed subject IDs.
*/
Set<SubjectId> getSubjectIds(DittoHeaders dittoHeaders, JsonWebToken jwt);

/**
* Checks whether the passed {@code resolvedSubject} (resolved via JWT and header placeholder mechanism) contains
* JsonArrays ({@code ["..."]} and expands those JsonArrays to multiple resolved subjects returned as resulting
* stream of this operation.
* <p>
* Is able to handle an arbitrary amount of JsonArrays in the passed resolvedSubjects.
*
* @param resolvedSubject the resolved subjects potentially containing JsonArrays as JsonArray-String values.
* @return a stream of a single subject when the passed in {@code resolvedSubject} did not contain any
* JsonArray-String notation or else a stream of multiple subjects with the JsonArrays being resolved to multiple
* results of the stream.
*/
static Stream<String> expandJsonArraysInResolvedSubject(final String resolvedSubject) {
final Matcher jsonArrayMatcher = JSON_ARRAY_PATTERN.matcher(resolvedSubject);
final int group = 1;
if (jsonArrayMatcher.find()) {
final String beforeMatched = resolvedSubject.substring(0, jsonArrayMatcher.start(group));
final String matchedStr =
resolvedSubject.substring(jsonArrayMatcher.start(group), jsonArrayMatcher.end(group));
final String afterMatched = resolvedSubject.substring(jsonArrayMatcher.end(group));
return JsonArray.of(matchedStr).stream()
.filter(JsonValue::isString)
.map(JsonValue::asString)
.flatMap(arrayStringElem -> expandJsonArraysInResolvedSubject(beforeMatched) // recurse!
.flatMap(before -> expandJsonArraysInResolvedSubject(afterMatched) // recurse!
.map(after -> before.concat(arrayStringElem).concat(after))
)
);
}
return Stream.of(resolvedSubject);
}
}
Expand Up @@ -23,6 +23,8 @@

import org.eclipse.ditto.model.base.auth.AuthorizationSubject;
import org.eclipse.ditto.model.jwt.JsonWebToken;
import org.eclipse.ditto.model.placeholders.ExpressionResolver;
import org.eclipse.ditto.model.placeholders.PlaceholderFactory;
import org.eclipse.ditto.model.policies.SubjectId;
import org.eclipse.ditto.signals.commands.base.exceptions.GatewayJwtIssuerNotSupportedException;

Expand Down Expand Up @@ -58,11 +60,15 @@ public List<AuthorizationSubject> getAuthorizationSubjects(final JsonWebToken js
final JwtSubjectIssuerConfig jwtSubjectIssuerConfig = jwtSubjectIssuersConfig.getConfigItem(issuer)
.orElseThrow(() -> GatewayJwtIssuerNotSupportedException.newBuilder(issuer).build());

return jsonWebToken.getSubjects()
.stream()
.map(subject -> SubjectId.newInstance(jwtSubjectIssuerConfig.getSubjectIssuer(), subject))
.map(AuthorizationSubject::newInstance)
.collect(Collectors.toList());
final ExpressionResolver expressionResolver = PlaceholderFactory.newExpressionResolver(
PlaceholderFactory.newPlaceholderResolver(JwtPlaceholder.getInstance(), jsonWebToken));

return jwtSubjectIssuerConfig.getAuthorizationSubjectTemplates().stream()
.map(expressionResolver::resolvePartially)
.flatMap((issuerWithSubject) -> JwtPlaceholder.expandJsonArraysInResolvedSubject(issuerWithSubject))
.map(subject -> SubjectId.newInstance(jwtSubjectIssuerConfig.getSubjectIssuer(), subject))
.map(AuthorizationSubject::newInstance)
.collect(Collectors.toList());
}

@Override
Expand Down
Expand Up @@ -17,7 +17,11 @@

import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.model.jwt.JsonWebToken;
import org.eclipse.ditto.model.placeholders.Placeholder;
Expand All @@ -27,6 +31,12 @@
*/
public final class JwtPlaceholder implements Placeholder<JsonWebToken> {

/**
* Compiled Pattern of a string containing any unresolved non-empty JsonArray-String notations inside.
* All strings matching this pattern are valid JSON arrays. Not all JSON arrays match this pattern.
*/
private static final Pattern JSON_ARRAY_PATTERN = Pattern.compile("(\\[\"(?:\\\\\"|[^\"])*+\"(?:,\"(?:\\\\\"|[^\"])*+\")*+])");

private static final JwtPlaceholder INSTANCE = new JwtPlaceholder();

private static final String PREFIX = "jwt";
Expand Down Expand Up @@ -62,4 +72,35 @@ public Optional<String> resolve(final JsonWebToken jwt, final String placeholder
return jwt.getBody().getValue(placeholder).map(JsonValue::formatAsString);
}

/**
* Checks whether the passed {@code resolvedSubject} (resolved via JWT and header placeholder mechanism) contains
* JsonArrays ({@code ["..."]} and expands those JsonArrays to multiple resolved subjects returned as resulting
* stream of this operation.
* <p>
* Is able to handle an arbitrary amount of JsonArrays in the passed resolvedSubjects.
*
* @param resolvedSubject the resolved subjects potentially containing JsonArrays as JsonArray-String values.
* @return a stream of a single subject when the passed in {@code resolvedSubject} did not contain any
* JsonArray-String notation or else a stream of multiple subjects with the JsonArrays being resolved to multiple
* results of the stream.
*/
public static Stream<String> expandJsonArraysInResolvedSubject(final String resolvedSubject) {
final Matcher jsonArrayMatcher = JSON_ARRAY_PATTERN.matcher(resolvedSubject);
final int group = 1;
if (jsonArrayMatcher.find()) {
final String beforeMatched = resolvedSubject.substring(0, jsonArrayMatcher.start(group));
final String matchedStr =
resolvedSubject.substring(jsonArrayMatcher.start(group), jsonArrayMatcher.end(group));
final String afterMatched = resolvedSubject.substring(jsonArrayMatcher.end(group));
return JsonArray.of(matchedStr).stream()
.filter(JsonValue::isString)
.map(JsonValue::asString)
.flatMap(arrayStringElem -> expandJsonArraysInResolvedSubject(beforeMatched) // recurse!
.flatMap(before -> expandJsonArraysInResolvedSubject(afterMatched) // recurse!
.map(after -> before.concat(arrayStringElem).concat(after))
)
);
}
return Stream.of(resolvedSubject);
}
}
Expand Up @@ -14,6 +14,10 @@

import static java.util.Objects.requireNonNull;

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

import javax.annotation.Nullable;
Expand All @@ -27,18 +31,35 @@
@Immutable
public final class JwtSubjectIssuerConfig {

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

private static final List<String> DEFAULT_AUTH_SUBJECT = Collections.singletonList("{{jwt:sub}}");

/**
* Constructs a new {@code JwtSubjectIssuerConfig}.
*
* @param subjectIssuer the subject issuer.
* @param issuer the issuer.
*
*/
public JwtSubjectIssuerConfig(final SubjectIssuer subjectIssuer, final String issuer) {
this(subjectIssuer, issuer, DEFAULT_AUTH_SUBJECT);
}

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

/**
Expand All @@ -59,25 +80,36 @@ public SubjectIssuer getSubjectIssuer() {
return subjectIssuer;
}

/**
* Returns the authorization subject templates
*
* @return the authorization subject templates
*/
public List<String> getAuthorizationSubjectTemplates() {
return authSubjectTemplates;
}

@Override
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) &&
Objects.equals(subjectIssuer, that.subjectIssuer);
Objects.equals(subjectIssuer, that.subjectIssuer) &&
Objects.equals(authSubjectTemplates, that.authSubjectTemplates);
}

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

@Override
public String toString() {
return getClass().getSimpleName() + " [" +
"issuer=" + issuer +
", subjectIssuer=" + subjectIssuer +
"subjectIssuer=" + subjectIssuer +
", issuer=" + issuer +
", 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.getValue(), entry.getKey()))
.map(entry -> new JwtSubjectIssuerConfig(entry.getKey(), entry.getValue().getIssuer(), entry.getValue().getAuthorizationSubjectTemplates()))
.collect(Collectors.toSet());
return new JwtSubjectIssuersConfig(configItems, config.getProtocol());
}
Expand Down

0 comments on commit 267ff63

Please sign in to comment.