Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / IsolatedAppClassLoader.java
blobb1027c1b1d141812bb722d5876ea09b8584b736c
1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools.development;
5 import com.google.appengine.tools.info.SdkImplInfo;
6 import com.google.appengine.tools.info.SdkInfo;
7 import com.google.appengine.tools.plugins.SharedConstants;
8 import com.google.apphosting.utils.io.IoUtil;
10 import sun.security.util.SecurityConstants;
12 import java.io.File;
13 import java.io.FilePermission;
14 import java.io.IOException;
15 import java.lang.reflect.ReflectPermission;
16 import java.net.MalformedURLException;
17 import java.net.URL;
18 import java.net.URLClassLoader;
19 import java.net.URLConnection;
20 import java.security.AccessController;
21 import java.security.AllPermission;
22 import java.security.CodeSource;
23 import java.security.Permission;
24 import java.security.PermissionCollection;
25 import java.security.Permissions;
26 import java.security.Policy;
27 import java.security.PrivilegedAction;
28 import java.security.ProtectionDomain;
29 import java.security.Provider;
30 import java.security.SecurityPermission;
31 import java.security.UnresolvedPermission;
32 import java.util.Enumeration;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.PropertyPermission;
36 import java.util.Set;
37 import java.util.logging.Level;
38 import java.util.logging.Logger;
39 import java.util.logging.LoggingPermission;
41 /**
42 * A webapp {@code ClassLoader}. This {@code ClassLoader} isolates
43 * webapps from the {@link DevAppServer} and anything else
44 * that might happen to be on the system classpath.
45 * It also grants the appropriate security permissions to the
46 * webapp classes that it loads.
49 public class IsolatedAppClassLoader extends URLClassLoader {
51 private static Logger logger = Logger.getLogger(IsolatedAppClassLoader.class.getName());
53 private final PermissionCollection appPermissions;
54 private final Permissions appPermissionsAsPermissions;
55 private final ClassLoader devAppServerClassLoader;
56 private final Set<URL> sharedCodeLibs;
57 private final Set<URL> agentRuntimeLibs;
59 public IsolatedAppClassLoader(File appRoot, File externalResourceDir, URL[] urls,
60 ClassLoader devAppServerClassLoader) {
61 super(urls, null);
62 checkWorkingDirectory(appRoot, externalResourceDir);
63 boolean allowWrites = inApplicationPreparationMode();
64 appPermissions = createAppPermissions(appRoot, externalResourceDir, allowWrites);
65 appPermissionsAsPermissions = new Permissions();
66 addAllPermissions(appPermissions, appPermissionsAsPermissions);
67 installPolicyProxy(appRoot);
68 this.devAppServerClassLoader = devAppServerClassLoader;
69 this.sharedCodeLibs = new HashSet<URL>(SdkInfo.getSharedLibs());
70 this.agentRuntimeLibs = new HashSet<URL>(SdkImplInfo.getAgentRuntimeLibs());
73 /**
74 * Issues a warning if the current working directory != {@code appRoot},
75 * or {@code externalResourceDir}.
77 * The working directory of remotely deployed apps always == appRoot.
78 * For DevAppServer, We don't currently force users to set their working
79 * directory equal to the appRoot. We also don't set it for them
80 * (due to extent ramifications). The best we can do at the moment is to
81 * warn them that they may experience permission problems in production
82 * if they access files in a working directory != appRoot.
84 * If we are using an external resource directory, then it is also fine
85 * for the working directory to point there.
87 * @param appRoot
89 private static void checkWorkingDirectory(File appRoot, File externalResourceDir) {
90 File workingDir = new File(System.getProperty("user.dir"));
92 String canonicalWorkingDir = null;
93 String canonicalAppRoot = null;
94 String canonicalExternalResourceDir = null;
96 try {
97 canonicalWorkingDir = workingDir.getCanonicalPath();
98 canonicalAppRoot = appRoot.getCanonicalPath();
99 if (externalResourceDir != null) {
100 canonicalExternalResourceDir = externalResourceDir.getCanonicalPath();
102 } catch (IOException e) {
103 logger.log(Level.FINE, "Unable to compare the working directory and app root.", e);
106 if (canonicalWorkingDir != null && !canonicalWorkingDir.equals(canonicalAppRoot)) {
107 if (canonicalExternalResourceDir != null
108 && canonicalWorkingDir.equals(canonicalExternalResourceDir)) {
109 return;
111 String newLine = System.getProperty("line.separator");
112 String workDir = workingDir.getAbsolutePath();
113 String appDir = appRoot.getAbsolutePath();
114 String msg = "Your working directory, (" + workDir + ") is not equal to your " + newLine
115 + "web application root (" + appDir + ")" + newLine
116 + "You will not be able to access files from your working directory on the "
117 + "production server." + newLine;
118 logger.warning(msg);
122 private static boolean inApplicationPreparationMode() {
123 boolean inMode = Boolean.valueOf(
124 System.getProperty(SharedConstants.APPLICATION_PREPARATION_MODE_SYSTEM_PROPERTY));
125 if (inMode) {
126 String newLine = System.getProperty("line.separator");
127 String msg = newLine + "** Running in application-preparation mode. **." + newLine
128 + "** Code will be allowed to write to you application "
129 + "directory while running locally. **" + newLine
130 + "** But writing will not be allowed when " + "your code is uploaded to App Engine! **"
131 + newLine;
132 logger.warning(msg);
134 return inMode;
137 @Override
138 public URL getResource(String name) {
139 URL resource = devAppServerClassLoader.getResource(name);
140 if (resource != null) {
141 if (resource.getProtocol().equals("jar")) {
142 int bang = resource.getPath().indexOf('!');
143 if (bang > 0) {
144 try {
145 URL url = new URL(resource.getPath().substring(0, bang));
146 if (sharedCodeLibs.contains(url)) {
147 return resource;
149 } catch (MalformedURLException ex) {
150 logger.log(Level.WARNING, "Unexpected exception while loading " + name, ex);
155 return super.getResource(name);
158 @Override
159 protected synchronized Class<?> loadClass(String name, boolean resolve)
160 throws ClassNotFoundException {
162 try {
163 final Class c = devAppServerClassLoader.loadClass(name);
165 CodeSource source = AccessController.doPrivileged(
166 new PrivilegedAction<CodeSource>() {
167 public CodeSource run() {
168 return c.getProtectionDomain().getCodeSource();
172 if (source == null) {
173 return c;
176 URL location = source.getLocation();
177 if (sharedCodeLibs.contains(location) ||
178 location.getFile().endsWith("/appengine-agent.jar")) {
179 if (resolve) {
180 resolveClass(c);
182 return c;
184 } catch (ClassNotFoundException e) {
187 return super.loadClass(name, resolve);
190 @Override
191 protected PermissionCollection getPermissions(CodeSource codesource) {
192 PermissionCollection permissions = super.getPermissions(codesource);
193 if (agentRuntimeLibs.contains(codesource.getLocation())) {
194 permissions.add(new AllPermission());
195 } else {
196 addAllPermissions(appPermissions, permissions);
198 return permissions;
201 public Permissions getAppPermissions() {
202 return appPermissionsAsPermissions;
205 private PermissionCollection createAppPermissions(
206 File appRoot, File externalResourceDir, boolean allowWriteAccess) {
207 PermissionCollection permissions = new Permissions();
208 addAllPermissions(buildPermissionsToAccessAppFiles(appRoot, allowWriteAccess), permissions);
209 if (externalResourceDir != null) {
210 addAllPermissions(
211 buildPermissionsToAccessAppFiles(externalResourceDir, allowWriteAccess), permissions);
214 if (Boolean.valueOf(System.getProperty("--enable_all_permissions"))) {
215 permissions.add(new AllPermission());
216 return permissions;
219 permissions.add(new RuntimePermission("getClassLoader"));
220 permissions.add(new RuntimePermission("setContextClassLoader"));
221 permissions.add(new RuntimePermission("createClassLoader"));
222 permissions.add(new RuntimePermission("getProtectionDomain"));
223 permissions.add(new RuntimePermission("accessDeclaredMembers"));
224 permissions.add(new ReflectPermission("suppressAccessChecks"));
225 permissions.add(new LoggingPermission("control", ""));
226 permissions.add(new RuntimePermission("getStackTrace"));
227 permissions.add(new RuntimePermission("getenv.*"));
228 permissions.add(new RuntimePermission("setIO"));
229 permissions.add(new PropertyPermission("*", "read,write"));
231 permissions.add(new
232 RuntimePermission("accessClassInPackage.com.sun.xml.internal.ws.*"));
234 permissions.add(new RuntimePermission("loadLibrary.keychain"));
236 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "getMetadata", null,
237 null));
238 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "setStateManager", null,
239 null));
240 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "manageMetadata", null,
241 null));
242 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission",
243 "closePersistenceManagerFactory", null, null));
245 permissions.add(new UnresolvedPermission("groovy.security.GroovyCodeSourcePermission", "*",
246 null, null));
248 permissions.add(new FilePermission(System.getProperty("user.dir") + File.separatorChar + "-",
249 SecurityConstants.FILE_READ_ACTION));
251 permissions.add(getJreReadPermission());
253 for (File f : SdkInfo.getSharedLibFiles()) {
254 permissions.add(new FilePermission(f.getAbsolutePath(), SecurityConstants.FILE_READ_ACTION));
257 permissions.add(new SecurityPermission("putProviderProperty.*"));
258 permissions.add(new SecurityPermission("insertProvider.*"));
259 permissions.add(new SecurityPermission("removeProvider.*"));
261 permissions.setReadOnly();
263 return permissions;
267 * This is a terrible hack so that we can get Jasper to grant JSPs
268 * the permissions they need (since JasperLoader is not configurable
269 * in terms of the permissions it grants). We know that JspRuntimeContext
270 * uses the permissions returned from Policy.getPermissions() on the
271 * codeSource for the path of the webapp context.
273 private void installPolicyProxy(File appRoot) {
275 Policy p = Policy.getPolicy();
276 if (p instanceof ProxyPolicy) {
277 return;
279 Policy.setPolicy(new ProxyPolicy(p, appRoot));
282 class ProxyPolicy extends Policy {
283 private Policy delegate;
284 private File appRoot;
285 ProxyPolicy(Policy delegate, File appRoot) {
286 this.delegate = delegate;
287 this.appRoot = appRoot;
290 @Override
291 public Provider getProvider() {
292 return delegate.getProvider();
295 @Override
296 public String getType() {
297 return delegate.getType();
300 @Override
301 public Parameters getParameters() {
302 return delegate.getParameters();
305 @Override
306 public PermissionCollection getPermissions(final CodeSource codeSource) {
307 return AccessController.doPrivileged(new PrivilegedAction<PermissionCollection>() {
308 @SuppressWarnings({"deprecation"})
309 public PermissionCollection run() {
310 PermissionCollection delegatePerms = delegate.getPermissions(codeSource);
312 try {
313 if (appRoot.toURL().equals(codeSource.getLocation())) {
314 Permissions newPerms = new Permissions();
315 addAllPermissions(delegatePerms, newPerms);
316 addAllPermissions(appPermissions, newPerms);
317 return newPerms;
319 } catch (MalformedURLException ex) {
320 throw new RuntimeException("Could not turn " + appRoot + "into a URL", ex);
322 return delegatePerms;
327 @Override
328 public PermissionCollection getPermissions(ProtectionDomain domain) {
329 return getPermissions(domain.getCodeSource());
332 @Override
333 public boolean implies(final ProtectionDomain domain, final Permission permission) {
334 return AccessController.doPrivileged(new PrivilegedAction<Boolean>(){
335 public Boolean run() {
336 return delegate.implies(domain, permission);
341 @Override
342 public void refresh() {
343 delegate.refresh();
347 private static PermissionCollection buildPermissionsToAccessAppFiles(
348 File contextRoot, boolean allowWrites) {
349 PermissionCollection permissions = new Permissions();
350 addPermissionsToAccessAppFiles(permissions, contextRoot, allowWrites);
352 List<File> allFiles = IoUtil.getFilesAndDirectories(contextRoot);
354 for (File file : allFiles) {
355 addPermissionsToAccessAppFiles(permissions, file, allowWrites);
358 permissions.setReadOnly();
359 return permissions;
362 private static void addPermissionsToAccessAppFiles(
363 PermissionCollection permissions, File fileOrDirectory, boolean allowWrites) {
364 String path = fileOrDirectory.getAbsolutePath();
365 permissions.add(new FilePermission(path, SecurityConstants.FILE_READ_ACTION));
366 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_READ_ACTION));
367 if (allowWrites) {
368 permissions.add(new FilePermission(path, SecurityConstants.FILE_WRITE_ACTION));
369 permissions.add(new FilePermission(path, SecurityConstants.FILE_DELETE_ACTION));
370 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_WRITE_ACTION));
371 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_DELETE_ACTION));
375 private static Permission getReadPermission(URL url) {
376 Permission p;
377 try {
378 URLConnection urlConnection = url.openConnection();
379 p = urlConnection.getPermission();
380 } catch (IOException e) {
381 throw new RuntimeException("Unable to obtain the permission for " + url, e);
383 return new FilePermission(p.getName(), SecurityConstants.FILE_READ_ACTION);
386 private static Permission getJreReadPermission() {
387 return getReadPermission(Object.class.getResource("/java/lang/Object.class"));
391 * Utility method that adds the contents of one permission collection (the
392 * source) into another permission collection (the dest).
394 private static void addAllPermissions(PermissionCollection src, PermissionCollection dest) {
395 Enumeration<Permission> srcElements = src.elements();
396 while (srcElements.hasMoreElements()) {
397 dest.add(srcElements.nextElement());