Skip to content

Commit

Permalink
Merge pull request #1996 from dimabarbul/jwt-placeholder-handle-nesting
Browse files Browse the repository at this point in the history
Add handling nested properties during resolving jwt placeholders
  • Loading branch information
thjaeckle authored Sep 2, 2024
2 parents 5e784c9 + de633fc commit bfafdfe
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
import static org.eclipse.ditto.base.model.common.ConditionChecker.argumentNotEmpty;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.jwt.model.JsonWebToken;
import org.eclipse.ditto.placeholders.Placeholder;
Expand Down Expand Up @@ -63,15 +63,23 @@ public boolean supports(final String name) {
public List<String> resolveValues(final JsonWebToken jwt, final String placeholder) {
argumentNotEmpty(placeholder, "placeholder");
checkNotNull(jwt, "jwt");
final Optional<JsonValue> value = jwt.getBody().getValue(placeholder);
return value.filter(JsonValue::isArray)
.map(JsonValue::asArray)
.map(array -> array.stream()
.map(JsonValue::formatAsString)
.toList())
.or(() -> value.map(JsonValue::formatAsString)
.map(Collections::singletonList))
.orElseGet(Collections::emptyList);
return resolveValues(jwt.getBody(), JsonPointer.of(placeholder))
.distinct()
.toList();
}

private static Stream<String> resolveValues(final JsonValue jsonValue, final JsonPointer jsonPointer) {
return jsonPointer.getRoot()
.<Stream<String>>map(root -> jsonValue.isArray()
? jsonValue.asArray().stream().flatMap(item -> resolveValues(item, jsonPointer))
: (jsonValue.isObject()
? jsonValue.asObject().getValue(root)
.map(v -> resolveValues(v, jsonPointer.nextLevel()))
.orElseGet(Stream::empty)
: Stream.empty()))
.orElseGet(() -> jsonValue.isArray()
? jsonValue.asArray().stream().map(JsonValue::formatAsString)
: Stream.of(jsonValue.formatAsString()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.gateway.service.security.authentication.jwt;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.jwt.model.JsonWebToken;
import org.junit.Test;

import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;

/**
* Tests {@link JwtPlaceholder}.
*/
public final class JwtPlaceholderTest {

private static final JwtPlaceholder UNDER_TEST = new JwtPlaceholder();

@Test
public void testHashCodeAndEquals() {
EqualsVerifier.forClass(JwtPlaceholder.class)
.suppress(Warning.INHERITED_DIRECTLY_FROM_OBJECT)
.usingGetClass()
.verify();
}

@Test
public void testResolveRootProperty() {
final JsonWebToken jwt = createToken("""
{
"sub": "jwt-subject"
}
""");
assertThat(UNDER_TEST.resolveValues(jwt, "sub"))
.containsExactlyInAnyOrder("jwt-subject");
}

@Test
public void testResolveObjectProperty() {
final JsonWebToken jwt = createToken("""
{
"authorization": {
"isFull": true
}
}
""");
assertThat(UNDER_TEST.resolveValues(jwt, "authorization"))
.containsExactlyInAnyOrder("{\"isFull\":true}");
}

@Test
public void testResolveNestedSimpleProperty() {
final JsonWebToken jwt = createToken("""
{
"authorization": {
"isFull": true
}
}
""");
assertThat(UNDER_TEST.resolveValues(jwt, "authorization/isFull"))
.containsExactlyInAnyOrder("true");
}

@Test
public void testResolveArrayProperty() {
final JsonWebToken jwt = createToken("""
{
"authorization": {
"roles": ["user", "admin"]
}
}
""");
assertThat(UNDER_TEST.resolveValues(jwt, "authorization/roles"))
.containsExactlyInAnyOrder("user", "admin");
}

@Test
public void testResolveSimplePropertyInArray() {
final JsonWebToken jwt = createToken("""
{
"authorization": {
"roles": [
{ "name": "user" },
{ "name": "admin" }
]
}
}
""");
assertThat(UNDER_TEST.resolveValues(jwt, "authorization/roles/name"))
.containsExactlyInAnyOrder("user", "admin");
}

@Test
public void testResolveObjectPropertyInArray() {
final JsonWebToken jwt = createToken("""
{
"authorization": {
"roles": [
{
"info": { "name": "user" }
},
{
"info": { "name": "admin" }
}
]
}
}
""");
assertThat(UNDER_TEST.resolveValues(jwt, "authorization/roles/info"))
.containsExactlyInAnyOrder("{\"name\":\"user\"}", "{\"name\":\"admin\"}");
}

@Test
public void testResolveArrayPropertyInArray() {
final JsonWebToken jwt = createToken("""
{
"authorization": {
"roles": [
{
"name": "user",
"claims": [ "read" ]
},
{
"name": "admin",
"claims": [ "read", "write" ]
}
]
}
}
""");
assertThat(UNDER_TEST.resolveValues(jwt, "authorization/roles/claims"))
.containsExactlyInAnyOrder("read", "write");
}

@Test
public void testResolveNestedPropertyOnSimpleValue() {
final JsonWebToken jwt = createToken("""
{
"authorization": {
"roles": [ "user", "admin" ]
}
}
""");
assertThat(UNDER_TEST.resolveValues(jwt, "authorization/roles/name"))
.isEmpty();
}

@Test
public void testResolveNestedPropertyOnMissingValue() {
final JsonWebToken jwt = createToken("""
{
"authorization": {}
}
""");
assertThat(UNDER_TEST.resolveValues(jwt, "authorization/roles"))
.isEmpty();
}

private static JsonWebToken createToken(final String body) {
final JsonWebToken jsonWebToken = mock(JsonWebToken.class);
when(jsonWebToken.getBody()).thenReturn(JsonObject.of(body));
return jsonWebToken;
}

}

0 comments on commit bfafdfe

Please sign in to comment.