OpenJDK / portola / portola
changeset 25188:e680ab7f208e
8015081: javax.security.auth.Subject.toString() throws NPE
Reviewed-by: xuelei, weijun
Contributed-by: Jamil Nimeh <jamil.j.nimeh@oracle.com>
author | ascarpino |
---|---|
date | Tue, 01 Jul 2014 09:46:20 -0700 |
parents | 08aff438def8 |
children | 2476da7d73bf |
files | jdk/src/share/classes/javax/security/auth/Subject.java jdk/test/javax/security/auth/Subject/Generic.java jdk/test/javax/security/auth/Subject/Serial.java jdk/test/javax/security/auth/Subject/Subject.java jdk/test/javax/security/auth/Subject/SubjectNullTests.java |
diffstat | 5 files changed, 1172 insertions(+), 90 deletions(-) [+] |
line wrap: on
line diff
--- a/jdk/src/share/classes/javax/security/auth/Subject.java Tue Jul 01 14:44:37 2014 +0100 +++ b/jdk/src/share/classes/javax/security/auth/Subject.java Tue Jul 01 09:46:20 2014 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -142,7 +142,9 @@ * <p> The newly constructed Sets check whether this {@code Subject} * has been set read-only before permitting subsequent modifications. * The newly created Sets also prevent illegal modifications - * by ensuring that callers have sufficient permissions. + * by ensuring that callers have sufficient permissions. These Sets + * also prohibit null elements, and attempts to add or query a null + * element will result in a {@code NullPointerException}. * * <p> To modify the Principals Set, the caller must have * {@code AuthPermission("modifyPrincipals")}. @@ -170,7 +172,9 @@ * These newly created Sets check whether this {@code Subject} * has been set read-only before permitting subsequent modifications. * The newly created Sets also prevent illegal modifications - * by ensuring that callers have sufficient permissions. + * by ensuring that callers have sufficient permissions. These Sets + * also prohibit null elements, and attempts to add or query a null + * element will result in a {@code NullPointerException}. * * <p> To modify the Principals Set, the caller must have * {@code AuthPermission("modifyPrincipals")}. @@ -194,17 +198,16 @@ * * @exception NullPointerException if the specified * {@code principals}, {@code pubCredentials}, - * or {@code privCredentials} are {@code null}. + * or {@code privCredentials} are {@code null}, + * or a null value exists within any of these three + * Sets. */ public Subject(boolean readOnly, Set<? extends Principal> principals, Set<?> pubCredentials, Set<?> privCredentials) { - - if (principals == null || - pubCredentials == null || - privCredentials == null) - throw new NullPointerException - (ResourcesMgr.getString("invalid.null.input.s.")); + collectionNullClean(principals); + collectionNullClean(pubCredentials); + collectionNullClean(privCredentials); this.principals = Collections.synchronizedSet(new SecureSet<Principal> (this, PRINCIPAL_SET, principals)); @@ -287,18 +290,17 @@ sm.checkPermission(AuthPermissionHolder.GET_SUBJECT_PERMISSION); } - if (acc == null) { - throw new NullPointerException(ResourcesMgr.getString + Objects.requireNonNull(acc, ResourcesMgr.getString ("invalid.null.AccessControlContext.provided")); - } // return the Subject from the DomainCombiner of the provided context return AccessController.doPrivileged (new java.security.PrivilegedAction<Subject>() { public Subject run() { DomainCombiner dc = acc.getDomainCombiner(); - if (!(dc instanceof SubjectDomainCombiner)) + if (!(dc instanceof SubjectDomainCombiner)) { return null; + } SubjectDomainCombiner sdc = (SubjectDomainCombiner)dc; return sdc.getSubject(); } @@ -347,9 +349,9 @@ if (sm != null) { sm.checkPermission(AuthPermissionHolder.DO_AS_PERMISSION); } - if (action == null) - throw new NullPointerException - (ResourcesMgr.getString("invalid.null.action.provided")); + + Objects.requireNonNull(action, + ResourcesMgr.getString("invalid.null.action.provided")); // set up the new Subject-based AccessControlContext // for doPrivileged @@ -410,9 +412,8 @@ sm.checkPermission(AuthPermissionHolder.DO_AS_PERMISSION); } - if (action == null) - throw new NullPointerException - (ResourcesMgr.getString("invalid.null.action.provided")); + Objects.requireNonNull(action, + ResourcesMgr.getString("invalid.null.action.provided")); // set up the new Subject-based AccessControlContext for doPrivileged final AccessControlContext currentAcc = AccessController.getContext(); @@ -467,9 +468,8 @@ sm.checkPermission(AuthPermissionHolder.DO_AS_PRIVILEGED_PERMISSION); } - if (action == null) - throw new NullPointerException - (ResourcesMgr.getString("invalid.null.action.provided")); + Objects.requireNonNull(action, + ResourcesMgr.getString("invalid.null.action.provided")); // set up the new Subject-based AccessControlContext // for doPrivileged @@ -534,9 +534,8 @@ sm.checkPermission(AuthPermissionHolder.DO_AS_PRIVILEGED_PERMISSION); } - if (action == null) - throw new NullPointerException - (ResourcesMgr.getString("invalid.null.action.provided")); + Objects.requireNonNull(action, + ResourcesMgr.getString("invalid.null.action.provided")); // set up the new Subject-based AccessControlContext for doPrivileged final AccessControlContext callerAcc = @@ -557,13 +556,14 @@ return java.security.AccessController.doPrivileged (new java.security.PrivilegedAction<AccessControlContext>() { public AccessControlContext run() { - if (subject == null) + if (subject == null) { return new AccessControlContext(acc, null); - else + } else { return new AccessControlContext (acc, new SubjectDomainCombiner(subject)); } + } }); } @@ -615,9 +615,8 @@ */ public <T extends Principal> Set<T> getPrincipals(Class<T> c) { - if (c == null) - throw new NullPointerException - (ResourcesMgr.getString("invalid.null.Class.provided")); + Objects.requireNonNull(c, + ResourcesMgr.getString("invalid.null.Class.provided")); // always return an empty Set instead of null // so LoginModules can add to the Set if necessary @@ -711,9 +710,8 @@ */ public <T> Set<T> getPublicCredentials(Class<T> c) { - if (c == null) - throw new NullPointerException - (ResourcesMgr.getString("invalid.null.Class.provided")); + Objects.requireNonNull(c, + ResourcesMgr.getString("invalid.null.Class.provided")); // always return an empty Set instead of null // so LoginModules can add to the Set if necessary @@ -758,9 +756,8 @@ // would do is protect the set operations themselves // (like size()), which don't seem security-sensitive. - if (c == null) - throw new NullPointerException - (ResourcesMgr.getString("invalid.null.Class.provided")); + Objects.requireNonNull(c, + ResourcesMgr.getString("invalid.null.Class.provided")); // always return an empty Set instead of null // so LoginModules can add to the Set if necessary @@ -790,11 +787,13 @@ */ public boolean equals(Object o) { - if (o == null) + if (o == null) { return false; + } - if (this == o) + if (this == o) { return true; + } if (o instanceof Subject) { @@ -969,11 +968,10 @@ Set<Principal> inputPrincs = (Set<Principal>)gf.get("principals", null); + Objects.requireNonNull(inputPrincs, + ResourcesMgr.getString("invalid.null.input.s.")); + // Rewrap the principals into a SecureSet - if (inputPrincs == null) { - throw new NullPointerException - (ResourcesMgr.getString("invalid.null.input.s.")); - } try { principals = Collections.synchronizedSet(new SecureSet<Principal> (this, PRINCIPAL_SET, inputPrincs)); @@ -993,13 +991,43 @@ } /** + * Tests for null-clean collections (both non-null reference and + * no null elements) + * + * @param coll A {@code Collection} to be tested for null references + * + * @exception NullPointerException if the specified collection is either + * {@code null} or contains a {@code null} element + */ + private static void collectionNullClean(Collection<?> coll) { + boolean hasNullElements = false; + + Objects.requireNonNull(coll, + ResourcesMgr.getString("invalid.null.input.s.")); + + try { + hasNullElements = coll.contains(null); + } catch (NullPointerException npe) { + // A null-hostile collection may choose to throw + // NullPointerException if contains(null) is called on it + // rather than returning false. + // If this happens we know the collection is null-clean. + hasNullElements = false; + } finally { + if (hasNullElements) { + throw new NullPointerException + (ResourcesMgr.getString("invalid.null.input.s.")); + } + } + } + + /** * Prevent modifications unless caller has permission. * * @serial include */ private static class SecureSet<E> - extends AbstractSet<E> - implements java.io.Serializable { + implements Set<E>, java.io.Serializable { private static final long serialVersionUID = 7911754171111800359L; @@ -1098,6 +1126,9 @@ public boolean add(E o) { + Objects.requireNonNull(o, + ResourcesMgr.getString("invalid.null.input.s.")); + if (subject.isReadOnly()) { throw new IllegalStateException (ResourcesMgr.getString("Subject.is.read.only")); @@ -1133,12 +1164,16 @@ // check for duplicates if (!elements.contains(o)) return elements.add(o); - else + else { return false; } + } public boolean remove(Object o) { + Objects.requireNonNull(o, + ResourcesMgr.getString("invalid.null.input.s.")); + final Iterator<E> e = iterator(); while (e.hasNext()) { E next; @@ -1153,12 +1188,7 @@ }); } - if (next == null) { - if (o == null) { - e.remove(); - return true; - } - } else if (next.equals(o)) { + if (next.equals(o)) { e.remove(); return true; } @@ -1167,6 +1197,10 @@ } public boolean contains(Object o) { + + Objects.requireNonNull(o, + ResourcesMgr.getString("invalid.null.input.s.")); + final Iterator<E> e = iterator(); while (e.hasNext()) { E next; @@ -1194,19 +1228,28 @@ }); } - if (next == null) { - if (o == null) { - return true; - } - } else if (next.equals(o)) { + if (next.equals(o)) { return true; } } return false; } + public boolean addAll(Collection<? extends E> c) { + boolean result = false; + + collectionNullClean(c); + + for (E item : c) { + result |= this.add(item); + } + + return result; + } + public boolean removeAll(Collection<?> c) { - Objects.requireNonNull(c); + collectionNullClean(c); + boolean modified = false; final Iterator<E> e = iterator(); while (e.hasNext()) { @@ -1224,30 +1267,34 @@ Iterator<?> ce = c.iterator(); while (ce.hasNext()) { - Object o = ce.next(); - if (next == null) { - if (o == null) { + if (next.equals(ce.next())) { e.remove(); modified = true; break; } - } else if (next.equals(o)) { - e.remove(); - modified = true; - break; - } } } return modified; } + public boolean containsAll(Collection<?> c) { + collectionNullClean(c); + + for (Object item : c) { + if (this.contains(item) == false) { + return false; + } + } + + return true; + } + public boolean retainAll(Collection<?> c) { - Objects.requireNonNull(c); + collectionNullClean(c); + boolean modified = false; - boolean retain = false; final Iterator<E> e = iterator(); while (e.hasNext()) { - retain = false; E next; if (which != Subject.PRIV_CREDENTIAL_SET) { next = e.next(); @@ -1260,26 +1307,12 @@ }); } - Iterator<?> ce = c.iterator(); - while (ce.hasNext()) { - Object o = ce.next(); - if (next == null) { - if (o == null) { - retain = true; - break; - } - } else if (next.equals(o)) { - retain = true; - break; + if (c.contains(next) == false) { + e.remove(); + modified = true; } } - if (!retain) { - e.remove(); - retain = false; - modified = true; - } - } return modified; } @@ -1301,6 +1334,73 @@ } } + public boolean isEmpty() { + return elements.isEmpty(); + } + + public Object[] toArray() { + final Iterator<E> e = iterator(); + while (e.hasNext()) { + // The next() method performs a security manager check + // on each element in the SecureSet. If we make it all + // the way through we should be able to simply return + // element's toArray results. Otherwise we'll let + // the SecurityException pass up the call stack. + e.next(); + } + + return elements.toArray(); + } + + public <T> T[] toArray(T[] a) { + final Iterator<E> e = iterator(); + while (e.hasNext()) { + // The next() method performs a security manager check + // on each element in the SecureSet. If we make it all + // the way through we should be able to simply return + // element's toArray results. Otherwise we'll let + // the SecurityException pass up the call stack. + e.next(); + } + + return elements.toArray(a); + } + + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof Set)) { + return false; + } + + Collection<?> c = (Collection<?>) o; + if (c.size() != size()) { + return false; + } + + try { + return containsAll(c); + } catch (ClassCastException unused) { + return false; + } catch (NullPointerException unused) { + return false; + } + } + + public int hashCode() { + int h = 0; + Iterator<E> i = iterator(); + while (i.hasNext()) { + E obj = i.next(); + if (obj != null) { + h += obj.hashCode(); + } + } + return h; + } + /** * Writes this object out to a stream (i.e., serializes it). * @@ -1338,12 +1438,16 @@ which = fields.get("which", 0); LinkedList<E> tmp = (LinkedList<E>) fields.get("elements", null); + + Subject.collectionNullClean(tmp); + if (tmp.getClass() != LinkedList.class) { elements = new LinkedList<E>(tmp); } else { elements = tmp; } } + } /**
--- a/jdk/test/javax/security/auth/Subject/Generic.java Tue Jul 01 14:44:37 2014 +0100 +++ b/jdk/test/javax/security/auth/Subject/Generic.java Tue Jul 01 09:46:20 2014 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2014 Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,7 @@ */ import java.security.*; -import javax.security.auth.*; +import javax.security.auth.Subject; public class Generic { public static void main(String[] args) throws Exception {
--- a/jdk/test/javax/security/auth/Subject/Serial.java Tue Jul 01 14:44:37 2014 +0100 +++ b/jdk/test/javax/security/auth/Subject/Serial.java Tue Jul 01 09:46:20 2014 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2002, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,7 @@ * @run main/othervm/policy=Serial.policy Serial */ -import javax.security.auth.*; +import javax.security.auth.Subject; import java.io.*; import java.util.*;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/javax/security/auth/Subject/Subject.java Tue Jul 01 09:46:20 2014 -0700 @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * An implementation of the Subject class that provides basic functionality + * for the construction of Subject objects with null Principal elements. + * This is a helper class for serialization tests tied to bug 8015081 + * (see SubjectNullTests.java). + */ +package jjjjj.security.auth; + +import sun.misc.HexDumpEncoder; +import javax.management.remote.JMXPrincipal; +import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.x500.X500Principal; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.lang.Exception; +import java.lang.RuntimeException; +import java.security.Principal; +import java.util.AbstractSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Set; + +import java.io.FileOutputStream; + +public class Subject implements java.io.Serializable { + + private static final long serialVersionUID = -8308522755600156056L; + + Set<Principal> principals; + private volatile boolean readOnly = false; + private static final int PRINCIPAL_SET = 1; + + public Subject(Set<? extends Principal> principals) { + this.principals = Collections.synchronizedSet(new SecureSet<Principal> + (this, PRINCIPAL_SET, principals)); + } + + public Set<Principal> getPrincipals() { + return principals; + } + + private static class SecureSet<E> + extends AbstractSet<E> + implements java.io.Serializable { + + private static final long serialVersionUID = 7911754171111800359L; + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("this$0", Subject.class), + new ObjectStreamField("elements", LinkedList.class), + new ObjectStreamField("which", int.class) + }; + + Subject subject; + LinkedList<E> elements; + private int which; + + SecureSet(Subject subject, int which, Set<? extends E> set) { + this.subject = subject; + this.which = which; + this.elements = new LinkedList<E>(set); + } + + public Iterator<E> iterator() { + return elements.iterator(); + } + + public int size() { + return elements.size(); + } + + private void writeObject(java.io.ObjectOutputStream oos) + throws java.io.IOException { + + ObjectOutputStream.PutField fields = oos.putFields(); + fields.put("this$0", subject); + fields.put("elements", elements); + fields.put("which", which); + oos.writeFields(); + } + } + + public static byte[] enc(Object obj) { + try { + HexDumpEncoder hex = new HexDumpEncoder(); + ByteArrayOutputStream bout; + bout = new ByteArrayOutputStream(); + new ObjectOutputStream(bout).writeObject(obj); + byte[] data = bout.toByteArray(); + for (int i = 0; i < data.length - 5; i++) { + if (data[i] == 'j' && data[i + 1] == 'j' && data[i + 2] == 'j' + && data[i + 3] == 'j' && data[i + 4] == 'j') { + System.arraycopy("javax".getBytes(), 0, data, i, 5); + } + } + return data; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/javax/security/auth/Subject/SubjectNullTests.java Tue Jul 01 09:46:20 2014 -0700 @@ -0,0 +1,852 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8015081 + * @compile Subject.java + * @compile SubjectNullTests.java + * @build SubjectNullTests + * @run main SubjectNullTests + * @summary javax.security.auth.Subject.toString() throws NPE + */ + +import java.io.File; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.io.IOException; +import java.security.Principal; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import javax.management.remote.JMXPrincipal; +import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; +import javax.security.auth.kerberos.KerberosPrincipal; + +public class SubjectNullTests { + + // Value templates for the constructor + private static Principal[] princVals = { + new X500Principal("CN=Tom Sawyer, ST=Missouri, C=US"), + new JMXPrincipal("Huckleberry Finn"), + new KerberosPrincipal("mtwain/author@LITERATURE.US") + }; + private static String[] pubVals = {"tsawyer", "hfinn", "mtwain"}; + private static String[] privVals = {"th3R!v3r", "oNth3R4ft", "5Cl3M3nz"}; + + // Templates for collection-based modifiers for the Subject + private static Principal[] tmplAddPrincs = { + new X500Principal("CN=John Doe, O=Bogus Corp."), + new KerberosPrincipal("jdoe/admin@BOGUSCORP.COM") + }; + private static String[] tmplAddPubVals = {"jdoe", "djoe"}; + private static String[] tmplAddPrvVals = {"b4dpa55w0rd", "pass123"}; + + /** + * Byte arrays used for deserialization: + * These byte arrays contain serialized Subjects and SecureSets, + * either with or without nulls. These use + * jjjjj.security.auth.Subject, which is a modified Subject + * implementation that allows the addition of null elements + */ + private static final byte[] SUBJ_NO_NULL = + jjjjj.security.auth.Subject.enc(makeSubjWithNull(false)); + private static final byte[] SUBJ_WITH_NULL = + jjjjj.security.auth.Subject.enc(makeSubjWithNull(true)); + private static final byte[] PRIN_NO_NULL = + jjjjj.security.auth.Subject.enc(makeSecSetWithNull(false)); + private static final byte[] PRIN_WITH_NULL = + jjjjj.security.auth.Subject.enc(makeSecSetWithNull(true)); + + /** + * Method to allow creation of a subject that can optionally + * insert a null reference into the principals Set. + */ + private static jjjjj.security.auth.Subject makeSubjWithNull( + boolean nullPrinc) { + Set<Principal> setPrinc = new HashSet<>(Arrays.asList(princVals)); + if (nullPrinc) { + setPrinc.add(null); + } + + return (new jjjjj.security.auth.Subject(setPrinc)); + } + + /** + * Method to allow creation of a SecureSet that can optionally + * insert a null reference. + */ + private static Set<Principal> makeSecSetWithNull(boolean nullPrinc) { + Set<Principal> setPrinc = new HashSet<>(Arrays.asList(princVals)); + if (nullPrinc) { + setPrinc.add(null); + } + + jjjjj.security.auth.Subject subj = + new jjjjj.security.auth.Subject(setPrinc); + + return subj.getPrincipals(); + } + + /** + * Construct a subject, and optionally place a null in any one + * of the three Sets used to initialize a Subject's values + */ + private static Subject makeSubj(boolean nullPrinc, boolean nullPub, + boolean nullPriv) { + Set<Principal> setPrinc = new HashSet<>(Arrays.asList(princVals)); + Set<String> setPubCreds = new HashSet<>(Arrays.asList(pubVals)); + Set<String> setPrvCreds = new HashSet<>(Arrays.asList(privVals)); + + if (nullPrinc) { + setPrinc.add(null); + } + + if (nullPub) { + setPubCreds.add(null); + } + + if (nullPriv) { + setPrvCreds.add(null); + } + + return (new Subject(false, setPrinc, setPubCreds, setPrvCreds)); + } + + /** + * Provide a simple interface for abstracting collection-on-collection + * functions + */ + public interface Function { + boolean execCollection(Set<?> subjSet, Collection<?> actorData); + } + + public static final Function methAdd = new Function() { + @SuppressWarnings("unchecked") + @Override + public boolean execCollection(Set<?> subjSet, Collection<?> actorData) { + return subjSet.addAll((Collection)actorData); + } + }; + + public static final Function methContains = new Function() { + @Override + public boolean execCollection(Set<?> subjSet, Collection<?> actorData) { + return subjSet.containsAll(actorData); + } + }; + + public static final Function methRemove = new Function() { + @Override + public boolean execCollection(Set<?> subjSet, Collection<?> actorData) { + return subjSet.removeAll(actorData); + } + }; + + public static final Function methRetain = new Function() { + @Override + public boolean execCollection(Set<?> subjSet, Collection<?> actorData) { + return subjSet.retainAll(actorData); + } + }; + + /** + * Run a test using a specified Collection method upon a Subject's + * SecureSet fields. This method expects NullPointerExceptions + * to be thrown, and throws RuntimeException when the operation + * succeeds + */ + private static void nullTestCollection(Function meth, Set<?> subjSet, + Collection<?> actorData) { + try { + meth.execCollection(subjSet, actorData); + throw new RuntimeException("Failed to throw NullPointerException"); + } catch (NullPointerException npe) { + System.out.println("Caught expected NullPointerException [PASS]"); + } + } + + /** + * Run a test using a specified Collection method upon a Subject's + * SecureSet fields. This method expects the function and arguments + * passed in to complete without exception. It returns false + * if either an exception occurs or the result of the operation is + * false. + */ + private static boolean validTestCollection(Function meth, Set<?> subjSet, + Collection<?> actorData) { + boolean result = false; + + try { + result = meth.execCollection(subjSet, actorData); + } catch (Exception exc) { + System.out.println("Caught exception " + exc); + } + + return result; + } + + /** + * Deserialize an object from a byte array. + * + * @param type The {@code Class} that the serialized file is supposed + * to contain. + * @param serBuffer The byte array containing the serialized object data + * + * @return An object of the type specified in the {@code type} parameter + */ + private static <T> T deserializeBuffer(Class<T> type, byte[] serBuffer) + throws IOException, ClassNotFoundException { + + ByteArrayInputStream bis = new ByteArrayInputStream(serBuffer); + ObjectInputStream ois = new ObjectInputStream(bis); + + T newObj = type.cast(ois.readObject()); + ois.close(); + bis.close(); + + return newObj; + } + + private static void testCTOR() { + System.out.println("------ constructor ------"); + + try { + // Case 1: Create a subject with a null principal + // Expected result: NullPointerException + Subject mtSubj = makeSubj(true, false, false); + throw new RuntimeException( + "constructor [principal w/ null]: Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println("constructor [principal w/ null]: " + + "NullPointerException [PASS]"); + } + + try { + // Case 2: Create a subject with a null public credential element + // Expected result: NullPointerException + Subject mtSubj = makeSubj(false, true, false); + throw new RuntimeException( + "constructor [pub cred w/ null]: Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println("constructor [pub cred w/ null]: " + + "NullPointerException [PASS]"); + } + + try { + // Case 3: Create a subject with a null private credential element + // Expected result: NullPointerException + Subject mtSubj = makeSubj(false, false, true); + throw new RuntimeException( + "constructor [priv cred w/ null]: Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println("constructor [priv cred w/ null]: " + + "NullPointerException [PASS]"); + } + + // Case 4: Create a new subject using the principals, public + // and private credentials from another well-formed subject + // Expected result: Successful construction + Subject srcSubj = makeSubj(false, false, false); + Subject mtSubj = new Subject(false, srcSubj.getPrincipals(), + srcSubj.getPublicCredentials(), + srcSubj.getPrivateCredentials()); + System.out.println("Construction from another well-formed Subject's " + + "principals/creds [PASS]"); + } + + @SuppressWarnings("unchecked") + private static void testDeserialize() throws Exception { + System.out.println("------ deserialize -----"); + + Subject subj = null; + Set<Principal> prin = null; + + // Case 1: positive deserialization test of a Subject + // Expected result: well-formed Subject + subj = deserializeBuffer(Subject.class, SUBJ_NO_NULL); + System.out.println("Positive deserialization test (Subject) passed"); + + // Case 2: positive deserialization test of a SecureSet + // Expected result: well-formed Set + prin = deserializeBuffer(Set.class, PRIN_NO_NULL); + System.out.println("Positive deserialization test (SecureSet) passed"); + + System.out.println( + "* Testing deserialization with null-poisoned objects"); + // Case 3: deserialization test of a null-poisoned Subject + // Expected result: NullPointerException + try { + subj = deserializeBuffer(Subject.class, SUBJ_WITH_NULL); + throw new RuntimeException("Failed to throw NullPointerException"); + } catch (NullPointerException npe) { + System.out.println("Caught expected NullPointerException [PASS]"); + } + + // Case 4: deserialization test of a null-poisoned SecureSet + // Expected result: NullPointerException + try { + prin = deserializeBuffer(Set.class, PRIN_WITH_NULL); + throw new RuntimeException("Failed to throw NullPointerException"); + } catch (NullPointerException npe) { + System.out.println("Caught expected NullPointerException [PASS]"); + } + } + + private static void testAdd() { + System.out.println("------ add() ------"); + // Create a well formed subject + Subject mtSubj = makeSubj(false, false, false); + + try { + // Case 1: Attempt to add null values to principal + // Expected result: NullPointerException + mtSubj.getPrincipals().add(null); + throw new RuntimeException( + "PRINCIPAL add(null): Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println( + "PRINCIPAL add(null): NullPointerException [PASS]"); + } + + try { + // Case 2: Attempt to add null into the public creds + // Expected result: NullPointerException + mtSubj.getPublicCredentials().add(null); + throw new RuntimeException( + "PUB CRED add(null): Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println( + "PUB CRED add(null): NullPointerException [PASS]"); + } + + try { + // Case 3: Attempt to add null into the private creds + // Expected result: NullPointerException + mtSubj.getPrivateCredentials().add(null); + throw new RuntimeException( + "PRIV CRED add(null): Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println( + "PRIV CRED add(null): NullPointerException [PASS]"); + } + } + + private static void testRemove() { + System.out.println("------ remove() ------"); + // Create a well formed subject + Subject mtSubj = makeSubj(false, false, false); + + try { + // Case 1: Attempt to remove null values from principal + // Expected result: NullPointerException + mtSubj.getPrincipals().remove(null); + throw new RuntimeException( + "PRINCIPAL remove(null): Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println( + "PRINCIPAL remove(null): NullPointerException [PASS]"); + } + + try { + // Case 2: Attempt to remove null from the public creds + // Expected result: NullPointerException + mtSubj.getPublicCredentials().remove(null); + throw new RuntimeException( + "PUB CRED remove(null): Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println( + "PUB CRED remove(null): NullPointerException [PASS]"); + } + + try { + // Case 3: Attempt to remove null from the private creds + // Expected result: NullPointerException + mtSubj.getPrivateCredentials().remove(null); + throw new RuntimeException( + "PRIV CRED remove(null): Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println( + "PRIV CRED remove(null): NullPointerException [PASS]"); + } + } + + private static void testContains() { + System.out.println("------ contains() ------"); + // Create a well formed subject + Subject mtSubj = makeSubj(false, false, false); + + try { + // Case 1: Attempt to check for null values in principals + // Expected result: NullPointerException + mtSubj.getPrincipals().contains(null); + throw new RuntimeException( + "PRINCIPAL contains(null): Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println( + "PRINCIPAL contains(null): NullPointerException [PASS]"); + } + + try { + // Case 2: Attempt to check for null in public creds + // Expected result: NullPointerException + mtSubj.getPublicCredentials().contains(null); + throw new RuntimeException( + "PUB CRED contains(null): Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println( + "PUB CRED contains(null): NullPointerException [PASS]"); + } + + try { + // Case 3: Attempt to check for null in private creds + // Expected result: NullPointerException + mtSubj.getPrivateCredentials().contains(null); + throw new RuntimeException( + "PRIV CRED contains(null): Failed to throw NPE"); + } catch (NullPointerException npe) { + System.out.println( + "PRIV CRED contains(null): NullPointerException [PASS]"); + } + } + + private static void testAddAll() { + // Create a well formed subject and additional collections + Subject mtSubj = makeSubj(false, false, false); + Set<Principal> morePrincs = new HashSet<>(Arrays.asList(tmplAddPrincs)); + Set<Object> morePubVals = new HashSet<>(Arrays.asList(tmplAddPubVals)); + Set<Object> morePrvVals = new HashSet<>(Arrays.asList(tmplAddPrvVals)); + + // Run one success test for each Subject family to verify the + // overloaded method works as intended. + Set<Principal> setPrin = mtSubj.getPrincipals(); + Set<Object> setPubCreds = mtSubj.getPublicCredentials(); + Set<Object> setPrvCreds = mtSubj.getPrivateCredentials(); + int prinOrigSize = setPrin.size(); + int pubOrigSize = setPubCreds.size(); + int prvOrigSize = setPrvCreds.size(); + + System.out.println("------ addAll() -----"); + + // Add the new members, then check the resulting size of the + // Subject attributes to verify they've increased by the proper + // amounts. + if ((validTestCollection(methAdd, setPrin, morePrincs) != true) || + (setPrin.size() != prinOrigSize + morePrincs.size())) + { + throw new RuntimeException("Failed addAll() on principals"); + } + if ((validTestCollection(methAdd, setPubCreds, + morePubVals) != true) || + (setPubCreds.size() != pubOrigSize + morePubVals.size())) + { + throw new RuntimeException("Failed addAll() on public creds"); + } + if ((validTestCollection(methAdd, setPrvCreds, + morePrvVals) != true) || + (setPrvCreds.size() != prvOrigSize + morePrvVals.size())) + { + throw new RuntimeException("Failed addAll() on private creds"); + } + System.out.println("Positive addAll() test passed"); + + // Now add null elements into each container, then retest + morePrincs.add(null); + morePubVals.add(null); + morePrvVals.add(null); + + System.out.println("* Testing addAll w/ null values on Principals"); + nullTestCollection(methAdd, mtSubj.getPrincipals(), null); + nullTestCollection(methAdd, mtSubj.getPrincipals(), morePrincs); + + System.out.println("* Testing addAll w/ null values on Public Creds"); + nullTestCollection(methAdd, mtSubj.getPublicCredentials(), null); + nullTestCollection(methAdd, mtSubj.getPublicCredentials(), + morePubVals); + + System.out.println("* Testing addAll w/ null values on Private Creds"); + nullTestCollection(methAdd, mtSubj.getPrivateCredentials(), null); + nullTestCollection(methAdd, mtSubj.getPrivateCredentials(), + morePrvVals); + } + + private static void testRemoveAll() { + // Create a well formed subject and additional collections + Subject mtSubj = makeSubj(false, false, false); + Set<Principal> remPrincs = new HashSet<>(); + Set<Object> remPubVals = new HashSet<>(); + Set<Object> remPrvVals = new HashSet<>(); + + remPrincs.add(new KerberosPrincipal("mtwain/author@LITERATURE.US")); + remPubVals.add("mtwain"); + remPrvVals.add("5Cl3M3nz"); + + // Run one success test for each Subject family to verify the + // overloaded method works as intended. + Set<Principal> setPrin = mtSubj.getPrincipals(); + Set<Object> setPubCreds = mtSubj.getPublicCredentials(); + Set<Object> setPrvCreds = mtSubj.getPrivateCredentials(); + int prinOrigSize = setPrin.size(); + int pubOrigSize = setPubCreds.size(); + int prvOrigSize = setPrvCreds.size(); + + System.out.println("------ removeAll() -----"); + + // Remove the specified members, then check the resulting size of the + // Subject attributes to verify they've decreased by the proper + // amounts. + if ((validTestCollection(methRemove, setPrin, remPrincs) != true) || + (setPrin.size() != prinOrigSize - remPrincs.size())) + { + throw new RuntimeException("Failed removeAll() on principals"); + } + if ((validTestCollection(methRemove, setPubCreds, + remPubVals) != true) || + (setPubCreds.size() != pubOrigSize - remPubVals.size())) + { + throw new RuntimeException("Failed removeAll() on public creds"); + } + if ((validTestCollection(methRemove, setPrvCreds, + remPrvVals) != true) || + (setPrvCreds.size() != prvOrigSize - remPrvVals.size())) + { + throw new RuntimeException("Failed removeAll() on private creds"); + } + System.out.println("Positive removeAll() test passed"); + + // Now add null elements into each container, then retest + remPrincs.add(null); + remPubVals.add(null); + remPrvVals.add(null); + + System.out.println("* Testing removeAll w/ null values on Principals"); + nullTestCollection(methRemove, mtSubj.getPrincipals(), null); + nullTestCollection(methRemove, mtSubj.getPrincipals(), remPrincs); + + System.out.println( + "* Testing removeAll w/ null values on Public Creds"); + nullTestCollection(methRemove, mtSubj.getPublicCredentials(), null); + nullTestCollection(methRemove, mtSubj.getPublicCredentials(), + remPubVals); + + System.out.println( + "* Testing removeAll w/ null values on Private Creds"); + nullTestCollection(methRemove, mtSubj.getPrivateCredentials(), null); + nullTestCollection(methRemove, mtSubj.getPrivateCredentials(), + remPrvVals); + } + + private static void testContainsAll() { + // Create a well formed subject and additional collections + Subject mtSubj = makeSubj(false, false, false); + Set<Principal> testPrincs = new HashSet<>(Arrays.asList(princVals)); + Set<Object> testPubVals = new HashSet<>(Arrays.asList(pubVals)); + Set<Object> testPrvVals = new HashSet<>(Arrays.asList(privVals)); + + System.out.println("------ containsAll() -----"); + + // Run one success test for each Subject family to verify the + // overloaded method works as intended. + if ((validTestCollection(methContains, mtSubj.getPrincipals(), + testPrincs) == false) && + (validTestCollection(methContains, mtSubj.getPublicCredentials(), + testPubVals) == false) && + (validTestCollection(methContains, + mtSubj.getPrivateCredentials(), testPrvVals) == false)) { + throw new RuntimeException("Valid containsAll() check failed"); + } + System.out.println("Positive containsAll() test passed"); + + // Now let's add a null into each collection and watch the fireworks. + testPrincs.add(null); + testPubVals.add(null); + testPrvVals.add(null); + + System.out.println( + "* Testing containsAll w/ null values on Principals"); + nullTestCollection(methContains, mtSubj.getPrincipals(), null); + nullTestCollection(methContains, mtSubj.getPrincipals(), testPrincs); + + System.out.println( + "* Testing containsAll w/ null values on Public Creds"); + nullTestCollection(methContains, mtSubj.getPublicCredentials(), + null); + nullTestCollection(methContains, mtSubj.getPublicCredentials(), + testPubVals); + + System.out.println( + "* Testing containsAll w/ null values on Private Creds"); + nullTestCollection(methContains, mtSubj.getPrivateCredentials(), + null); + nullTestCollection(methContains, mtSubj.getPrivateCredentials(), + testPrvVals); + } + + private static void testRetainAll() { + // Create a well formed subject and additional collections + Subject mtSubj = makeSubj(false, false, false); + Set<Principal> remPrincs = new HashSet<>(Arrays.asList(tmplAddPrincs)); + Set<Object> remPubVals = new HashSet<>(Arrays.asList(tmplAddPubVals)); + Set<Object> remPrvVals = new HashSet<>(Arrays.asList(tmplAddPrvVals)); + + // Add in values that exist within the Subject + remPrincs.add(princVals[2]); + remPubVals.add(pubVals[2]); + remPrvVals.add(privVals[2]); + + // Run one success test for each Subject family to verify the + // overloaded method works as intended. + Set<Principal> setPrin = mtSubj.getPrincipals(); + Set<Object> setPubCreds = mtSubj.getPublicCredentials(); + Set<Object> setPrvCreds = mtSubj.getPrivateCredentials(); + int prinOrigSize = setPrin.size(); + int pubOrigSize = setPubCreds.size(); + int prvOrigSize = setPrvCreds.size(); + + System.out.println("------ retainAll() -----"); + + // Retain the specified members (those that exist in the Subject) + // and validate the results. + if (validTestCollection(methRetain, setPrin, remPrincs) == false || + setPrin.size() != 1 || setPrin.contains(princVals[2]) == false) + { + throw new RuntimeException("Failed retainAll() on principals"); + } + + if (validTestCollection(methRetain, setPubCreds, + remPubVals) == false || + setPubCreds.size() != 1 || + setPubCreds.contains(pubVals[2]) == false) + { + throw new RuntimeException("Failed retainAll() on public creds"); + } + if (validTestCollection(methRetain, setPrvCreds, + remPrvVals) == false || + setPrvCreds.size() != 1 || + setPrvCreds.contains(privVals[2]) == false) + { + throw new RuntimeException("Failed retainAll() on private creds"); + } + System.out.println("Positive retainAll() test passed"); + + // Now add null elements into each container, then retest + remPrincs.add(null); + remPubVals.add(null); + remPrvVals.add(null); + + System.out.println("* Testing retainAll w/ null values on Principals"); + nullTestCollection(methRetain, mtSubj.getPrincipals(), null); + nullTestCollection(methRetain, mtSubj.getPrincipals(), remPrincs); + + System.out.println( + "* Testing retainAll w/ null values on Public Creds"); + nullTestCollection(methRetain, mtSubj.getPublicCredentials(), null); + nullTestCollection(methRetain, mtSubj.getPublicCredentials(), + remPubVals); + + System.out.println( + "* Testing retainAll w/ null values on Private Creds"); + nullTestCollection(methRetain, mtSubj.getPrivateCredentials(), null); + nullTestCollection(methRetain, mtSubj.getPrivateCredentials(), + remPrvVals); + } + + private static void testIsEmpty() { + Subject populatedSubj = makeSubj(false, false, false); + Subject emptySubj = new Subject(); + + System.out.println("------ isEmpty() -----"); + + if (populatedSubj.getPrincipals().isEmpty()) { + throw new RuntimeException( + "Populated Subject Principals incorrectly returned empty"); + } + if (emptySubj.getPrincipals().isEmpty() == false) { + throw new RuntimeException( + "Empty Subject Principals incorrectly returned non-empty"); + } + System.out.println("isEmpty() test passed"); + } + + private static void testSecureSetEquals() { + System.out.println("------ SecureSet.equals() -----"); + + Subject subj = makeSubj(false, false, false); + Subject subjComp = makeSubj(false, false, false); + + // Case 1: null comparison [expect false] + if (subj.getPublicCredentials().equals(null) != false) { + throw new RuntimeException( + "equals(null) incorrectly returned true"); + } + + // Case 2: Self-comparison [expect true] + Set<Principal> princs = subj.getPrincipals(); + princs.equals(subj.getPrincipals()); + + // Case 3: Comparison with non-Set type [expect false] + List<Principal> listPrinc = new LinkedList<>(Arrays.asList(princVals)); + if (subj.getPublicCredentials().equals(listPrinc) != false) { + throw new RuntimeException( + "equals([Non-Set]) incorrectly returned true"); + } + + // Case 4: SecureSets of differing sizes [expect false] + Subject subj1princ = new Subject(); + Subject subj2princ = new Subject(); + subj1princ.getPrincipals().add( + new X500Principal("CN=Tom Sawyer, ST=Missouri, C=US")); + subj1princ.getPrincipals().add( + new X500Principal("CN=John Doe, O=Bogus Corp.")); + subj2princ.getPrincipals().add( + new X500Principal("CN=Tom Sawyer, ST=Missouri, C=US")); + if (subj1princ.getPrincipals().equals( + subj2princ.getPrincipals()) != false) { + throw new RuntimeException( + "equals([differing sizes]) incorrectly returned true"); + } + + // Case 5: Content equality test [expect true] + Set<Principal> equalSet = new HashSet<>(Arrays.asList(princVals)); + if (subj.getPrincipals().equals(equalSet) != true) { + throw new RuntimeException( + "equals([equivalent set]) incorrectly returned false"); + } + + // Case 5: Content inequality test [expect false] + // Note: to not fall into the size inequality check the two + // sets need to have the same number of elements. + Set<Principal> inequalSet = + new HashSet<Principal>(Arrays.asList(tmplAddPrincs)); + inequalSet.add(new JMXPrincipal("Samuel Clemens")); + + if (subj.getPrincipals().equals(inequalSet) != false) { + throw new RuntimeException( + "equals([equivalent set]) incorrectly returned false"); + } + System.out.println("SecureSet.equals() tests passed"); + } + + private static void testSecureSetHashCode() { + System.out.println("------ SecureSet.hashCode() -----"); + + Subject subj = makeSubj(false, false, false); + + // Make sure two other Set types that we know are equal per + // SecureSet.equals() and verify their hashCodes are also the same + Set<Principal> equalHashSet = new HashSet<>(Arrays.asList(princVals)); + + if (subj.getPrincipals().hashCode() != equalHashSet.hashCode()) { + throw new RuntimeException( + "SecureSet and HashSet hashCodes() differ"); + } + System.out.println("SecureSet.hashCode() tests passed"); + } + + private static void testToArray() { + System.out.println("------ toArray() -----"); + + Subject subj = makeSubj(false, false, false); + + // Case 1: no-parameter toArray with equality comparison + // Expected result: true + List<Object> alSubj = Arrays.asList(subj.getPrincipals().toArray()); + List<Principal> alPrincs = Arrays.asList(princVals); + + if (alSubj.size() != alPrincs.size() || + alSubj.containsAll(alPrincs) != true) { + throw new RuntimeException( + "Unexpected inequality on returned toArray()"); + } + + // Case 2: generic-type toArray where passed array is of sufficient + // size. + // Expected result: returned Array is reference-equal to input param + // and content equal to data used to construct the originating Subject. + Principal[] pBlock = new Principal[3]; + Principal[] pBlockRef = subj.getPrincipals().toArray(pBlock); + alSubj = Arrays.asList((Object[])pBlockRef); + + if (pBlockRef != pBlock) { + throw new RuntimeException( + "Unexpected reference-inequality on returned toArray(T[])"); + } else if (alSubj.size() != alPrincs.size() || + alSubj.containsAll(alPrincs) != true) { + throw new RuntimeException( + "Unexpected content-inequality on returned toArray(T[])"); + } + + // Case 3: generic-type toArray where passed array is of + // insufficient size. + // Expected result: returned Array is not reference-equal to + // input param but is content equal to data used to construct the + // originating Subject. + pBlock = new Principal[1]; + pBlockRef = subj.getPrincipals().toArray(pBlock); + alSubj = Arrays.asList((Object[])pBlockRef); + + if (pBlockRef == pBlock) { + throw new RuntimeException( + "Unexpected reference-equality on returned toArray(T[])"); + } else if (alSubj.size() != alPrincs.size() || + alSubj.containsAll(alPrincs) != true) { + throw new RuntimeException( + "Unexpected content-inequality on returned toArray(T[])"); + } + System.out.println("toArray() tests passed"); + } + + public static void main(String[] args) throws Exception { + + testCTOR(); + + testDeserialize(); + + testAdd(); + + testRemove(); + + testContains(); + + testAddAll(); + + testRemoveAll(); + + testContainsAll(); + + testRetainAll(); + + testIsEmpty(); + + testSecureSetEquals(); + + testSecureSetHashCode(); + + testToArray(); + } +}