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