Preserve Null Claim Values

Prior to this commit ClaimTypeConverter returned the claims with the
original value for all the claims with a null converted value.
The changes allows ClaimTypeConverter to overwrite and return claims
with converted value of null.

Closes gh-10135
This commit is contained in:
Fabio Guenci 2021-07-27 18:24:11 +02:00 committed by Josh Cummings
parent fefe985242
commit 8c1201ae49
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
3 changed files with 31 additions and 29 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -52,18 +52,19 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
private final Map<String, Converter<Object, ?>> claimTypeConverters; private final Map<String, Converter<Object, ?>> claimTypeConverters;
private final Converter<Map<String, Object>, Map<String, Object>> delegate;
/** /**
* Constructs a {@link MappedJwtClaimSetConverter} with the provided arguments * Constructs a {@link MappedJwtClaimSetConverter} with the provided arguments
* *
* This will completely replace any set of default converters. * This will completely replace any set of default converters.
*
* A converter that returns {@code null} removes the claim from the claim set. A
* converter that returns a non-{@code null} value adds or replaces that claim in the
* claim set.
* @param claimTypeConverters The {@link Map} of converters to use * @param claimTypeConverters The {@link Map} of converters to use
*/ */
public MappedJwtClaimSetConverter(Map<String, Converter<Object, ?>> claimTypeConverters) { public MappedJwtClaimSetConverter(Map<String, Converter<Object, ?>> claimTypeConverters) {
Assert.notNull(claimTypeConverters, "claimTypeConverters cannot be null"); Assert.notNull(claimTypeConverters, "claimTypeConverters cannot be null");
this.claimTypeConverters = claimTypeConverters; this.claimTypeConverters = claimTypeConverters;
this.delegate = new ClaimTypeConverter(claimTypeConverters);
} }
/** /**
@ -87,6 +88,10 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
* *
* To completely replace the underlying {@link Map} of converters, see * To completely replace the underlying {@link Map} of converters, see
* {@link MappedJwtClaimSetConverter#MappedJwtClaimSetConverter(Map)}. * {@link MappedJwtClaimSetConverter#MappedJwtClaimSetConverter(Map)}.
*
* A converter that returns {@code null} removes the claim from the claim set. A
* converter that returns a non-{@code null} value adds or replaces that claim in the
* claim set.
* @param claimTypeConverters * @param claimTypeConverters
* @return An instance of {@link MappedJwtClaimSetConverter} that contains the * @return An instance of {@link MappedJwtClaimSetConverter} that contains the
* converters provided, plus any defaults that were not overridden. * converters provided, plus any defaults that were not overridden.
@ -143,9 +148,16 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
@Override @Override
public Map<String, Object> convert(Map<String, Object> claims) { public Map<String, Object> convert(Map<String, Object> claims) {
Assert.notNull(claims, "claims cannot be null"); Assert.notNull(claims, "claims cannot be null");
Map<String, Object> mappedClaims = this.delegate.convert(claims); Map<String, Object> mappedClaims = new HashMap<>(claims);
mappedClaims = removeClaims(mappedClaims); for (Map.Entry<String, Converter<Object, ?>> entry : this.claimTypeConverters.entrySet()) {
mappedClaims = addClaims(mappedClaims); String claimName = entry.getKey();
Converter<Object, ?> converter = entry.getValue();
if (converter != null) {
Object claim = claims.get(claimName);
Object mappedClaim = converter.convert(claim);
mappedClaims.compute(claimName, (key, value) -> mappedClaim);
}
}
Instant issuedAt = (Instant) mappedClaims.get(JwtClaimNames.IAT); Instant issuedAt = (Instant) mappedClaims.get(JwtClaimNames.IAT);
Instant expiresAt = (Instant) mappedClaims.get(JwtClaimNames.EXP); Instant expiresAt = (Instant) mappedClaims.get(JwtClaimNames.EXP);
if (issuedAt == null && expiresAt != null) { if (issuedAt == null && expiresAt != null) {
@ -154,24 +166,4 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
return mappedClaims; return mappedClaims;
} }
private Map<String, Object> removeClaims(Map<String, Object> claims) {
Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, Object> entry : claims.entrySet()) {
if (entry.getValue() != null) {
result.put(entry.getKey(), entry.getValue());
}
}
return result;
}
private Map<String, Object> addClaims(Map<String, Object> claims) {
Map<String, Object> result = new HashMap<>(claims);
for (Map.Entry<String, Converter<Object, ?>> entry : this.claimTypeConverters.entrySet()) {
if (!claims.containsKey(entry.getKey()) && entry.getValue().convert(null) != null) {
result.put(entry.getKey(), entry.getValue().convert(null));
}
}
return result;
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -123,8 +123,18 @@ public class MappedJwtClaimSetConverterTests {
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234"); assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
} }
// gh-10135
@Test @Test
public void convertWhenConverterReturnsNullThenClaimIsRemoved() { public void convertWhenConverterReturnsNullThenClaimIsRemoved() {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.singletonMap(JwtClaimNames.NBF, (nbfClaimValue) -> null));
Map<String, Object> source = Collections.singletonMap(JwtClaimNames.NBF, Instant.now());
Map<String, Object> target = converter.convert(source);
assertThat(target).doesNotContainKey(JwtClaimNames.NBF);
}
@Test
public void convertWhenClaimValueIsNullThenClaimIsRemoved() {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap()); MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
Map<String, Object> source = Collections.singletonMap(JwtClaimNames.ISS, null); Map<String, Object> source = Collections.singletonMap(JwtClaimNames.ISS, null);
Map<String, Object> target = converter.convert(source); Map<String, Object> target = converter.convert(source);