Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / IsolatedAppClassLoader.java
blob14f461115a36c8fb42fe68447e520b5bd8012a0b
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 import javax.xml.bind.JAXBPermission;
43 /**
44 * A webapp {@code ClassLoader}. This {@code ClassLoader} isolates
45 * webapps from the {@link DevAppServer} and anything else
46 * that might happen to be on the system classpath.
47 * It also grants the appropriate security permissions to the
48 * webapp classes that it loads.
51 public class IsolatedAppClassLoader extends URLClassLoader {
53 private static final Logger logger = Logger.getLogger(IsolatedAppClassLoader.class.getName());
55 private static final String DEV_APP_SERVER_AGENT
56 = "com.google.appengine.tools.development.agent.AppEngineDevAgent";
58 private final PermissionCollection appPermissions;
59 private final Permissions appPermissionsAsPermissions;
60 private final ClassLoader devAppServerClassLoader;
61 private final Set<URL> sharedCodeLibs;
62 private final Set<URL> agentRuntimeLibs;
64 public IsolatedAppClassLoader(File appRoot, File externalResourceDir, URL[] urls,
65 ClassLoader devAppServerClassLoader) {
66 super(urls, null);
67 checkWorkingDirectory(appRoot, externalResourceDir);
68 boolean allowWrites = inApplicationPreparationMode();
69 appPermissions = createAppPermissions(appRoot, externalResourceDir, allowWrites);
70 appPermissionsAsPermissions = new Permissions();
71 addAllPermissions(appPermissions, appPermissionsAsPermissions);
72 installPolicyProxy(appRoot);
73 this.devAppServerClassLoader = devAppServerClassLoader;
74 this.sharedCodeLibs = new HashSet<URL>(SdkInfo.getSharedLibs());
75 this.agentRuntimeLibs = new HashSet<URL>(SdkImplInfo.getAgentRuntimeLibs());
78 /**
79 * Issues a warning if the current working directory != {@code appRoot},
80 * or {@code externalResourceDir}.
82 * The working directory of remotely deployed apps always == appRoot.
83 * For DevAppServer, We don't currently force users to set their working
84 * directory equal to the appRoot. We also don't set it for them
85 * (due to extent ramifications). The best we can do at the moment is to
86 * warn them that they may experience permission problems in production
87 * if they access files in a working directory != appRoot.
89 * If we are using an external resource directory, then it is also fine
90 * for the working directory to point there.
92 * @param appRoot
94 private static void checkWorkingDirectory(File appRoot, File externalResourceDir) {
95 File workingDir = new File(System.getProperty("user.dir"));
97 String canonicalWorkingDir = null;
98 String canonicalAppRoot = null;
99 String canonicalExternalResourceDir = null;
101 try {
102 canonicalWorkingDir = workingDir.getCanonicalPath();
103 canonicalAppRoot = appRoot.getCanonicalPath();
104 if (externalResourceDir != null) {
105 canonicalExternalResourceDir = externalResourceDir.getCanonicalPath();
107 } catch (IOException e) {
108 logger.log(Level.FINE, "Unable to compare the working directory and app root.", e);
111 if (canonicalWorkingDir != null && !canonicalWorkingDir.equals(canonicalAppRoot)) {
112 if (canonicalExternalResourceDir != null
113 && canonicalWorkingDir.equals(canonicalExternalResourceDir)) {
114 return;
116 String newLine = System.getProperty("line.separator");
117 String workDir = workingDir.getAbsolutePath();
118 String appDir = appRoot.getAbsolutePath();
119 String msg = "Your working directory, (" + workDir + ") is not equal to your " + newLine
120 + "web application root (" + appDir + ")" + newLine
121 + "You will not be able to access files from your working directory on the "
122 + "production server." + newLine;
123 logger.warning(msg);
127 private static boolean inApplicationPreparationMode() {
128 boolean inMode = Boolean.parseBoolean(
129 System.getProperty(SharedConstants.APPLICATION_PREPARATION_MODE_SYSTEM_PROPERTY));
130 if (inMode) {
131 String newLine = System.getProperty("line.separator");
132 String msg = newLine + "** Running in application-preparation mode. **." + newLine
133 + "** Code will be allowed to write to you application "
134 + "directory while running locally. **" + newLine
135 + "** But writing will not be allowed when " + "your code is uploaded to App Engine! **"
136 + newLine;
137 logger.warning(msg);
139 return inMode;
142 @Override
143 public URL getResource(String name) {
144 URL resource = devAppServerClassLoader.getResource(name);
145 if (resource != null) {
146 if (resource.getProtocol().equals("jar")) {
147 int bang = resource.getPath().indexOf('!');
148 if (bang > 0) {
149 try {
150 URL url = new URL(resource.getPath().substring(0, bang));
151 if (sharedCodeLibs.contains(url)) {
152 return resource;
154 } catch (MalformedURLException ex) {
155 logger.log(Level.WARNING, "Unexpected exception while loading " + name, ex);
160 return super.getResource(name);
163 @Override
164 protected synchronized Class<?> loadClass(String name, boolean resolve)
165 throws ClassNotFoundException {
167 try {
168 final Class<?> c = devAppServerClassLoader.loadClass(name);
170 CodeSource source = AccessController.doPrivileged(
171 new PrivilegedAction<CodeSource>() {
172 @Override
173 public CodeSource run() {
174 return c.getProtectionDomain().getCodeSource();
178 if (source == null) {
179 return c;
182 URL location = source.getLocation();
183 if (sharedCodeLibs.contains(location) ||
184 location.getFile().endsWith("/appengine-agent.jar")
185 || name.equals(DEV_APP_SERVER_AGENT)) {
186 if (resolve) {
187 resolveClass(c);
189 return c;
191 } catch (ClassNotFoundException e) {
194 return super.loadClass(name, resolve);
197 @Override
198 protected PermissionCollection getPermissions(CodeSource codesource) {
199 PermissionCollection permissions = super.getPermissions(codesource);
200 if (agentRuntimeLibs.contains(codesource.getLocation())) {
201 permissions.add(new AllPermission());
202 } else {
203 addAllPermissions(appPermissions, permissions);
205 return permissions;
208 public Permissions getAppPermissions() {
209 return appPermissionsAsPermissions;
212 private PermissionCollection createAppPermissions(
213 File appRoot, File externalResourceDir, boolean allowWriteAccess) {
214 PermissionCollection permissions = new Permissions();
215 addAllPermissions(buildPermissionsToAccessAppFiles(appRoot, allowWriteAccess), permissions);
216 if (externalResourceDir != null) {
217 addAllPermissions(
218 buildPermissionsToAccessAppFiles(externalResourceDir, allowWriteAccess), permissions);
221 if (Boolean.valueOf(System.getProperty("--enable_all_permissions"))) {
222 permissions.add(new AllPermission());
223 return permissions;
226 permissions.add(new RuntimePermission("getClassLoader"));
227 permissions.add(new RuntimePermission("setContextClassLoader"));
228 permissions.add(new RuntimePermission("createClassLoader"));
229 permissions.add(new RuntimePermission("getProtectionDomain"));
230 permissions.add(new RuntimePermission("accessDeclaredMembers"));
231 permissions.add(new ReflectPermission("suppressAccessChecks"));
232 permissions.add(new LoggingPermission("control", ""));
233 permissions.add(new RuntimePermission("getStackTrace"));
234 permissions.add(new RuntimePermission("getenv.*"));
235 permissions.add(new RuntimePermission("setIO"));
236 permissions.add(new PropertyPermission("*", "read,write"));
238 permissions.add(new RuntimePermission("accessClassInPackage.com.sun.xml.internal.ws.*"));
239 permissions.add(new RuntimePermission("accessClassInPackage.com.sun.xml.internal.ws"));
240 permissions.add(
241 new RuntimePermission("accessClassInPackage.com.sun.xml.internal.stream.*"));
242 permissions.add(
243 new RuntimePermission("accessClassInPackage.com.sun.xml.internal.messaging.*"));
244 permissions.add(
245 new RuntimePermission("accessClassInPackage.com.sun.org.apache.xerces.internal.*"));
246 permissions.add(
247 new RuntimePermission("accessClassInPackage.com.sun.org.apache.xalan.internal.*"));
248 permissions.add(
249 new RuntimePermission("accessClassInPackage.com.sun.org.apache.xml.internal.security.*"));
250 permissions.add(
251 new RuntimePermission("accessClassInPackage.org.jcp.xml.dsig.internal.*"));
252 permissions.add(new RuntimePermission("accessClassInPackage.com.sun.proxy"));
254 permissions.add(new RuntimePermission("loadLibrary.keychain"));
256 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "getMetadata", null,
257 null));
258 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "setStateManager", null,
259 null));
260 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "manageMetadata", null,
261 null));
262 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission",
263 "closePersistenceManagerFactory", null, null));
265 permissions.add(new UnresolvedPermission("groovy.security.GroovyCodeSourcePermission", "*",
266 null, null));
268 permissions.add(new FilePermission(System.getProperty("user.dir") + File.separatorChar + "-",
269 SecurityConstants.FILE_READ_ACTION));
271 permissions.add(getJreReadPermission());
273 for (File f : SdkInfo.getSharedLibFiles()) {
274 permissions.add(new FilePermission(f.getAbsolutePath(), SecurityConstants.FILE_READ_ACTION));
277 permissions.add(new SecurityPermission("putProviderProperty.*"));
278 permissions.add(new SecurityPermission("insertProvider.*"));
279 permissions.add(new SecurityPermission("removeProvider.*"));
281 permissions.add(new SecurityPermission("getProperty.ssl.KeyManagerFactory.algorithm"));
283 permissions.add(new JAXBPermission("setDatatypeConverter"));
285 permissions.setReadOnly();
287 return permissions;
291 * This is a terrible hack so that we can get Jasper to grant JSPs
292 * the permissions they need (since JasperLoader is not configurable
293 * in terms of the permissions it grants). We know that JspRuntimeContext
294 * uses the permissions returned from Policy.getPermissions() on the
295 * codeSource for the path of the webapp context.
297 private void installPolicyProxy(File appRoot) {
299 Policy p = Policy.getPolicy();
300 if (p instanceof ProxyPolicy) {
301 return;
303 Policy.setPolicy(new ProxyPolicy(p, appRoot));
306 class ProxyPolicy extends Policy {
307 private Policy delegate;
308 private File appRoot;
309 ProxyPolicy(Policy delegate, File appRoot) {
310 this.delegate = delegate;
311 this.appRoot = appRoot;
314 @Override
315 public Provider getProvider() {
316 return delegate.getProvider();
319 @Override
320 public String getType() {
321 return delegate.getType();
324 @Override
325 public Parameters getParameters() {
326 return delegate.getParameters();
329 @Override
330 public PermissionCollection getPermissions(final CodeSource codeSource) {
331 return AccessController.doPrivileged(new PrivilegedAction<PermissionCollection>() {
332 @SuppressWarnings({"deprecation"})
333 @Override
334 public PermissionCollection run() {
335 PermissionCollection delegatePerms = delegate.getPermissions(codeSource);
337 try {
338 if (appRoot.toURL().equals(codeSource.getLocation())) {
339 Permissions newPerms = new Permissions();
340 addAllPermissions(delegatePerms, newPerms);
341 addAllPermissions(appPermissions, newPerms);
342 return newPerms;
344 } catch (MalformedURLException ex) {
345 throw new RuntimeException("Could not turn " + appRoot + "into a URL", ex);
347 return delegatePerms;
352 @Override
353 public PermissionCollection getPermissions(ProtectionDomain domain) {
354 return getPermissions(domain.getCodeSource());
357 @Override
358 public boolean implies(final ProtectionDomain domain, final Permission permission) {
359 return AccessController.doPrivileged(new PrivilegedAction<Boolean>(){
360 @Override
361 public Boolean run() {
362 return delegate.implies(domain, permission);
367 @Override
368 public void refresh() {
369 delegate.refresh();
373 private static PermissionCollection buildPermissionsToAccessAppFiles(
374 File contextRoot, boolean allowWrites) {
375 PermissionCollection permissions = new Permissions();
376 addPermissionsToAccessAppFiles(permissions, contextRoot, allowWrites);
378 List<File> allFiles = IoUtil.getFilesAndDirectories(contextRoot);
380 for (File file : allFiles) {
381 addPermissionsToAccessAppFiles(permissions, file, allowWrites);
384 permissions.setReadOnly();
385 return permissions;
388 private static void addPermissionsToAccessAppFiles(
389 PermissionCollection permissions, File fileOrDirectory, boolean allowWrites) {
390 String path = fileOrDirectory.getAbsolutePath();
391 permissions.add(new FilePermission(path, SecurityConstants.FILE_READ_ACTION));
392 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_READ_ACTION));
393 if (allowWrites) {
394 permissions.add(new FilePermission(path, SecurityConstants.FILE_WRITE_ACTION));
395 permissions.add(new FilePermission(path, SecurityConstants.FILE_DELETE_ACTION));
396 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_WRITE_ACTION));
397 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_DELETE_ACTION));
401 private static Permission getReadPermission(URL url) {
402 Permission p;
403 try {
404 URLConnection urlConnection = url.openConnection();
405 p = urlConnection.getPermission();
406 } catch (IOException e) {
407 throw new RuntimeException("Unable to obtain the permission for " + url, e);
409 return new FilePermission(p.getName(), SecurityConstants.FILE_READ_ACTION);
412 private static Permission getJreReadPermission() {
413 return getReadPermission(Object.class.getResource("/java/lang/Object.class"));
417 * Utility method that adds the contents of one permission collection (the
418 * source) into another permission collection (the dest).
420 private static void addAllPermissions(PermissionCollection src, PermissionCollection dest) {
421 Enumeration<Permission> srcElements = src.elements();
422 while (srcElements.hasMoreElements()) {
423 dest.add(srcElements.nextElement());