Skip to content

Commit 947d11f

Browse files
committed
Update SecurityJackson2Modules
Fixes gh-4370
1 parent b3a60a8 commit 947d11f

File tree

2 files changed

+242
-8
lines changed

2 files changed

+242
-8
lines changed

core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java

+116-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2016 the original author or authors.
2+
* Copyright 2015-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,17 +16,18 @@
1616

1717
package org.springframework.security.jackson2;
1818

19+
import com.fasterxml.jackson.annotation.JacksonAnnotation;
1920
import com.fasterxml.jackson.annotation.JsonTypeInfo;
20-
import com.fasterxml.jackson.databind.Module;
21-
import com.fasterxml.jackson.databind.ObjectMapper;
22-
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
21+
import com.fasterxml.jackson.databind.*;
22+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
23+
import com.fasterxml.jackson.databind.jsontype.*;
2324
import org.apache.commons.logging.Log;
2425
import org.apache.commons.logging.LogFactory;
26+
import org.springframework.core.annotation.AnnotationUtils;
2527
import org.springframework.util.ClassUtils;
2628

27-
import java.util.ArrayList;
28-
import java.util.Arrays;
29-
import java.util.List;
29+
import java.io.IOException;
30+
import java.util.*;
3031

3132
/**
3233
* This utility class will find all the SecurityModules in classpath.
@@ -65,7 +66,7 @@ public static void enableDefaultTyping(ObjectMapper mapper) {
6566
if(mapper != null) {
6667
TypeResolverBuilder<?> typeBuilder = mapper.getDeserializationConfig().getDefaultTyper(null);
6768
if (typeBuilder == null) {
68-
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
69+
mapper.setDefaultTyping(createWhitelistedDefaultTyping());
6970
}
7071
}
7172
}
@@ -103,4 +104,111 @@ public static List<Module> getModules(ClassLoader loader) {
103104
}
104105
return modules;
105106
}
107+
108+
/**
109+
* Creates a TypeResolverBuilder that performs whitelisting.
110+
* @return a TypeResolverBuilder that performs whitelisting.
111+
*/
112+
private static TypeResolverBuilder<? extends TypeResolverBuilder> createWhitelistedDefaultTyping() {
113+
TypeResolverBuilder<? extends TypeResolverBuilder> result = new WhitelistTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL);
114+
result = result.init(JsonTypeInfo.Id.CLASS, null);
115+
result = result.inclusion(JsonTypeInfo.As.PROPERTY);
116+
return result;
117+
}
118+
119+
/**
120+
* An implementation of {@link ObjectMapper.DefaultTypeResolverBuilder} that overrides the {@link TypeIdResolver}
121+
* with {@link WhitelistTypeIdResolver}.
122+
* @author Rob Winch
123+
*/
124+
static class WhitelistTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
125+
126+
public WhitelistTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) {
127+
super(defaultTyping);
128+
}
129+
130+
protected TypeIdResolver idResolver(MapperConfig<?> config,
131+
JavaType baseType, Collection<NamedType> subtypes, boolean forSer, boolean forDeser) {
132+
TypeIdResolver result = super.idResolver(config, baseType, subtypes, forSer, forDeser);
133+
return new WhitelistTypeIdResolver(result);
134+
}
135+
}
136+
137+
/**
138+
* A {@link TypeIdResolver} that delegates to an existing implementation and throws an IllegalStateException if the
139+
* class being looked up is not whitelisted, does not provide an explicit mixin, and is not annotated with Jackson
140+
* mappings. See https://github.com/spring-projects/spring-security/issues/4370
141+
*/
142+
static class WhitelistTypeIdResolver implements TypeIdResolver {
143+
private static final Set<String> WHITELIST_CLASS_NAMES = Collections.unmodifiableSet(new HashSet(Arrays.asList(
144+
"java.util.ArrayList",
145+
"java.util.Collections$EmptyMap",
146+
"java.util.Date",
147+
"java.util.TreeMap",
148+
"org.springframework.security.core.context.SecurityContextImpl"
149+
)));
150+
151+
private final TypeIdResolver delegate;
152+
153+
WhitelistTypeIdResolver(TypeIdResolver delegate) {
154+
this.delegate = delegate;
155+
}
156+
157+
@Override
158+
public void init(JavaType baseType) {
159+
delegate.init(baseType);
160+
}
161+
162+
@Override
163+
public String idFromValue(Object value) {
164+
return delegate.idFromValue(value);
165+
}
166+
167+
@Override
168+
public String idFromValueAndType(Object value, Class<?> suggestedType) {
169+
return delegate.idFromValueAndType(value, suggestedType);
170+
}
171+
172+
@Override
173+
public String idFromBaseType() {
174+
return delegate.idFromBaseType();
175+
}
176+
177+
@Override
178+
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
179+
DeserializationConfig config = (DeserializationConfig) context.getConfig();
180+
JavaType result = delegate.typeFromId(context, id);
181+
String className = result.getRawClass().getName();
182+
if(isWhitelisted(className)) {
183+
return delegate.typeFromId(context, id);
184+
}
185+
boolean isExplicitMixin = config.findMixInClassFor(result.getRawClass()) != null;
186+
if(isExplicitMixin) {
187+
return result;
188+
}
189+
JacksonAnnotation jacksonAnnotation = AnnotationUtils.findAnnotation(result.getRawClass(), JacksonAnnotation.class);
190+
if(jacksonAnnotation != null) {
191+
return result;
192+
}
193+
throw new IllegalArgumentException("The class with " + id + " and name of " + className + " is not whitelisted. " +
194+
"If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. " +
195+
"If the serialization is only done by a trusted source, you can also enable default typing. " +
196+
"See https://github.com/spring-projects/spring-security/issues/4370 for details");
197+
}
198+
199+
private boolean isWhitelisted(String id) {
200+
return WHITELIST_CLASS_NAMES.contains(id);
201+
}
202+
203+
@Override
204+
public String getDescForKnownTypeIds() {
205+
return delegate.getDescForKnownTypeIds();
206+
}
207+
208+
@Override
209+
public JsonTypeInfo.Id getMechanism() {
210+
return delegate.getMechanism();
211+
}
212+
213+
}
106214
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2015-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
package org.springframework.security.jackson2;
19+
20+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
21+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
22+
import com.fasterxml.jackson.annotation.JsonIgnoreType;
23+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
24+
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
28+
import java.lang.annotation.*;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.assertj.core.api.Assertions.fail;
32+
33+
/**
34+
* @author Rob Winch
35+
* @since 5.0
36+
*/
37+
public class SecurityJackson2ModulesTests {
38+
private ObjectMapper mapper;
39+
40+
@Before
41+
public void setup() {
42+
mapper = new ObjectMapper();
43+
SecurityJackson2Modules.enableDefaultTyping(mapper);
44+
}
45+
46+
@Test
47+
public void readValueWhenNotWhitelistedOrMappedThenThrowsException() throws Exception {
48+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
49+
try {
50+
mapper.readValue(content, Object.class);
51+
fail("Expected Exception");
52+
} catch(RuntimeException e) {
53+
assertThat(e).hasMessageContaining("whitelisted");
54+
}
55+
}
56+
57+
@Test
58+
public void readValueWhenExplicitDefaultTypingAfterSecuritySetupThenReadsAsSpecificType() throws Exception {
59+
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
60+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
61+
62+
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
63+
}
64+
65+
@Test
66+
public void readValueWhenExplicitDefaultTypingBeforeSecuritySetupThenReadsAsSpecificType() throws Exception {
67+
mapper = new ObjectMapper();
68+
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
69+
SecurityJackson2Modules.enableDefaultTyping(mapper);
70+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
71+
72+
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
73+
}
74+
75+
@Test
76+
public void readValueWhenAnnotatedThenReadsAsSpecificType() throws Exception {
77+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelistedButAnnotated\",\"property\":\"bar\"}";
78+
79+
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelistedButAnnotated.class);
80+
}
81+
82+
@Test
83+
public void readValueWhenMixinProvidedThenReadsAsSpecificType() throws Exception {
84+
mapper.addMixIn(NotWhitelisted.class, NotWhitelistedMixin.class);
85+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
86+
87+
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
88+
}
89+
90+
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
91+
@Retention(RetentionPolicy.RUNTIME)
92+
@Documented
93+
public @interface NotJacksonAnnotation {}
94+
95+
@NotJacksonAnnotation
96+
static class NotWhitelisted {
97+
private String property = "bar";
98+
99+
public String getProperty() {
100+
return property;
101+
}
102+
103+
public void setProperty(String property) {
104+
}
105+
}
106+
107+
@JsonIgnoreType(false)
108+
static class NotWhitelistedButAnnotated {
109+
private String property = "bar";
110+
111+
public String getProperty() {
112+
return property;
113+
}
114+
115+
public void setProperty(String property) {
116+
}
117+
}
118+
119+
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
120+
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
121+
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
122+
@JsonIgnoreProperties(ignoreUnknown = true)
123+
abstract class NotWhitelistedMixin {
124+
125+
}
126+
}

0 commit comments

Comments
 (0)