Committed by
Gerrit Code Review
Harden DefaultApplication against modification.
Change-Id: I3342e4961096a49ad9761952d73b4a08ea0eebeb
Showing
2 changed files
with
158 additions
and
6 deletions
| ... | @@ -15,6 +15,8 @@ | ... | @@ -15,6 +15,8 @@ |
| 15 | */ | 15 | */ |
| 16 | package org.onosproject.core; | 16 | package org.onosproject.core; |
| 17 | 17 | ||
| 18 | +import com.google.common.collect.ImmutableList; | ||
| 19 | +import com.google.common.collect.ImmutableSet; | ||
| 18 | import org.onosproject.security.Permission; | 20 | import org.onosproject.security.Permission; |
| 19 | 21 | ||
| 20 | import java.net.URI; | 22 | import java.net.URI; |
| ... | @@ -76,12 +78,18 @@ public class DefaultApplication implements Application { | ... | @@ -76,12 +78,18 @@ public class DefaultApplication implements Application { |
| 76 | this.category = checkNotNull(category, "Category cannot be null"); | 78 | this.category = checkNotNull(category, "Category cannot be null"); |
| 77 | this.url = url; | 79 | this.url = url; |
| 78 | this.readme = checkNotNull(readme, "Readme cannot be null"); | 80 | this.readme = checkNotNull(readme, "Readme cannot be null"); |
| 79 | - this.icon = icon; | 81 | + this.icon = icon == null ? new byte[0] : icon.clone(); |
| 80 | this.role = checkNotNull(role, "Role cannot be null"); | 82 | this.role = checkNotNull(role, "Role cannot be null"); |
| 81 | - this.permissions = checkNotNull(permissions, "Permissions cannot be null"); | 83 | + this.permissions = ImmutableSet.copyOf( |
| 84 | + checkNotNull(permissions, "Permissions cannot be null") | ||
| 85 | + ); | ||
| 82 | this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null"); | 86 | this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null"); |
| 83 | - this.features = checkNotNull(features, "Features cannot be null"); | 87 | + this.features = ImmutableList.copyOf( |
| 84 | - this.requiredApps = checkNotNull(requiredApps, "Required apps cannot be null"); | 88 | + checkNotNull(features, "Features cannot be null") |
| 89 | + ); | ||
| 90 | + this.requiredApps = ImmutableList.copyOf( | ||
| 91 | + checkNotNull(requiredApps, "Required apps cannot be null") | ||
| 92 | + ); | ||
| 85 | checkArgument(!features.isEmpty(), "There must be at least one feature"); | 93 | checkArgument(!features.isEmpty(), "There must be at least one feature"); |
| 86 | } | 94 | } |
| 87 | 95 | ||
| ... | @@ -117,7 +125,7 @@ public class DefaultApplication implements Application { | ... | @@ -117,7 +125,7 @@ public class DefaultApplication implements Application { |
| 117 | 125 | ||
| 118 | @Override | 126 | @Override |
| 119 | public byte[] icon() { | 127 | public byte[] icon() { |
| 120 | - return icon; | 128 | + return icon.clone(); |
| 121 | } | 129 | } |
| 122 | 130 | ||
| 123 | @Override | 131 | @Override | ... | ... |
| ... | @@ -15,12 +15,22 @@ | ... | @@ -15,12 +15,22 @@ |
| 15 | */ | 15 | */ |
| 16 | package org.onosproject.core; | 16 | package org.onosproject.core; |
| 17 | 17 | ||
| 18 | +import com.google.common.collect.ImmutableList; | ||
| 19 | +import com.google.common.collect.ImmutableSet; | ||
| 18 | import com.google.common.testing.EqualsTester; | 20 | import com.google.common.testing.EqualsTester; |
| 19 | import org.junit.Test; | 21 | import org.junit.Test; |
| 22 | +import org.onosproject.security.AppPermission; | ||
| 23 | +import org.onosproject.security.Permission; | ||
| 20 | 24 | ||
| 25 | +import java.util.ArrayList; | ||
| 26 | +import java.util.HashSet; | ||
| 27 | +import java.util.List; | ||
| 21 | import java.util.Optional; | 28 | import java.util.Optional; |
| 29 | +import java.util.Set; | ||
| 22 | 30 | ||
| 31 | +import static org.junit.Assert.assertArrayEquals; | ||
| 23 | import static org.junit.Assert.assertEquals; | 32 | import static org.junit.Assert.assertEquals; |
| 33 | +import static org.junit.Assert.assertNotNull; | ||
| 24 | import static org.junit.Assert.assertTrue; | 34 | import static org.junit.Assert.assertTrue; |
| 25 | import static org.onosproject.app.DefaultApplicationDescriptionTest.*; | 35 | import static org.onosproject.app.DefaultApplicationDescriptionTest.*; |
| 26 | 36 | ||
| ... | @@ -43,7 +53,7 @@ public class DefaultApplicationTest { | ... | @@ -43,7 +53,7 @@ public class DefaultApplicationTest { |
| 43 | assertEquals("incorrect category", CATEGORY, app.category()); | 53 | assertEquals("incorrect category", CATEGORY, app.category()); |
| 44 | assertEquals("incorrect URL", URL, app.url()); | 54 | assertEquals("incorrect URL", URL, app.url()); |
| 45 | assertEquals("incorrect readme", README, app.readme()); | 55 | assertEquals("incorrect readme", README, app.readme()); |
| 46 | - assertEquals("incorrect icon", ICON, app.icon()); | 56 | + assertArrayEquals("incorrect icon", ICON, app.icon()); |
| 47 | assertEquals("incorrect role", ROLE, app.role()); | 57 | assertEquals("incorrect role", ROLE, app.role()); |
| 48 | assertEquals("incorrect permissions", PERMS, app.permissions()); | 58 | assertEquals("incorrect permissions", PERMS, app.permissions()); |
| 49 | assertEquals("incorrect features repo", FURL, app.featuresRepo().get()); | 59 | assertEquals("incorrect features repo", FURL, app.featuresRepo().get()); |
| ... | @@ -69,4 +79,138 @@ public class DefaultApplicationTest { | ... | @@ -69,4 +79,138 @@ public class DefaultApplicationTest { |
| 69 | new EqualsTester().addEqualityGroup(a1, a2) | 79 | new EqualsTester().addEqualityGroup(a1, a2) |
| 70 | .addEqualityGroup(a3).addEqualityGroup(a4).testEquals(); | 80 | .addEqualityGroup(a3).addEqualityGroup(a4).testEquals(); |
| 71 | } | 81 | } |
| 82 | + | ||
| 83 | + | ||
| 84 | + private static final byte[] ICON_ORIG = new byte[] {1, 2, 3, 4}; | ||
| 85 | + | ||
| 86 | + @Test | ||
| 87 | + public void immutableIcon() { | ||
| 88 | + byte[] iconSourceData = ICON_ORIG.clone(); | ||
| 89 | + | ||
| 90 | + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, | ||
| 91 | + CATEGORY, URL, README, iconSourceData, ROLE, | ||
| 92 | + PERMS, Optional.of(FURL), FEATURES, APPS); | ||
| 93 | + | ||
| 94 | + // can we modify the icon after getting a reference to the app? | ||
| 95 | + byte[] icon = app.icon(); | ||
| 96 | + assertArrayEquals("did not start with orig icon", ICON_ORIG, icon); | ||
| 97 | + | ||
| 98 | + // now the hack | ||
| 99 | + for (int i = 0, n = ICON_ORIG.length; i < n; i++) { | ||
| 100 | + icon[i] = 0; | ||
| 101 | + } | ||
| 102 | + // if the reference to the internal array is given out, the hack | ||
| 103 | + // will succeed and this next assertion fails | ||
| 104 | + assertArrayEquals("no longer orig icon", ICON_ORIG, app.icon()); | ||
| 105 | + | ||
| 106 | + // what if we modify the source data? | ||
| 107 | + for (int i = 0, n = ICON_ORIG.length; i < n; i++) { | ||
| 108 | + iconSourceData[i] = 0; | ||
| 109 | + } | ||
| 110 | + // if the application just saved a reference to the given array | ||
| 111 | + // this next assertion fails | ||
| 112 | + assertArrayEquals("modifying source alters appicon", ICON_ORIG, app.icon()); | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + private static final Permission PERM_W = | ||
| 116 | + new Permission(AppPermission.class.getName(), "FLOWRULE_WRITE"); | ||
| 117 | + private static final Permission PERM_R = | ||
| 118 | + new Permission(AppPermission.class.getName(), "FLOWRULE_READ"); | ||
| 119 | + | ||
| 120 | + private static final Permission JUNK_PERM = new Permission("foo", "bar"); | ||
| 121 | + | ||
| 122 | + private static final Set<Permission> PERMS_ORIG = ImmutableSet.of(PERM_W, PERM_R); | ||
| 123 | + private static final Set<Permission> PERMS_UNSAFE = new HashSet<>(PERMS_ORIG); | ||
| 124 | + | ||
| 125 | + | ||
| 126 | + @Test | ||
| 127 | + public void immutablePermissions() { | ||
| 128 | +// Set<Permission> p = PERMS_ORIG; | ||
| 129 | + Set<Permission> p = PERMS_UNSAFE; | ||
| 130 | + | ||
| 131 | + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, | ||
| 132 | + CATEGORY, URL, README, ICON, ROLE, | ||
| 133 | + p, Optional.of(FURL), FEATURES, APPS); | ||
| 134 | + | ||
| 135 | + Set<Permission> perms = app.permissions(); | ||
| 136 | + try { | ||
| 137 | + perms.add(JUNK_PERM); | ||
| 138 | + } catch (UnsupportedOperationException e) { | ||
| 139 | + // set is immutable | ||
| 140 | + } | ||
| 141 | + assertTrue("no write perm", app.permissions().contains(PERM_W)); | ||
| 142 | + assertTrue("no read perm", app.permissions().contains(PERM_R)); | ||
| 143 | + assertEquals("extra perms", 2, app.permissions().size()); | ||
| 144 | + | ||
| 145 | + // DONE: review - is it sufficient to expect caller to pass in ImmutableSet ? | ||
| 146 | + // Issue Resolved with Immutable collections used during construction. | ||
| 147 | + | ||
| 148 | + // If we just pass in a HashSet, the contents would be modifiable by | ||
| 149 | + // an external party. (Making the field final just means that the | ||
| 150 | + // reference to the set can never change; the contents may still...) | ||
| 151 | + | ||
| 152 | + // Similar reasoning can be applied to these two fields also: | ||
| 153 | + // List<String> features | ||
| 154 | + // List<String> requiredApps | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + private static final String FOO = "foo"; | ||
| 158 | + private static final String BAR = "bar"; | ||
| 159 | + private static final String FIFI = "fifi"; | ||
| 160 | + private static final String EVIL = "Bwahahahaha!"; | ||
| 161 | + | ||
| 162 | + private static final List<String> FEATURES_ORIG = ImmutableList.of(FOO, BAR); | ||
| 163 | + private static final List<String> FEATURES_UNSAFE = new ArrayList<>(FEATURES_ORIG); | ||
| 164 | + | ||
| 165 | + private static final List<String> REQ_APPS_ORIG = ImmutableList.of(FIFI); | ||
| 166 | + private static final List<String> REQ_APPS_UNSAFE = new ArrayList<>(REQ_APPS_ORIG); | ||
| 167 | + | ||
| 168 | + @Test | ||
| 169 | + public void immutableFeatures() { | ||
| 170 | +// List<String> f = FEATURES_ORIG; | ||
| 171 | + List<String> f = FEATURES_UNSAFE; | ||
| 172 | + | ||
| 173 | + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, | ||
| 174 | + CATEGORY, URL, README, ICON, ROLE, | ||
| 175 | + PERMS, Optional.of(FURL), f, APPS); | ||
| 176 | + | ||
| 177 | + List<String> features = app.features(); | ||
| 178 | + try { | ||
| 179 | + features.add(EVIL); | ||
| 180 | + } catch (UnsupportedOperationException e) { | ||
| 181 | + // list is immutable | ||
| 182 | + } | ||
| 183 | + assertTrue("no foo feature", features.contains(FOO)); | ||
| 184 | + assertTrue("no bar feature", features.contains(BAR)); | ||
| 185 | + assertEquals("extra features!", 2, features.size()); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + @Test | ||
| 189 | + public void immutableRequiredApps() { | ||
| 190 | +// List<String> ra = REQ_APPS_ORIG; | ||
| 191 | + List<String> ra = REQ_APPS_UNSAFE; | ||
| 192 | + | ||
| 193 | + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, | ||
| 194 | + CATEGORY, URL, README, ICON, ROLE, | ||
| 195 | + PERMS, Optional.of(FURL), FEATURES, ra); | ||
| 196 | + | ||
| 197 | + List<String> reqApps = app.requiredApps(); | ||
| 198 | + try { | ||
| 199 | + reqApps.add(EVIL); | ||
| 200 | + } catch (UnsupportedOperationException e) { | ||
| 201 | + // list is immutable | ||
| 202 | + } | ||
| 203 | + assertTrue("no fifi required app", reqApps.contains(FIFI)); | ||
| 204 | + assertEquals("extra required apps!", 1, reqApps.size()); | ||
| 205 | + } | ||
| 206 | + | ||
| 207 | + @Test | ||
| 208 | + public void nullIcon() { | ||
| 209 | + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, | ||
| 210 | + CATEGORY, URL, README, null, ROLE, | ||
| 211 | + PERMS, Optional.of(FURL), FEATURES, APPS); | ||
| 212 | + byte[] icon = app.icon(); | ||
| 213 | + assertNotNull("null icon", icon); | ||
| 214 | + assertEquals("unexpected size", 0, icon.length); | ||
| 215 | + } | ||
| 72 | } | 216 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
-
Please register or login to post a comment