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