| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| package org.apache.wiki.auth; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.security.AccessControlException; |
| import java.security.AccessController; |
| import java.security.KeyStore; |
| import java.security.Permission; |
| import java.security.Principal; |
| import java.security.PrivilegedAction; |
| import java.security.ProtectionDomain; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.security.auth.Subject; |
| import javax.security.auth.spi.LoginModule; |
| |
| import org.apache.commons.lang.ArrayUtils; |
| import org.apache.log4j.Logger; |
| import org.apache.wiki.InternalWikiException; |
| import org.apache.wiki.WikiEngine; |
| import org.apache.wiki.WikiSession; |
| import org.apache.wiki.api.exceptions.WikiException; |
| import org.apache.wiki.auth.authorize.Group; |
| import org.apache.wiki.auth.authorize.GroupDatabase; |
| import org.apache.wiki.auth.authorize.GroupManager; |
| import org.apache.wiki.auth.authorize.Role; |
| import org.apache.wiki.auth.authorize.WebContainerAuthorizer; |
| import org.apache.wiki.auth.permissions.AllPermission; |
| import org.apache.wiki.auth.permissions.GroupPermission; |
| import org.apache.wiki.auth.permissions.PermissionFactory; |
| import org.apache.wiki.auth.permissions.WikiPermission; |
| import org.apache.wiki.auth.user.UserDatabase; |
| import org.apache.wiki.auth.user.UserProfile; |
| import org.freshcookies.security.policy.PolicyReader; |
| import org.jdom2.JDOMException; |
| |
| /** |
| * Helper class for verifying JSPWiki's security configuration. Invoked by |
| * <code>admin/SecurityConfig.jsp</code>. |
| * @since 2.4 |
| */ |
| public final class SecurityVerifier |
| { |
| private static final long serialVersionUID = -3859563355089169941L; |
| |
| private WikiEngine m_engine; |
| |
| private boolean m_isSecurityPolicyConfigured = false; |
| |
| private Principal[] m_policyPrincipals = new Principal[0]; |
| |
| private WikiSession m_session; |
| |
| /** Message prefix for errors. */ |
| public static final String ERROR = "Error."; |
| |
| /** Message prefix for warnings. */ |
| public static final String WARNING = "Warning."; |
| |
| /** Message prefix for information messages. */ |
| public static final String INFO = "Info."; |
| |
| /** Message topic for policy errors. */ |
| public static final String ERROR_POLICY = "Error.Policy"; |
| |
| /** Message topic for policy warnings. */ |
| public static final String WARNING_POLICY = "Warning.Policy"; |
| |
| /** Message topic for policy information messages. */ |
| public static final String INFO_POLICY = "Info.Policy"; |
| |
| /** Message topic for JAAS errors. */ |
| public static final String ERROR_JAAS = "Error.Jaas"; |
| |
| /** Message topic for JAAS warnings. */ |
| public static final String WARNING_JAAS = "Warning.Jaas"; |
| |
| /** Message topic for role-checking errors. */ |
| public static final String ERROR_ROLES = "Error.Roles"; |
| |
| /** Message topic for role-checking information messages. */ |
| public static final String INFO_ROLES = "Info.Roles"; |
| |
| /** Message topic for user database errors. */ |
| public static final String ERROR_DB = "Error.UserDatabase"; |
| |
| /** Message topic for user database warnings. */ |
| public static final String WARNING_DB = "Warning.UserDatabase"; |
| |
| /** Message topic for user database information messages. */ |
| public static final String INFO_DB = "Info.UserDatabase"; |
| |
| /** Message topic for group database errors. */ |
| public static final String ERROR_GROUPS = "Error.GroupDatabase"; |
| |
| /** Message topic for group database warnings. */ |
| public static final String WARNING_GROUPS = "Warning.GroupDatabase"; |
| |
| /** Message topic for group database information messages. */ |
| public static final String INFO_GROUPS = "Info.GroupDatabase"; |
| |
| /** Message topic for JAAS information messages. */ |
| public static final String INFO_JAAS = "Info.Jaas"; |
| |
| private static final String[] CONTAINER_ACTIONS = new String[] |
| { "View pages", "Comment on existing pages", |
| "Edit pages", "Upload attachments", "Create a new group", "Rename an existing page", "Delete pages" }; |
| |
| private static final String[] CONTAINER_JSPS = new String[] |
| { "/Wiki.jsp", "/Comment.jsp", "/Edit.jsp", |
| "/Upload.jsp", "/NewGroup.jsp", "/Rename.jsp", "/Delete.jsp" }; |
| |
| private static final String BG_GREEN = "bgcolor=\"#c0ffc0\""; |
| |
| private static final String BG_RED = "bgcolor=\"#ffc0c0\""; |
| |
| private static final Logger LOG = Logger.getLogger( SecurityVerifier.class.getName() ); |
| |
| /** |
| * Constructs a new SecurityVerifier for a supplied WikiEngine and WikiSession. |
| * @param engine the wiki engine |
| * @param session the wiki session (typically, that of an administrator) |
| */ |
| public SecurityVerifier( WikiEngine engine, WikiSession session ) |
| { |
| super(); |
| m_engine = engine; |
| m_session = session; |
| m_session.clearMessages(); |
| verifyJaas(); |
| verifyPolicy(); |
| try |
| { |
| verifyPolicyAndContainerRoles(); |
| } |
| catch ( WikiException e ) |
| { |
| m_session.addMessage( ERROR_ROLES, e.getMessage() ); |
| } |
| verifyGroupDatabase(); |
| verifyUserDatabase(); |
| } |
| |
| /** |
| * Returns an array of unique Principals from the JSPWIki security policy |
| * file. This array will be zero-length if the policy file was not |
| * successfully located, or if the file did not specify any Principals in |
| * the policy. |
| * @return the array of principals |
| */ |
| public Principal[] policyPrincipals() |
| { |
| return m_policyPrincipals; |
| } |
| |
| /** |
| * Formats and returns an HTML table containing sample permissions and what |
| * roles are allowed to have them. This method will throw an |
| * {@link IllegalStateException} if the authorizer is not of type |
| * {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer} |
| * @return the formatted HTML table containing the result of the tests |
| */ |
| public String policyRoleTable() |
| { |
| Principal[] roles = m_policyPrincipals; |
| String wiki = m_engine.getApplicationName(); |
| |
| String[] pages = new String[] |
| { "Main", "Index", "GroupTest", "GroupAdmin" }; |
| String[] pageActions = new String[] |
| { "view", "edit", "modify", "rename", "delete" }; |
| |
| String[] groups = new String[] |
| { "Admin", "TestGroup", "Foo" }; |
| String[] groupActions = new String[] |
| { "view", "edit", null, null, "delete" }; |
| |
| // Calculate column widths |
| String colWidth; |
| if ( pageActions.length > 0 && roles.length > 0 ) |
| { |
| colWidth = (67f / ( pageActions.length * roles.length )) + "%"; |
| } |
| else |
| { |
| colWidth = "67%"; |
| } |
| |
| StringBuilder s = new StringBuilder(); |
| |
| // Write the table header |
| s.append( "<table class=\"wikitable\" border=\"1\">\n" ); |
| s.append( " <colgroup span=\"1\" width=\"33%\"/>\n" ); |
| s.append( " <colgroup span=\"" + pageActions.length * roles.length + "\" width=\"" + colWidth |
| + "\" align=\"center\"/>\n" ); |
| s.append( " <tr>\n" ); |
| s.append( " <th rowspan=\"2\" valign=\"bottom\">Permission</th>\n" ); |
| for( int i = 0; i < roles.length; i++ ) |
| { |
| s.append( " <th colspan=\"" + pageActions.length + "\" title=\"" + roles[i].getClass().getName() + "\">" |
| + roles[i].getName() + "</th>\n" ); |
| } |
| s.append( " </tr>\n" ); |
| |
| // Print a column for each role |
| s.append( " <tr>\n" ); |
| for( int i = 0; i < roles.length; i++ ) |
| { |
| for( String pageAction : pageActions ) |
| { |
| String action = pageAction.substring( 0, 1 ); |
| s.append( " <th title=\"" + pageAction + "\">" + action + "</th>\n" ); |
| } |
| } |
| s.append( " </tr>\n" ); |
| |
| // Write page permission tests first |
| for( String page : pages ) |
| { |
| s.append( " <tr>\n" ); |
| s.append( " <td>PagePermission \"" + wiki + ":" + page + "\"</td>\n" ); |
| for( Principal role : roles ) |
| { |
| for( String pageAction : pageActions ) |
| { |
| Permission permission = PermissionFactory.getPagePermission( wiki + ":" + page, pageAction ); |
| s.append( printPermissionTest( permission, role, 1 ) ); |
| } |
| } |
| s.append( " </tr>\n" ); |
| } |
| |
| // Now do the group tests |
| for( String group : groups ) |
| { |
| s.append( " <tr>\n" ); |
| s.append( " <td>GroupPermission \"" + wiki + ":" + group + "\"</td>\n" ); |
| for( Principal role : roles ) |
| { |
| for( String groupAction : groupActions ) |
| { |
| Permission permission = null; |
| if ( groupAction != null) |
| { |
| permission = new GroupPermission( wiki + ":" + group, groupAction ); |
| } |
| s.append( printPermissionTest( permission, role, 1 ) ); |
| } |
| } |
| s.append( " </tr>\n" ); |
| } |
| |
| |
| // Now check the wiki-wide permissions |
| String[] wikiPerms = new String[] |
| { "createGroups", "createPages", "login", "editPreferences", "editProfile" }; |
| for( String wikiPerm : wikiPerms ) |
| { |
| s.append( " <tr>\n" ); |
| s.append( " <td>WikiPermission \"" + wiki + "\",\"" + wikiPerm + "\"</td>\n" ); |
| for( Principal role : roles ) |
| { |
| Permission permission = new WikiPermission( wiki, wikiPerm ); |
| s.append( printPermissionTest( permission, role, pageActions.length ) ); |
| } |
| s.append( " </tr>\n" ); |
| } |
| |
| // Lastly, check for AllPermission |
| s.append( " <tr>\n" ); |
| s.append( " <td>AllPermission \"" + wiki + "\"</td>\n" ); |
| for( Principal role : roles ) |
| { |
| Permission permission = new AllPermission( wiki ); |
| s.append( printPermissionTest( permission, role, pageActions.length ) ); |
| } |
| s.append( " </tr>\n" ); |
| |
| // We're done! |
| s.append( "</table>" ); |
| return s.toString(); |
| } |
| |
| /** |
| * Prints a <td> HTML element with the results of a permission test. |
| * @param permission the permission to format |
| * @param principal |
| * @param cols |
| */ |
| private String printPermissionTest( Permission permission, Principal principal, int cols ) |
| { |
| StringBuilder s = new StringBuilder(); |
| if ( permission == null ) |
| { |
| s.append( " <td colspan=\"" + cols + "\" align=\"center\" title=\"N/A\">" ); |
| s.append( " </td>\n" ); |
| } |
| else |
| { |
| boolean allowed = verifyStaticPermission( principal, permission ); |
| s.append( " <td colspan=\"" + cols + "\" align=\"center\" title=\"" ); |
| s.append( allowed ? "ALLOW: " : "DENY: " ); |
| s.append( permission.getClass().getName() ); |
| s.append( " "" ); |
| s.append( permission.getName() ); |
| s.append( """ ); |
| if ( permission.getName() != null ) |
| { |
| s.append( ","" ); |
| s.append( permission.getActions() ); |
| s.append( """ ); |
| } |
| s.append( " " ); |
| s.append( principal.getClass().getName() ); |
| s.append( " "" ); |
| s.append( principal.getName() ); |
| s.append( """ ); |
| s.append( "\"" ); |
| s.append( allowed ? BG_GREEN + ">" : BG_RED + ">" ); |
| s.append( " </td>\n" ); |
| } |
| return s.toString(); |
| } |
| |
| /** |
| * Formats and returns an HTML table containing the roles the web container |
| * is aware of, and whether each role maps to particular JSPs. This method |
| * throws an {@link IllegalStateException} if the authorizer is not of type |
| * {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer} |
| * @return the formatted HTML table containing the result of the tests |
| * @throws WikiException if tests fail for unexpected reasons |
| */ |
| public String containerRoleTable() throws WikiException |
| { |
| |
| AuthorizationManager authorizationManager = m_engine.getAuthorizationManager(); |
| Authorizer authorizer = authorizationManager.getAuthorizer(); |
| |
| // If authorizer not WebContainerAuthorizer, print error message |
| if ( !( authorizer instanceof WebContainerAuthorizer ) ) |
| { |
| throw new IllegalStateException( "Authorizer should be WebContainerAuthorizer" ); |
| } |
| |
| // Now, print a table with JSP pages listed on the left, and |
| // an evaluation of each pages' constraints for each role |
| // we discovered |
| StringBuilder s = new StringBuilder(); |
| Principal[] roles = authorizer.getRoles(); |
| s.append( "<table class=\"wikitable\" border=\"1\">\n" ); |
| s.append( "<thead>\n" ); |
| s.append( " <tr>\n" ); |
| s.append( " <th rowspan=\"2\">Action</th>\n" ); |
| s.append( " <th rowspan=\"2\">Page</th>\n" ); |
| s.append( " <th colspan=\"" + roles.length + 1 + "\">Roles</th>\n" ); |
| s.append( " </tr>\n" ); |
| s.append( " <tr>\n" ); |
| s.append( " <th>Anonymous</th>\n" ); |
| for( Principal role : roles ) |
| { |
| s.append( " <th>" + role.getName() + "</th>\n" ); |
| } |
| s.append( "</tr>\n" ); |
| s.append( "</thead>\n" ); |
| s.append( "<tbody>\n" ); |
| |
| try |
| { |
| WebContainerAuthorizer wca = (WebContainerAuthorizer) authorizer; |
| for( int i = 0; i < CONTAINER_ACTIONS.length; i++ ) |
| { |
| String action = CONTAINER_ACTIONS[i]; |
| String jsp = CONTAINER_JSPS[i]; |
| |
| // Print whether the page is constrained for each role |
| boolean allowsAnonymous = !wca.isConstrained( jsp, Role.ALL ); |
| s.append( " <tr>\n" ); |
| s.append( " <td>" + action + "</td>\n" ); |
| s.append( " <td>" + jsp + "</td>\n" ); |
| s.append( " <td title=\"" ); |
| s.append( allowsAnonymous ? "ALLOW: " : "DENY: " ); |
| s.append( jsp ); |
| s.append( " Anonymous" ); |
| s.append( "\"" ); |
| s.append( allowsAnonymous ? BG_GREEN + ">" : BG_RED + ">" ); |
| s.append( " </td>\n" ); |
| for( Principal role : roles ) |
| { |
| boolean allowed = allowsAnonymous || wca.isConstrained( jsp, (Role)role ); |
| s.append( " <td title=\"" ); |
| s.append( allowed ? "ALLOW: " : "DENY: " ); |
| s.append( jsp ); |
| s.append( " " ); |
| s.append( role.getClass().getName() ); |
| s.append( " "" ); |
| s.append( role.getName() ); |
| s.append( """ ); |
| s.append( "\"" ); |
| s.append( allowed ? BG_GREEN + ">" : BG_RED + ">" ); |
| s.append( " </td>\n" ); |
| } |
| s.append( " </tr>\n" ); |
| } |
| } |
| catch( JDOMException e ) |
| { |
| // If we couldn't evaluate constraints it means |
| // there's some sort of IO mess or parsing issue |
| LOG.error( "Malformed XML in web.xml", e ); |
| throw new InternalWikiException( e.getClass().getName() + ": " + e.getMessage() , e); |
| } |
| |
| s.append( "</tbody>\n" ); |
| s.append( "</table>\n" ); |
| return s.toString(); |
| } |
| |
| /** |
| * Returns <code>true</code> if the Java security policy is configured |
| * correctly, and it verifies as valid. |
| * @return the result of the configuration check |
| */ |
| public boolean isSecurityPolicyConfigured() |
| { |
| return m_isSecurityPolicyConfigured; |
| } |
| |
| /** |
| * If the active Authorizer is the WebContainerAuthorizer, returns the roles |
| * it knows about; otherwise, a zero-length array. |
| * @return the roles parsed from <code>web.xml</code>, or a zero-length array |
| * @throws WikiException if the web authorizer cannot obtain the list of roles |
| */ |
| public Principal[] webContainerRoles() throws WikiException |
| { |
| Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer(); |
| if ( authorizer instanceof WebContainerAuthorizer ) |
| { |
| return ( (WebContainerAuthorizer) authorizer ).getRoles(); |
| } |
| return new Principal[0]; |
| } |
| |
| /** |
| * Verifies that the roles given in the security policy are reflected by the |
| * container <code>web.xml</code> file. |
| * @throws WikiException if the web authorizer cannot verify the roles |
| */ |
| protected void verifyPolicyAndContainerRoles() throws WikiException |
| { |
| Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer(); |
| Principal[] containerRoles = authorizer.getRoles(); |
| boolean missing = false; |
| for( Principal principal : m_policyPrincipals ) |
| { |
| if ( principal instanceof Role ) |
| { |
| Role role = (Role) principal; |
| boolean isContainerRole = ArrayUtils.contains( containerRoles, role ); |
| if ( !Role.isBuiltInRole( role ) && !isContainerRole ) |
| { |
| m_session.addMessage( ERROR_ROLES, "Role '" + role.getName() + "' is defined in security policy but not in web.xml." ); |
| missing = true; |
| } |
| } |
| } |
| if ( !missing ) |
| { |
| m_session.addMessage( INFO_ROLES, "Every non-standard role defined in the security policy was also found in web.xml." ); |
| } |
| } |
| |
| /** |
| * Verifies that the group datbase was initialized properly, and that |
| * user add and delete operations work as they should. |
| */ |
| protected void verifyGroupDatabase() |
| { |
| GroupManager mgr = m_engine.getGroupManager(); |
| GroupDatabase db = null; |
| try |
| { |
| db = m_engine.getGroupManager().getGroupDatabase(); |
| } |
| catch ( WikiSecurityException e ) |
| { |
| m_session.addMessage( ERROR_GROUPS, "Could not retrieve GroupManager: " + e.getMessage() ); |
| } |
| |
| // Check for obvious error conditions |
| if ( mgr == null || db == null ) |
| { |
| if ( mgr == null ) |
| { |
| m_session.addMessage( ERROR_GROUPS, "GroupManager is null; JSPWiki could not " + |
| "initialize it. Check the error logs." ); |
| } |
| if ( db == null ) |
| { |
| m_session.addMessage( ERROR_GROUPS, "GroupDatabase is null; JSPWiki could not " + |
| "initialize it. Check the error logs." ); |
| } |
| return; |
| } |
| |
| // Everything initialized OK... |
| |
| // Tell user what class of database this is. |
| m_session.addMessage( INFO_GROUPS, "GroupDatabase is of type '" + db.getClass().getName() + |
| "'. It appears to be initialized properly." ); |
| |
| // Now, see how many groups we have. |
| int oldGroupCount = 0; |
| try |
| { |
| Group[] groups = db.groups(); |
| oldGroupCount = groups.length; |
| m_session.addMessage( INFO_GROUPS, "The group database contains " + oldGroupCount + " groups." ); |
| } |
| catch ( WikiSecurityException e ) |
| { |
| m_session.addMessage( ERROR_GROUPS, "Could not obtain a list of current groups: " + e.getMessage() ); |
| return; |
| } |
| |
| // Try adding a bogus group with random name |
| String name = "TestGroup" + System.currentTimeMillis(); |
| Group group = null; |
| try |
| { |
| // Create dummy test group |
| group = mgr.parseGroup( name, "", true ); |
| Principal user = new WikiPrincipal( "TestUser" ); |
| group.add( user ); |
| db.save( group, new WikiPrincipal("SecurityVerifier") ); |
| |
| // Make sure the group saved successfully |
| if ( db.groups().length == oldGroupCount ) |
| { |
| m_session.addMessage( ERROR_GROUPS, "Could not add a test group to the database." ); |
| return; |
| } |
| m_session.addMessage( INFO_GROUPS, "The group database allows new groups to be created, as it should." ); |
| } |
| catch ( WikiSecurityException e ) |
| { |
| m_session.addMessage( ERROR_GROUPS, "Could not add a group to the database: " + e.getMessage() ); |
| return; |
| } |
| |
| // Now delete the group; should be back to old count |
| try |
| { |
| db.delete( group ); |
| if ( db.groups().length != oldGroupCount ) |
| { |
| m_session.addMessage( ERROR_GROUPS, "Could not delete a test group from the database." ); |
| return; |
| } |
| m_session.addMessage( INFO_GROUPS, "The group database allows groups to be deleted, as it should." ); |
| } |
| catch ( WikiSecurityException e ) |
| { |
| m_session.addMessage( ERROR_GROUPS, "Could not delete a test group from the database: " + e.getMessage() ); |
| return; |
| } |
| |
| m_session.addMessage( INFO_GROUPS, "The group database configuration looks fine." ); |
| } |
| |
| /** |
| * Verfies the JAAS configuration. The configuration is valid if value of the |
| * <code>jspwiki.properties<code> property |
| * {@value org.apache.wiki.auth.AuthenticationManager#PROP_LOGIN_MODULE} |
| * resolves to a valid class on the classpath. |
| */ |
| protected void verifyJaas() |
| { |
| // Verify that the specified JAAS moduie corresponds to a class we can load successfully. |
| String jaasClass = m_engine.getWikiProperties().getProperty( AuthenticationManager.PROP_LOGIN_MODULE ); |
| if ( jaasClass == null || jaasClass.length() == 0 ) |
| { |
| m_session.addMessage( ERROR_JAAS, "The value of the '" + AuthenticationManager.PROP_LOGIN_MODULE + |
| "' property was null or blank. This is a fatal error. This value should be set to a valid LoginModule implementation " + |
| "on the classpath." ); |
| return; |
| } |
| |
| // See if we can find the LoginModule on the classpath |
| Class< ? > c = null; |
| try |
| { |
| m_session.addMessage( INFO_JAAS, "The property '" + AuthenticationManager.PROP_LOGIN_MODULE + |
| "' specified the class '" + jaasClass + ".'" ); |
| c = Class.forName( jaasClass ); |
| } |
| catch( ClassNotFoundException e ) |
| { |
| m_session.addMessage( ERROR_JAAS, "We could not find the the class '" + jaasClass + "' on the " + |
| "classpath. This is fatal error." ); |
| } |
| |
| // Is the specified class actually a LoginModule? |
| if ( LoginModule.class.isAssignableFrom( c ) ) |
| { |
| m_session.addMessage( INFO_JAAS, "We found the the class '" + jaasClass + "' on the " + |
| "classpath, and it is a LoginModule implementation. Good!" ); |
| } |
| else |
| { |
| m_session.addMessage( ERROR_JAAS, "We found the the class '" + jaasClass + "' on the " + |
| "classpath, but it does not seem to be LoginModule implementation! This is fatal error." ); |
| } |
| } |
| |
| /** |
| * Looks up a file name based on a JRE system property and returns the associated |
| * File object if it exists. This method adds messages with the topic prefix |
| * {@link #ERROR} and {@link #INFO} as appropriate, with the suffix matching the |
| * supplied property. |
| * @param property the system property to look up |
| * @return the file object, or <code>null</code> if not found |
| */ |
| protected File getFileFromProperty( String property ) |
| { |
| String propertyValue = null; |
| try |
| { |
| propertyValue = System.getProperty( property ); |
| if ( propertyValue == null ) |
| { |
| m_session.addMessage( "Error." + property, "The system property '" + property + "' is null." ); |
| return null; |
| } |
| |
| // |
| // It's also possible to use "==" to mark a property. We remove that |
| // here so that we can actually find the property file, then. |
| // |
| if( propertyValue.startsWith("=") ) |
| { |
| propertyValue = propertyValue.substring(1); |
| } |
| |
| try |
| { |
| m_session.addMessage( "Info." + property, "The system property '" + property + "' is set to: " |
| + propertyValue + "." ); |
| |
| // Prepend a file: prefix if not there already |
| if ( !propertyValue.startsWith( "file:" ) ) |
| { |
| propertyValue = "file:" + propertyValue; |
| } |
| URL url = new URL( propertyValue ); |
| File file = new File( url.getPath() ); |
| if ( file.exists() ) |
| { |
| m_session.addMessage( "Info." + property, "File '" + propertyValue + "' exists in the filesystem." ); |
| return file; |
| } |
| } |
| catch( MalformedURLException e ) |
| { |
| // Swallow exception because we can't find it anyway |
| } |
| m_session.addMessage( "Error." + property, "File '" + propertyValue |
| + "' doesn't seem to exist. This might be a problem." ); |
| return null; |
| } |
| catch( SecurityException e ) |
| { |
| m_session.addMessage( "Error." + property, "We could not read system property '" + property |
| + "'. This is probably because you are running with a security manager." ); |
| return null; |
| } |
| } |
| |
| /** |
| * Verfies the Java security policy configuration. The configuration is |
| * valid if value of the local policy (at <code>WEB-INF/jspwiki.policy</code> |
| * resolves to an existing file, and the policy file contained therein |
| * represents a valid policy. |
| */ |
| @SuppressWarnings("unchecked") |
| protected void verifyPolicy() |
| { |
| // Look up the policy file and set the status text. |
| URL policyURL = AuthenticationManager.findConfigFile( m_engine, AuthorizationManager.DEFAULT_POLICY ); |
| String path = policyURL.getPath(); |
| if ( path.startsWith("file:") ) |
| { |
| path = path.substring( 5 ); |
| } |
| File policyFile = new File( path ); |
| |
| // Next, verify the policy |
| try |
| { |
| // Get the file |
| PolicyReader policy = new PolicyReader( policyFile ); |
| m_session.addMessage( INFO_POLICY, "The security policy '" + policy.getFile() + "' exists." ); |
| |
| // See if there is a keystore that's valid |
| KeyStore ks = policy.getKeyStore(); |
| if ( ks == null ) |
| { |
| m_session.addMessage( WARNING_POLICY, |
| "Policy file does not have a keystore... at least not one that we can locate. If your policy file " + |
| "does not contain any 'signedBy' blocks, this is probably ok." ); |
| } |
| else |
| { |
| m_session.addMessage( INFO_POLICY, |
| "The security policy specifies a keystore, and we were able to locate it in the filesystem." ); |
| } |
| |
| // Verify the file |
| policy.read(); |
| List<Exception> errors = policy.getMessages(); |
| if ( errors.size() > 0 ) |
| { |
| for( Exception e : errors ) |
| { |
| m_session.addMessage( ERROR_POLICY, e.getMessage() ); |
| } |
| } |
| else |
| { |
| m_session.addMessage( INFO_POLICY, "The security policy looks fine." ); |
| m_isSecurityPolicyConfigured = true; |
| } |
| |
| // Stash the unique principals mentioned in the file, |
| // plus our standard roles. |
| Set<Principal> principals = new LinkedHashSet<Principal>(); |
| principals.add( Role.ALL ); |
| principals.add( Role.ANONYMOUS ); |
| principals.add( Role.ASSERTED ); |
| principals.add( Role.AUTHENTICATED ); |
| ProtectionDomain[] domains = policy.getProtectionDomains(); |
| for ( ProtectionDomain domain : domains ) |
| { |
| for( Principal principal : domain.getPrincipals() ) |
| { |
| principals.add( principal ); |
| } |
| } |
| m_policyPrincipals = principals.toArray( new Principal[principals.size()] ); |
| } |
| catch( IOException e ) |
| { |
| m_session.addMessage( ERROR_POLICY, e.getMessage() ); |
| } |
| } |
| |
| /** |
| * Verifies that a particular Principal possesses a Permission, as defined |
| * in the security policy file. |
| * @param principal the principal |
| * @param permission the permission |
| * @return the result, based on consultation with the active Java security |
| * policy |
| */ |
| protected boolean verifyStaticPermission( Principal principal, final Permission permission ) |
| { |
| Subject subject = new Subject(); |
| subject.getPrincipals().add( principal ); |
| boolean allowedByGlobalPolicy = ((Boolean) |
| Subject.doAsPrivileged( subject, new PrivilegedAction<Object>() |
| { |
| public Object run() |
| { |
| try |
| { |
| AccessController.checkPermission( permission ); |
| return Boolean.TRUE; |
| } |
| catch ( AccessControlException e ) |
| { |
| return Boolean.FALSE; |
| } |
| } |
| }, null )).booleanValue(); |
| |
| if ( allowedByGlobalPolicy ) |
| { |
| return true; |
| } |
| |
| // Check local policy |
| Principal[] principals = new Principal[]{ principal }; |
| return m_engine.getAuthorizationManager().allowedByLocalPolicy( principals, permission ); |
| } |
| |
| /** |
| * Verifies that the user datbase was initialized properly, and that |
| * user add and delete operations work as they should. |
| */ |
| protected void verifyUserDatabase() |
| { |
| UserDatabase db = m_engine.getUserManager().getUserDatabase(); |
| |
| // Check for obvious error conditions |
| if ( db == null ) |
| { |
| m_session.addMessage( ERROR_DB, "UserDatabase is null; JSPWiki could not " + |
| "initialize it. Check the error logs." ); |
| return; |
| } |
| |
| if ( db instanceof UserManager.DummyUserDatabase ) |
| { |
| m_session.addMessage( ERROR_DB, "UserDatabase is DummyUserDatabase; JSPWiki " + |
| "may not have been able to initialize the database you supplied in " + |
| "jspwiki.properties, or you left the 'jspwiki.userdatabase' property " + |
| "blank. Check the error logs." ); |
| } |
| |
| // Tell user what class of database this is. |
| m_session.addMessage( INFO_DB, "UserDatabase is of type '" + db.getClass().getName() + |
| "'. It appears to be initialized properly." ); |
| |
| // Now, see how many users we have. |
| int oldUserCount = 0; |
| try |
| { |
| Principal[] users = db.getWikiNames(); |
| oldUserCount = users.length; |
| m_session.addMessage( INFO_DB, "The user database contains " + oldUserCount + " users." ); |
| } |
| catch ( WikiSecurityException e ) |
| { |
| m_session.addMessage( ERROR_DB, "Could not obtain a list of current users: " + e.getMessage() ); |
| return; |
| } |
| |
| // Try adding a bogus user with random name |
| String loginName = "TestUser" + System.currentTimeMillis(); |
| try |
| { |
| UserProfile profile = db.newProfile(); |
| profile.setEmail( "jspwiki.tests@mailinator.com" ); |
| profile.setLoginName( loginName ); |
| profile.setFullname( "FullName"+loginName ); |
| profile.setPassword( "password" ); |
| db.save(profile); |
| |
| // Make sure the profile saved successfully |
| if ( db.getWikiNames().length == oldUserCount ) |
| { |
| m_session.addMessage( ERROR_DB, "Could not add a test user to the database." ); |
| return; |
| } |
| m_session.addMessage( INFO_DB, "The user database allows new users to be created, as it should." ); |
| } |
| catch ( WikiSecurityException e ) |
| { |
| m_session.addMessage( ERROR_DB, "Could not add a test user to the database: " + e.getMessage() ); |
| return; |
| } |
| |
| // Now delete the profile; should be back to old count |
| try |
| { |
| db.deleteByLoginName( loginName ); |
| if ( db.getWikiNames().length != oldUserCount ) |
| { |
| m_session.addMessage( ERROR_DB, "Could not delete a test user from the database." ); |
| return; |
| } |
| m_session.addMessage( INFO_DB, "The user database allows users to be deleted, as it should." ); |
| } |
| catch ( WikiSecurityException e ) |
| { |
| m_session.addMessage( ERROR_DB, "Could not delete a test user to the database: " + e.getMessage() ); |
| return; |
| } |
| |
| m_session.addMessage( INFO_DB, "The user database configuration looks fine." ); |
| } |
| } |