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