Version 1.7.4
[gae.git] / java / src / main / com / google / appengine / tools / development / IsolatedAppClassLoader.java
blob63dc81e1cb9d103e7b64ab58db45c0b7504480d2
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 static final String DEV_APP_SERVER_AGENT
54 = "com.google.appengine.tools.development.agent.AppEngineDevAgent";
56 private final PermissionCollection appPermissions;
57 private final Permissions appPermissionsAsPermissions;
58 private final ClassLoader devAppServerClassLoader;
59 private final Set<URL> sharedCodeLibs;
60 private final Set<URL> agentRuntimeLibs;
62 public IsolatedAppClassLoader(File appRoot, File externalResourceDir, URL[] urls,
63 ClassLoader devAppServerClassLoader) {
64 super(urls, null);
65 checkWorkingDirectory(appRoot, externalResourceDir);
66 boolean allowWrites = inApplicationPreparationMode();
67 appPermissions = createAppPermissions(appRoot, externalResourceDir, allowWrites);
68 appPermissionsAsPermissions = new Permissions();
69 addAllPermissions(appPermissions, appPermissionsAsPermissions);
70 installPolicyProxy(appRoot);
71 this.devAppServerClassLoader = devAppServerClassLoader;
72 this.sharedCodeLibs = new HashSet<URL>(SdkInfo.getSharedLibs());
73 this.agentRuntimeLibs = new HashSet<URL>(SdkImplInfo.getAgentRuntimeLibs());
76 /**
77 * Issues a warning if the current working directory != {@code appRoot},
78 * or {@code externalResourceDir}.
80 * The working directory of remotely deployed apps always == appRoot.
81 * For DevAppServer, We don't currently force users to set their working
82 * directory equal to the appRoot. We also don't set it for them
83 * (due to extent ramifications). The best we can do at the moment is to
84 * warn them that they may experience permission problems in production
85 * if they access files in a working directory != appRoot.
87 * If we are using an external resource directory, then it is also fine
88 * for the working directory to point there.
90 * @param appRoot
92 private static void checkWorkingDirectory(File appRoot, File externalResourceDir) {
93 File workingDir = new File(System.getProperty("user.dir"));
95 String canonicalWorkingDir = null;
96 String canonicalAppRoot = null;
97 String canonicalExternalResourceDir = null;
99 try {
100 canonicalWorkingDir = workingDir.getCanonicalPath();
101 canonicalAppRoot = appRoot.getCanonicalPath();
102 if (externalResourceDir != null) {
103 canonicalExternalResourceDir = externalResourceDir.getCanonicalPath();
105 } catch (IOException e) {
106 logger.log(Level.FINE, "Unable to compare the working directory and app root.", e);
109 if (canonicalWorkingDir != null && !canonicalWorkingDir.equals(canonicalAppRoot)) {
110 if (canonicalExternalResourceDir != null
111 && canonicalWorkingDir.equals(canonicalExternalResourceDir)) {
112 return;
114 String newLine = System.getProperty("line.separator");
115 String workDir = workingDir.getAbsolutePath();
116 String appDir = appRoot.getAbsolutePath();
117 String msg = "Your working directory, (" + workDir + ") is not equal to your " + newLine
118 + "web application root (" + appDir + ")" + newLine
119 + "You will not be able to access files from your working directory on the "
120 + "production server." + newLine;
121 logger.warning(msg);
125 private static boolean inApplicationPreparationMode() {
126 boolean inMode = Boolean.valueOf(
127 System.getProperty(SharedConstants.APPLICATION_PREPARATION_MODE_SYSTEM_PROPERTY));
128 if (inMode) {
129 String newLine = System.getProperty("line.separator");
130 String msg = newLine + "** Running in application-preparation mode. **." + newLine
131 + "** Code will be allowed to write to you application "
132 + "directory while running locally. **" + newLine
133 + "** But writing will not be allowed when " + "your code is uploaded to App Engine! **"
134 + newLine;
135 logger.warning(msg);
137 return inMode;
140 @Override
141 public URL getResource(String name) {
142 URL resource = devAppServerClassLoader.getResource(name);
143 if (resource != null) {
144 if (resource.getProtocol().equals("jar")) {
145 int bang = resource.getPath().indexOf('!');
146 if (bang > 0) {
147 try {
148 URL url = new URL(resource.getPath().substring(0, bang));
149 if (sharedCodeLibs.contains(url)) {
150 return resource;
152 } catch (MalformedURLException ex) {
153 logger.log(Level.WARNING, "Unexpected exception while loading " + name, ex);
158 return super.getResource(name);
161 @Override
162 protected synchronized Class<?> loadClass(String name, boolean resolve)
163 throws ClassNotFoundException {
165 try {
166 final Class c = devAppServerClassLoader.loadClass(name);
168 CodeSource source = AccessController.doPrivileged(
169 new PrivilegedAction<CodeSource>() {
170 public CodeSource run() {
171 return c.getProtectionDomain().getCodeSource();
175 if (source == null) {
176 return c;
179 URL location = source.getLocation();
180 if (sharedCodeLibs.contains(location) ||
181 location.getFile().endsWith("/appengine-agent.jar")
182 || name.equals(DEV_APP_SERVER_AGENT)) {
183 if (resolve) {
184 resolveClass(c);
186 return c;
188 } catch (ClassNotFoundException e) {
191 return super.loadClass(name, resolve);
194 @Override
195 protected PermissionCollection getPermissions(CodeSource codesource) {
196 PermissionCollection permissions = super.getPermissions(codesource);
197 if (agentRuntimeLibs.contains(codesource.getLocation())) {
198 permissions.add(new AllPermission());
199 } else {
200 addAllPermissions(appPermissions, permissions);
202 return permissions;
205 public Permissions getAppPermissions() {
206 return appPermissionsAsPermissions;
209 private PermissionCollection createAppPermissions(
210 File appRoot, File externalResourceDir, boolean allowWriteAccess) {
211 PermissionCollection permissions = new Permissions();
212 addAllPermissions(buildPermissionsToAccessAppFiles(appRoot, allowWriteAccess), permissions);
213 if (externalResourceDir != null) {
214 addAllPermissions(
215 buildPermissionsToAccessAppFiles(externalResourceDir, allowWriteAccess), permissions);
218 if (Boolean.valueOf(System.getProperty("--enable_all_permissions"))) {
219 permissions.add(new AllPermission());
220 return permissions;
223 permissions.add(new RuntimePermission("getClassLoader"));
224 permissions.add(new RuntimePermission("setContextClassLoader"));
225 permissions.add(new RuntimePermission("createClassLoader"));
226 permissions.add(new RuntimePermission("getProtectionDomain"));
227 permissions.add(new RuntimePermission("accessDeclaredMembers"));
228 permissions.add(new ReflectPermission("suppressAccessChecks"));
229 permissions.add(new LoggingPermission("control", ""));
230 permissions.add(new RuntimePermission("getStackTrace"));
231 permissions.add(new RuntimePermission("getenv.*"));
232 permissions.add(new RuntimePermission("setIO"));
233 permissions.add(new PropertyPermission("*", "read,write"));
235 permissions.add(new
236 RuntimePermission("accessClassInPackage.com.sun.xml.internal.ws.*"));
238 permissions.add(new RuntimePermission("loadLibrary.keychain"));
240 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "getMetadata", null,
241 null));
242 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "setStateManager", null,
243 null));
244 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "manageMetadata", null,
245 null));
246 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission",
247 "closePersistenceManagerFactory", null, null));
249 permissions.add(new UnresolvedPermission("groovy.security.GroovyCodeSourcePermission", "*",
250 null, null));
252 permissions.add(new FilePermission(System.getProperty("user.dir") + File.separatorChar + "-",
253 SecurityConstants.FILE_READ_ACTION));
255 permissions.add(getJreReadPermission());
257 for (File f : SdkInfo.getSharedLibFiles()) {
258 permissions.add(new FilePermission(f.getAbsolutePath(), SecurityConstants.FILE_READ_ACTION));
261 permissions.add(new SecurityPermission("putProviderProperty.*"));
262 permissions.add(new SecurityPermission("insertProvider.*"));
263 permissions.add(new SecurityPermission("removeProvider.*"));
265 permissions.add(new SecurityPermission("getProperty.ssl.KeyManagerFactory.algorithm"));
267 permissions.setReadOnly();
269 return permissions;
273 * This is a terrible hack so that we can get Jasper to grant JSPs
274 * the permissions they need (since JasperLoader is not configurable
275 * in terms of the permissions it grants). We know that JspRuntimeContext
276 * uses the permissions returned from Policy.getPermissions() on the
277 * codeSource for the path of the webapp context.
279 private void installPolicyProxy(File appRoot) {
281 Policy p = Policy.getPolicy();
282 if (p instanceof ProxyPolicy) {
283 return;
285 Policy.setPolicy(new ProxyPolicy(p, appRoot));
288 class ProxyPolicy extends Policy {
289 private Policy delegate;
290 private File appRoot;
291 ProxyPolicy(Policy delegate, File appRoot) {
292 this.delegate = delegate;
293 this.appRoot = appRoot;
296 @Override
297 public Provider getProvider() {
298 return delegate.getProvider();
301 @Override
302 public String getType() {
303 return delegate.getType();
306 @Override
307 public Parameters getParameters() {
308 return delegate.getParameters();
311 @Override
312 public PermissionCollection getPermissions(final CodeSource codeSource) {
313 return AccessController.doPrivileged(new PrivilegedAction<PermissionCollection>() {
314 @SuppressWarnings({"deprecation"})
315 public PermissionCollection run() {
316 PermissionCollection delegatePerms = delegate.getPermissions(codeSource);
318 try {
319 if (appRoot.toURL().equals(codeSource.getLocation())) {
320 Permissions newPerms = new Permissions();
321 addAllPermissions(delegatePerms, newPerms);
322 addAllPermissions(appPermissions, newPerms);
323 return newPerms;
325 } catch (MalformedURLException ex) {
326 throw new RuntimeException("Could not turn " + appRoot + "into a URL", ex);
328 return delegatePerms;
333 @Override
334 public PermissionCollection getPermissions(ProtectionDomain domain) {
335 return getPermissions(domain.getCodeSource());
338 @Override
339 public boolean implies(final ProtectionDomain domain, final Permission permission) {
340 return AccessController.doPrivileged(new PrivilegedAction<Boolean>(){
341 public Boolean run() {
342 return delegate.implies(domain, permission);
347 @Override
348 public void refresh() {
349 delegate.refresh();
353 private static PermissionCollection buildPermissionsToAccessAppFiles(
354 File contextRoot, boolean allowWrites) {
355 PermissionCollection permissions = new Permissions();
356 addPermissionsToAccessAppFiles(permissions, contextRoot, allowWrites);
358 List<File> allFiles = IoUtil.getFilesAndDirectories(contextRoot);
360 for (File file : allFiles) {
361 addPermissionsToAccessAppFiles(permissions, file, allowWrites);
364 permissions.setReadOnly();
365 return permissions;
368 private static void addPermissionsToAccessAppFiles(
369 PermissionCollection permissions, File fileOrDirectory, boolean allowWrites) {
370 String path = fileOrDirectory.getAbsolutePath();
371 permissions.add(new FilePermission(path, SecurityConstants.FILE_READ_ACTION));
372 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_READ_ACTION));
373 if (allowWrites) {
374 permissions.add(new FilePermission(path, SecurityConstants.FILE_WRITE_ACTION));
375 permissions.add(new FilePermission(path, SecurityConstants.FILE_DELETE_ACTION));
376 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_WRITE_ACTION));
377 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_DELETE_ACTION));
381 private static Permission getReadPermission(URL url) {
382 Permission p;
383 try {
384 URLConnection urlConnection = url.openConnection();
385 p = urlConnection.getPermission();
386 } catch (IOException e) {
387 throw new RuntimeException("Unable to obtain the permission for " + url, e);
389 return new FilePermission(p.getName(), SecurityConstants.FILE_READ_ACTION);
392 private static Permission getJreReadPermission() {
393 return getReadPermission(Object.class.getResource("/java/lang/Object.class"));
397 * Utility method that adds the contents of one permission collection (the
398 * source) into another permission collection (the dest).
400 private static void addAllPermissions(PermissionCollection src, PermissionCollection dest) {
401 Enumeration<Permission> srcElements = src.elements();
402 while (srcElements.hasMoreElements()) {
403 dest.add(srcElements.nextElement());