001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2005 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.io.IOException;
024: import java.net.MalformedURLException;
025: import java.net.URL;
026: import java.security.*;
027: import java.util.Iterator;
028: import java.util.LinkedHashSet;
029: import java.util.List;
030: import java.util.Set;
031:
032: import javax.security.auth.Subject;
033: import javax.security.auth.login.LoginContext;
034:
035: import org.apache.commons.lang.ArrayUtils;
036: import org.apache.log4j.Logger;
037: import org.freshcookies.security.policy.PolicyReader;
038: import org.jdom.JDOMException;
039:
040: import com.ecyrd.jspwiki.InternalWikiException;
041: import com.ecyrd.jspwiki.WikiEngine;
042: import com.ecyrd.jspwiki.WikiException;
043: import com.ecyrd.jspwiki.WikiSession;
044: import com.ecyrd.jspwiki.auth.authorize.*;
045: import com.ecyrd.jspwiki.auth.permissions.AllPermission;
046: import com.ecyrd.jspwiki.auth.permissions.GroupPermission;
047: import com.ecyrd.jspwiki.auth.permissions.PermissionFactory;
048: import com.ecyrd.jspwiki.auth.permissions.WikiPermission;
049: import com.ecyrd.jspwiki.auth.user.DefaultUserProfile;
050: import com.ecyrd.jspwiki.auth.user.UserDatabase;
051: import com.ecyrd.jspwiki.auth.user.UserProfile;
052:
053: /**
054: * Helper class for verifying JSPWiki's security configuration. Invoked by
055: * <code>admin/SecurityConfig.jsp</code>.
056: * @author Andrew Jaquith
057: * @since 2.4
058: */
059: public final class SecurityVerifier {
060: private static final long serialVersionUID = -3859563355089169941L;
061:
062: private WikiEngine m_engine;
063:
064: private File m_jaasConfig = null;
065:
066: private boolean m_isJaasConfigured = false;
067:
068: private boolean m_isSecurityPolicyConfigured = false;
069:
070: private Principal[] m_policyPrincipals = new Principal[0];
071:
072: private WikiSession m_session;
073:
074: /** Message prefix for errors. */
075: public static final String ERROR = "Error.";
076:
077: /** Message prefix for warnings. */
078: public static final String WARNING = "Warning.";
079:
080: /** Message prefix for information messages. */
081: public static final String INFO = "Info.";
082:
083: /** Message topic for policy errors. */
084: public static final String ERROR_POLICY = "Error.Policy";
085:
086: /** Message topic for policy warnings. */
087: public static final String WARNING_POLICY = "Warning.Policy";
088:
089: /** Message topic for policy information messages. */
090: public static final String INFO_POLICY = "Info.Policy";
091:
092: /** Message topic for JAAS errors. */
093: public static final String ERROR_JAAS = "Error.Jaas";
094:
095: /** Message topic for JAAS warnings. */
096: public static final String WARNING_JAAS = "Warning.Jaas";
097:
098: /** Message topic for role-checking errors. */
099: public static final String ERROR_ROLES = "Error.Roles";
100:
101: /** Message topic for role-checking information messages. */
102: public static final String INFO_ROLES = "Info.Roles";
103:
104: /** Message topic for user database errors. */
105: public static final String ERROR_DB = "Error.UserDatabase";
106:
107: /** Message topic for user database warnings. */
108: public static final String WARNING_DB = "Warning.UserDatabase";
109:
110: /** Message topic for user database information messages. */
111: public static final String INFO_DB = "Info.UserDatabase";
112:
113: /** Message topic for group database errors. */
114: public static final String ERROR_GROUPS = "Error.GroupDatabase";
115:
116: /** Message topic for group database warnings. */
117: public static final String WARNING_GROUPS = "Warning.GroupDatabase";
118:
119: /** Message topic for group database information messages. */
120: public static final String INFO_GROUPS = "Info.GroupDatabase";
121:
122: /** Message topic for JAAS information messages. */
123: public static final String INFO_JAAS = "Info.Jaas";
124:
125: private static final String[] CONTAINER_ACTIONS = new String[] {
126: "View pages", "Comment on existing pages", "Edit pages",
127: "Upload attachments", "Create a new group",
128: "Rename an existing page", "Delete pages" };
129:
130: private static final String[] CONTAINER_JSPS = new String[] {
131: "/Wiki.jsp", "/Comment.jsp", "/Edit.jsp", "/Upload.jsp",
132: "/NewGroup.jsp", "/Rename.jsp", "/Delete.jsp" };
133:
134: private static final String BG_GREEN = "bgcolor=\"#c0ffc0\"";
135:
136: private static final String BG_RED = "bgcolor=\"#ffc0c0\"";
137:
138: private static final Logger LOG = Logger
139: .getLogger(SecurityVerifier.class.getName());
140:
141: /**
142: * Constructs a new SecurityVerifier for a supplied WikiEngine and WikiSession.
143: * @param engine the wiki engine
144: * @param session the wiki session (typically, that of an administrator)
145: */
146: public SecurityVerifier(WikiEngine engine, WikiSession session) {
147: super ();
148: m_engine = engine;
149: m_session = session;
150: m_session.clearMessages();
151: verifyJaas();
152: verifyPolicy();
153: try {
154: verifyPolicyAndContainerRoles();
155: } catch (WikiException e) {
156: m_session.addMessage(ERROR_ROLES, e.getMessage());
157: }
158: verifyGroupDatabase();
159: verifyUserDatabase();
160: }
161:
162: /**
163: * Returns an array of unique Principals from the JSPWIki security policy
164: * file. This array will be zero-length if the policy file was not
165: * successfully located, or if the file did not specify any Principals in
166: * the policy.
167: * @return the array of principals
168: */
169: public final Principal[] policyPrincipals() {
170: return m_policyPrincipals;
171: }
172:
173: /**
174: * Formats and returns an HTML table containing sample permissions and what
175: * roles are allowed to have them. This method will throw an
176: * {@link IllegalStateException} if the authorizer is not of type
177: * {@link com.ecyrd.jspwiki.auth.authorize.WebContainerAuthorizer}
178: * @return the formatted HTML table containing the result of the tests
179: */
180: public final String policyRoleTable() {
181: Principal[] roles = m_policyPrincipals;
182: String wiki = m_engine.getApplicationName();
183:
184: String[] pages = new String[] { "Main", "Index", "GroupTest",
185: "GroupAdmin" };
186: String[] pageActions = new String[] { "view", "edit", "modify",
187: "rename", "delete" };
188:
189: String[] groups = new String[] { "Admin", "TestGroup", "Foo" };
190: String[] groupActions = new String[] { "view", "edit", null,
191: null, "delete" };
192:
193: // Calculate column widths
194: String colWidth;
195: if (pageActions.length > 0 && roles.length > 0) {
196: colWidth = String
197: .valueOf(67f / (pageActions.length * roles.length))
198: + "%";
199: } else {
200: colWidth = "67%";
201: }
202:
203: StringBuffer s = new StringBuffer();
204:
205: // Write the table header
206: s.append("<table class=\"wikitable\" border=\"1\">\n");
207: s.append(" <colgroup span=\"1\" width=\"33%\"/>\n");
208: s.append(" <colgroup span=\"" + pageActions.length
209: * roles.length + "\" width=\"" + colWidth
210: + "\" align=\"center\"/>\n");
211: s.append(" <tr>\n");
212: s
213: .append(" <th rowspan=\"2\" valign=\"bottom\">Permission</th>\n");
214: for (int i = 0; i < roles.length; i++) {
215: s.append(" <th colspan=\"" + pageActions.length
216: + "\" title=\"" + roles[i].getClass().getName()
217: + "\">" + roles[i].getName() + "</th>\n");
218: }
219: s.append(" </tr>\n");
220:
221: // Print a column for each role
222: s.append(" <tr>\n");
223: for (int i = 0; i < roles.length; i++) {
224: for (int j = 0; j < pageActions.length; j++) {
225: String action = pageActions[j].substring(0, 1);
226: s.append(" <th title=\"" + pageActions[j] + "\">"
227: + action + "</th>\n");
228: }
229: }
230: s.append(" </tr>\n");
231:
232: // Write page permission tests first
233: for (int i = 0; i < pages.length; i++) {
234: String page = pages[i];
235: s.append(" <tr>\n");
236: s.append(" <td>PagePermission \"" + wiki + ":" + page
237: + "\"</td>\n");
238: for (int j = 0; j < roles.length; j++) {
239: for (int k = 0; k < pageActions.length; k++) {
240: Permission permission = PermissionFactory
241: .getPagePermission(wiki + ":" + page,
242: pageActions[k]);
243: s.append(printPermissionTest(permission, roles[j],
244: 1));
245: }
246: }
247: s.append(" </tr>\n");
248: }
249:
250: // Now do the group tests
251: for (int i = 0; i < groups.length; i++) {
252: String group = groups[i];
253: s.append(" <tr>\n");
254: s.append(" <td>GroupPermission \"" + wiki + ":" + group
255: + "\"</td>\n");
256: for (int j = 0; j < roles.length; j++) {
257: for (int k = 0; k < groupActions.length; k++) {
258: Permission permission = null;
259: if (groupActions[k] != null) {
260: permission = new GroupPermission(wiki + ":"
261: + group, groupActions[k]);
262: }
263: s.append(printPermissionTest(permission, roles[j],
264: 1));
265: }
266: }
267: s.append(" </tr>\n");
268: }
269:
270: // Now check the wiki-wide permissions
271: String[] wikiPerms = new String[] { "createGroups",
272: "createPages", "login", "editPreferences",
273: "editProfile" };
274: for (int i = 0; i < wikiPerms.length; i++) {
275: s.append(" <tr>\n");
276: s.append(" <td>WikiPermission \"" + wiki + "\",\""
277: + wikiPerms[i] + "\"</td>\n");
278: for (int j = 0; j < roles.length; j++) {
279: Permission permission = new WikiPermission(wiki,
280: wikiPerms[i]);
281: s.append(printPermissionTest(permission, roles[j],
282: pageActions.length));
283: }
284: s.append(" </tr>\n");
285: }
286:
287: // Lastly, check for AllPermission
288: s.append(" <tr>\n");
289: s.append(" <td>AllPermission \"" + wiki + "\"</td>\n");
290: for (int j = 0; j < roles.length; j++) {
291: Permission permission = new AllPermission(wiki);
292: s.append(printPermissionTest(permission, roles[j],
293: pageActions.length));
294: }
295: s.append(" </tr>\n");
296:
297: // We're done!
298: s.append("</table>");
299: return s.toString();
300: }
301:
302: /**
303: * Prints a <td> HTML element with the results of a permission test.
304: * @param perm the permission to format
305: * @param allowed whether the permission is allowed
306: */
307: private final String printPermissionTest(Permission permission,
308: Principal principal, int cols) {
309: StringBuffer s = new StringBuffer();
310: if (permission == null) {
311: s.append(" <td colspan=\"" + cols
312: + "\" align=\"center\" title=\"N/A\">");
313: s.append(" </td>\n");
314: } else {
315: boolean allowed = verifyStaticPermission(principal,
316: permission);
317: s.append(" <td colspan=\"" + cols
318: + "\" align=\"center\" title=\"");
319: s.append(allowed ? "ALLOW: " : "DENY: ");
320: s.append(permission.getClass().getName());
321: s.append(" "");
322: s.append(permission.getName());
323: s.append(""");
324: if (permission.getName() != null) {
325: s.append(","");
326: s.append(permission.getActions());
327: s.append(""");
328: }
329: s.append(" ");
330: s.append(principal.getClass().getName());
331: s.append(" "");
332: s.append(principal.getName());
333: s.append(""");
334: s.append("\"");
335: s.append(allowed ? BG_GREEN + ">" : BG_RED + ">");
336: s.append(" </td>\n");
337: }
338: return s.toString();
339: }
340:
341: /**
342: * Formats and returns an HTML table containing the roles the web container
343: * is aware of, and whether each role maps to particular JSPs. This method
344: * throws an {@link IllegalStateException} if the authorizer is not of type
345: * {@link com.ecyrd.jspwiki.auth.authorize.WebContainerAuthorizer}
346: * @return the formatted HTML table containing the result of the tests
347: * @throws WikiException if tests fail for unexpected reasons
348: */
349: public final String containerRoleTable() throws WikiException {
350:
351: AuthorizationManager authorizationManager = m_engine
352: .getAuthorizationManager();
353: Authorizer authorizer = authorizationManager.getAuthorizer();
354:
355: // If authorizer not WebContainerAuthorizer, print error message
356: if (!(authorizer instanceof WebContainerAuthorizer)) {
357: throw new IllegalStateException(
358: "Authorizer should be WebContainerAuthorizer");
359: }
360:
361: // Now, print a table with JSP pages listed on the left, and
362: // an evaluation of each pages' constraints for each role
363: // we discovered
364: StringBuffer s = new StringBuffer();
365: Principal[] roles = authorizer.getRoles();
366: s.append("<table class=\"wikitable\" border=\"1\">\n");
367: s.append("<thead>\n");
368: s.append(" <tr>\n");
369: s.append(" <th rowspan=\"2\">Action</th>\n");
370: s.append(" <th rowspan=\"2\">Page</th>\n");
371: s.append(" <th colspan=\"" + roles.length + 1
372: + "\">Roles</th>\n");
373: s.append(" </tr>\n");
374: s.append(" <tr>\n");
375: s.append(" <th>Anonymous</th>\n");
376: for (int i = 0; i < roles.length; i++) {
377: s.append(" <th>" + roles[i].getName() + "</th>\n");
378: }
379: s.append("</tr>\n");
380: s.append("</thead>\n");
381: s.append("<tbody>\n");
382:
383: try {
384: WebContainerAuthorizer wca = (WebContainerAuthorizer) authorizer;
385: for (int i = 0; i < CONTAINER_ACTIONS.length; i++) {
386: String action = CONTAINER_ACTIONS[i];
387: String jsp = CONTAINER_JSPS[i];
388:
389: // Print whether the page is constrained for each role
390: boolean allowsAnonymous = !wca.isConstrained(jsp,
391: Role.ALL);
392: s.append(" <tr>\n");
393: s.append(" <td>" + action + "</td>\n");
394: s.append(" <td>" + jsp + "</td>\n");
395: s.append(" <td title=\"");
396: s.append(allowsAnonymous ? "ALLOW: " : "DENY: ");
397: s.append(jsp);
398: s.append(" Anonymous");
399: s.append("\"");
400: s.append(allowsAnonymous ? BG_GREEN + ">" : BG_RED
401: + ">");
402: s.append(" </td>\n");
403: for (int j = 0; j < roles.length; j++) {
404: Role role = (Role) roles[j];
405: boolean allowed = allowsAnonymous
406: || wca.isConstrained(jsp, role);
407: s.append(" <td title=\"");
408: s.append(allowed ? "ALLOW: " : "DENY: ");
409: s.append(jsp);
410: s.append(" ");
411: s.append(role.getClass().getName());
412: s.append(" "");
413: s.append(role.getName());
414: s.append(""");
415: s.append("\"");
416: s.append(allowed ? BG_GREEN + ">" : BG_RED + ">");
417: s.append(" </td>\n");
418: }
419: s.append(" </tr>\n");
420: }
421: } catch (JDOMException e) {
422: // If we couldn't evaluate constraints it means
423: // there's some sort of IO mess or parsing issue
424: LOG.error("Malformed XML in web.xml", e);
425: throw new InternalWikiException(e.getClass().getName()
426: + ": " + e.getMessage());
427: }
428:
429: s.append("</tbody>\n");
430: s.append("</table>\n");
431: return s.toString();
432: }
433:
434: /**
435: * Returns <code>true</code> if JAAS is configured correctly.
436: * @return the result of the configuration check
437: */
438: public final boolean isJaasConfigured() {
439: return m_isJaasConfigured;
440: }
441:
442: /**
443: * Returns <code>true</code> if the JAAS login configuration was already
444: * set when JSPWiki started up. We determine this value by consulting a
445: * protected member field of {@link AuthenticationManager}, which was set
446: * at in initialization by {@link PolicyLoader}.
447: * @return <code>true</code> if {@link PolicyLoader} successfully set the
448: * policy, or <code>false</code> for any other reason.
449: */
450: public final boolean isJaasConfiguredAtStartup() {
451: return m_engine.getAuthenticationManager().m_isJaasConfiguredAtStartup;
452: }
453:
454: /**
455: * Returns <code>true</code> if JSPWiki can locate a named JAAS login
456: * configuration.
457: * @param config the name of the application (e.g.,
458: * <code>JSPWiki-container</code>).
459: * @return <code>true</code> if found; <code>false</code> otherwise
460: */
461: protected final boolean isJaasConfigurationAvailable(String config) {
462: try {
463: m_session.addMessage(INFO_JAAS, "We found the '" + config
464: + "' login configuration.");
465: new LoginContext(config);
466: return true;
467: } catch (Exception e) {
468: m_session.addMessage(ERROR_JAAS, "We could not find the '"
469: + config + "' login configuration.</p>");
470: return false;
471: }
472: }
473:
474: /**
475: * Returns <code>true</code> if the Java security policy is configured
476: * correctly, and it verifies as valid.
477: * @return the result of the configuration check
478: */
479: public final boolean isSecurityPolicyConfigured() {
480: return m_isSecurityPolicyConfigured;
481: }
482:
483: /**
484: * If the active Authorizer is the WebContainerAuthorizer, returns the roles
485: * it knows about; otherwise, a zero-length array.
486: * @return the roles parsed from <code>web.xml</code>, or a zero-length array
487: * @throws WikiException if the web authorizer cannot obtain the list of roles
488: */
489: public final Principal[] webContainerRoles() throws WikiException {
490: Authorizer authorizer = m_engine.getAuthorizationManager()
491: .getAuthorizer();
492: if (authorizer instanceof WebContainerAuthorizer) {
493: return ((WebContainerAuthorizer) authorizer).getRoles();
494: }
495: return new Principal[0];
496: }
497:
498: /**
499: * Verifies that the roles given in the security policy are reflected by the
500: * container <code>web.xml</code> file.
501: * @throws WikiException if the web authorizer cannot verify the roles
502: */
503: protected final void verifyPolicyAndContainerRoles()
504: throws WikiException {
505: Authorizer authorizer = m_engine.getAuthorizationManager()
506: .getAuthorizer();
507: Principal[] containerRoles = authorizer.getRoles();
508: boolean missing = false;
509: for (int i = 0; i < m_policyPrincipals.length; i++) {
510: Principal principal = m_policyPrincipals[i];
511: if (principal instanceof Role) {
512: Role role = (Role) principal;
513: boolean isContainerRole = ArrayUtils.contains(
514: containerRoles, role);
515: if (!Role.isBuiltInRole(role) && !isContainerRole) {
516: m_session
517: .addMessage(
518: ERROR_ROLES,
519: "Role '"
520: + role.getName()
521: + "' is defined in security policy but not in web.xml.");
522: missing = true;
523: }
524: }
525: }
526: if (!missing) {
527: m_session
528: .addMessage(
529: INFO_ROLES,
530: "Every non-standard role defined in the security policy was also found in web.xml.");
531: }
532: }
533:
534: /**
535: * Verifies that the group datbase was initialized properly, and that
536: * user add and delete operations work as they should.
537: */
538: protected final void verifyGroupDatabase() {
539: GroupManager mgr = m_engine.getGroupManager();
540: GroupDatabase db = null;
541: try {
542: db = m_engine.getGroupManager().getGroupDatabase();
543: } catch (WikiSecurityException e) {
544: m_session.addMessage(ERROR_GROUPS,
545: "Could not retrieve GroupManager: "
546: + e.getMessage());
547: }
548:
549: // Check for obvious error conditions
550: if (mgr == null || db == null) {
551: if (mgr == null) {
552: m_session
553: .addMessage(
554: ERROR_GROUPS,
555: "GroupManager is null; JSPWiki could not "
556: + "initialize it. Check the error logs.");
557: }
558: if (db == null) {
559: m_session
560: .addMessage(
561: ERROR_GROUPS,
562: "GroupDatabase is null; JSPWiki could not "
563: + "initialize it. Check the error logs.");
564: }
565: return;
566: }
567:
568: // Everything initialized OK...
569:
570: // Tell user what class of database this is.
571: m_session.addMessage(INFO_GROUPS, "GroupDatabase is of type '"
572: + db.getClass().getName()
573: + "'. It appears to be initialized properly.");
574:
575: // Now, see how many groups we have.
576: int oldGroupCount = 0;
577: try {
578: Group[] groups = db.groups();
579: oldGroupCount = groups.length;
580: m_session.addMessage(INFO_GROUPS,
581: "The group database contains " + oldGroupCount
582: + " groups.");
583: } catch (WikiSecurityException e) {
584: m_session.addMessage(ERROR_GROUPS,
585: "Could not obtain a list of current groups: "
586: + e.getMessage());
587: return;
588: }
589:
590: // Try adding a bogus group with random name
591: String name = "TestGroup"
592: + String.valueOf(System.currentTimeMillis());
593: Group group = null;
594: try {
595: // Create dummy test group
596: group = mgr.parseGroup(name, "", true);
597: Principal user = new WikiPrincipal("TestUser");
598: group.add(user);
599: db.save(group, new WikiPrincipal("SecurityVerifier"));
600:
601: // Make sure the group saved successfully
602: if (db.groups().length == oldGroupCount) {
603: m_session.addMessage(ERROR_GROUPS,
604: "Could not add a test group to the database.");
605: return;
606: }
607: m_session
608: .addMessage(INFO_GROUPS,
609: "The group database allows new groups to be created, as it should.");
610: } catch (WikiSecurityException e) {
611: m_session.addMessage(ERROR_GROUPS,
612: "Could not add a group to the database: "
613: + e.getMessage());
614: return;
615: }
616:
617: // Now delete the group; should be back to old count
618: try {
619: db.delete(group);
620: if (db.groups().length != oldGroupCount) {
621: m_session
622: .addMessage(ERROR_GROUPS,
623: "Could not delete a test group from the database.");
624: return;
625: }
626: m_session
627: .addMessage(INFO_GROUPS,
628: "The group database allows groups to be deleted, as it should.");
629: } catch (WikiSecurityException e) {
630: m_session.addMessage(ERROR_GROUPS,
631: "Could not delete a test group from the database: "
632: + e.getMessage());
633: return;
634: }
635:
636: m_session.addMessage(INFO_GROUPS,
637: "The group database configuration looks fine.");
638: }
639:
640: /**
641: * Verfies the JAAS configuration. The configuration is valid if value of
642: * the system property <code>java.security.auth.login.config</code>
643: * resolves to an existing file, and we can find the JAAS login
644: * configurations for <code>JSPWiki-container</code> and
645: * <code>JSPWiki-custom</code>.
646: */
647: protected final void verifyJaas() {
648: // See if JAAS is on
649: AuthorizationManager authMgr = m_engine
650: .getAuthorizationManager();
651: if (!authMgr.isJAASAuthorized()) {
652: m_session
653: .addMessage(
654: ERROR_JAAS,
655: "JSPWiki's JAAS-based authentication "
656: + "and authorization system is turned off (your <code>jspwiki.properties</code> "
657: + "contains the setting 'jspwiki.security = container'. This "
658: + "setting disables authorization checks and is meant for testing "
659: + "and troubleshooting only. The test results on this page will not "
660: + "be reliable as a result. You should set this to 'jaas' "
661: + "so that security works properly.");
662: }
663:
664: // Validate the property is set correctly
665: m_jaasConfig = getFileFromProperty("java.security.auth.login.config");
666:
667: // Look for the JSPWiki-container config
668: boolean foundJaasContainerConfig = isJaasConfigurationAvailable("JSPWiki-container");
669:
670: // Look for the JSPWiki-custom config
671: boolean foundJaasCustomConfig = isJaasConfigurationAvailable("JSPWiki-custom");
672:
673: m_isJaasConfigured = m_jaasConfig != null
674: && foundJaasContainerConfig && foundJaasCustomConfig;
675: }
676:
677: /**
678: * Looks up a file name based on a JRE system property and returns the associated
679: * File object if it exists. This method adds messages with the topic prefix
680: * {@link #ERROR} and {@link #INFO} as appropriate, with the suffix matching the
681: * supplied property.
682: * @param property the system property to look up
683: * @return the file object, or <code>null</code> if not found
684: */
685: protected final File getFileFromProperty(String property) {
686: String propertyValue = null;
687: try {
688: propertyValue = System.getProperty(property);
689: if (propertyValue == null) {
690: m_session.addMessage("Error." + property,
691: "The system property '" + property
692: + "' is null.");
693: return null;
694: }
695:
696: //
697: // It's also possible to use "==" to mark a property. We remove that
698: // here so that we can actually find the property file, then.
699: //
700: if (propertyValue.startsWith("=")) {
701: propertyValue = propertyValue.substring(1);
702: }
703:
704: try {
705: m_session
706: .addMessage("Info." + property,
707: "The system property '" + property
708: + "' is set to: "
709: + propertyValue + ".");
710:
711: // Prepend a file: prefix if not there already
712: if (!propertyValue.startsWith("file:")) {
713: propertyValue = "file:" + propertyValue;
714: }
715: URL url = new URL(propertyValue);
716: File file = new File(url.getPath());
717: if (file.exists()) {
718: m_session.addMessage("Info." + property, "File '"
719: + propertyValue
720: + "' exists in the filesystem.");
721: return file;
722: }
723: } catch (MalformedURLException e) {
724: // Swallow exception because we can't find it anyway
725: }
726: m_session
727: .addMessage(
728: "Error." + property,
729: "File '"
730: + propertyValue
731: + "' doesn't seem to exist. This might be a problem.");
732: return null;
733: } catch (SecurityException e) {
734: m_session
735: .addMessage(
736: "Error." + property,
737: "We could not read system property '"
738: + property
739: + "'. This is probably because you are running with a security manager.");
740: return null;
741: }
742: }
743:
744: /**
745: * Verfies the Java security policy configuration. The configuration is
746: * valid if value of the local policy (at <code>WEB-INF/jspwiki.policy</code>
747: * resolves to an existing file, and the policy file contained therein
748: * represents a valid policy.
749: */
750: protected final void verifyPolicy() {
751: // Look up the policy file and set the status text.
752: URL policyURL = AuthenticationManager.findConfigFile(m_engine,
753: AuthorizationManager.DEFAULT_POLICY);
754: String path = policyURL.getPath();
755: if (path.startsWith("file:")) {
756: path = path.substring(5);
757: }
758: File policyFile = new File(path);
759:
760: // Next, verify the policy
761: try {
762: // Get the file
763: PolicyReader policy = new PolicyReader(policyFile);
764: m_session.addMessage(INFO_POLICY, "The security policy '"
765: + policy.getFile() + "' exists.");
766:
767: // See if there is a keystore that's valid
768: KeyStore ks = policy.getKeyStore();
769: if (ks == null) {
770: m_session
771: .addMessage(ERROR_POLICY,
772: "Policy file does not have a keystore... at least not one that we can locate.");
773: } else {
774: m_session
775: .addMessage(
776: INFO_POLICY,
777: "The security policy specifies a keystore, and we were able to locate it in the filesystem.");
778: }
779:
780: // Verify the file
781: policy.read();
782: List errors = policy.getMessages();
783: if (errors.size() > 0) {
784: for (Iterator it = errors.iterator(); it.hasNext();) {
785: Exception e = (Exception) it.next();
786: m_session.addMessage(ERROR_POLICY, e.getMessage());
787: }
788: } else {
789: m_session.addMessage(INFO_POLICY,
790: "The security policy looks fine.");
791: m_isSecurityPolicyConfigured = true;
792: }
793:
794: // Stash the unique principals mentioned in the file,
795: // plus our standard roles.
796: Set principals = new LinkedHashSet();
797: principals.add(Role.ALL);
798: principals.add(Role.ANONYMOUS);
799: principals.add(Role.ASSERTED);
800: principals.add(Role.AUTHENTICATED);
801: ProtectionDomain[] domains = policy.getProtectionDomains();
802: for (int i = 0; i < domains.length; i++) {
803: Principal[] domainPrincipals = domains[i]
804: .getPrincipals();
805: for (int j = 0; j < domainPrincipals.length; j++) {
806: principals.add(domainPrincipals[j]);
807: }
808: }
809: m_policyPrincipals = (Principal[]) principals
810: .toArray(new Principal[principals.size()]);
811: } catch (IOException e) {
812: m_session.addMessage(ERROR_POLICY, e.getMessage());
813: }
814: }
815:
816: /**
817: * Verifies that a particular Principal possesses a Permission, as defined
818: * in the security policy file.
819: * @param principal the principal
820: * @param permission the permission
821: * @return the result, based on consultation with the active Java security
822: * policy
823: */
824: protected final boolean verifyStaticPermission(Principal principal,
825: final Permission permission) {
826: Subject subject = new Subject();
827: subject.getPrincipals().add(principal);
828: boolean allowedByGlobalPolicy = ((Boolean) Subject
829: .doAsPrivileged(subject, new PrivilegedAction() {
830: public Object run() {
831: try {
832: AccessController
833: .checkPermission(permission);
834: return Boolean.TRUE;
835: } catch (AccessControlException e) {
836: return Boolean.FALSE;
837: }
838: }
839: }, null)).booleanValue();
840:
841: if (allowedByGlobalPolicy) {
842: return true;
843: }
844:
845: // Check local policy
846: Principal[] principals = new Principal[] { principal };
847: return m_engine.getAuthorizationManager().allowedByLocalPolicy(
848: principals, permission);
849: }
850:
851: /**
852: * Verifies that the user datbase was initialized properly, and that
853: * user add and delete operations work as they should.
854: */
855: protected final void verifyUserDatabase() {
856: UserDatabase db = m_engine.getUserManager().getUserDatabase();
857:
858: // Check for obvious error conditions
859: if (db == null) {
860: m_session.addMessage(ERROR_DB,
861: "UserDatabase is null; JSPWiki could not "
862: + "initialize it. Check the error logs.");
863: return;
864: }
865:
866: if (db instanceof UserManager.DummyUserDatabase) {
867: m_session
868: .addMessage(
869: ERROR_DB,
870: "UserDatabase is DummyUserDatabase; JSPWiki "
871: + "may not have been able to initialize the database you supplied in "
872: + "jspwiki.properties, or you left the 'jspwiki.userdatabase' property "
873: + "blank. Check the error logs.");
874: }
875:
876: // Tell user what class of database this is.
877: m_session.addMessage(INFO_DB, "UserDatabase is of type '"
878: + db.getClass().getName()
879: + "'. It appears to be initialized properly.");
880:
881: // Now, see how many users we have.
882: int oldUserCount = 0;
883: try {
884: Principal[] users = db.getWikiNames();
885: oldUserCount = users.length;
886: m_session.addMessage(INFO_DB, "The user database contains "
887: + oldUserCount + " users.");
888: } catch (WikiSecurityException e) {
889: m_session.addMessage(ERROR_DB,
890: "Could not obtain a list of current users: "
891: + e.getMessage());
892: return;
893: }
894:
895: // Try adding a bogus user with random name
896: String loginName = "TestUser"
897: + String.valueOf(System.currentTimeMillis());
898: try {
899: UserProfile profile = new DefaultUserProfile();
900: profile.setEmail("testuser@testville.com");
901: profile.setLoginName(loginName);
902: profile.setFullname("FullName" + loginName);
903: profile.setPassword("password");
904: db.save(profile);
905:
906: // Make sure the profile saved successfully
907: if (db.getWikiNames().length == oldUserCount) {
908: m_session.addMessage(ERROR_DB,
909: "Could not add a test user to the database.");
910: return;
911: }
912: m_session
913: .addMessage(INFO_DB,
914: "The user database allows new users to be created, as it should.");
915: } catch (WikiSecurityException e) {
916: m_session.addMessage(ERROR_DB,
917: "Could not add a test user to the database: "
918: + e.getMessage());
919: return;
920: }
921:
922: // Now delete the profile; should be back to old count
923: try {
924: db.deleteByLoginName(loginName);
925: if (db.getWikiNames().length != oldUserCount) {
926: m_session
927: .addMessage(ERROR_DB,
928: "Could not delete a test user from the database.");
929: return;
930: }
931: m_session
932: .addMessage(INFO_DB,
933: "The user database allows users to be deleted, as it should.");
934: } catch (WikiSecurityException e) {
935: m_session.addMessage(ERROR_DB,
936: "Could not delete a test user to the database: "
937: + e.getMessage());
938: return;
939: }
940:
941: m_session.addMessage(INFO_DB,
942: "The user database configuration looks fine.");
943: }
944:
945: /**
946: * Returns the location of the JAAS configuration file if and only if the
947: * <code>java.security.auth.login.config</code> is set <em>and</em> the
948: * file it points to exists in the file system; returns <code>null</code>
949: * in all other cases.
950: * @return the location of the JAAS configuration file
951: */
952: public final File jaasConfiguration() {
953: return m_jaasConfig;
954: }
955: }
|