Skip to content

Commit 5dee853

Browse files
committed
Update SecurityJackson2Modules
Fixes gh-4370
1 parent 489ffcf commit 5dee853

File tree

2 files changed

+238
-7
lines changed

2 files changed

+238
-7
lines changed

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

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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.*;
31+
32+
/**
33+
* @author Rob Winch
34+
* @since 5.0
35+
*/
36+
public class SecurityJackson2ModulesTests {
37+
private ObjectMapper mapper;
38+
39+
@Before
40+
public void setup() {
41+
mapper = new ObjectMapper();
42+
SecurityJackson2Modules.enableDefaultTyping(mapper);
43+
}
44+
45+
@Test
46+
public void readValueWhenNotWhitelistedOrMappedThenThrowsException() throws Exception {
47+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
48+
assertThatThrownBy(() -> {
49+
mapper.readValue(content, Object.class);
50+
}
51+
).hasStackTraceContaining("whitelisted");
52+
}
53+
54+
@Test
55+
public void readValueWhenExplicitDefaultTypingAfterSecuritySetupThenReadsAsSpecificType() throws Exception {
56+
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
57+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
58+
59+
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
60+
}
61+
62+
@Test
63+
public void readValueWhenExplicitDefaultTypingBeforeSecuritySetupThenReadsAsSpecificType() throws Exception {
64+
mapper = new ObjectMapper();
65+
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
66+
SecurityJackson2Modules.enableDefaultTyping(mapper);
67+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
68+
69+
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
70+
}
71+
72+
@Test
73+
public void readValueWhenAnnotatedThenReadsAsSpecificType() throws Exception {
74+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelistedButAnnotated\",\"property\":\"bar\"}";
75+
76+
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelistedButAnnotated.class);
77+
}
78+
79+
@Test
80+
public void readValueWhenMixinProvidedThenReadsAsSpecificType() throws Exception {
81+
mapper.addMixIn(NotWhitelisted.class, NotWhitelistedMixin.class);
82+
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
83+
84+
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
85+
}
86+
87+
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
88+
@Retention(RetentionPolicy.RUNTIME)
89+
@Documented
90+
public @interface NotJacksonAnnotation {}
91+
92+
@NotJacksonAnnotation
93+
static class NotWhitelisted {
94+
private String property = "bar";
95+
96+
public String getProperty() {
97+
return property;
98+
}
99+
100+
public void setProperty(String property) {
101+
}
102+
}
103+
104+
@JsonIgnoreType(false)
105+
static class NotWhitelistedButAnnotated {
106+
private String property = "bar";
107+
108+
public String getProperty() {
109+
return property;
110+
}
111+
112+
public void setProperty(String property) {
113+
}
114+
}
115+
116+
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
117+
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
118+
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
119+
@JsonIgnoreProperties(ignoreUnknown = true)
120+
abstract class NotWhitelistedMixin {
121+
122+
}
123+
}

0 commit comments

Comments
 (0)