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
;
13 import java
.io
.FilePermission
;
14 import java
.io
.IOException
;
15 import java
.lang
.reflect
.ReflectPermission
;
16 import java
.net
.MalformedURLException
;
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
;
37 import java
.util
.logging
.Level
;
38 import java
.util
.logging
.Logger
;
39 import java
.util
.logging
.LoggingPermission
;
41 import javax
.xml
.bind
.JAXBPermission
;
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
) {
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());
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.
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;
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
)) {
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
;
127 private static boolean inApplicationPreparationMode() {
128 boolean inMode
= Boolean
.parseBoolean(
129 System
.getProperty(SharedConstants
.APPLICATION_PREPARATION_MODE_SYSTEM_PROPERTY
));
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! **"
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('!');
150 URL url
= new URL(resource
.getPath().substring(0, bang
));
151 if (sharedCodeLibs
.contains(url
)) {
154 } catch (MalformedURLException ex
) {
155 logger
.log(Level
.WARNING
, "Unexpected exception while loading " + name
, ex
);
160 return super.getResource(name
);
164 protected synchronized Class
<?
> loadClass(String name
, boolean resolve
)
165 throws ClassNotFoundException
{
168 final Class
<?
> c
= devAppServerClassLoader
.loadClass(name
);
170 CodeSource source
= AccessController
.doPrivileged(
171 new PrivilegedAction
<CodeSource
>() {
173 public CodeSource
run() {
174 return c
.getProtectionDomain().getCodeSource();
178 if (source
== null) {
182 URL location
= source
.getLocation();
183 if (sharedCodeLibs
.contains(location
) ||
184 location
.getFile().endsWith("/appengine-agent.jar")
185 || name
.equals(DEV_APP_SERVER_AGENT
)) {
191 } catch (ClassNotFoundException e
) {
194 return super.loadClass(name
, resolve
);
198 protected PermissionCollection
getPermissions(CodeSource codesource
) {
199 PermissionCollection permissions
= super.getPermissions(codesource
);
200 if (agentRuntimeLibs
.contains(codesource
.getLocation())) {
201 permissions
.add(new AllPermission());
203 addAllPermissions(appPermissions
, 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) {
218 buildPermissionsToAccessAppFiles(externalResourceDir
, allowWriteAccess
), permissions
);
221 if (Boolean
.valueOf(System
.getProperty("--enable_all_permissions"))) {
222 permissions
.add(new AllPermission());
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"));
241 new RuntimePermission("accessClassInPackage.com.sun.xml.internal.stream.*"));
243 new RuntimePermission("accessClassInPackage.com.sun.xml.internal.messaging.*"));
245 new RuntimePermission("accessClassInPackage.com.sun.org.apache.xerces.internal.*"));
247 new RuntimePermission("accessClassInPackage.com.sun.org.apache.xalan.internal.*"));
249 new RuntimePermission("accessClassInPackage.com.sun.org.apache.xml.internal.security.*"));
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,
258 permissions
.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "setStateManager", null,
260 permissions
.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "manageMetadata", null,
262 permissions
.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission",
263 "closePersistenceManagerFactory", null, null));
265 permissions
.add(new UnresolvedPermission("groovy.security.GroovyCodeSourcePermission", "*",
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();
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
) {
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
;
315 public Provider
getProvider() {
316 return delegate
.getProvider();
320 public String
getType() {
321 return delegate
.getType();
325 public Parameters
getParameters() {
326 return delegate
.getParameters();
330 public PermissionCollection
getPermissions(final CodeSource codeSource
) {
331 return AccessController
.doPrivileged(new PrivilegedAction
<PermissionCollection
>() {
332 @SuppressWarnings({"deprecation"})
334 public PermissionCollection
run() {
335 PermissionCollection delegatePerms
= delegate
.getPermissions(codeSource
);
338 if (appRoot
.toURL().equals(codeSource
.getLocation())) {
339 Permissions newPerms
= new Permissions();
340 addAllPermissions(delegatePerms
, newPerms
);
341 addAllPermissions(appPermissions
, newPerms
);
344 } catch (MalformedURLException ex
) {
345 throw new RuntimeException("Could not turn " + appRoot
+ "into a URL", ex
);
347 return delegatePerms
;
353 public PermissionCollection
getPermissions(ProtectionDomain domain
) {
354 return getPermissions(domain
.getCodeSource());
358 public boolean implies(final ProtectionDomain domain
, final Permission permission
) {
359 return AccessController
.doPrivileged(new PrivilegedAction
<Boolean
>(){
361 public Boolean
run() {
362 return delegate
.implies(domain
, permission
);
368 public void 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();
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
));
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
) {
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());