Updated files attached, including update to State of Serialization draft.
Note that the attached source code can be provided under the Oracle agreement.
I'm a little time poor, so please contribute suggestions etc. Regards, Peter. On 26/07/2014 7:57 PM, Peter Firmstone wrote:
I'm somewhat time poor at present, it's a draft. The intent is to open a discussion to: 1. Address security issues2. Make Serializable2 api completely public and backward compatible with the existing serial stream protocol.3. Allow Serializable to be switched off via a jvm switch On 22/07/2014 1:10 AM, Tom Hawtin wrote:On 20/07/2014 11:57, Peter Firmstone wrote:Since private methods are only be called by the ObjectOutputStream /ObjectInputStream, during de-serialisation, subclass are not responsiblefor calling these methods, hence subclass ProtectionDomain's are not present in the Thread's AccessControlContext and as such are missing from security checks, this is why it's currently essential for classesto ensure that de-serialisation isn't performed in a privileged context.It's more complicated than that. Even final serialisable classes may have security checks.You've highlited an issue with security on the Java platform, it's possible for an object to escape after a security check has been performed by a constructor.It's bad practise for a Serializable object to have a security check from within it's constructor, however a Serializable object may extend an object with a zero argument constructor, such as ClassLoader.The trick for an attacker is to deserialize within a privileged context, even when a ClassCastException occurs, it does so after the object has been created, if an attacker can get a reference to it before it's garbage collected...Prior to calling a constructor, if a class hasn't been loaded, class static initializers are also called.Li Gong proposed the method guard pattern, page 176, inside Java 2 Platform Security, second edition, this fixes the issue of objects that potentially escape, however performance with the existing security infrastructure is a problem, that can be easily fixed, but that's another topic (on security-dev).What we really need to do is enable an administrator to limit classes allowed to be Serialized, via a configuration file, with Java 9, we'llneed to know which module too.To improve security, it would be preferable to use a deserialization constructor, required to be called by subclasses in the class hierarchies, placing their ProtectionDomains in the stack context, avoiding a number of security issues. Another benefit is the ability to use final fields, while checking invariants during construction.Certainly it would be better have a mechanism that better fitted in with non-serialisation mechanisms. Addressing this without unraveling too much when pulling on a thread, and without increasing complexity of corner cases, is non-trivial.By providing a new interface, using only public api, but also the same stream format, an upgrade and backward compatible path can be provided.This allows both interfaces to co-exist and for corner cases to remain supported within Serializable, however it would also allow administrators to switch off Serializable and use only Serializable2.Complicated? How so? Regards, Peter.
State of Serialization.odt
Description: application/vnd.oasis.opendocument.text
Provides I/O interfaces and utility classes.
|
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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.apache.river.api.io; /** * Portable is an extension to the Java Serialization Framework. * * Portable objects are suitable for use as immutable value objects with * final fields that may be freely replicated, alternatively they are also * suited as safely published thread safe mutable objects used * to store service snapshots in a {@link net.jini.io.MarshalledInstance} for * fail over replication to other nodes, or to upgrade a service. * <p> * Portable objects are not serialized, instead they are only created using an * accessible constructor, public static factory method or builder object making them * more suitable for security; validating class invariants and concurrent code * that relies on immutability and safe publication of fields using final or * volatile. * <p> * Portable Objects are created remotely with an AccessControlContext * containing one ProtectionDomain with a CodeSource that has a null location * and null Certificates. * Only minimal permissions granted to any location by the administrator will apply. * Minimal privilege is required to prevent remote instantiation * of ClassLoader, Policy, SecurityManager, or any other type of object with * security checks performed during construction. The developer is free to * use privileged access, including login context from within constructors and * methods. * <p> * The serial form of a Portable object is managed by PortableFactory * and is solely dependant on the classes, parameters and signatures of methods * or constructors. * <p> * Portable Objects with equal PortableFactory's shall be identical * in Object form after un-marshaling within an identical jvm and one * may be substituted for the other to reduce network traffic. * <p> * Portable Objects that are equal in Object form are not guaranteed to be equal * in serial form. The implementor may enforce serial form equality by ensuring * identical methods of creation are used for equal objects and document it in * Javadoc. Portable objects equal in Object form at one node should also be * equal after un-marshaling to a second remote node even when serial form differs. * <p> * Portable Objects (boomerangs) that are duplicated across * nodes may not be equal when returning to a local node after construction and * redistribution on different nodes. Later versions of code may elect to * use different classes, constructors or method signatures that result in * inequality. * <p> * Portable objects while free to evolve and possibly having completely different * classes or being completely unequal after distribution to separate nodes, * must always share a common public interface or superclass for referential * purposes, this may of course be Object, however if it is, it should be stated * clearly in Javadoc to avoid ClassCastException's upon un-marshaling. * <p> * Portable objects have no version, instead PortableFactory contains all * information required to recreate any Portable Object. * For this reason, Portable objects cannot be used as Entry * objects, as they are dependant on published serial form. It may be possible * in a later release to use Portable objects as fields in Entry objects, this * is not supported presently. * <p> * Portable objects are highly recommended for use as value objects in domain * driven design, they may also be used for value objects. PortableFactory can * be used to create the root entity in an aggregate. <p> * Although final is not enforced, all fields should be final or volatile, safe * construction must be honored 'this' must not be allowed to * escape during construction, Portable objects will be exposed to multiple * threads on multiple nodes, without external synchronization. * <p> * Portable objects are thread safe. * <p> * Do not use Portable if you don't intend to honor this contract, use * Serializable instead. * <p> * Caveat:<br> * Portable Objects cannot be stored directly in a * {@link java.rmi.MarshalledObject}, a {@link net.jini.io.MarshalledInstance} * must first be created and converted, also a Portable Object will be * returned as a {@link PortableFactory} when {@link java.rmi.MarshalledObject} * is un-marshaled, a {@link java.rmi.MarshalledObject} must first be * converted to {@link net.jini.io.MarshalledInstance} before un-marshaling. * <p> * @author Peter Firmstone. * @since 3.0.0 */ public interface Portable { /** * Prepare for transport in a PortableObjectOutputStream. * ObjectInput uses PortableFactory to create the Portable Object at the * remote end using a constructor, static method or object method. * * @return A PortableFactory, PortableObjectInputStream uses PortableFactory * to create a Portable Object at the remote end using a constructor, * static method or an object method. */ PortableFactory factory(); }
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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.apache.river.api.io; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Serializable; import java.io.StreamCorruptedException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.ProtocolException; import java.security.AccessControlContext; import java.security.AccessController; import java.security.CodeSource; import java.security.Guard; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.security.cert.Certificate; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; /** * Portable form, required to create objects during de-serialization, * using a constructor, static method or an Object method. * * This object must be Thread confined, it is not thread safe. It should be * created on demand, it is primarily for use by {@link PortableObjectInputStream} * and {@link PortableObjectOutputStream}, it is created by {@link Portable} * implementations. * * Internal state is guarded, arrays are not defensively copied. * * This is compatible with Version 2 of the Java Serialization Protocol. * * @author Peter Firmstone. * @see Portable * @see PortablePermission * @see Serializable * @see Externalizable * @since 3.0.0 */ public final class PortableFactory implements Externalizable { private static final long serialVersionUID = 1L; /* Guard private state */ private static final Guard distributable = new PortablePermission("Portable"); /* Minimal protocol to write primitives directly to stream, only for parameters. * Strings are written as Objects, since they are a special case, identical * strings are sent by reference to the first and not duplicated. * Class is also a special case, it too is not duplicated. * * Primitives are written separately to objects, the first value written * to ObjectOutput is the length of the parameter array, each parameter is * proceeded by a byte header indicating its type. For null values, only the * byte header is written to stream. * */ private static byte VERSION = 1; private static final byte BOOLEAN = 0; private static final byte BYTE = 1; private static final byte CHAR = 2; private static final byte SHORT = 3; private static final byte INT = 4; private static final byte LONG = 5; private static final byte FLOAT = 6; private static final byte DOUBLE = 7; private static final byte OBJECT = 8; private static final byte NULL = 9; // Serial Form private Object classOrObject; private String method; private Class [] parameterTypes; private Object [] parameters; // Private local object state. private int hash; private boolean constructed; // default value is false. /** * Public method provided for java serialization framework. */ public PortableFactory(){ constructed = false; } /** * Reflection is used at the remote end, with information provided to * PortableFactory, to call a constructor, static method * or an object method after de-serialization by PortableObjectInputStream. * <p> * Information given to PortableFactory is guarded by PortablePermission. * <p> * Instantiation of a Portable object at a remote endpoint proceeds as follows: * <ul><li> * If factoryClassOrObject is a Class and methodName is null, a constructor * of that Class is called reflectively with parameterTypes and parameters. * </li><li> * If factoryClassOrObject is a Class and methodName is defined, a static * method with that name is called reflectively on that Class with * parameterTypes and parameters. * </li><li> * If factoryClassOrObject is an Object and methodName is defined, a method * with that name is called reflectively on that Object with * parameterTypes and parameters. * </li></ul> * <p> * Tip: Object versions of primitive values and String parameters * are relatively fast as are primitive arrays. * Object versions of primitive parameters are writen to DataOutput * as primitives. * <p> * Constructor parameters must either be Serializable, Externalizable or * Portable objects. * <p> * Creation is performed using only privileges granted to all CodeSource's, * if there are no default grants set by the policy administrator, the * creation will be performed with no privileges enabled. * <p> * To avoid security vulnerabilities, policy grants to any CodeSource * should be very limited. * <p> * * @param factoryClassOrObject will be used for constructor, factory static method, * or builder Object. * @param methodName name of static factory method, null if using a constructor. * @param parameterTypes Type signature of method or constructor, or null. * @param parameters array of Objects to be passed to constructor, or null. * @see PortablePermission */ public PortableFactory(Object factoryClassOrObject, String methodName, Class[] parameterTypes, Object [] parameters){ classOrObject = factoryClassOrObject; method = methodName; this.parameterTypes = parameterTypes; this.parameters = parameters; constructed = true; if ( (parameterTypes != null && parameterTypes.length != parameters.length) || (parameters != null && parameters.length > 127)) throw new IllegalArgumentException("Array lengths don't match, or arrays are too long," + " parameter array limit 127, " + "you need to see a shrink if you need this many parameters"); int hash = 7; hash = 89 * hash + (this.classOrObject != null ? this.classOrObject.hashCode() : 0); hash = 89 * hash + (this.method != null ? this.method.hashCode() : 0); hash = 89 * hash + Arrays.hashCode(this.parameterTypes); hash = 89 * hash + Arrays.deepHashCode(this.parameters); this.hash = hash; } Object create() throws IOException { // Perform creation with minimum privileges, so remote code cannot // create URLClassLoader etc. // a CodeSource with null URL is used instead of a null CodeSource so // that an administrator can grant limited default privileges if desired. // Eg to read a default system property. AccessControlContext acc; ProtectionDomain [] pd = new ProtectionDomain[1]; pd[0] = new ProtectionDomain(new CodeSource(null,(Certificate[]) null), null); acc = new AccessControlContext(pd); try { return AccessController.doPrivileged(new PrivilegedExceptionAction(){ @Override public Object run() throws Exception { Method m; Constructor c; Class clazz; boolean object; if (classOrObject instanceof Class) { clazz = (Class) classOrObject; object = false; } else { clazz = classOrObject.getClass(); object = true; } if (method != null){ m = clazz.getMethod(method, parameterTypes); if (object) return m.invoke(classOrObject, parameters); return m.invoke(null, parameters); } else { c = clazz.getConstructor(parameterTypes); return c.newInstance(parameters); } } } , acc); } catch (PrivilegedActionException ex) { Logger.getLogger(PortableFactory.class.getName()).log(Level.SEVERE, null, ex); throw new IOException(ex); } } // Inherit documentation public void writeExternal(ObjectOutput out) throws IOException { distributable.checkGuard(null); if (! constructed) throw new IOException("Attempt to write blank PortableFactory"); out.writeByte(VERSION); out.writeObject(classOrObject); out.writeObject(method); /* don't clone arrays for defensive copies, it's up to constructing * object to do so if needs to. */ out.writeObject(parameterTypes); int l = parameterTypes != null ? parameterTypes.length : 0; for (int i = 0; i < l; i++){ writeObject(parameters[i], out); } } /** * Object primitive values parameters are sent as their values to avoid * Serialization overhead, this is only performed because primitives aren't * Objects so can't be used directly. Primitive arrays are Objects so * they can be used, therefore there's no need to handle them here. */ private void writeObject(Object o, ObjectOutput out ) throws IOException{ if (o == null) { out.writeByte(NULL); return; } if (o instanceof Boolean){ out.writeByte(BOOLEAN); out.writeBoolean(((Boolean) o).booleanValue()); return; } if (o instanceof Byte){ out.writeByte(BYTE); out.writeByte(((Byte)o).byteValue()); return; } if (o instanceof Character){ out.writeByte(CHAR); out.writeChar(((Character)o).charValue()); return; } if (o instanceof Short){ out.writeByte(SHORT); out.writeShort(((Short)o).shortValue()); return; } if (o instanceof Integer){ out.writeByte(INT); out.writeInt(((Integer)o).intValue()); return; } if (o instanceof Long){ out.writeByte(LONG); out.writeLong(((Long)o).longValue()); return; } if (o instanceof Float){ out.writeByte(FLOAT); out.writeFloat(((Float)o).floatValue()); return; } if (o instanceof Double){ out.writeByte(DOUBLE); out.writeDouble(((Double)o).doubleValue()); return; } // Arrays are treated as Objects, java serialization is relatively // efficient with primitive arrays. out.writeByte(OBJECT); out.writeObject(o); } private Object readObject(ObjectInput in) throws IOException, ClassNotFoundException{ byte b = in.readByte(); switch(b){ case BOOLEAN: boolean bool = in.readBoolean(); return Boolean.valueOf(bool); case BYTE: byte bite = in.readByte(); return Byte.valueOf(bite); case CHAR: char ch = in.readChar(); return Character.valueOf(ch); case SHORT: short sh = in.readShort(); return Short.valueOf(sh); case INT: int i = in.readInt(); return Integer.valueOf(i); case LONG: long l = in.readLong(); return Long.valueOf(l); case FLOAT: float f = in.readFloat(); return Float.valueOf(f); case DOUBLE: double d = in.readDouble(); return Double.valueOf(d); case OBJECT: return in.readObject(); case NULL: return null; default: throw new StreamCorruptedException("out of range byte read from stream"); } } // Inherit documentation. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { /* If created during deserialisation, we needn't be defensive, this * will never be shared with other threads and will be replaced by * a fully constructed thread safe immutable object. */ if (constructed) throw new IllegalStateException("Object already constructed"); constructed = true; /* Don't defensively copy arrays, the object is used immediately after * deserialization to construct the Portable Object, the fields are * not accessed again, it is up to creator methods themselves to * preserve invariants. */ byte version = in.readByte(); // In future we could potentially handle different versions, but for now, // bail out. if (version != VERSION) throw new ProtocolException("Incompatible PortableFactory protocol"); classOrObject = in.readObject(); method = (String) in.readObject(); parameterTypes = (Class[]) in.readObject(); int len = parameterTypes != null ? parameterTypes.length : 0; parameters = len == 0 ? null : new Object[len]; for (int i = 0; i < len; i++){ parameters[i] = readObject(in); } int hash = 7; hash = 89 * hash + (this.classOrObject != null ? this.classOrObject.hashCode() : 0); hash = 89 * hash + (this.method != null ? this.method.hashCode() : 0); hash = 89 * hash + Arrays.hashCode(this.parameterTypes); hash = 89 * hash + Arrays.deepHashCode(this.parameters); this.hash = hash; } // equals and hashcode are implemented to avoid sending duplicates in // object streams. @Override public int hashCode() { return hash; } @Override public boolean equals(Object o){ if (!(o instanceof PortableFactory)) return false; if ( hash != o.hashCode()) return false; PortableFactory other = (PortableFactory) o; if ( classOrObject == null && other.classOrObject != null) return false; if ( classOrObject != null && ! classOrObject.equals(other.classOrObject)) return false; if ( method == null && other.method != null) return false; if ( method != null && ! method.equals(other.method)) return false; if (!Arrays.equals(parameterTypes, other.parameterTypes)) return false; if (!Arrays.deepEquals(parameters, other.parameters)) return false; return true; // A locally constructed instance may be equal to a deserialized one. } }
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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.apache.river.api.io; import java.io.IOException; import java.io.InputStream; import java.io.InvalidClassException; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; import java.security.AccessController; import java.security.Guard; import java.security.PrivilegedAction; import java.util.Set; import java.util.TreeSet; /** * PortableObjectInputStream, an extension to Java serialization that allows * for immutable and safely constructed objects * * @author Peter. * @since 3.0.0 */ public class PortableObjectInputStream extends ObjectInputStream { private final Set<String> allowed; private final Guard check = new PortablePermission("Trust"); private final InputStreamWrapper in; private boolean trusted; public static ObjectInputStream create(InputStream in) throws IOException{ PortableObjectInputStream result = new PortableObjectInputStream( new InputStreamWrapper(in, 0L), new TreeSet<String>(), true ); AccessController.doPrivileged(new EnableResolveObject(result)); return result; } public static ObjectInputStream create(InputStream in, Set<String> allowedClassNames, long allowedBytes, boolean trusted ) throws IOException { PortableObjectInputStream result = new PortableObjectInputStream( new InputStreamWrapper(in, allowedBytes), new TreeSet<String>(allowedClassNames), trusted ); AccessController.doPrivileged(new EnableResolveObject(result)); return result; } /** * Caller must have SerializablePermission("enableSubstitution") to call * this method. * * @param in * @throws IOException */ protected PortableObjectInputStream(InputStream in) throws IOException{ this(new InputStreamWrapper(in, 0L), new TreeSet<String>(), true); } protected PortableObjectInputStream(InputStream in, Set<String> allowedClassNames, long allowedBytes) throws IOException { this( new InputStreamWrapper(in, allowedBytes), new TreeSet<String>(allowedClassNames), false ); } private PortableObjectInputStream(InputStreamWrapper in, Set<String> allowedClassNames, boolean trusted) throws IOException { super(in); allowed = allowedClassNames; this.in = in; this.trusted = trusted; } public void trusted(boolean trust){ check.checkGuard(null); trusted = trust; } public void addTrusted(String className){ check.checkGuard(null); allowed.add(className); } public void counting(boolean counting){ check.checkGuard(null); in.counting(counting); } private void enableResolveObject(){ super.enableResolveObject(true); } @Override protected Object resolveObject(Object o) throws IOException{ if (o instanceof PortableFactory) return ((PortableFactory)o).create(); return o; } /* * Only deserialize allowed instances of classes */ @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String className = desc.getName(); if (!trusted && !allowed.contains(className)) { throw new InvalidClassException( "Unauthorized deserialization attempt", className); } return super.resolveClass(desc); } private static class EnableResolveObject implements PrivilegedAction{ private final PortableObjectInputStream in; EnableResolveObject(PortableObjectInputStream in){ this.in = in; } @Override public Object run() { in.enableResolveObject(); return null; } } private static class InputStreamWrapper extends InputStream { private final InputStream in; private final long limit; private long count; private boolean counting; InputStreamWrapper(InputStream in, long limit){ this.in = in; this.counting = limit != 0; this.count = 0; this.limit = limit; } private void check() throws IOException { if (count > limit) { close(); throw new IOException("Allowed stream length exceeded"); } } void counting(boolean counting){ this.counting = counting; } @Override public int read() throws IOException { if (counting){ count++; check(); } return in.read(); } @Override public int read(byte[] arg0) throws IOException { if (counting){ count = count + arg0.length; check(); } return in.read(arg0); } @Override public int read(byte[] arg0, int arg1, int arg2) throws IOException { if (counting){ count = count + arg2; check(); } return in.read(arg0, arg1, arg2); } @Override public long skip(long n) throws IOException { return in.skip(n); } @Override public int available() throws IOException { return in.available(); } @Override public void close() throws IOException { in.close(); } @Override public void mark(int readlimit) { in.mark(readlimit); } @Override public void reset() throws IOException { in.reset(); } @Override public boolean markSupported() { return in.markSupported(); } } }
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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.apache.river.api.io; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.security.AccessController; import java.security.PrivilegedAction; /** * PortableObjectOutputStream replaces @ref{Portable} instances * in the OutputStream with a PortableFactory that recreates the * Portable Object during unmarshalling. * * @author peter * @since 3.0.0 */ public class PortableObjectOutputStream extends ObjectOutputStream { public static ObjectOutputStream create(OutputStream out) throws IOException{ PortableObjectOutputStream result = new PortableObjectOutputStream(out); AccessController.doPrivileged(new EnableReplaceObject(result)); return result; } protected PortableObjectOutputStream (OutputStream out) throws IOException{ super(out); } @Override protected Object replaceObject(Object o){ if (o instanceof Portable) return ((Portable)o).factory(); return o; } private void enableReplaceObject(){ super.enableReplaceObject(true); } private static class EnableReplaceObject implements PrivilegedAction{ private final PortableObjectOutputStream out; EnableReplaceObject(PortableObjectOutputStream out){ this.out = out; } @Override public Object run() { out.enableReplaceObject(); return null; } } }
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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.apache.river.api.io; import java.io.ObjectOutput; import java.security.BasicPermission; /** * This Permission allows an object to be Portable by an implementation of * ObjectOutput * * @author peter * @see PortableFactory * @see ObjectOutput * @since 3.0.0 */ public class PortablePermission extends BasicPermission{ private static final long serialVersionUID = 1L; public PortablePermission(String name){ super(name); } }