Log Message
Add security framework to limit handled types while unmarshalling.
Modified Paths
- trunk/xstream/src/java/com/thoughtworks/xstream/XStream.java
- trunk/xstream/src/java/com/thoughtworks/xstream/mapper/CachingMapper.java
- trunk/xstream-distribution/src/content/changes.html
Added Paths
- trunk/xstream/src/java/com/thoughtworks/xstream/mapper/SecurityMapper.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/
- trunk/xstream/src/java/com/thoughtworks/xstream/security/AnyTypePermission.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/ArrayTypePermission.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/ExplicitTypePermission.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/ForbiddenClassException.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/NoPermission.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/NoTypePermission.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/NullPermission.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/PrimitiveTypePermission.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/RegExpTypePermission.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/TypePermission.java
- trunk/xstream/src/java/com/thoughtworks/xstream/security/WildcardTypePermission.java
- trunk/xstream/src/test/com/thoughtworks/xstream/mapper/SecurityMapperTest.java
Diff
Modified: trunk/xstream/src/java/com/thoughtworks/xstream/XStream.java (2209 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/XStream.java 2014-01-08 17:21:47 UTC (rev 2209)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/XStream.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2003, 2004, 2005, 2006 Joe Walnes.
- * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 XStream Committers.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
@@ -137,8 +137,16 @@
import com.thoughtworks.xstream.mapper.MapperWrapper;
import com.thoughtworks.xstream.mapper.OuterClassMapper;
import com.thoughtworks.xstream.mapper.PackageAliasingMapper;
+import com.thoughtworks.xstream.mapper.SecurityMapper;
import com.thoughtworks.xstream.mapper.SystemAttributeAliasingMapper;
import com.thoughtworks.xstream.mapper.XStream11XmlFriendlyMapper;
+import com.thoughtworks.xstream.security.AnyTypePermission;
+import com.thoughtworks.xstream.security.ExplicitTypePermission;
+import com.thoughtworks.xstream.security.NoPermission;
+import com.thoughtworks.xstream.security.NoTypePermission;
+import com.thoughtworks.xstream.security.RegExpTypePermission;
+import com.thoughtworks.xstream.security.TypePermission;
+import com.thoughtworks.xstream.security.WildcardTypePermission;
/**
@@ -274,7 +282,7 @@
* The XStream instance is thread-safe. That is, once the XStream instance has been created and
* configured, it may be shared across multiple threads allowing objects to be
* serialized/deserialized concurrently. <em>Note, that this only applies if annotations are not
- * auto-detected on -the-fly.</em>
+ * auto-detected on-the-fly.</em>
* </p>
* <h3>Implicit collections</h3>
*
@@ -310,6 +318,7 @@
private ImmutableTypesMapper immutableTypesMapper;
private ImplicitCollectionMapper implicitCollectionMapper;
private LocalConversionMapper localConversionMapper;
+ private SecurityMapper securityMapper;
private AnnotationConfiguration annotationConfiguration;
public static final int NO_REFERENCES = 1001;
@@ -539,6 +548,7 @@
this.mapper = mapper == null ? buildMapper() : mapper;
setupMappers();
+ setupSecurity();
setupAliases();
setupDefaultImplementations();
setupConverters();
@@ -569,6 +579,7 @@
}
mapper = new LocalConversionMapper(mapper);
mapper = new ImmutableTypesMapper(mapper);
+ mapper = new SecurityMapper(mapper);
if (JVM.is15()) {
mapper = buildMapperDynamically(ANNOTATION_MAPPER_TYPE, new Class[]{
Mapper.class, ConverterRegistry.class, ConverterLookup.class,
@@ -622,9 +633,19 @@
.lookupMapperOfType(ImmutableTypesMapper.class);
localConversionMapper = (LocalConversionMapper)this.mapper
.lookupMapperOfType(LocalConversionMapper.class);
+ securityMapper = (SecurityMapper)this.mapper
+ .lookupMapperOfType(SecurityMapper.class);
annotationConfiguration = (AnnotationConfiguration)this.mapper
.lookupMapperOfType(AnnotationConfiguration.class);
}
+
+ protected void setupSecurity() {
+ if (securityMapper == null) {
+ return;
+ }
+
+ addPermission(AnyTypePermission.ANY);
+ }
protected void setupAliases() {
if (classAliasingMapper == null) {
@@ -1955,4 +1976,127 @@
annotationConfiguration.autodetectAnnotations(mode);
}
}
+
+ /**
+ * Add a new security permission.
+ *
+ * <p>
+ * Permissions are evaluated in the added sequence. An instance of {@link NoTypePermission} or
+ * {@link AnyTypePermission} will implicitly wipe any existing permission.
+ * </p>
+ *
+ * @param permission the permission to add
+ * @since upcoming
+ */
+ public void addPermission(TypePermission permission) {
+ if (securityMapper != null) {
+ securityMapper.addPermission(permission);
+ }
+ }
+
+ /**
+ * Add security permission for explicit types by name.
+ *
+ * @param names the type names to allow
+ * @since upcoming
+ */
+ public void allowTypes(String... names) {
+ addPermission(new ExplicitTypePermission(names));
+ }
+
+ /**
+ * Add security permission for types matching one of the specified regular expressions.
+ *
+ * @param regexps the regular expressions to allow type names
+ * @since upcoming
+ */
+ public void allowTypesByRegExp(String... regexps) {
+ addPermission(new RegExpTypePermission(regexps));
+ }
+
+ /**
+ * Add security permission for types matching one of the specified regular expressions.
+ *
+ * @param regexps the regular expressions to allow type names
+ * @since upcoming
+ */
+ public void allowTypesByRegExp(Pattern... regexps) {
+ addPermission(new RegExpTypePermission(regexps));
+ }
+
+ /**
+ * Add security permission for types matching one of the specified wildcard patterns.
+ * <p>
+ * Supported are patterns with path expressions using dot as separator:
+ * </p>
+ * <ul>
+ * <li>?: one non-control character except separator, e.g. for 'java.net.Inet?Address'</li>
+ * <li>*: arbitrary number of non-control characters except separator, e.g. for types in a package like 'java.lang.*'</li>
+ * <li>**: arbitrary number of non-control characters including separator, e.g. for types in a package and subpackages like 'java.lang.**'</li>
+ * </ul>
+ *
+ * @param patterns the patterns to allow type names
+ * @since upcoming
+ */
+ public void allowTypesByWildcard(String... patterns) {
+ addPermission(new WildcardTypePermission(patterns));
+ }
+
+ /**
+ * Add security permission denying another one.
+ *
+ * @param permission the permission to deny
+ * @since upcoming
+ */
+ public void denyPermission(TypePermission permission) {
+ addPermission(new NoPermission(permission));
+ }
+
+ /**
+ * Add security permission forbidding explicit types by name.
+ *
+ * @param names the type names to forbid
+ * @since upcoming
+ */
+ public void denyTypes(String... names) {
+ denyPermission(new ExplicitTypePermission(names));
+ }
+
+ /**
+ * Add security permission forbidding types matching one of the specified regular expressions.
+ *
+ * @param regexps the regular expressions to forbid type names
+ * @since upcoming
+ */
+ public void denyTypesByRegExp(String... regexps) {
+ denyPermission(new RegExpTypePermission(regexps));
+ }
+
+ /**
+ * Add security permission forbidding types matching one of the specified regular expressions.
+ *
+ * @param regexps the regular expressions to forbid type names
+ * @since upcoming
+ */
+ public void denyTypesByRegExp(Pattern... regexps) {
+ denyPermission(new RegExpTypePermission(regexps));
+ }
+
+ /**
+ * Add security permission forbidding types matching one of the specified wildcard patterns.
+ * <p>
+ * Supported are patterns with path expressions using dot as separator:
+ * </p>
+ * <ul>
+ * <li>?: one non-control character except separator, e.g. for 'java.net.Inet?Address'</li>
+ * <li>*: arbitrary number of non-control characters except separator, e.g. for types in a package like 'java.lang.*'</li>
+ * <li>**: arbitrary number of non-control characters including separator, e.g. for types in a package and subpackages like 'java.lang.**'</li>
+ * </ul>
+ *
+ * @param patterns the patterns to forbid names
+ * @since upcoming
+ */
+ public void denyTypesByWildcard(String... patterns) {
+ denyPermission(new WildcardTypePermission(patterns));
+ }
}
Modified: trunk/xstream/src/java/com/thoughtworks/xstream/mapper/CachingMapper.java (2209 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/mapper/CachingMapper.java 2014-01-08 17:21:47 UTC (rev 2209)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/mapper/CachingMapper.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2005 Joe Walnes.
- * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013 XStream Committers.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013, 2014 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
@@ -15,7 +15,9 @@
import java.util.HashMap;
import java.util.Map;
+import com.thoughtworks.xstream.XStreamException;
import com.thoughtworks.xstream.core.Caching;
+import com.thoughtworks.xstream.security.ForbiddenClassException;
/**
* Mapper that caches which names map to which classes. Prevents repetitive searching and class loading.
@@ -38,13 +40,16 @@
if (cached instanceof Class) {
return (Class)cached;
}
- throw (CannotResolveClassException)cached;
+ throw (XStreamException)cached;
}
try {
Class result = super.realClass(elementName);
realClassCache.put(elementName, result);
return result;
+ } catch (ForbiddenClassException e) {
+ realClassCache.put(elementName, e);
+ throw e;
} catch (CannotResolveClassException e) {
realClassCache.put(elementName, e);
throw e;
Added: trunk/xstream/src/java/com/thoughtworks/xstream/mapper/SecurityMapper.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/mapper/SecurityMapper.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/mapper/SecurityMapper.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 08. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.mapper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.thoughtworks.xstream.security.AnyTypePermission;
+import com.thoughtworks.xstream.security.ForbiddenClassException;
+import com.thoughtworks.xstream.security.NoTypePermission;
+import com.thoughtworks.xstream.security.TypePermission;
+
+
+/**
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class SecurityMapper extends MapperWrapper {
+
+ private final List<TypePermission> permissions;
+
+ /**
+ * Construct a SecurityMapper.
+ *
+ * @param wrapped the mapper chain
+ * @since upcoming
+ */
+ public SecurityMapper(final Mapper wrapped) {
+ this(wrapped, (TypePermission[])null);
+ }
+
+ /**
+ * Construct a SecurityMapper.
+ *
+ * @param wrapped the mapper chain
+ * @param permissions the predefined permissions
+ * @since upcoming
+ */
+ public SecurityMapper(final Mapper wrapped, final TypePermission... permissions) {
+ super(wrapped);
+ this.permissions = permissions == null //
+ ? new ArrayList<TypePermission>()
+ : new ArrayList<TypePermission>(Arrays.asList(permissions));
+ }
+
+ /**
+ * Add a new permission.
+ * <p>
+ * Permissions are evaluated in the added sequence. An instance of {@link NoTypePermission} or
+ * {@link AnyTypePermission} will implicitly wipe any existing permission.
+ * </p>
+ *
+ * @param permission the permission to add.
+ * @since upcoming
+ */
+ public void addPermission(final TypePermission permission) {
+ if (permission.equals(NoTypePermission.NONE) || permission.equals(AnyTypePermission.ANY))
+ permissions.clear();
+ permissions.add(permission);
+ }
+
+ @Override
+ public Class realClass(final String elementName) {
+ final Class type = super.realClass(elementName);
+ for (final TypePermission permission : permissions)
+ if (permission.allows(type))
+ return type;
+ throw new ForbiddenClassException(type);
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/mapper/SecurityMapper.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/AnyTypePermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/AnyTypePermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/AnyTypePermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 08. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+/**
+ * Permission for any type and <code>null</code>.
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class AnyTypePermission implements TypePermission {
+ /**
+ * @since upcoming
+ */
+ public static final TypePermission ANY = new AnyTypePermission();
+
+ @Override
+ public boolean allows(Class<?> type) {
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 3;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj != null && obj.getClass() == AnyTypePermission.class;
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/AnyTypePermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/ArrayTypePermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/ArrayTypePermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/ArrayTypePermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 09. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+/**
+ * Permission for any array type.
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class ArrayTypePermission implements TypePermission {
+ /**
+ * @since upcoming
+ */
+ public static final TypePermission ARRAYS = new ArrayTypePermission();
+
+ @Override
+ public boolean allows(Class<?> type) {
+ return type != null && type.isArray();
+ }
+
+ @Override
+ public int hashCode() {
+ return 13;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj != null && obj.getClass() == ArrayTypePermission.class;
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/ArrayTypePermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/ExplicitTypePermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/ExplicitTypePermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/ExplicitTypePermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 09. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Explicit permission for a type with a name matching one in the provided list.
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class ExplicitTypePermission implements TypePermission {
+
+ final Set<String> names;
+
+ /**
+ * @since upcoming
+ */
+ public ExplicitTypePermission(String...names) {
+ this.names = names == null ? Collections.<String>emptySet() : new HashSet<String>(Arrays.asList(names));
+ }
+
+ @Override
+ public boolean allows(Class<?> type) {
+ if (type == null)
+ return false;
+ return names.contains(type.getName());
+ }
+
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/ExplicitTypePermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/ForbiddenClassException.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/ForbiddenClassException.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/ForbiddenClassException.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 08. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+import com.thoughtworks.xstream.XStreamException;
+
+/**
+ * Exception thrown for a forbidden class.
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class ForbiddenClassException extends XStreamException {
+
+ /**
+ * Construct a ForbiddenClassException.
+ * @param type the forbidden class
+ * @since upcoming
+ */
+ public ForbiddenClassException(Class<?> type) {
+ super(type == null ? "null" : type.getName());
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/ForbiddenClassException.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/NoPermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/NoPermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/NoPermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 09. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+/**
+ * Wrapper to negate another type permission.
+ * <p>
+ * If the wrapped {@link TypePermission} allows the type, this instance will throw a {@link ForbiddenClassException}
+ * instead. An instance of this permission cannot be used to allow a type.
+ * </p>
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class NoPermission implements TypePermission {
+
+ private final TypePermission permission;
+
+ /**
+ * Construct a NoPermission.
+ *
+ * @param permission the permission to negate or <code>null</code> to forbid any type
+ * @since upcoming
+ */
+ public NoPermission(final TypePermission permission) {
+ this.permission = permission;
+ }
+
+ @Override
+ public boolean allows(final Class<?> type) {
+ if (permission == null || permission.allows(type)) {
+ throw new ForbiddenClassException(type);
+ }
+ return false;
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/NoPermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/NoTypePermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/NoTypePermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/NoTypePermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 08. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+/**
+ * No permission for any type.
+ * <p>
+ * Can be used to skip any existing default permission.
+ * </p>
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class NoTypePermission implements TypePermission {
+
+ /**
+ * @since upcoming
+ */
+ public static final TypePermission NONE = new NoTypePermission();
+
+ @Override
+ public boolean allows(Class<?> type) {
+ throw new ForbiddenClassException(type);
+ }
+
+ @Override
+ public int hashCode() {
+ return 1;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj != null && obj.getClass() == NoTypePermission.class;
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/NoTypePermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/NullPermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/NullPermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/NullPermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 09. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Permission for <code>null</code> or XStream's null replacement type.
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class NullPermission implements TypePermission {
+ /**
+ * @since upcoming
+ */
+ public static final TypePermission NULL = new NullPermission();
+
+ @Override
+ public boolean allows(Class<?> type) {
+ return type == null || type == Mapper.Null.class;
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/NullPermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/PrimitiveTypePermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/PrimitiveTypePermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/PrimitiveTypePermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 09. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+import com.thoughtworks.xstream.core.util.Primitives;
+
+/**
+ * Permission for any primitive type and its boxed counterpart.
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class PrimitiveTypePermission implements TypePermission {
+ /**
+ * @since upcoming
+ */
+ public static final TypePermission PRIMITIVES = new PrimitiveTypePermission();
+
+ @Override
+ public boolean allows(Class<?> type) {
+ return type != null && type.isPrimitive() || Primitives.isBoxed(type);
+ }
+
+ @Override
+ public int hashCode() {
+ return 7;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj != null && obj.getClass() == PrimitiveTypePermission.class;
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/PrimitiveTypePermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/RegExpTypePermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/RegExpTypePermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/RegExpTypePermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 09. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+import java.util.regex.Pattern;
+
+
+/**
+ * Permission for any type with a name matching one of the provided regular expressions.
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class RegExpTypePermission implements TypePermission {
+
+ private final Pattern[] patterns;
+
+ public RegExpTypePermission(final String... patterns) {
+ this(getPatterns(patterns));
+ }
+
+ public RegExpTypePermission(final Pattern... patterns) {
+ this.patterns = patterns == null ? new Pattern[0] : patterns;
+ }
+
+ @Override
+ public boolean allows(final Class<?> type) {
+ if (type != null) {
+ final String name = type.getName();
+ for (final Pattern pattern : patterns)
+ if (pattern.matcher(name).matches())
+ return true;
+ }
+ return false;
+ }
+
+ private static Pattern[] getPatterns(final String... patterns) {
+ if (patterns == null)
+ return null;
+ final Pattern[] array = new Pattern[patterns.length];
+ for (int i = 0; i < array.length; ++i)
+ array[i] = Pattern.compile(patterns[i]);
+ return array;
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/RegExpTypePermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/TypePermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/TypePermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/TypePermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 08. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+/**
+ * Definition of a type permission.
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public interface TypePermission {
+ /**
+ * Check permission for a provided type.
+ *
+ * @param type the type to check
+ * @return <code>true</code> if provided type is allowed, <code>false</code> if permission does not handle the type
+ * @throws ForbiddenClassException if provided type is explicitly forbidden
+ * @since upcoming
+ */
+ boolean allows(Class<?> type);
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/TypePermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/java/com/thoughtworks/xstream/security/WildcardTypePermission.java (0 => 2210)
--- trunk/xstream/src/java/com/thoughtworks/xstream/security/WildcardTypePermission.java (rev 0)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/security/WildcardTypePermission.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 09. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.security;
+
+/**
+ * Permission for any type with a name matching one of the provided wildcard expressions.
+ *
+ * <p>
+ * Supported are patterns with path expressions using dot as separator:
+ * </p>
+ * <ul>
+ * <li>?: one non-control character except separator, e.g. for 'java.net.Inet?Address'</li>
+ * <li>*: arbitrary number of non-control characters except separator, e.g. for types in a package like 'java.lang.*'</li>
+ * <li>**: arbitrary number of non-control characters including separator, e.g. for types in a package and subpackages like 'java.lang.**'</li>
+ * </ul>
+ * <p>
+ * The complete range of UTF-8 characters is supported except control characters.
+ * </p>
+ *
+ * @author Jörg Schaible
+ * @since upcoming
+ */
+public class WildcardTypePermission extends RegExpTypePermission {
+
+ /**
+ * @since upcoming
+ */
+ public WildcardTypePermission(final String... patterns) {
+ super(getRegExpPatterns(patterns));
+ }
+
+ private static String[] getRegExpPatterns(final String... wildcards) {
+ if (wildcards == null)
+ return null;
+ final String[] regexps = new String[wildcards.length];
+ for (int i = 0; i < wildcards.length; ++i) {
+ final String wildcardExpression = wildcards[i];
+ final StringBuilder result = new StringBuilder(wildcardExpression.length() * 2);
+ result.append("(?u)");
+ final int length = wildcardExpression.length();
+ for (int j = 0; j < length; j++) {
+ final char ch = wildcardExpression.charAt(j);
+ switch (ch) {
+ case '\\':
+ case '.':
+ case '+':
+ case '|':
+ case '[':
+ case ']':
+ case '(':
+ case ')':
+ case '^':
+ case '$':
+ result.append('\\').append(ch);
+ break;
+
+ case '?':
+ result.append('.');
+ break;
+
+ case '*':
+ // see "General Category Property" in http://www.unicode.org/reports/tr18/
+ if (j + 1 < length && wildcardExpression.charAt(j + 1) == '*') {
+ result.append("[\\P{C}]*");
+ j++;
+ } else {
+ result.append("[\\P{C}&&[^").append('.').append("]]*");
+ }
+ break;
+
+ default:
+ result.append(ch);
+ break;
+ }
+ }
+ regexps[i] = result.toString();
+ }
+ return regexps;
+ }
+}
Property changes on: trunk/xstream/src/java/com/thoughtworks/xstream/security/WildcardTypePermission.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Added: trunk/xstream/src/test/com/thoughtworks/xstream/mapper/SecurityMapperTest.java (0 => 2210)
--- trunk/xstream/src/test/com/thoughtworks/xstream/mapper/SecurityMapperTest.java (rev 0)
+++ trunk/xstream/src/test/com/thoughtworks/xstream/mapper/SecurityMapperTest.java 2014-01-09 19:03:16 UTC (rev 2210)
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 09. January 2014 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.mapper;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.QuickWriter;
+import com.thoughtworks.xstream.security.AnyTypePermission;
+import com.thoughtworks.xstream.security.ArrayTypePermission;
+import com.thoughtworks.xstream.security.ExplicitTypePermission;
+import com.thoughtworks.xstream.security.ForbiddenClassException;
+import com.thoughtworks.xstream.security.NoTypePermission;
+import com.thoughtworks.xstream.security.NullPermission;
+import com.thoughtworks.xstream.security.PrimitiveTypePermission;
+import com.thoughtworks.xstream.security.RegExpTypePermission;
+import com.thoughtworks.xstream.security.TypePermission;
+import com.thoughtworks.xstream.security.WildcardTypePermission;
+
+import junit.framework.TestCase;
+
+
+/**
+ * Tests the {@link SecurityMapper} and the {@link TypePermission} implementations.
+ *
+ * @author Jörg Schaible
+ */
+public class SecurityMapperTest extends TestCase {
+
+ private SecurityMapper mapper;
+ private Map<String, Class<?>> classMap;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ classMap = new HashMap<String, Class<?>>();
+ mapper = new SecurityMapper(new MapperWrapper(null) {
+ @Override
+ public Class realClass(final String elementName) {
+ return classMap.get(elementName);
+ }
+ });
+ }
+
+ private void register(final Class<?>... types) {
+ for (final Class<?> type : types) {
+ classMap.put(type.getName(), type);
+ }
+ }
+
+ public void testAnyType() {
+ register(String.class, URL.class, List.class);
+ mapper.addPermission(NoTypePermission.NONE);
+ mapper.addPermission(AnyTypePermission.ANY);
+ assertSame(String.class, mapper.realClass(String.class.getName()));
+ assertSame(List.class, mapper.realClass(List.class.getName()));
+ assertNull(mapper.realClass(null));
+ }
+
+ public void testNoType() {
+ register(String.class, URL.class, List.class);
+ mapper.addPermission(NoTypePermission.NONE);
+ try {
+ mapper.realClass(String.class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(String.class.getName(), e.getMessage());
+ }
+ try {
+ mapper.realClass(null);
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals("null", e.getMessage());
+ }
+ }
+
+ public void testNullType() {
+ register(String.class, Mapper.Null.class);
+ mapper.addPermission(NullPermission.NULL);
+ assertSame(Mapper.Null.class, mapper.realClass(Mapper.Null.class.getName()));
+ assertNull(mapper.realClass(null));
+ try {
+ mapper.realClass(String.class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(String.class.getName(), e.getMessage());
+ }
+ }
+
+ public void testPrimitiveTypes() {
+ register(String.class, int.class, Integer.class, char[].class, Character[].class);
+ mapper.addPermission(PrimitiveTypePermission.PRIMITIVES);
+ assertSame(int.class, mapper.realClass(int.class.getName()));
+ assertSame(Integer.class, mapper.realClass(Integer.class.getName()));
+ try {
+ mapper.realClass(String.class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(String.class.getName(), e.getMessage());
+ }
+ try {
+ mapper.realClass(null);
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals("null", e.getMessage());
+ }
+ try {
+ mapper.realClass(char[].class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(char[].class.getName(), e.getMessage());
+ }
+ }
+
+ public void testArrayTypes() {
+ register(String.class, int.class, Integer.class, char[].class, Character[].class);
+ mapper.addPermission(ArrayTypePermission.ARRAYS);
+ assertSame(char[].class, mapper.realClass(char[].class.getName()));
+ assertSame(Character[].class, mapper.realClass(Character[].class.getName()));
+ try {
+ mapper.realClass(String.class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(String.class.getName(), e.getMessage());
+ }
+ try {
+ mapper.realClass(null);
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals("null", e.getMessage());
+ }
+ try {
+ mapper.realClass(int.class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(int.class.getName(), e.getMessage());
+ }
+ }
+
+ public void testExplicitTypes() {
+ register(String.class, List.class);
+ mapper.addPermission(new ExplicitTypePermission(String.class.getName(), List.class.getName()));
+ assertSame(String.class, mapper.realClass(String.class.getName()));
+ assertSame(List.class, mapper.realClass(List.class.getName()));
+ try {
+ mapper.realClass(null);
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals("null", e.getMessage());
+ }
+ }
+
+ public void testNamesWithRegExps() {
+ class Foo$_0 {}
+ final Class<?> anonymous = new Object() {}.getClass();
+ register(String.class, JVM.class, QuickWriter.class, Foo$_0.class, anonymous, DefaultClassMapperTest.class);
+ mapper.addPermission(new RegExpTypePermission(".*Test", ".*\\.core\\..*", ".*SecurityMapperTest\\$.+"));
+ assertSame(DefaultClassMapperTest.class, mapper.realClass(DefaultClassMapperTest.class.getName()));
+ assertSame(JVM.class, mapper.realClass(JVM.class.getName()));
+ assertSame(QuickWriter.class, mapper.realClass(QuickWriter.class.getName()));
+ assertSame(Foo$_0.class, mapper.realClass(Foo$_0.class.getName()));
+ assertSame(anonymous, mapper.realClass(anonymous.getName()));
+ try {
+ mapper.realClass(String.class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(String.class.getName(), e.getMessage());
+ }
+ }
+
+ public void testNamesWithWildcardPatterns() {
+ class Foo$_0 {}
+ class Foo$_1 {}
+ final Class<?> anonymous = new Object() {}.getClass();
+ register(String.class, JVM.class, QuickWriter.class, Foo$_0.class, Foo$_1.class, anonymous);
+ mapper.addPermission(new WildcardTypePermission("**.*_0", "**.core.*", "**.SecurityMapperTest$?"));
+ assertSame(JVM.class, mapper.realClass(JVM.class.getName()));
+ assertSame(Foo$_0.class, mapper.realClass(Foo$_0.class.getName()));
+ assertSame(anonymous, mapper.realClass(anonymous.getName()));
+ try {
+ mapper.realClass(String.class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(String.class.getName(), e.getMessage());
+ }
+ try {
+ mapper.realClass(QuickWriter.class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(QuickWriter.class.getName(), e.getMessage());
+ }
+ try {
+ mapper.realClass(Foo$_1.class.getName());
+ fail("Thrown " + ForbiddenClassException.class.getName() + " expected");
+ } catch (final ForbiddenClassException e) {
+ assertEquals(Foo$_1.class.getName(), e.getMessage());
+ }
+ }
+}
Property changes on: trunk/xstream/src/test/com/thoughtworks/xstream/mapper/SecurityMapperTest.java
___________________________________________________________________
Added: svn:keywords
Added: svn:eol-style
Modified: trunk/xstream-distribution/src/content/changes.html (2209 => 2210)
--- trunk/xstream-distribution/src/content/changes.html 2014-01-08 17:21:47 UTC (rev 2209)
+++ trunk/xstream-distribution/src/content/changes.html 2014-01-09 19:03:16 UTC (rev 2210)
@@ -63,6 +63,7 @@
<h2>Major changes</h2>
<ul>
+ <li>Add security framework to limit handled types while unmarshalling.</li>
<li>java.bean.EventHandler no longer handled automatically because of severe security vulnerability.</li>
<li>JIRA:XSTR-751: New SunLimitedUnsafeReflectionProvider that uses undocumented features only to allocate new
instances as required on Dalvik.</li>
@@ -82,6 +83,11 @@
<h2>API changes</h2>
<ul>
+ <li>Added package c.t.x.security with interface TypePermission, all its implementations and
+ ForbiddenClassException.</li>
+ <li>Added c.t.x.mapper.SecurityMapper handling the new type permissions.</li>
+ <li>Added methods addPermission, denyPermission, allowTypesXXX and denyTypesXXX to c.t.x.XStream to setup
+ security at unmarshalling time.</li>
<li>Added c.t.x.converters.reflection.SunLimitedUnsafeReflectionProvider.</li>
<li>Deprecated c.t.x.converters.reflection.Sun14ReflectionProvider in favor of new
c.t.x.converters.reflection.SunUnsafeReflectionProvider.</li>
To unsubscribe from this list please visit:
