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