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