001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.auth;
021:
022: import java.io.File;
023: import java.net.URL;
024: import java.security.AccessController;
025: import java.security.Policy;
026: import java.security.PrivilegedAction;
027: import java.security.Security;
028:
029: import javax.security.auth.login.Configuration;
030:
031: import org.apache.log4j.Logger;
032:
033: /**
034: * <p>
035: * Initializes JVM configurations for JAAS and Java 2 security policy. Callers
036: * can use the static methods in this class ({@link #isJaasConfigured()}
037: * and {@link #isSecurityPolicyConfigured()}) to inquire whether a JAAS
038: * login configuration exists, or whether a custom Java security policy is in
039: * use. Additional methods allow callers to set the JAAS and security policy
040: * configurations to supplied URLs ({@link #setJaasConfiguration(URL)}
041: * and {@link #setSecurityPolicy(URL)}).
042: * </p>
043: * <p>
044: * If either the JAAS configuration and security policy are set using methods in
045: * this class, the resulting configuration or policy is <i>global</i> to the
046: * JVM. Thus, in a multi-webapp scenario, this means that the first webapp to be
047: * loaded by the container wins. Thus, for containers hosting multiple wikis,
048: * the administrator will need to manually configure the
049: * <code>java.security.policy</code> and
050: * <code>java.security.auth.login.config properties</code>. In other words,
051: * multi-wiki deployments will always require manual (one-time) configuration.
052: * </p>
053: * <p>
054: * The security policy-related methods {@link #isSecurityPolicyConfigured()}
055: * and {@link #setSecurityPolicy(URL)}) assumes that the web container
056: * doesn't use a "double-equals" command-line assignment
057: * to override the security policy ( <i>e.g. </i>,
058: * <code>-Djava.security.policy==jspwiki.policy</code>). Note that Tomcat 4
059: * and higher, when run using the "-security" option, does this.
060: * </p>
061: * <p>
062: * To interoperate with <i>any</i> container running with a security manager, the
063: * container's JVM security policy should include a short set of permission
064: * grant similar to the following:
065: * </p>
066: * <blockquote><code>keystore "jspwiki.jks";<br/>
067: * ...<br/>
068: * grant signedBy "jspwiki" {<br/>
069: * permission java.security.SecurityPermission, "getPolicy";<br/>
070: * permission java.security.SecurityPermission, "setPolicy";<br/>
071: * permission java.util.PropertyPermission "java.security.auth.login.config", "write";<br/>
072: * permission java.util.PropertyPermission "java.security.policy", "read,write";<br/>
073: * permission javax.security.auth.AuthPermission, "getLoginConfiguration";<br/>
074: * permission javax.security.auth.AuthPermission, "setLoginConfiguration";<br/>
075: * };</code>
076: * </blockquote>
077: * <p>
078: * The <code>signedBy</code> value should match the alias of a digital
079: * certificate in the named keystore ( <i>e.g. </i>, <code>jspwiki.jks</code>).
080: * If the full path to the keystore is not suppled, it is assumed to be in the
081: * same directory as the policy file.
082: * </p>
083: *
084: * @author Andrew Jaquith
085: * @since 2.3
086: */
087: public final class PolicyLoader {
088: protected static final Logger log = Logger
089: .getLogger(PolicyLoader.class);
090:
091: /**
092: * Private constructor to prevent direct instantiation.
093: */
094: private PolicyLoader() {
095: }
096:
097: /**
098: * <p>
099: * Returns <code>true</code> if the JAAS login configuration exists.
100: * Normally, JAAS is configured by setting the system property
101: * <code>java.security.auth.login.config</code> at JVM startup.
102: * </p>
103: * <p>
104: * This method attempts to perform a highly privileged operation. If the JVM
105: * runs with a SecurityManager, the following permission must be granted to
106: * the codesource containing this class:
107: * </p>
108: * <code><ul>
109: * <li>permission javax.security.auth.AuthPermission,
110: * "getLoginConfiguration"</li>
111: * </ul></code>
112: *
113: * @return <code>true</code> if
114: * {@link javax.security.auth.login.Configuration#getConfiguration()}
115: * is not <code>null</code> ; <code>false</code> otherwise.
116: * @throws SecurityException if the codesource containing this class posesses
117: * insufficient permmissions when running with a SecurityManager
118: */
119: public static final boolean isJaasConfigured()
120: throws SecurityException {
121: Boolean configured = (Boolean) AccessController
122: .doPrivileged(new PrivilegedAction() {
123:
124: public Object run() {
125: boolean isConfigured = false;
126: try {
127: Configuration config = Configuration
128: .getConfiguration();
129: isConfigured = config != null;
130: } catch (SecurityException e) {
131: }
132: return Boolean.valueOf(isConfigured);
133: }
134: });
135: return configured.booleanValue();
136: }
137:
138: /**
139: * <p>
140: * Returns <code>true</code> if a custom Java security policy configuration
141: * exists. Normally, the Java security policy is configured by setting the
142: * system property <code>java.security.policy</code> at JVM startup.
143: * </p>
144: * <p>
145: * This method attempts to perform a highly privileged operation. If the JVM
146: * runs with a SecurityManager, the following permission must be granted to
147: * the codesource containing this class:
148: * </p>
149: * <code><ul>
150: * <li>permission java.util.PropertyPermission
151: * "java.security.policy", "read"</li>
152: * </ul></code>
153: *
154: * @return <code>true</code> if the system property
155: * <code>java.security.policy</code> is not <code>null</code>;
156: * <code>false</code> otherwise.
157: * @throws SecurityException if the codesource containing this class posesses
158: * insufficient permmissions when running with a SecurityManager
159: */
160: public static final boolean isSecurityPolicyConfigured()
161: throws SecurityException {
162: String policy = (String) AccessController
163: .doPrivileged(new PrivilegedAction() {
164:
165: public Object run() {
166: return System
167: .getProperty("java.security.policy");
168: }
169: });
170:
171: if (policy != null) {
172: log.info("Java security policy already set to: " + policy
173: + ". (Leaving it alone...)");
174:
175: //
176: // Do a bit of a sanity checks to help people who are not familiar with JAAS.
177: //
178: if (policy.startsWith("file:"))
179: policy = policy.substring("file:".length());
180:
181: File f = new File(policy);
182:
183: if (!f.exists()) {
184: log
185: .warn("You have set your 'java.security.policy' to point at '"
186: + f.getAbsolutePath()
187: + "', "
188: + "but that file does not seem to exist. I'll continue anyway, since this may be "
189: + "something specific to your servlet container. Just consider yourself warned.");
190: }
191:
192: File jks = new File(f.getParentFile(), "jspwiki.jks");
193:
194: if (!jks.exists() || !jks.canRead()) {
195: log
196: .warn("I could not locate the JSPWiki keystore ('jspwiki.jks') in the same directory "
197: + "as your jspwiki.policy file. On many servlet containers, such as Tomcat, this "
198: + "needs to be done. If you keep having access right permissions, please try "
199: + "copying your WEB-INF/jspwiki.jks to "
200: + f.getParentFile().getAbsolutePath());
201: } else {
202: log
203: .info("Found 'jspwiki.jks' from '"
204: + f.getParentFile().getAbsolutePath()
205: + "'. If you are having "
206: + "permission issues after an upgrade, please make sure that this file matches the one that "
207: + "came with your distribution archive.");
208: }
209:
210: }
211: return policy != null;
212: }
213:
214: /**
215: * Sets the JAAS login configuration file, overwriting the existing
216: * configuration. If the configuration file pointed to by the URL does not
217: * exist, a SecurityException is thrown.
218: * <p>
219: * This method attempts to perform several highly privileged operations. If
220: * the JVM runs with a SecurityManager, the following permissions must be
221: * granted to the codesource containing this class:
222: * </p>
223: * <code><ul>
224: * <li>permission java.util.PropertyPermission
225: * "java.security.auth.login.config", "write"</li>
226: * <li>permission javax.security.auth.AuthPermission,
227: * "getLoginConfiguration"</li>
228: * <li>permission javax.security.auth.AuthPermission,
229: * "setLoginConfiguration"</li>
230: * </ul></code>
231: *
232: * @param url the URL of the login configuration file. If the URL contains
233: * properties such as <code>${java.home}</code>, they will be
234: * expanded.
235: * @throws SecurityException if:
236: * <ul>
237: * <li>the supplied URL is <code>null</code></li>
238: * <li>properties cannot be expanded</li>
239: * <li>the codesource containing this class does not posesses
240: * sufficient permmissions when running with a SecurityManager</li>
241: * </ul>
242: */
243: public static final void setJaasConfiguration(final URL url)
244: throws SecurityException {
245: if (url == null) {
246: throw new SecurityException(
247: "URL for JAAS configuration cannot be null.");
248: }
249:
250: // Get JAAS configuration class; default is Sun provider
251: String defaultConfigClass;
252: defaultConfigClass = (String) AccessController
253: .doPrivileged(new PrivilegedAction() {
254: public Object run() {
255: return Security
256: .getProperty("login.configuration.provider");
257: }
258: });
259:
260: if (defaultConfigClass == null) {
261: defaultConfigClass = "com.sun.security.auth.login.ConfigFile";
262: }
263:
264: // Now, set the new config
265: final String config_class = defaultConfigClass;
266: AccessController.doPrivileged(new PrivilegedAction() {
267:
268: public Object run() {
269: // Remove old policy, then set our config URL and instantiate new instance
270: try {
271: Configuration.setConfiguration(null);
272: System.setProperty(
273: "java.security.auth.login.config", url
274: .toExternalForm());
275: Configuration config = (Configuration) Class
276: .forName(config_class).newInstance();
277: Configuration.setConfiguration(config);
278: return null;
279: } catch (Exception e) {
280: throw new SecurityException(e.getMessage());
281: }
282: }
283: });
284: }
285:
286: /**
287: * <p>
288: * Sets the Java security policy, overwriting any custom policy settings. This
289: * method sets the value of the system property
290: * <code>java.security.policy</code> to the supplied URL, then calls
291: * {@link java.security.Policy#setPolicy(java.security.Policy)} with a
292: * newly-instantiated instance of
293: * <code>sun.security.provider.PolicyFile</code> (the J2SE default
294: * implementation). The new Policy, once set, reloads the default system
295: * policies enumerated by the <code>policy.url.<i>n</i></code> entries in
296: * <code><i>JAVA_HOME</i>/lib/security/java.policy</code>, followed by the
297: * user-supplied policy file.
298: * </p>
299: * <p>
300: * This method attempts to perform several highly privileged operations. If
301: * the JVM runs with a SecurityManager, the following permissions must be
302: * granted to the codesource containing this class:
303: * </p>
304: * <code><ul>
305: * <li>permission java.security.SecurityPermission, "getPolicy"
306: * </li>
307: * <li>permission java.security.SecurityPermission, "setPolicy"
308: * </li>
309: * <li>permission java.util.PropertyPermission}
310: * "java.security.policy", "write"</li>
311: * </ul></code>
312: *
313: * @param url the URL of the security policy file. If the URL contains
314: * properties such as <code>${java.home}</code>, they will be
315: * expanded.
316: * @throws SecurityException if:
317: * <ul>
318: * <li>the supplied URL is <code>null</code></li>
319: * <li>properties cannot be expanded</li>
320: * <li>the codesource containing this class does not posesses
321: * sufficient permmissions when running with a SecurityManager</li>
322: * <li>the JVM's current Policy implementation is not of type
323: * <code>sun.security.provider.PolicyFile</code></li>
324: * </ul>
325: */
326: public static final void setSecurityPolicy(final URL url)
327: throws SecurityException {
328: if (url == null) {
329: throw new SecurityException(
330: "URL for security policy cannot be null.");
331: }
332:
333: // Get policy class; default is Sun provider
334: String defaultPolicyClass;
335: defaultPolicyClass = (String) AccessController
336: .doPrivileged(new PrivilegedAction() {
337: public Object run() {
338: return Security.getProperty("policy.provider");
339: }
340: });
341:
342: if (defaultPolicyClass == null) {
343: defaultPolicyClass = "sun.security.provider.PolicyFile";
344: }
345:
346: // Now, set the new policy
347: final String policyClass = defaultPolicyClass;
348: AccessController.doPrivileged(new PrivilegedAction() {
349:
350: public Object run() {
351: // Remove old policy, then set our policy URL and instantiate new instance
352: try {
353: Policy.setPolicy(null);
354: System.setProperty("java.security.policy", url
355: .toExternalForm());
356:
357: Policy policy = (Policy) Class.forName(policyClass)
358: .newInstance();
359: Policy.setPolicy(policy);
360: return null;
361: } catch (Exception e) {
362: throw new SecurityException(e.getMessage());
363: }
364: }
365: });
366: }
367:
368: }
|