Externalize coercion in ClaimAccessor

Fixes gh-6245
This commit is contained in:
Joe Grandja
2019-05-10 15:07:53 -04:00
parent 3c7aa4243f
commit aa767ec8bf
20 changed files with 1430 additions and 200 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,14 +15,12 @@
*/
package org.springframework.security.oauth2.core;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.util.Assert;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -49,7 +47,7 @@ public interface ClaimAccessor {
*/
default Boolean containsClaim(String claim) {
Assert.notNull(claim, "claim cannot be null");
return this.getClaims().containsKey(claim);
return getClaims().containsKey(claim);
}
/**
@@ -59,11 +57,8 @@ public interface ClaimAccessor {
* @return the claim value or {@code null} if it does not exist or is equal to {@code null}
*/
default String getClaimAsString(String claim) {
if (!this.containsClaim(claim)) {
return null;
}
Object claimValue = this.getClaims().get(claim);
return (claimValue != null ? claimValue.toString() : null);
return !containsClaim(claim) ? null :
ClaimConversionService.getSharedInstance().convert(getClaims().get(claim), String.class);
}
/**
@@ -73,7 +68,8 @@ public interface ClaimAccessor {
* @return the claim value or {@code null} if it does not exist
*/
default Boolean getClaimAsBoolean(String claim) {
return (this.containsClaim(claim) ? Boolean.valueOf(this.getClaimAsString(claim)) : null);
return !containsClaim(claim) ? null :
ClaimConversionService.getSharedInstance().convert(getClaims().get(claim), Boolean.class);
}
/**
@@ -83,23 +79,16 @@ public interface ClaimAccessor {
* @return the claim value or {@code null} if it does not exist
*/
default Instant getClaimAsInstant(String claim) {
if (!this.containsClaim(claim)) {
if (!containsClaim(claim)) {
return null;
}
Object claimValue = this.getClaims().get(claim);
if (Long.class.isAssignableFrom(claimValue.getClass()) ||
Integer.class.isAssignableFrom(claimValue.getClass()) ||
Double.class.isAssignableFrom(claimValue.getClass())) {
return Instant.ofEpochSecond(((Number) claimValue).longValue());
Object claimValue = getClaims().get(claim);
Instant convertedValue = ClaimConversionService.getSharedInstance().convert(claimValue, Instant.class);
if (convertedValue == null) {
throw new IllegalArgumentException("Unable to convert claim '" + claim +
"' of type '" + claimValue.getClass() + "' to Instant.");
}
if (Date.class.isAssignableFrom(claimValue.getClass())) {
return ((Date) claimValue).toInstant();
}
if (Instant.class.isAssignableFrom(claimValue.getClass())) {
return (Instant) claimValue;
}
throw new IllegalArgumentException("Unable to convert claim '" + claim +
"' of type '" + claimValue.getClass() + "' to Instant.");
return convertedValue;
}
/**
@@ -109,14 +98,16 @@ public interface ClaimAccessor {
* @return the claim value or {@code null} if it does not exist
*/
default URL getClaimAsURL(String claim) {
if (!this.containsClaim(claim)) {
if (!containsClaim(claim)) {
return null;
}
try {
return new URL(this.getClaimAsString(claim));
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Unable to convert claim '" + claim + "' to URL: " + ex.getMessage(), ex);
Object claimValue = getClaims().get(claim);
URL convertedValue = ClaimConversionService.getSharedInstance().convert(claimValue, URL.class);
if (convertedValue == null) {
throw new IllegalArgumentException("Unable to convert claim '" + claim +
"' of type '" + claimValue.getClass() + "' to URL.");
}
return convertedValue;
}
/**
@@ -126,13 +117,22 @@ public interface ClaimAccessor {
* @param claim the name of the claim
* @return the claim value or {@code null} if it does not exist or cannot be assigned to a {@code Map}
*/
@SuppressWarnings("unchecked")
default Map<String, Object> getClaimAsMap(String claim) {
if (!this.containsClaim(claim) || !Map.class.isAssignableFrom(this.getClaims().get(claim).getClass())) {
if (!containsClaim(claim)) {
return null;
}
Map<String, Object> claimValues = new HashMap<>();
((Map<?, ?>) this.getClaims().get(claim)).forEach((k, v) -> claimValues.put(k.toString(), v));
return claimValues;
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
final TypeDescriptor targetDescriptor = TypeDescriptor.map(
Map.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Object.class));
Object claimValue = getClaims().get(claim);
Map<String, Object> convertedValue = (Map<String, Object>) ClaimConversionService.getSharedInstance().convert(
claimValue, sourceDescriptor, targetDescriptor);
if (convertedValue == null) {
throw new IllegalArgumentException("Unable to convert claim '" + claim +
"' of type '" + claimValue.getClass() + "' to Map.");
}
return convertedValue;
}
/**
@@ -142,12 +142,21 @@ public interface ClaimAccessor {
* @param claim the name of the claim
* @return the claim value or {@code null} if it does not exist or cannot be assigned to a {@code List}
*/
@SuppressWarnings("unchecked")
default List<String> getClaimAsStringList(String claim) {
if (!this.containsClaim(claim) || !List.class.isAssignableFrom(this.getClaims().get(claim).getClass())) {
if (!containsClaim(claim)) {
return null;
}
List<String> claimValues = new ArrayList<>();
((List<?>) this.getClaims().get(claim)).forEach(e -> claimValues.add(e.toString()));
return claimValues;
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
final TypeDescriptor targetDescriptor = TypeDescriptor.collection(
List.class, TypeDescriptor.valueOf(String.class));
Object claimValue = getClaims().get(claim);
List<String> convertedValue = (List<String>) ClaimConversionService.getSharedInstance().convert(
claimValue, sourceDescriptor, targetDescriptor);
if (convertedValue == null) {
throw new IllegalArgumentException("Unable to convert claim '" + claim +
"' of type '" + claimValue.getClass() + "' to List.");
}
return convertedValue;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.security.oauth2.core.ClaimAccessor;
/**
* A {@link ConversionService} configured with converters
* that provide type conversion for claim values.
*
* @author Joe Grandja
* @since 5.2
* @see GenericConversionService
* @see ClaimAccessor
*/
public final class ClaimConversionService extends GenericConversionService {
private static volatile ClaimConversionService sharedInstance;
private ClaimConversionService() {
addConverters(this);
}
/**
* Returns a shared instance of {@code ClaimConversionService}.
*
* @return a shared instance of {@code ClaimConversionService}
*/
public static ClaimConversionService getSharedInstance() {
ClaimConversionService sharedInstance = ClaimConversionService.sharedInstance;
if (sharedInstance == null) {
synchronized (ClaimConversionService.class) {
sharedInstance = ClaimConversionService.sharedInstance;
if (sharedInstance == null) {
sharedInstance = new ClaimConversionService();
ClaimConversionService.sharedInstance = sharedInstance;
}
}
}
return sharedInstance;
}
/**
* Adds the converters that provide type conversion for claim values
* to the provided {@link ConverterRegistry}.
*
* @param converterRegistry the registry of converters to add to
*/
public static void addConverters(ConverterRegistry converterRegistry) {
converterRegistry.addConverter(new ObjectToStringConverter());
converterRegistry.addConverter(new ObjectToBooleanConverter());
converterRegistry.addConverter(new ObjectToInstantConverter());
converterRegistry.addConverter(new ObjectToURLConverter());
converterRegistry.addConverter(new ObjectToListStringConverter());
converterRegistry.addConverter(new ObjectToMapStringObjectConverter());
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A {@link Converter} that provides type conversion for claim values.
*
* @author Joe Grandja
* @since 5.2
* @see Converter
*/
public final class ClaimTypeConverter implements Converter<Map<String, Object>, Map<String, Object>> {
private final Map<String, Converter<Object, ?>> claimTypeConverters;
/**
* Constructs a {@code ClaimTypeConverter} using the provided parameters.
*
* @param claimTypeConverters a {@link Map} of {@link Converter}(s) keyed by claim name
*/
public ClaimTypeConverter(Map<String, Converter<Object, ?>> claimTypeConverters) {
Assert.notEmpty(claimTypeConverters, "claimTypeConverters cannot be empty");
Assert.noNullElements(claimTypeConverters.values().toArray(), "Converter(s) cannot be null");
this.claimTypeConverters = Collections.unmodifiableMap(new LinkedHashMap<>(claimTypeConverters));
}
@Override
public Map<String, Object> convert(Map<String, Object> claims) {
if (CollectionUtils.isEmpty(claims)) {
return claims;
}
Map<String, Object> result = new HashMap<>(claims);
this.claimTypeConverters.forEach((claimName, typeConverter) -> {
if (claims.containsKey(claimName)) {
Object claim = claims.get(claimName);
Object mappedClaim = typeConverter.convert(claim);
if (mappedClaim != null) {
result.put(claimName, mappedClaim);
}
}
});
return result;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import java.util.Collections;
import java.util.Set;
/**
* @author Joe Grandja
* @since 5.2
*/
final class ObjectToBooleanConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, Boolean.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
if (source instanceof Boolean) {
return source;
}
return Boolean.valueOf(source.toString());
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
/**
* @author Joe Grandja
* @since 5.2
*/
final class ObjectToInstantConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, Instant.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
if (source instanceof Instant) {
return source;
}
if (source instanceof Date) {
return ((Date) source).toInstant();
}
if (source instanceof Number) {
return Instant.ofEpochSecond(((Number) source).longValue());
}
try {
return Instant.ofEpochSecond(Long.parseLong(source.toString()));
} catch (Exception ex) {
// Ignore
}
try {
return Instant.parse(source.toString());
} catch (Exception ex) {
// Ignore
}
return null;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.util.ClassUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Joe Grandja
* @since 5.2
*/
final class ObjectToListStringConverter implements ConditionalGenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> convertibleTypes = new LinkedHashSet<>();
convertibleTypes.add(new ConvertiblePair(Object.class, List.class));
convertibleTypes.add(new ConvertiblePair(Object.class, Collection.class));
return convertibleTypes;
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
if (targetType.getElementTypeDescriptor() == null ||
targetType.getElementTypeDescriptor().getType().equals(String.class) ||
sourceType == null ||
ClassUtils.isAssignable(sourceType.getType(), targetType.getElementTypeDescriptor().getType())) {
return true;
}
return false;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
if (source instanceof List) {
List<?> sourceList = (List<?>) source;
if (!sourceList.isEmpty() && sourceList.get(0) instanceof String) {
return source;
}
}
if (source instanceof Collection) {
return ((Collection<?>) source).stream()
.filter(Objects::nonNull)
.map(Objects::toString)
.collect(Collectors.toList());
}
return Collections.singletonList(source.toString());
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author Joe Grandja
* @since 5.2
*/
final class ObjectToMapStringObjectConverter implements ConditionalGenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, Map.class));
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
if (targetType.getElementTypeDescriptor() == null ||
targetType.getMapKeyTypeDescriptor().getType().equals(String.class)) {
return true;
}
return false;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
if (!(source instanceof Map)) {
return null;
}
Map<?, ?> sourceMap = (Map<?, ?>) source;
if (!sourceMap.isEmpty() && sourceMap.keySet().iterator().next() instanceof String) {
return source;
}
Map<String, Object> result = new HashMap<>();
sourceMap.forEach((k, v) -> result.put(k.toString(), v));
return result;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import java.util.Collections;
import java.util.Set;
/**
* @author Joe Grandja
* @since 5.2
*/
final class ObjectToStringConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, String.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return source == null ? null : source.toString();
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.Set;
/**
* @author Joe Grandja
* @since 5.2
*/
final class ObjectToURLConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, URL.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
if (source instanceof URL) {
return source;
}
try {
return new URI(source.toString()).toURL();
} catch (Exception ex) {
// Ignore
}
return null;
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.assertj.core.util.Lists;
import org.junit.Test;
import org.springframework.core.convert.ConversionService;
import java.net.URL;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ClaimConversionService}.
*
* @author Joe Grandja
* @since 5.2
*/
public class ClaimConversionServiceTests {
private final ConversionService conversionService = ClaimConversionService.getSharedInstance();
@Test
public void convertStringWhenNullThenReturnNull() {
assertThat(this.conversionService.convert(null, String.class)).isNull();
}
@Test
public void convertStringWhenStringThenReturnSame() {
assertThat(this.conversionService.convert("string", String.class)).isSameAs("string");
}
@Test
public void convertStringWhenNumberThenConverts() {
assertThat(this.conversionService.convert(1234, String.class)).isEqualTo("1234");
}
@Test
public void convertBooleanWhenNullThenReturnNull() {
assertThat(this.conversionService.convert(null, Boolean.class)).isNull();
}
@Test
public void convertBooleanWhenBooleanThenReturnSame() {
assertThat(this.conversionService.convert(Boolean.TRUE, Boolean.class)).isSameAs(Boolean.TRUE);
}
@Test
public void convertBooleanWhenStringTrueThenConverts() {
assertThat(this.conversionService.convert("true", Boolean.class)).isEqualTo(Boolean.TRUE);
}
@Test
public void convertBooleanWhenNotConvertibleThenReturnBooleanFalse() {
assertThat(this.conversionService.convert("not-convertible-boolean", Boolean.class)).isEqualTo(Boolean.FALSE);
}
@Test
public void convertInstantWhenNullThenReturnNull() {
assertThat(this.conversionService.convert(null, Instant.class)).isNull();
}
@Test
public void convertInstantWhenInstantThenReturnSame() {
Instant instant = Instant.now();
assertThat(this.conversionService.convert(instant, Instant.class)).isSameAs(instant);
}
@Test
public void convertInstantWhenDateThenConverts() {
Instant instant = Instant.now();
assertThat(this.conversionService.convert(Date.from(instant), Instant.class)).isEqualTo(instant);
}
@Test
public void convertInstantWhenNumberThenConverts() {
Instant instant = Instant.now();
assertThat(this.conversionService.convert(instant.getEpochSecond(), Instant.class))
.isEqualTo(instant.truncatedTo(ChronoUnit.SECONDS));
}
@Test
public void convertInstantWhenStringThenConverts() {
Instant instant = Instant.now();
assertThat(this.conversionService.convert(String.valueOf(instant.getEpochSecond()), Instant.class))
.isEqualTo(instant.truncatedTo(ChronoUnit.SECONDS));
assertThat(this.conversionService.convert(String.valueOf(instant.toString()), Instant.class)).isEqualTo(instant);
}
@Test
public void convertInstantWhenNotConvertibleThenReturnNull() {
assertThat(this.conversionService.convert("not-convertible-instant", Instant.class)).isNull();
}
@Test
public void convertUrlWhenNullThenReturnNull() {
assertThat(this.conversionService.convert(null, URL.class)).isNull();
}
@Test
public void convertUrlWhenUrlThenReturnSame() throws Exception {
URL url = new URL("https://localhost");
assertThat(this.conversionService.convert(url, URL.class)).isSameAs(url);
}
@Test
public void convertUrlWhenStringThenConverts() throws Exception {
String urlString = "https://localhost";
URL url = new URL(urlString);
assertThat(this.conversionService.convert(urlString, URL.class)).isEqualTo(url);
}
@Test
public void convertUrlWhenNotConvertibleThenReturnNull() {
assertThat(this.conversionService.convert("not-convertible-url", URL.class)).isNull();
}
@Test
public void convertCollectionStringWhenNullThenReturnNull() {
assertThat(this.conversionService.convert(null, Collection.class)).isNull();
}
@Test
public void convertCollectionStringWhenListStringThenReturnSame() {
List<String> list = Lists.list("1", "2", "3", "4");
assertThat(this.conversionService.convert(list, Collection.class)).isSameAs(list);
}
@Test
public void convertCollectionStringWhenListNumberThenConverts() {
assertThat(this.conversionService.convert(Lists.list(1, 2, 3, 4), Collection.class))
.isEqualTo(Lists.list("1", "2", "3", "4"));
}
@Test
public void convertCollectionStringWhenNotConvertibleThenReturnSingletonList() {
String string = "not-convertible-collection";
assertThat(this.conversionService.convert(string, Collection.class))
.isEqualTo(Collections.singletonList(string));
}
@Test
public void convertListStringWhenNullThenReturnNull() {
assertThat(this.conversionService.convert(null, List.class)).isNull();
}
@Test
public void convertListStringWhenListStringThenReturnSame() {
List<String> list = Lists.list("1", "2", "3", "4");
assertThat(this.conversionService.convert(list, List.class)).isSameAs(list);
}
@Test
public void convertListStringWhenListNumberThenConverts() {
assertThat(this.conversionService.convert(Lists.list(1, 2, 3, 4), List.class))
.isEqualTo(Lists.list("1", "2", "3", "4"));
}
@Test
public void convertListStringWhenNotConvertibleThenReturnSingletonList() {
String string = "not-convertible-list";
assertThat(this.conversionService.convert(string, List.class))
.isEqualTo(Collections.singletonList(string));
}
@Test
public void convertMapStringObjectWhenNullThenReturnNull() {
assertThat(this.conversionService.convert(null, Map.class)).isNull();
}
@Test
public void convertMapStringObjectWhenMapStringObjectThenReturnSame() {
Map<String, Object> mapStringObject = new HashMap<String, Object>() {
{
put("key1", "value1");
put("key2", "value2");
put("key3", "value3");
}
};
assertThat(this.conversionService.convert(mapStringObject, Map.class)).isSameAs(mapStringObject);
}
@Test
public void convertMapStringObjectWhenMapIntegerObjectThenConverts() {
Map<String, Object> mapStringObject = new HashMap<String, Object>() {
{
put("1", "value1");
put("2", "value2");
put("3", "value3");
}
};
Map<Integer, Object> mapIntegerObject = new HashMap<Integer, Object>() {
{
put(1, "value1");
put(2, "value2");
put(3, "value3");
}
};
assertThat(this.conversionService.convert(mapIntegerObject, Map.class)).isEqualTo(mapStringObject);
}
@Test
public void convertMapStringObjectWhenNotConvertibleThenReturnNull() {
List<String> notConvertibleList = Lists.list("1", "2", "3", "4");
assertThat(this.conversionService.convert(notConvertibleList, Map.class)).isNull();
}
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.converter;
import org.assertj.core.util.Lists;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import java.net.URL;
import java.time.Instant;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link ClaimTypeConverter}.
*
* @author Joe Grandja
* @since 5.2
*/
public class ClaimTypeConverterTests {
private static final String STRING_CLAIM = "string-claim";
private static final String BOOLEAN_CLAIM = "boolean-claim";
private static final String INSTANT_CLAIM = "instant-claim";
private static final String URL_CLAIM = "url-claim";
private static final String COLLECTION_STRING_CLAIM = "collection-string-claim";
private static final String LIST_STRING_CLAIM = "list-string-claim";
private static final String MAP_STRING_OBJECT_CLAIM = "map-string-object-claim";
private ClaimTypeConverter claimTypeConverter;
@Before
@SuppressWarnings("unchecked")
public void setup() {
Converter<Object, ?> stringConverter = getConverter(TypeDescriptor.valueOf(String.class));
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class));
Converter<Object, ?> collectionStringConverter = getConverter(
TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
Converter<Object, ?> listStringConverter = getConverter(
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
Converter<Object, ?> mapStringObjectConverter = getConverter(
TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Object.class)));
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
claimTypeConverters.put(STRING_CLAIM, stringConverter);
claimTypeConverters.put(BOOLEAN_CLAIM, booleanConverter);
claimTypeConverters.put(INSTANT_CLAIM, instantConverter);
claimTypeConverters.put(URL_CLAIM, urlConverter);
claimTypeConverters.put(COLLECTION_STRING_CLAIM, collectionStringConverter);
claimTypeConverters.put(LIST_STRING_CLAIM, listStringConverter);
claimTypeConverters.put(MAP_STRING_OBJECT_CLAIM, mapStringObjectConverter);
this.claimTypeConverter = new ClaimTypeConverter(claimTypeConverters);
}
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor);
}
@Test
public void constructorWhenConvertersNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new ClaimTypeConverter(null))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
public void constructorWhenConvertersHasNullConverterThenThrowIllegalArgumentException() {
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
claimTypeConverters.put("claim1", null);
assertThatThrownBy(() -> new ClaimTypeConverter(claimTypeConverters))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
public void convertWhenClaimsEmptyThenReturnSame() {
Map<String, Object> claims = new HashMap<>();
assertThat(this.claimTypeConverter.convert(claims)).isSameAs(claims);
}
@Test
public void convertWhenAllClaimsRequireConversionThenConvertAll() throws Exception {
Instant instant = Instant.now();
URL url = new URL("https://localhost");
List<Number> listNumber = Lists.list(1, 2, 3, 4);
List<String> listString = Lists.list("1", "2", "3", "4");
Map<Integer, Object> mapIntegerObject = new HashMap<>();
mapIntegerObject.put(1, "value1");
Map<String, Object> mapStringObject = new HashMap<>();
mapStringObject.put("1", "value1");
Map<String, Object> claims = new HashMap<>();
claims.put(STRING_CLAIM, Boolean.TRUE);
claims.put(BOOLEAN_CLAIM, "true");
claims.put(INSTANT_CLAIM, instant.toString());
claims.put(URL_CLAIM, url.toExternalForm());
claims.put(COLLECTION_STRING_CLAIM, listNumber);
claims.put(LIST_STRING_CLAIM, listNumber);
claims.put(MAP_STRING_OBJECT_CLAIM, mapIntegerObject);
claims = this.claimTypeConverter.convert(claims);
assertThat(claims.get(STRING_CLAIM)).isEqualTo("true");
assertThat(claims.get(BOOLEAN_CLAIM)).isEqualTo(Boolean.TRUE);
assertThat(claims.get(INSTANT_CLAIM)).isEqualTo(instant);
assertThat(claims.get(URL_CLAIM)).isEqualTo(url);
assertThat(claims.get(COLLECTION_STRING_CLAIM)).isEqualTo(listString);
assertThat(claims.get(LIST_STRING_CLAIM)).isEqualTo(listString);
assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isEqualTo(mapStringObject);
}
@Test
public void convertWhenNoClaimsRequireConversionThenConvertNone() throws Exception {
String string = "value";
Boolean bool = Boolean.TRUE;
Instant instant = Instant.now();
URL url = new URL("https://localhost");
List<String> listString = Lists.list("1", "2", "3", "4");
Map<String, Object> mapStringObject = new HashMap<>();
mapStringObject.put("1", "value1");
Map<String, Object> claims = new HashMap<>();
claims.put(STRING_CLAIM, string);
claims.put(BOOLEAN_CLAIM, bool);
claims.put(INSTANT_CLAIM, instant);
claims.put(URL_CLAIM, url);
claims.put(COLLECTION_STRING_CLAIM, listString);
claims.put(LIST_STRING_CLAIM, listString);
claims.put(MAP_STRING_OBJECT_CLAIM, mapStringObject);
claims = this.claimTypeConverter.convert(claims);
assertThat(claims.get(STRING_CLAIM)).isSameAs(string);
assertThat(claims.get(BOOLEAN_CLAIM)).isSameAs(bool);
assertThat(claims.get(INSTANT_CLAIM)).isSameAs(instant);
assertThat(claims.get(URL_CLAIM)).isSameAs(url);
assertThat(claims.get(COLLECTION_STRING_CLAIM)).isSameAs(listString);
assertThat(claims.get(LIST_STRING_CLAIM)).isSameAs(listString);
assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isSameAs(mapStringObject);
}
@Test
public void convertWhenConverterNotAvailableThenDoesNotConvert() {
Map<String, Object> claims = new HashMap<>();
claims.put("claim1", "value1");
claims = this.claimTypeConverter.convert(claims);
assertThat(claims.get("claim1")).isSameAs("value1");
}
}