App Engine Java SDK version 1.7.0
[gae.git] / java / src / main / com / google / appengine / tools / development / IsolatedAppClassLoader.java
blob2e70263f56786899201011fb232189dd4403a78b
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.apphosting.utils.io.IoUtil;
9 import sun.security.util.SecurityConstants;
11 import java.io.File;
12 import java.io.FilePermission;
13 import java.io.IOException;
14 import java.lang.reflect.ReflectPermission;
15 import java.net.MalformedURLException;
16 import java.net.URL;
17 import java.net.URLClassLoader;
18 import java.net.URLConnection;
19 import java.security.AccessController;
20 import java.security.AllPermission;
21 import java.security.CodeSource;
22 import java.security.Permission;
23 import java.security.PermissionCollection;
24 import java.security.Permissions;
25 import java.security.Policy;
26 import java.security.PrivilegedAction;
27 import java.security.ProtectionDomain;
28 import java.security.Provider;
29 import java.security.SecurityPermission;
30 import java.security.UnresolvedPermission;
31 import java.util.Enumeration;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.PropertyPermission;
35 import java.util.Set;
36 import java.util.logging.Level;
37 import java.util.logging.Logger;
38 import java.util.logging.LoggingPermission;
40 /**
41 * A webapp {@code ClassLoader}. This {@code ClassLoader} isolates
42 * webapps from the {@link DevAppServer} and anything else
43 * that might happen to be on the system classpath.
44 * It also grants the appropriate security permissions to the
45 * webapp classes that it loads.
48 class IsolatedAppClassLoader extends URLClassLoader {
50 private static Logger logger = Logger.getLogger(IsolatedAppClassLoader.class.getName());
52 private final PermissionCollection appPermissions;
53 private final Permissions appPermissionsAsPermissions;
54 private final ClassLoader devAppServerClassLoader;
55 private final Set<URL> sharedCodeLibs;
56 private final Set<URL> agentRuntimeLibs;
58 public IsolatedAppClassLoader(File appRoot, File externalResourceDir, URL[] urls,
59 ClassLoader devAppServerClassLoader) {
60 super(urls, null);
61 checkWorkingDirectory(appRoot, externalResourceDir);
62 appPermissions = createAppPermissions(appRoot, externalResourceDir);
63 appPermissionsAsPermissions = new Permissions();
64 addAllPermissions(appPermissions, appPermissionsAsPermissions);
65 installPolicyProxy(appRoot);
66 this.devAppServerClassLoader = devAppServerClassLoader;
67 this.sharedCodeLibs = new HashSet<URL>(SdkInfo.getSharedLibs());
68 this.agentRuntimeLibs = new HashSet<URL>(SdkImplInfo.getAgentRuntimeLibs());
71 /**
72 * Issues a warning if the current working directory != {@code appRoot},
73 * or {@code externalResourceDir}.
75 * The working directory of remotely deployed apps always == appRoot.
76 * For DevAppServer, We don't currently force users to set their working
77 * directory equal to the appRoot. We also don't set it for them
78 * (due to extent ramifications). The best we can do at the moment is to
79 * warn them that they may experience permission problems in production
80 * if they access files in a working directory != appRoot.
82 * If we are using an external resource directory, then it is also fine
83 * for the working directory to point there.
85 * @param appRoot
87 private static void checkWorkingDirectory(File appRoot, File externalResourceDir) {
88 File workingDir = new File(System.getProperty("user.dir"));
90 String canonicalWorkingDir = null;
91 String canonicalAppRoot = null;
92 String canonicalExternalResourceDir = null;
94 try {
95 canonicalWorkingDir = workingDir.getCanonicalPath();
96 canonicalAppRoot = appRoot.getCanonicalPath();
97 if (externalResourceDir != null) {
98 canonicalExternalResourceDir = externalResourceDir.getCanonicalPath();
100 } catch (IOException e) {
101 logger.log(Level.FINE, "Unable to compare the working directory and app root.", e);
104 if (canonicalWorkingDir != null && !canonicalWorkingDir.equals(canonicalAppRoot)) {
105 if (canonicalExternalResourceDir != null
106 && canonicalWorkingDir.equals(canonicalExternalResourceDir)) {
107 return;
109 String newLine = System.getProperty("line.separator");
110 String workDir = workingDir.getAbsolutePath();
111 String appDir = appRoot.getAbsolutePath();
112 String msg = "Your working directory, (" + workDir + ") is not equal to your " + newLine +
113 "web application root (" + appDir + ")" + newLine +
114 "You will not be able to access files from your working directory on the " +
115 "production server." + newLine;
116 logger.warning(msg);
120 @Override
121 public URL getResource(String name) {
122 URL resource = devAppServerClassLoader.getResource(name);
123 if (resource != null) {
124 if (resource.getProtocol().equals("jar")) {
125 int bang = resource.getPath().indexOf('!');
126 if (bang > 0) {
127 try {
128 URL url = new URL(resource.getPath().substring(0, bang));
129 if (sharedCodeLibs.contains(url)) {
130 return resource;
132 } catch (MalformedURLException ex) {
133 logger.log(Level.WARNING, "Unexpected exception while loading " + name, ex);
138 return super.getResource(name);
141 @Override
142 protected synchronized Class<?> loadClass(String name, boolean resolve)
143 throws ClassNotFoundException {
145 try {
146 final Class c = devAppServerClassLoader.loadClass(name);
148 CodeSource source = AccessController.doPrivileged(
149 new PrivilegedAction<CodeSource>() {
150 public CodeSource run() {
151 return c.getProtectionDomain().getCodeSource();
155 if (source == null) {
156 return c;
159 URL location = source.getLocation();
160 if (sharedCodeLibs.contains(location) ||
161 location.getFile().endsWith("/appengine-agent.jar")) {
162 if (resolve) {
163 resolveClass(c);
165 return c;
167 } catch (ClassNotFoundException e) {
170 return super.loadClass(name, resolve);
173 @Override
174 protected PermissionCollection getPermissions(CodeSource codesource) {
175 PermissionCollection permissions = super.getPermissions(codesource);
176 if (agentRuntimeLibs.contains(codesource.getLocation())) {
177 permissions.add(new AllPermission());
178 } else {
179 addAllPermissions(appPermissions, permissions);
181 return permissions;
184 public Permissions getAppPermissions() {
185 return appPermissionsAsPermissions;
188 private PermissionCollection createAppPermissions(File appRoot, File externalResourceDir) {
189 PermissionCollection permissions = new Permissions();
191 permissions.add(new FilePermission(appRoot.getAbsolutePath() + File.separatorChar + "-",
192 SecurityConstants.FILE_READ_ACTION));
193 addAllPermissions(buildPermissionsToReadAppFiles(appRoot), permissions);
194 if (externalResourceDir != null) {
195 permissions.add(new FilePermission(
196 externalResourceDir.getAbsolutePath() + File.separatorChar + "-",
197 SecurityConstants.FILE_READ_ACTION));
198 addAllPermissions(buildPermissionsToReadAppFiles(externalResourceDir), permissions);
201 if (Boolean.valueOf(System.getProperty("--enable_all_permissions"))) {
202 permissions.add(new AllPermission());
203 return permissions;
206 permissions.add(new RuntimePermission("getClassLoader"));
207 permissions.add(new RuntimePermission("setContextClassLoader"));
208 permissions.add(new RuntimePermission("createClassLoader"));
209 permissions.add(new RuntimePermission("getProtectionDomain"));
210 permissions.add(new RuntimePermission("accessDeclaredMembers"));
211 permissions.add(new ReflectPermission("suppressAccessChecks"));
212 permissions.add(new LoggingPermission("control", ""));
213 permissions.add(new RuntimePermission("getStackTrace"));
214 permissions.add(new RuntimePermission("getenv.*"));
215 permissions.add(new RuntimePermission("setIO"));
216 permissions.add(new PropertyPermission("*", "read,write"));
218 permissions.add(new
219 RuntimePermission("accessClassInPackage.com.sun.xml.internal.ws.*"));
221 permissions.add(new RuntimePermission("loadLibrary.keychain"));
223 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "getMetadata", null,
224 null));
225 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "setStateManager", null,
226 null));
227 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "manageMetadata", null,
228 null));
229 permissions.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission",
230 "closePersistenceManagerFactory", null, null));
232 permissions.add(new UnresolvedPermission("groovy.security.GroovyCodeSourcePermission", "*",
233 null, null));
235 permissions.add(new FilePermission(System.getProperty("user.dir") + File.separatorChar + "-",
236 SecurityConstants.FILE_READ_ACTION));
238 permissions.add(getJreReadPermission());
240 for (File f : SdkInfo.getSharedLibFiles()) {
241 permissions.add(new FilePermission(f.getAbsolutePath(), SecurityConstants.FILE_READ_ACTION));
244 permissions.add(new SecurityPermission("putProviderProperty.*"));
245 permissions.add(new SecurityPermission("insertProvider.*"));
246 permissions.add(new SecurityPermission("removeProvider.*"));
248 permissions.setReadOnly();
250 return permissions;
254 * This is a terrible hack so that we can get Jasper to grant JSPs
255 * the permissions they need (since JasperLoader is not configurable
256 * in terms of the permissions it grants). We know that JspRuntimeContext
257 * uses the permissions returned from Policy.getPermissions() on the
258 * codeSource for the path of the webapp context.
260 private void installPolicyProxy(File appRoot) {
262 Policy p = Policy.getPolicy();
263 if (p instanceof ProxyPolicy) {
264 return;
266 Policy.setPolicy(new ProxyPolicy(p, appRoot));
269 class ProxyPolicy extends Policy {
270 private Policy delegate;
271 private File appRoot;
272 ProxyPolicy(Policy delegate, File appRoot) {
273 this.delegate = delegate;
274 this.appRoot = appRoot;
277 @Override
278 public Provider getProvider() {
279 return delegate.getProvider();
282 @Override
283 public String getType() {
284 return delegate.getType();
287 @Override
288 public Parameters getParameters() {
289 return delegate.getParameters();
292 @Override
293 public PermissionCollection getPermissions(final CodeSource codeSource) {
294 return AccessController.doPrivileged(new PrivilegedAction<PermissionCollection>() {
295 @SuppressWarnings({"deprecation"})
296 public PermissionCollection run() {
297 PermissionCollection delegatePerms = delegate.getPermissions(codeSource);
299 try {
300 if (appRoot.toURL().equals(codeSource.getLocation())) {
301 Permissions newPerms = new Permissions();
302 addAllPermissions(delegatePerms, newPerms);
303 addAllPermissions(appPermissions, newPerms);
304 return newPerms;
306 } catch (MalformedURLException ex) {
307 throw new RuntimeException("Could not turn " + appRoot + "into a URL", ex);
309 return delegatePerms;
314 @Override
315 public PermissionCollection getPermissions(ProtectionDomain domain) {
316 return getPermissions(domain.getCodeSource());
319 @Override
320 public boolean implies(final ProtectionDomain domain, final Permission permission) {
321 return AccessController.doPrivileged(new PrivilegedAction<Boolean>(){
322 public Boolean run() {
323 return delegate.implies(domain, permission);
328 @Override
329 public void refresh() {
330 delegate.refresh();
334 private static PermissionCollection buildPermissionsToReadAppFiles(File contextRoot) {
335 PermissionCollection permissions = new Permissions();
336 String path = contextRoot.getAbsolutePath();
337 permissions.add(new FilePermission(path, SecurityConstants.FILE_READ_ACTION));
338 permissions.add(new FilePermission(path + "/-", SecurityConstants.FILE_READ_ACTION));
340 List<File> allFiles = IoUtil.getFilesAndDirectories(contextRoot);
342 for (File file : allFiles) {
343 String filePath = file.getAbsolutePath();
344 permissions.add(new FilePermission(filePath, SecurityConstants.FILE_READ_ACTION));
347 permissions.setReadOnly();
348 return permissions;
351 private static Permission getReadPermission(URL url) {
352 Permission p;
353 try {
354 URLConnection urlConnection = url.openConnection();
355 p = urlConnection.getPermission();
356 } catch (IOException e) {
357 throw new RuntimeException("Unable to obtain the permission for " + url, e);
359 return new FilePermission(p.getName(), SecurityConstants.FILE_READ_ACTION);
362 private static Permission getJreReadPermission() {
363 return getReadPermission(Object.class.getResource("/java/lang/Object.class"));
367 * Utility method that adds the contents of one permission collection (the
368 * source) into another permission collection (the dest).
370 private static void addAllPermissions(PermissionCollection src, PermissionCollection dest) {
371 Enumeration<Permission> srcElements = src.elements();
372 while (srcElements.hasMoreElements()) {
373 dest.add(srcElements.nextElement());