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
;
12 import java
.io
.FilePermission
;
13 import java
.io
.IOException
;
14 import java
.lang
.reflect
.ReflectPermission
;
15 import java
.net
.MalformedURLException
;
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
;
36 import java
.util
.logging
.Level
;
37 import java
.util
.logging
.Logger
;
38 import java
.util
.logging
.LoggingPermission
;
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
) {
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());
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.
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;
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
)) {
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
;
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('!');
128 URL url
= new URL(resource
.getPath().substring(0, bang
));
129 if (sharedCodeLibs
.contains(url
)) {
132 } catch (MalformedURLException ex
) {
133 logger
.log(Level
.WARNING
, "Unexpected exception while loading " + name
, ex
);
138 return super.getResource(name
);
142 protected synchronized Class
<?
> loadClass(String name
, boolean resolve
)
143 throws ClassNotFoundException
{
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) {
159 URL location
= source
.getLocation();
160 if (sharedCodeLibs
.contains(location
) ||
161 location
.getFile().endsWith("/appengine-agent.jar")) {
167 } catch (ClassNotFoundException e
) {
170 return super.loadClass(name
, resolve
);
174 protected PermissionCollection
getPermissions(CodeSource codesource
) {
175 PermissionCollection permissions
= super.getPermissions(codesource
);
176 if (agentRuntimeLibs
.contains(codesource
.getLocation())) {
177 permissions
.add(new AllPermission());
179 addAllPermissions(appPermissions
, 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());
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"));
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,
225 permissions
.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "setStateManager", null,
227 permissions
.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission", "manageMetadata", null,
229 permissions
.add(new UnresolvedPermission("javax.jdo.spi.JDOPermission",
230 "closePersistenceManagerFactory", null, null));
232 permissions
.add(new UnresolvedPermission("groovy.security.GroovyCodeSourcePermission", "*",
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();
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
) {
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
;
278 public Provider
getProvider() {
279 return delegate
.getProvider();
283 public String
getType() {
284 return delegate
.getType();
288 public Parameters
getParameters() {
289 return delegate
.getParameters();
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
);
300 if (appRoot
.toURL().equals(codeSource
.getLocation())) {
301 Permissions newPerms
= new Permissions();
302 addAllPermissions(delegatePerms
, newPerms
);
303 addAllPermissions(appPermissions
, newPerms
);
306 } catch (MalformedURLException ex
) {
307 throw new RuntimeException("Could not turn " + appRoot
+ "into a URL", ex
);
309 return delegatePerms
;
315 public PermissionCollection
getPermissions(ProtectionDomain domain
) {
316 return getPermissions(domain
.getCodeSource());
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
);
329 public void 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();
351 private static Permission
getReadPermission(URL url
) {
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());