Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / IsolatedAppClassLoader.java
blob36fd177213b84583de165e2d216e10eed1a66395
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 @Override
171 public CodeSource run() {
172 return c.getProtectionDomain().getCodeSource();
176 if (source == null) {
177 return c;
180 URL location = source.getLocation();
181 if (sharedCodeLibs.contains(location) ||
182 location.getFile().endsWith("/appengine-agent.jar")
183 || name.equals(DEV_APP_SERVER_AGENT)) {
184 if (resolve) {
185 resolveClass(c);
187 return c;
189 } catch (ClassNotFoundException e) {
192 return super.loadClass(name, resolve);
195 @Override
196 protected PermissionCollection getPermissions(CodeSource codesource) {
197 PermissionCollection permissions = super.getPermissions(codesource);
198 if (agentRuntimeLibs.contains(codesource.getLocation())) {
199 permissions.add(new AllPermission());
200 } else {
201 addAllPermissions(appPermissions, permissions);
203 return permissions;
206 public Permissions getAppPermissions() {
207 return appPermissionsAsPermissions;
210 private PermissionCollection createAppPermissions(
211 File appRoot, File externalResourceDir, boolean allowWriteAccess) {
212 PermissionCollection permissions = new Permissions();
213 addAllPermissions(buildPermissionsToAccessAppFiles(appRoot, allowWriteAccess), permissions);
214 if (externalResourceDir != null) {
215 addAllPermissions(
216 buildPermissionsToAccessAppFiles(externalResourceDir, allowWriteAccess), permissions);
219 if (Boolean.valueOf(System.getProperty("--enable_all_permissions"))) {
220 permissions.add(new AllPermission());
221 return permissions;
224 permissions.add(new RuntimePermission("getClassLoader"));
225 permissions.add(new RuntimePermission("setContextClassLoader"));
226 permissions.add(new RuntimePermission("createClassLoader"));
227 permissions.add(new RuntimePermission("getProtectionDomain"));
228 permissions.add(new RuntimePermission("accessDeclaredMembers"));
229 permissions.add(new ReflectPermission("suppressAccessChecks"));
230 permissions.add(new LoggingPermission("control", ""));
231 permissions.add(new RuntimePermission("getStackTrace"));
232 permissions.add(new RuntimePermission("getenv.*"));
233 permissions.add(new RuntimePermission("setIO"));
234 permissions.add(new PropertyPermission("*", "read,write"));
236 permissions.add(new
237 RuntimePermission("accessClassInPackage.com.sun.xml.internal.ws.*"));
239 permissions.add(new RuntimePermission("loadLibrary.keychain"));
241 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "getMetadata", null,
242 null));
243 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "setStateManager", null,
244 null));
245 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "manageMetadata", null,
246 null));
247 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission",
248 "closePersistenceManagerFactory", null, null));
250 permissions.add(new UnresolvedPermission("groovy.security.GroovyCodeSourcePermission", "*",
251 null, null));
253 permissions.add(new FilePermission(System.getProperty("user.dir") + File.separatorChar + "-",
254 SecurityConstants.FILE_READ_ACTION));
256 permissions.add(getJreReadPermission());
258 for (File f : SdkInfo.getSharedLibFiles()) {
259 permissions.add(new FilePermission(f.getAbsolutePath(), SecurityConstants.FILE_READ_ACTION));
262 permissions.add(new SecurityPermission("putProviderProperty.*"));
263 permissions.add(new SecurityPermission("insertProvider.*"));
264 permissions.add(new SecurityPermission("removeProvider.*"));
266 permissions.add(new SecurityPermission("getProperty.ssl.KeyManagerFactory.algorithm"));
268 permissions.setReadOnly();
270 return permissions;
274 * This is a terrible hack so that we can get Jasper to grant JSPs
275 * the permissions they need (since JasperLoader is not configurable
276 * in terms of the permissions it grants). We know that JspRuntimeContext
277 * uses the permissions returned from Policy.getPermissions() on the
278 * codeSource for the path of the webapp context.
280 private void installPolicyProxy(File appRoot) {
282 Policy p = Policy.getPolicy();
283 if (p instanceof ProxyPolicy) {
284 return;
286 Policy.setPolicy(new ProxyPolicy(p, appRoot));
289 class ProxyPolicy extends Policy {
290 private Policy delegate;
291 private File appRoot;
292 ProxyPolicy(Policy delegate, File appRoot) {
293 this.delegate = delegate;
294 this.appRoot = appRoot;
297 @Override
298 public Provider getProvider() {
299 return delegate.getProvider();
302 @Override
303 public String getType() {
304 return delegate.getType();
307 @Override
308 public Parameters getParameters() {
309 return delegate.getParameters();
312 @Override
313 public PermissionCollection getPermissions(final CodeSource codeSource) {
314 return AccessController.doPrivileged(new PrivilegedAction<PermissionCollection>() {
315 @SuppressWarnings({"deprecation"})
316 @Override
317 public PermissionCollection run() {
318 PermissionCollection delegatePerms = delegate.getPermissions(codeSource);
320 try {
321 if (appRoot.toURL().equals(codeSource.getLocation())) {
322 Permissions newPerms = new Permissions();
323 addAllPermissions(delegatePerms, newPerms);
324 addAllPermissions(appPermissions, newPerms);
325 return newPerms;
327 } catch (MalformedURLException ex) {
328 throw new RuntimeException("Could not turn " + appRoot + "into a URL", ex);
330 return delegatePerms;
335 @Override
336 public PermissionCollection getPermissions(ProtectionDomain domain) {
337 return getPermissions(domain.getCodeSource());
340 @Override
341 public boolean implies(final ProtectionDomain domain, final Permission permission) {
342 return AccessController.doPrivileged(new PrivilegedAction<Boolean>(){
343 @Override
344 public Boolean run() {
345 return delegate.implies(domain, permission);
350 @Override
351 public void refresh() {
352 delegate.refresh();
356 private static PermissionCollection buildPermissionsToAccessAppFiles(
357 File contextRoot, boolean allowWrites) {
358 PermissionCollection permissions = new Permissions();
359 addPermissionsToAccessAppFiles(permissions, contextRoot, allowWrites);
361 List<File> allFiles = IoUtil.getFilesAndDirectories(contextRoot);
363 for (File file : allFiles) {
364 addPermissionsToAccessAppFiles(permissions, file, allowWrites);
367 permissions.setReadOnly();
368 return permissions;
371 private static void addPermissionsToAccessAppFiles(
372 PermissionCollection permissions, File fileOrDirectory, boolean allowWrites) {
373 String path = fileOrDirectory.getAbsolutePath();
374 permissions.add(new FilePermission(path, SecurityConstants.FILE_READ_ACTION));
375 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_READ_ACTION));
376 if (allowWrites) {
377 permissions.add(new FilePermission(path, SecurityConstants.FILE_WRITE_ACTION));
378 permissions.add(new FilePermission(path, SecurityConstants.FILE_DELETE_ACTION));
379 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_WRITE_ACTION));
380 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_DELETE_ACTION));
384 private static Permission getReadPermission(URL url) {
385 Permission p;
386 try {
387 URLConnection urlConnection = url.openConnection();
388 p = urlConnection.getPermission();
389 } catch (IOException e) {
390 throw new RuntimeException("Unable to obtain the permission for " + url, e);
392 return new FilePermission(p.getName(), SecurityConstants.FILE_READ_ACTION);
395 private static Permission getJreReadPermission() {
396 return getReadPermission(Object.class.getResource("/java/lang/Object.class"));
400 * Utility method that adds the contents of one permission collection (the
401 * source) into another permission collection (the dest).
403 private static void addAllPermissions(PermissionCollection src, PermissionCollection dest) {
404 Enumeration<Permission> srcElements = src.elements();
405 while (srcElements.hasMoreElements()) {
406 dest.add(srcElements.nextElement());