You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/02/13 06:54:24 UTC
svn commit: r627255 [12/41] - in
/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src: ./ com/ com/ecyrd/
com/ecyrd/jspwiki/ com/ecyrd/jspwiki/action/ com/ecyrd/jspwiki/attachment/
com/ecyrd/jspwiki/auth/ com/ecyrd/jspwiki/auth/acl/ com/ecyrd/jspwiki...
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,951 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package com.ecyrd.jspwiki.auth;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.*;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.log4j.Logger;
+import org.freshcookies.security.policy.PolicyReader;
+import org.jdom.JDOMException;
+
+import com.ecyrd.jspwiki.InternalWikiException;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiException;
+import com.ecyrd.jspwiki.WikiSession;
+import com.ecyrd.jspwiki.auth.authorize.*;
+import com.ecyrd.jspwiki.auth.permissions.AllPermission;
+import com.ecyrd.jspwiki.auth.permissions.GroupPermission;
+import com.ecyrd.jspwiki.auth.permissions.PermissionFactory;
+import com.ecyrd.jspwiki.auth.permissions.WikiPermission;
+import com.ecyrd.jspwiki.auth.user.DefaultUserProfile;
+import com.ecyrd.jspwiki.auth.user.UserDatabase;
+import com.ecyrd.jspwiki.auth.user.UserProfile;
+
+/**
+ * Helper class for verifying JSPWiki's security configuration. Invoked by
+ * <code>admin/SecurityConfig.jsp</code>.
+ * @author Andrew Jaquith
+ * @since 2.4
+ */
+public final class SecurityVerifier
+{
+ private static final long serialVersionUID = -3859563355089169941L;
+
+ private WikiEngine m_engine;
+
+ private File m_jaasConfig = null;
+
+ private boolean m_isJaasConfigured = false;
+
+ 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 final 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 com.ecyrd.jspwiki.auth.authorize.WebContainerAuthorizer}
+ * @return the formatted HTML table containing the result of the tests
+ */
+ public final 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 = String.valueOf( 67f / ( pageActions.length * roles.length ) ) + "%";
+ }
+ else
+ {
+ colWidth = "67%";
+ }
+
+ StringBuffer s = new StringBuffer();
+
+ // 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( int j = 0; j < pageActions.length; j++ )
+ {
+ String action = pageActions[j].substring( 0, 1 );
+ s.append( " <th title=\"" + pageActions[j] + "\">" + action + "</th>\n" );
+ }
+ }
+ s.append( " </tr>\n" );
+
+ // Write page permission tests first
+ for( int i = 0; i < pages.length; i++ )
+ {
+ String page = pages[i];
+ s.append( " <tr>\n" );
+ s.append( " <td>PagePermission \"" + wiki + ":" + page + "\"</td>\n" );
+ for( int j = 0; j < roles.length; j++ )
+ {
+ for( int k = 0; k < pageActions.length; k++ )
+ {
+ Permission permission = PermissionFactory.getPagePermission( wiki + ":" + page, pageActions[k] );
+ s.append( printPermissionTest( permission, roles[j], 1 ) );
+ }
+ }
+ s.append( " </tr>\n" );
+ }
+
+ // Now do the group tests
+ for( int i = 0; i < groups.length; i++ )
+ {
+ String group = groups[i];
+ s.append( " <tr>\n" );
+ s.append( " <td>GroupPermission \"" + wiki + ":" + group + "\"</td>\n" );
+ for( int j = 0; j < roles.length; j++ )
+ {
+ for( int k = 0; k < groupActions.length; k++ )
+ {
+ Permission permission = null;
+ if ( groupActions[k] != null)
+ {
+ permission = new GroupPermission( wiki + ":" + group, groupActions[k] );
+ }
+ s.append( printPermissionTest( permission, roles[j], 1 ) );
+ }
+ }
+ s.append( " </tr>\n" );
+ }
+
+
+ // Now check the wiki-wide permissions
+ String[] wikiPerms = new String[]
+ { "createGroups", "createPages", "login", "editPreferences", "editProfile" };
+ for( int i = 0; i < wikiPerms.length; i++ )
+ {
+ s.append( " <tr>\n" );
+ s.append( " <td>WikiPermission \"" + wiki + "\",\"" + wikiPerms[i] + "\"</td>\n" );
+ for( int j = 0; j < roles.length; j++ )
+ {
+ Permission permission = new WikiPermission( wiki, wikiPerms[i] );
+ s.append( printPermissionTest( permission, roles[j], pageActions.length ) );
+ }
+ s.append( " </tr>\n" );
+ }
+
+ // Lastly, check for AllPermission
+ s.append( " <tr>\n" );
+ s.append( " <td>AllPermission \"" + wiki + "\"</td>\n" );
+ for( int j = 0; j < roles.length; j++ )
+ {
+ Permission permission = new AllPermission( wiki );
+ s.append( printPermissionTest( permission, roles[j], 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 perm the permission to format
+ * @param allowed whether the permission is allowed
+ */
+ private final String printPermissionTest( Permission permission, Principal principal, int cols )
+ {
+ StringBuffer s = new StringBuffer();
+ 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 com.ecyrd.jspwiki.auth.authorize.WebContainerAuthorizer}
+ * @return the formatted HTML table containing the result of the tests
+ * @throws WikiException if tests fail for unexpected reasons
+ */
+ public final 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
+ StringBuffer s = new StringBuffer();
+ 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( int i = 0; i < roles.length; i++ )
+ {
+ s.append( " <th>" + roles[i].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( int j = 0; j < roles.length; j++ )
+ {
+ Role role = (Role) roles[j];
+ boolean allowed = allowsAnonymous || wca.isConstrained( jsp, 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() );
+ }
+
+ s.append( "</tbody>\n" );
+ s.append( "</table>\n" );
+ return s.toString();
+ }
+
+ /**
+ * Returns <code>true</code> if JAAS is configured correctly.
+ * @return the result of the configuration check
+ */
+ public final boolean isJaasConfigured()
+ {
+ return m_isJaasConfigured;
+ }
+
+ /**
+ * Returns <code>true</code> if the JAAS login configuration was already
+ * set when JSPWiki started up. We determine this value by consulting a
+ * protected member field of {@link AuthenticationManager}, which was set
+ * at in initialization by {@link PolicyLoader}.
+ * @return <code>true</code> if {@link PolicyLoader} successfully set the
+ * policy, or <code>false</code> for any other reason.
+ */
+ public final boolean isJaasConfiguredAtStartup()
+ {
+ return m_engine.getAuthenticationManager().m_isJaasConfiguredAtStartup;
+ }
+
+ /**
+ * Returns <code>true</code> if JSPWiki can locate a named JAAS login
+ * configuration.
+ * @param config the name of the application (e.g.,
+ * <code>JSPWiki-container</code>).
+ * @return <code>true</code> if found; <code>false</code> otherwise
+ */
+ protected final boolean isJaasConfigurationAvailable( String config )
+ {
+ try
+ {
+ m_session.addMessage( INFO_JAAS, "We found the '" + config + "' login configuration." );
+ new LoginContext( config );
+ return true;
+ }
+ catch( Exception e )
+ {
+ m_session.addMessage( ERROR_JAAS, "We could not find the '" + config + "' login configuration.</p>" );
+ return false;
+ }
+ }
+
+ /**
+ * 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 final 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 final 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 final void verifyPolicyAndContainerRoles() throws WikiException
+ {
+ Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer();
+ Principal[] containerRoles = authorizer.getRoles();
+ boolean missing = false;
+ for( int i = 0; i < m_policyPrincipals.length; i++ )
+ {
+ Principal principal = m_policyPrincipals[i];
+ 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 final 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" + String.valueOf( 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 system property <code>java.security.auth.login.config</code>
+ * resolves to an existing file, and we can find the JAAS login
+ * configurations for <code>JSPWiki-container</code> and
+ * <code>JSPWiki-custom</code>.
+ */
+ protected final void verifyJaas()
+ {
+ // See if JAAS is on
+ AuthorizationManager authMgr = m_engine.getAuthorizationManager();
+ if ( !authMgr.isJAASAuthorized() )
+ {
+ m_session.addMessage( ERROR_JAAS, "JSPWiki's JAAS-based authentication " +
+ "and authorization system is turned off (your <code>jspwiki.properties</code> " +
+ "contains the setting 'jspwiki.security = container'. This " +
+ "setting disables authorization checks and is meant for testing " +
+ "and troubleshooting only. The test results on this page will not " +
+ "be reliable as a result. You should set this to 'jaas' " +
+ "so that security works properly." );
+ }
+
+ // Validate the property is set correctly
+ m_jaasConfig = getFileFromProperty( "java.security.auth.login.config" );
+
+ // Look for the JSPWiki-container config
+ boolean foundJaasContainerConfig = isJaasConfigurationAvailable( "JSPWiki-container" );
+
+ // Look for the JSPWiki-custom config
+ boolean foundJaasCustomConfig = isJaasConfigurationAvailable( "JSPWiki-custom" );
+
+ m_isJaasConfigured = m_jaasConfig != null && foundJaasContainerConfig && foundJaasCustomConfig;
+ }
+
+ /**
+ * 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 final 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.
+ */
+ protected final 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( ERROR_POLICY,
+ "Policy file does not have a keystore... at least not one that we can locate." );
+ }
+ 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 errors = policy.getMessages();
+ if ( errors.size() > 0 )
+ {
+ for( Iterator it = errors.iterator(); it.hasNext(); )
+ {
+ Exception e = (Exception) it.next();
+ 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 principals = new LinkedHashSet();
+ principals.add( Role.ALL );
+ principals.add( Role.ANONYMOUS );
+ principals.add( Role.ASSERTED );
+ principals.add( Role.AUTHENTICATED );
+ ProtectionDomain[] domains = policy.getProtectionDomains();
+ for ( int i = 0; i < domains.length; i++ )
+ {
+ Principal[] domainPrincipals = domains[i].getPrincipals();
+ for( int j = 0; j < domainPrincipals.length; j++ )
+ {
+ principals.add( domainPrincipals[j] );
+ }
+ }
+ m_policyPrincipals = (Principal[]) 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 final boolean verifyStaticPermission( Principal principal, final Permission permission )
+ {
+ Subject subject = new Subject();
+ subject.getPrincipals().add( principal );
+ boolean allowedByGlobalPolicy = ((Boolean)
+ Subject.doAsPrivileged( subject, new PrivilegedAction()
+ {
+ 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 final 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" + String.valueOf( System.currentTimeMillis() );
+ try
+ {
+ UserProfile profile = new DefaultUserProfile();
+ profile.setEmail("testuser@testville.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." );
+ }
+
+ /**
+ * Returns the location of the JAAS configuration file if and only if the
+ * <code>java.security.auth.login.config</code> is set <em>and</em> the
+ * file it points to exists in the file system; returns <code>null</code>
+ * in all other cases.
+ * @return the location of the JAAS configuration file
+ */
+ public final File jaasConfiguration()
+ {
+ return m_jaasConfig;
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SessionMonitor.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SessionMonitor.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SessionMonitor.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SessionMonitor.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,286 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2006 JSPWiki Development Team
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+package com.ecyrd.jspwiki.auth;
+
+import java.security.Principal;
+import java.util.*;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiSession;
+import com.ecyrd.jspwiki.event.WikiEventListener;
+import com.ecyrd.jspwiki.event.WikiEventManager;
+import com.ecyrd.jspwiki.event.WikiSecurityEvent;
+import com.ecyrd.jspwiki.rpc.json.JSONRPCManager;
+
+/**
+ * <p>Manages WikiSession's for different WikiEngine's.</p>
+ * <p>The WikiSession's are stored both in the remote user
+ * HttpSession and in the SessionMonitor for the WikeEngine.
+ * This class must be configured as a session listener in the
+ * web.xml for the wiki web application.
+ * </p>
+ */
+public class SessionMonitor implements HttpSessionListener
+{
+ private static Logger log = Logger.getLogger( SessionMonitor.class );
+
+ /** Map with WikiEngines as keys, and SessionMonitors as values. */
+ private static Map c_monitors = new HashMap();
+
+ /** Weak hashmap with HttpSessions as keys, and WikiSessions as values. */
+ private final Map m_sessions = new WeakHashMap();
+
+ private WikiEngine m_engine;
+
+ private final PrincipalComparator m_comparator = new PrincipalComparator();
+
+ /**
+ * Returns the instance of the SessionMonitor for this wiki.
+ * Only one SessionMonitor exists per WikiEngine.
+ * @param engine the wiki engine
+ * @return the session monitor
+ */
+ public static final SessionMonitor getInstance( WikiEngine engine )
+ {
+ if( engine == null )
+ {
+ throw new IllegalArgumentException( "Engine cannot be null." );
+ }
+ SessionMonitor monitor;
+
+ synchronized( c_monitors )
+ {
+ monitor = (SessionMonitor) c_monitors.get(engine);
+ if( monitor == null )
+ {
+ monitor = new SessionMonitor(engine);
+
+ c_monitors.put( engine, monitor );
+ }
+ }
+ return monitor;
+ }
+
+ /**
+ * Construct the SessionListener
+ */
+ public SessionMonitor()
+ {
+ }
+
+ private SessionMonitor( WikiEngine engine )
+ {
+ m_engine = engine;
+ }
+
+ /**
+ * Just looks for a WikiSession; does not create a new one.
+ * This method may return <code>null</code>, <em>and
+ * callers should check for this value</em>.
+ *
+ * @param session the user's HTTP session
+ * @return the WikiSession, if found
+ */
+ private WikiSession findSession( HttpSession session )
+ {
+ WikiSession wikiSession = null;
+ String sid = ( session == null ) ? "(null)" : session.getId();
+ WikiSession storedSession = (WikiSession)m_sessions.get( sid );
+
+ // If the weak reference returns a wiki session, return it
+ if( storedSession != null )
+ {
+ if( log.isDebugEnabled() )
+ {
+ log.debug( "Looking up WikiSession for session ID=" + sid + "... found it" );
+ }
+ wikiSession = storedSession;
+ }
+
+ return wikiSession;
+ }
+ /**
+ * <p>Looks up the wiki session associated with a user's Http session
+ * and adds it to the session cache. This method will return the
+ * "guest session" as constructed by {@link WikiSession#guestSession(WikiEngine)}
+ * if the HttpSession is not currently associated with a WikiSession.
+ * This method is guaranteed to return a non-<code>null</code> WikiSession.</p>
+ * <p>Internally, the session is stored in a HashMap; keys are
+ * the HttpSession objects, while the values are
+ * {@link java.lang.ref.WeakReference}-wrapped WikiSessions.</p>
+ * @param session the HTTP session
+ * @return the wiki session
+ */
+ public final WikiSession find( HttpSession session )
+ {
+ WikiSession wikiSession = findSession(session);
+ String sid = ( session == null ) ? "(null)" : session.getId();
+
+ // Otherwise, create a new guest session and stash it.
+ if( wikiSession == null )
+ {
+ if( log.isDebugEnabled() )
+ {
+ log.debug( "Looking up WikiSession for session ID=" + sid + "... not found. Creating guestSession()" );
+ }
+ wikiSession = WikiSession.guestSession( m_engine );
+ synchronized( m_sessions )
+ {
+ m_sessions.put( sid, wikiSession );
+ }
+ }
+
+ return wikiSession;
+ }
+
+ /**
+ * Removes the wiki session associated with the user's HttpSession
+ * from the session cache.
+ * @param session the user's HTTP session
+ */
+ public final void remove( HttpSession session )
+ {
+ if ( session == null )
+ {
+ throw new IllegalArgumentException( "Session cannot be null." );
+ }
+ synchronized ( m_sessions )
+ {
+ m_sessions.remove( session.getId() );
+ }
+ }
+
+ /**
+ * Returns the current number of active wiki sessions.
+ * @return the number of sessions
+ */
+ public final int sessions()
+ {
+ return userPrincipals().length;
+ }
+
+ /**
+ * <p>Returns the current wiki users as a sorted array of
+ * Principal objects. The principals are those returned by
+ * each WikiSession's {@link WikiSession#getUserPrincipal()}'s
+ * method.</p>
+ * <p>To obtain the list of current WikiSessions, we iterate
+ * through our session Map and obtain the list of values,
+ * which are WikiSessions wrapped in {@link java.lang.ref.WeakReference}
+ * objects. Those <code>WeakReference</code>s whose <code>get()</code>
+ * method returns non-<code>null</code> values are valid
+ * sessions.</p>
+ * @return the array of user principals
+ */
+ public final Principal[] userPrincipals()
+ {
+ Collection principals = new ArrayList();
+ for ( Iterator it = m_sessions.values().iterator(); it.hasNext(); )
+ {
+ WikiSession session = (WikiSession)it.next();
+
+ principals.add( session.getUserPrincipal() );
+ }
+ Principal[] p = (Principal[])principals.toArray( new Principal[principals.size()] );
+ Arrays.sort( p, m_comparator );
+ return p;
+ }
+
+ /**
+ * Registers a WikiEventListener with this instance.
+ * @param listener the event listener
+ * @since 2.4.75
+ */
+ public final synchronized void addWikiEventListener( WikiEventListener listener )
+ {
+ WikiEventManager.addWikiEventListener( this, listener );
+ }
+
+ /**
+ * Un-registers a WikiEventListener with this instance.
+ * @param listener the event listener
+ * @since 2.4.75
+ */
+ public final synchronized void removeWikiEventListener( WikiEventListener listener )
+ {
+ WikiEventManager.removeWikiEventListener( this, listener );
+ }
+
+ /**
+ * Fires a WikiSecurityEvent to all registered listeners.
+ * @param type the event type
+ * @param principal the user principal associated with this session
+ * @param session the wiki session
+ * @since 2.4.75
+ */
+ protected final void fireEvent( int type, Principal principal, WikiSession session )
+ {
+ if( WikiEventManager.isListening(this) )
+ {
+ WikiEventManager.fireEvent(this,new WikiSecurityEvent(this,type,principal,session));
+ }
+ }
+
+ /**
+ * Fires when the web container creates a new HTTP session.
+ *
+ * @param se the HTTP session event
+ */
+ public void sessionCreated( HttpSessionEvent se )
+ {
+ HttpSession session = se.getSession();
+
+ JSONRPCManager.sessionCreated(session);
+ }
+
+ /**
+ * Removes the user's WikiSession from the internal session cache when the web
+ * container destoys an HTTP session.
+ * @param se the HTTP session event
+ */
+ public void sessionDestroyed( HttpSessionEvent se )
+ {
+ HttpSession session = se.getSession();
+ Iterator it = c_monitors.values().iterator();
+ while( it.hasNext() )
+ {
+ SessionMonitor monitor = (SessionMonitor)it.next();
+
+ WikiSession storedSession = monitor.findSession(session);
+
+ monitor.remove(session);
+
+ log.debug("Removed session "+session.getId()+".");
+
+ if( storedSession != null )
+ {
+ fireEvent( WikiSecurityEvent.SESSION_EXPIRED,
+ storedSession.getLoginPrincipal(),
+ storedSession );
+ }
+ }
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/UserManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/UserManager.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/UserManager.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/UserManager.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,836 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+package com.ecyrd.jspwiki.auth;
+
+import java.security.Permission;
+import java.security.Principal;
+import java.text.MessageFormat;
+import java.util.*;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.AddressException;
+import javax.servlet.http.HttpServletRequest;
+
+import net.sourceforge.stripes.action.UrlBinding;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.action.LoginActionBean;
+import com.ecyrd.jspwiki.auth.permissions.AllPermission;
+import com.ecyrd.jspwiki.auth.permissions.WikiPermission;
+import com.ecyrd.jspwiki.auth.user.AbstractUserDatabase;
+import com.ecyrd.jspwiki.auth.user.DuplicateUserException;
+import com.ecyrd.jspwiki.auth.user.UserDatabase;
+import com.ecyrd.jspwiki.auth.user.UserProfile;
+import com.ecyrd.jspwiki.event.WikiEventListener;
+import com.ecyrd.jspwiki.event.WikiEventManager;
+import com.ecyrd.jspwiki.event.WikiSecurityEvent;
+import com.ecyrd.jspwiki.filters.PageFilter;
+import com.ecyrd.jspwiki.filters.SpamFilter;
+import com.ecyrd.jspwiki.i18n.InternationalizationManager;
+import com.ecyrd.jspwiki.rpc.RPCCallable;
+import com.ecyrd.jspwiki.rpc.json.JSONRPCManager;
+import com.ecyrd.jspwiki.ui.InputValidator;
+import com.ecyrd.jspwiki.util.ClassUtil;
+import com.ecyrd.jspwiki.util.MailUtil;
+import com.ecyrd.jspwiki.workflow.*;
+
+/**
+ * Provides a facade for obtaining user information.
+ * @author Janne Jalkanen
+ * @author Andrew Jaquith
+ * @since 2.3
+ */
+public final class UserManager
+{
+ private static final String USERDATABASE_PACKAGE = "com.ecyrd.jspwiki.auth.user";
+ private static final String SESSION_MESSAGES = "profile";
+ private static final String PARAM_EMAIL = "email";
+ private static final String PARAM_FULLNAME = "fullname";
+ private static final String PARAM_PASSWORD = "password";
+ private static final String PARAM_LOGINNAME = "loginname";
+ private static final String UNKNOWN_CLASS = "<unknown>";
+
+ private WikiEngine m_engine;
+
+ private static Logger log = Logger.getLogger(UserManager.class);
+
+ /** Message key for the "save profile" message. */
+ public static final String SAVE_APPROVER = "workflow.createUserProfile";
+ private static final String PROP_DATABASE = "jspwiki.userdatabase";
+ protected static final String SAVE_TASK_MESSAGE_KEY = "task.createUserProfile";
+ protected static final String SAVED_PROFILE = "userProfile";
+ protected static final String SAVE_DECISION_MESSAGE_KEY = "decision.createUserProfile";
+ protected static final String FACT_SUBMITTER = "fact.submitter";
+ protected static final String PREFS_LOGIN_NAME = "prefs.loginname";
+ protected static final String PREFS_FULL_NAME = "prefs.fullname";
+ protected static final String PREFS_EMAIL = "prefs.email";
+
+ // private static final String PROP_ACLMANAGER = "jspwiki.aclManager";
+
+ /** Associateds wiki sessions with profiles */
+ private final Map<WikiSession,UserProfile> m_profiles = new WeakHashMap<WikiSession,UserProfile>();
+
+ /** The user database loads, manages and persists user identities */
+ private UserDatabase m_database;
+
+ private boolean m_useJAAS = true;
+
+ /**
+ * Constructs a new UserManager instance.
+ */
+ public UserManager()
+ {
+ }
+
+ /**
+ * Initializes the engine for its nefarious purposes.
+ * @param engine the current wiki engine
+ * @param props the wiki engine initialization properties
+ */
+ public final void initialize( WikiEngine engine, Properties props )
+ {
+ m_engine = engine;
+
+ m_useJAAS = AuthenticationManager.SECURITY_JAAS.equals( props.getProperty(AuthenticationManager.PROP_SECURITY, AuthenticationManager.SECURITY_JAAS ) );
+
+ // Attach the PageManager as a listener
+ // TODO: it would be better if we did this in PageManager directly
+ addWikiEventListener( engine.getPageManager() );
+
+ JSONRPCManager.registerGlobalObject( "users", new JSONUserModule(), new AllPermission(null) );
+ }
+
+ /**
+ * Returns the UserDatabase employed by this WikiEngine. The UserDatabase is
+ * lazily initialized by this method, if it does not exist yet. If the
+ * initialization fails, this method will use the inner class
+ * DummyUserDatabase as a default (which is enough to get JSPWiki running).
+ * @return the dummy user database
+ * @since 2.3
+ */
+ public final UserDatabase getUserDatabase()
+ {
+ // FIXME: Must not throw RuntimeException, but something else.
+ if( m_database != null )
+ {
+ return m_database;
+ }
+
+ if( !m_useJAAS )
+ {
+ m_database = new DummyUserDatabase();
+ return m_database;
+ }
+
+ String dbClassName = UNKNOWN_CLASS;
+
+ try
+ {
+ dbClassName = WikiEngine.getRequiredProperty( m_engine.getWikiProperties(),
+ PROP_DATABASE );
+
+ log.info("Attempting to load user database class "+dbClassName);
+ Class dbClass = ClassUtil.findClass( USERDATABASE_PACKAGE, dbClassName );
+ m_database = (UserDatabase) dbClass.newInstance();
+ m_database.initialize( m_engine, m_engine.getWikiProperties() );
+ log.info("UserDatabase initialized.");
+ }
+ catch( NoRequiredPropertyException e )
+ {
+ log.error( "You have not set the '"+PROP_DATABASE+"'. You need to do this if you want to enable user management by JSPWiki." );
+ }
+ catch( ClassNotFoundException e )
+ {
+ log.error( "UserDatabase class " + dbClassName + " cannot be found", e );
+ }
+ catch( InstantiationException e )
+ {
+ log.error( "UserDatabase class " + dbClassName + " cannot be created", e );
+ }
+ catch( IllegalAccessException e )
+ {
+ log.error( "You are not allowed to access this user database class", e );
+ }
+ finally
+ {
+ if( m_database == null )
+ {
+ log.info("I could not create a database object you specified (or didn't specify), so I am falling back to a default.");
+ m_database = new DummyUserDatabase();
+ }
+ }
+
+ return m_database;
+ }
+
+ /**
+ * <p>Retrieves the {@link com.ecyrd.jspwiki.auth.user.UserProfile}for the
+ * user in a wiki session. If the user is authenticated, the UserProfile
+ * returned will be the one stored in the user database; if one does not
+ * exist, a new one will be initialized and returned. If the user is
+ * anonymous or asserted, the UserProfile will <i>always</i> be newly
+ * initialized to prevent spoofing of identities. If a UserProfile needs to
+ * be initialized, its
+ * {@link com.ecyrd.jspwiki.auth.user.UserProfile#isNew()} method will
+ * return <code>true</code>, and its login name will will be set
+ * automatically if the user is authenticated. Note that this method does
+ * not modify the retrieved (or newly created) profile otherwise; other
+ * fields in the user profile may be <code>null</code>.</p>
+ * <p>If a new UserProfile was created, but its
+ * {@link com.ecyrd.jspwiki.auth.user.UserProfile#isNew()} method returns
+ * <code>false</code>, this method throws an {@link IllegalStateException}.
+ * This is meant as a quality check for UserDatabase providers;
+ * it should only be thrown if the implementation is faulty.</p>
+ * @param session the wiki session, which may not be <code>null</code>
+ * @return the user's profile, which will be newly initialized if the user
+ * is anonymous or asserted, or if the user cannot be found in the user
+ * database
+ */
+ public final UserProfile getUserProfile( WikiSession session )
+ {
+ // Look up cached user profile
+ UserProfile profile = m_profiles.get( session );
+ boolean newProfile = profile == null;
+ Principal user = null;
+
+ // If user is authenticated, figure out if this is an existing profile
+ if ( session.isAuthenticated() )
+ {
+ user = session.getUserPrincipal();
+ try
+ {
+ profile = getUserDatabase().find( user.getName() );
+ newProfile = false;
+ }
+ catch( NoSuchPrincipalException e )
+ {
+ }
+ }
+
+ if ( newProfile )
+ {
+ profile = getUserDatabase().newProfile();
+ if ( user != null )
+ {
+ profile.setLoginName( user.getName() );
+ }
+ if ( !profile.isNew() )
+ {
+ throw new IllegalStateException(
+ "New profile should be marked 'new'. Check your UserProfile implementation." );
+ }
+ }
+
+ // Stash the profile for next time
+ m_profiles.put( session, profile );
+ return profile;
+ }
+
+ /**
+ * <p>
+ * Saves the {@link com.ecyrd.jspwiki.auth.user.UserProfile}for the user in
+ * a wiki session. This method verifies that a user profile to be saved
+ * doesn't collide with existing profiles; that is, the login name
+ * or full name is already used by another profile. If the profile
+ * collides, a <code>DuplicateUserException</code> is thrown. After saving
+ * the profile, the user database changes are committed, and the user's
+ * credential set is refreshed; if custom authentication is used, this means
+ * the user will be automatically be logged in.
+ * </p>
+ * <p>
+ * When the user's profile is saved succcessfully, this method fires a
+ * {@link WikiSecurityEvent#PROFILE_SAVE} event with the WikiSession as the
+ * source and the UserProfile as target. For existing profiles, if the
+ * user's full name changes, this method also fires a "name changed"
+ * event ({@link WikiSecurityEvent#PROFILE_NAME_CHANGED}) with the
+ * WikiSession as the source and an array containing the old and new
+ * UserProfiles, respectively. The <code>NAME_CHANGED</code> event allows
+ * the GroupManager and PageManager can change group memberships and
+ * ACLs if needed.
+ * </p>
+ * <p>
+ * Note that WikiSessions normally attach event listeners to the
+ * UserManager, so changes to the profile will automatically cause the
+ * correct Principals to be reloaded into the current WikiSession's Subject.
+ * </p>
+ * @param session the wiki session, which may not be <code>null</code>
+ * @param profile the user profile, which may not be <code>null</code>
+ * @throws DuplicateUserException if the proposed profile's login name or full name collides with another
+ * @throws WikiException if the save fails for some reason. If the current user does not have
+ * permission to save the profile, this will be a {@link com.ecyrd.jspwiki.auth.WikiSecurityException};
+ * if if the user profile must be approved before it can be saved, it will be a
+ * {@link com.ecyrd.jspwiki.workflow.DecisionRequiredException}. All other WikiException
+ * indicate a condition that is not normal is probably due to mis-configuration
+ */
+ public final void setUserProfile( WikiSession session, UserProfile profile ) throws DuplicateUserException, WikiException
+ {
+ // Verify user is allowed to save profile!
+ Permission p = new WikiPermission( m_engine.getApplicationName(), WikiPermission.EDIT_PROFILE_ACTION );
+ if ( !m_engine.getAuthorizationManager().checkPermission( session, p ) )
+ {
+ throw new WikiSecurityException( "You are not allowed to save wiki profiles." );
+ }
+
+ // Check if profile is new, and see if container allows creation
+ boolean newProfile = profile.isNew();
+
+ // Check if another user profile already has the fullname or loginname
+ UserProfile oldProfile = getUserProfile( session );
+ boolean nameChanged = ( oldProfile == null || oldProfile.getFullname() == null )
+ ? false
+ : !( oldProfile.getFullname().equals( profile.getFullname() ) &&
+ oldProfile.getLoginName().equals( profile.getLoginName() ) );
+ UserProfile otherProfile;
+ try
+ {
+ otherProfile = getUserDatabase().findByLoginName( profile.getLoginName() );
+ if ( otherProfile != null && !otherProfile.equals( oldProfile ) )
+ {
+ throw new DuplicateUserException( "The login name '" + profile.getLoginName() + "' is already taken." );
+ }
+ }
+ catch( NoSuchPrincipalException e )
+ {
+ }
+ try
+ {
+ otherProfile = getUserDatabase().findByFullName( profile.getFullname() );
+ if ( otherProfile != null && !otherProfile.equals( oldProfile ) )
+ {
+ throw new DuplicateUserException( "The full name '" + profile.getFullname() + "' is already taken." );
+ }
+ }
+ catch( NoSuchPrincipalException e )
+ {
+ }
+
+ // For new accounts, create approval workflow for user profile save.
+ if ( newProfile && oldProfile != null && oldProfile.isNew() )
+ {
+ WorkflowBuilder builder = WorkflowBuilder.getBuilder( m_engine );
+ Principal submitter = session.getUserPrincipal();
+ Task completionTask = new SaveUserProfileTask( m_engine );
+
+ // Add user profile attribute as Facts for the approver (if required)
+ boolean hasEmail = profile.getEmail() != null;
+ Fact[] facts = new Fact[ hasEmail ? 4 : 3];
+ facts[0] = new Fact( PREFS_FULL_NAME, profile.getFullname() );
+ facts[1] = new Fact( PREFS_LOGIN_NAME, profile.getLoginName() );
+ facts[2] = new Fact( FACT_SUBMITTER, submitter.getName() );
+ if ( hasEmail )
+ {
+ facts[3] = new Fact( PREFS_EMAIL, profile.getEmail() );
+ }
+ Workflow workflow = builder.buildApprovalWorkflow( submitter,
+ SAVE_APPROVER,
+ null,
+ SAVE_DECISION_MESSAGE_KEY,
+ facts,
+ completionTask,
+ null );
+
+ workflow.setAttribute( SAVED_PROFILE, profile );
+ m_engine.getWorkflowManager().start(workflow);
+
+ boolean approvalRequired = workflow.getCurrentStep() instanceof Decision;
+
+ // If the profile requires approval, redirect user to message page
+ if ( approvalRequired )
+ {
+ throw new DecisionRequiredException( "This profile must be approved before it becomes active" );
+ }
+
+ // If the profile doesn't need approval, then just log the user in
+
+ try
+ {
+ AuthenticationManager mgr = m_engine.getAuthenticationManager();
+ if ( newProfile && !mgr.isContainerAuthenticated() )
+ {
+ mgr.login( session, profile.getLoginName(), profile.getPassword() );
+ }
+ }
+ catch ( WikiException e )
+ {
+ throw new WikiSecurityException( e.getMessage() );
+ }
+
+ // Alert all listeners that the profile changed...
+ // ...this will cause credentials to be reloaded in the wiki session
+ fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
+ }
+
+ // For existing accounts, just save the profile
+ else
+ {
+ // If login name changed, rename it first
+ if ( nameChanged && oldProfile != null && !oldProfile.getLoginName().equals( profile.getLoginName() ) )
+ {
+ getUserDatabase().rename( oldProfile.getLoginName(), profile.getLoginName() );
+ }
+
+ // Now, save the profile (userdatabase will take care of timestamps for us)
+ getUserDatabase().save( profile );
+
+ if ( nameChanged )
+ {
+ // Fire an event if the login name or full name changed
+ UserProfile[] profiles = new UserProfile[] { oldProfile, profile };
+ fireEvent( WikiSecurityEvent.PROFILE_NAME_CHANGED, session, profiles );
+ }
+ else
+ {
+ // Fire an event that says we have new a new profile (new principals)
+ fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
+ }
+ }
+ }
+
+ /**
+ * <p> Extracts user profile parameters from the HTTP request and populates
+ * a UserProfile with them. The UserProfile will either be a copy of the
+ * user's existing profile (if one can be found), or a new profile (if not).
+ * The rules for populating the profile as as follows: </p> <ul> <li>If the
+ * <code>email</code> or <code>password</code> parameter values differ
+ * from those in the existing profile, the passed parameters override the
+ * old values.</li> <li>For new profiles, the user-supplied
+ * <code>fullname</code parameter is always
+ * used; for existing profiles the existing value is used, and whatever
+ * value the user supplied is discarded. The wiki name is automatically
+ * computed by taking the full name and extracting all whitespace.</li>
+ * <li>In all cases, the
+ * created/last modified timestamps of the user's existing or new profile
+ * always override whatever values the user supplied.</li> <li>If
+ * container authentication is used, the login name property of the profile
+ * is set to the name of
+ * {@link com.ecyrd.jspwiki.WikiSession#getLoginPrincipal()}. Otherwise,
+ * the value of the <code>loginname</code> parameter is used.</li> </ul>
+ * @param context the current wiki context
+ * @return a new, populated user profile
+ */
+ public final UserProfile parseProfile( WikiContext context )
+ {
+ // Retrieve the user's profile (may have been previously cached)
+ UserProfile profile = getUserProfile( context.getWikiSession() );
+ HttpServletRequest request = context.getContext().getRequest();
+
+ // Extract values from request stream (cleanse whitespace as needed)
+ String loginName = request.getParameter( PARAM_LOGINNAME );
+ String password = request.getParameter( PARAM_PASSWORD );
+ String fullname = request.getParameter( PARAM_FULLNAME );
+ String email = request.getParameter( PARAM_EMAIL );
+ loginName = InputValidator.isBlank( loginName ) ? null : loginName;
+ password = InputValidator.isBlank( password ) ? null : password;
+ fullname = InputValidator.isBlank( fullname ) ? null : fullname;
+ email = InputValidator.isBlank( email ) ? null : email;
+
+ // A special case if we have container authentication
+ if ( m_engine.getAuthenticationManager().isContainerAuthenticated() )
+ {
+ // If authenticated, login name is always taken from container
+ if ( context.getWikiSession().isAuthenticated() )
+ {
+ loginName = context.getWikiSession().getLoginPrincipal().getName();
+ }
+ }
+
+ // Set the profile fields!
+ profile.setLoginName( loginName );
+ profile.setEmail( email );
+ profile.setFullname( fullname );
+ profile.setPassword( password );
+ return profile;
+ }
+
+ /**
+ * Validates a user profile, and appends any errors to the session errors
+ * list. If the profile is new, the password will be checked to make sure it
+ * isn't null. Otherwise, the password is checked for length and that it
+ * matches the value of the 'password2' HTTP parameter. Note that we have a
+ * special case when container-managed authentication is used and the user
+ * is not authenticated; this will always cause validation to fail. Any
+ * validation errors are added to the wiki session's messages collection
+ * (see {@link WikiSession#getMessages()}.
+ * @param context the current wiki context
+ * @param profile the supplied UserProfile
+ * @deprecated
+ */
+ public final void validateProfile( WikiContext context, UserProfile profile )
+ {
+ boolean isNew = profile.isNew();
+ WikiSession session = context.getWikiSession();
+ InputValidator validator = new InputValidator( SESSION_MESSAGES, session );
+ ResourceBundle rb = context.getBundle( InternationalizationManager.CORE_BUNDLE );
+
+ //
+ // Query the SpamFilter first
+ //
+
+ List ls = m_engine.getFilterManager().getFilterList();
+ for( Iterator i = ls.iterator(); i.hasNext(); )
+ {
+ PageFilter pf = (PageFilter)i.next();
+
+ if( pf instanceof SpamFilter )
+ {
+ if( ((SpamFilter)pf).isValidUserProfile( context, profile ) == false )
+ {
+ session.addMessage( SESSION_MESSAGES, "Invalid userprofile" );
+ return;
+ }
+ break;
+ }
+ }
+
+ // If container-managed auth and user not logged in, throw an error
+ // unless we're allowed to add profiles to the container
+ if ( m_engine.getAuthenticationManager().isContainerAuthenticated()
+ && !context.getWikiSession().isAuthenticated()
+ && !getUserDatabase().isSharedWithContainer() )
+ {
+ session.addMessage( SESSION_MESSAGES, rb.getString("security.error.createprofilebeforelogin") );
+ }
+
+ validator.validateNotNull( profile.getLoginName(), rb.getString("security.user.loginname") );
+ validator.validateNotNull( profile.getFullname(), rb.getString("security.user.fullname") );
+ validator.validate( profile.getEmail(), rb.getString("security.user.email"), InputValidator.EMAIL );
+
+ // If new profile, passwords must match and can't be null
+ if ( !m_engine.getAuthenticationManager().isContainerAuthenticated() )
+ {
+ String password = profile.getPassword();
+ if ( password == null )
+ {
+ if ( isNew )
+ {
+ session.addMessage( SESSION_MESSAGES, rb.getString("security.error.blankpassword") );
+ }
+ }
+ else
+ {
+ HttpServletRequest request = context.getHttpRequest();
+ String password2 = ( request == null ) ? null : request.getParameter( "password2" );
+ if ( !password.equals( password2 ) )
+ {
+ session.addMessage( SESSION_MESSAGES, rb.getString("security.error.passwordnomatch") );
+ }
+ }
+ }
+
+ UserProfile otherProfile;
+ String fullName = profile.getFullname();
+ String loginName = profile.getLoginName();
+
+ // It's illegal to use as a full name someone else's login name
+ try
+ {
+ otherProfile = getUserDatabase().find( fullName );
+ if ( otherProfile != null && !profile.equals( otherProfile ) && !fullName.equals( otherProfile.getFullname() ) )
+ {
+ Object[] args = { fullName };
+ session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalfullname"),
+ args ) );
+ }
+ }
+ catch ( NoSuchPrincipalException e)
+ { /* It's clean */ }
+
+ // It's illegal to use as a login name someone else's full name
+ try
+ {
+ otherProfile = getUserDatabase().find( loginName );
+ if ( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) )
+ {
+ Object[] args = { loginName };
+ session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalloginname"),
+ args ) );
+ }
+ }
+ catch ( NoSuchPrincipalException e)
+ { /* It's clean */ }
+ }
+
+ public Principal[] listWikiNames()
+ throws WikiSecurityException
+ {
+ return getUserDatabase().getWikiNames();
+ }
+
+ /**
+ * This is a database that gets used if nothing else is available. It does
+ * nothing of note - it just mostly thorws NoSuchPrincipalExceptions if
+ * someone tries to log in.
+ * @author Janne Jalkanen
+ */
+ public static class DummyUserDatabase extends AbstractUserDatabase
+ {
+
+ /**
+ * No-op.
+ * @throws WikiSecurityException never...
+ */
+ public void commit() throws WikiSecurityException
+ {
+ // No operation
+ }
+
+ /**
+ * No-op.
+ * @param loginName the login name to delete
+ * @throws WikiSecurityException never...
+ */
+ public void deleteByLoginName( String loginName ) throws WikiSecurityException
+ {
+ // No operation
+ }
+
+ /**
+ * No-op; always throws <code>NoSuchPrincipalException</code>.
+ * @param index the name to search for
+ * @return the user profile
+ * @throws NoSuchPrincipalException never...
+ */
+ public UserProfile findByEmail(String index) throws NoSuchPrincipalException
+ {
+ throw new NoSuchPrincipalException("No user profiles available");
+ }
+
+ /**
+ * No-op; always throws <code>NoSuchPrincipalException</code>.
+ * @param index the name to search for
+ * @return the user profile
+ * @throws NoSuchPrincipalException never...
+ */
+ public UserProfile findByFullName(String index) throws NoSuchPrincipalException
+ {
+ throw new NoSuchPrincipalException("No user profiles available");
+ }
+
+ /**
+ * No-op; always throws <code>NoSuchPrincipalException</code>.
+ * @param index the name to search for
+ * @return the user profile
+ * @throws NoSuchPrincipalException never...
+ */
+ public UserProfile findByLoginName(String index) throws NoSuchPrincipalException
+ {
+ throw new NoSuchPrincipalException("No user profiles available");
+ }
+
+ /**
+ * No-op; always throws <code>NoSuchPrincipalException</code>.
+ * @param index the name to search for
+ * @return the user profile
+ * @throws NoSuchPrincipalException never...
+ */
+ public UserProfile findByWikiName(String index) throws NoSuchPrincipalException
+ {
+ throw new NoSuchPrincipalException("No user profiles available");
+ }
+
+ /**
+ * No-op.
+ * @return a zero-length array
+ * @throws WikiSecurityException never...
+ */
+ public Principal[] getWikiNames() throws WikiSecurityException
+ {
+ return new Principal[0];
+ }
+
+ /**
+ * No-op.
+ * @param engine the wiki engine
+ * @param props the properties used to initialize the wiki engine
+ * @throws NoRequiredPropertyException never...
+ */
+ public void initialize(WikiEngine engine, Properties props) throws NoRequiredPropertyException
+ {
+ }
+
+ /**
+ * No-op.
+ * @return <code>false</code>
+ */
+ public boolean isSharedWithContainer()
+ {
+ return false;
+ }
+
+ /**
+ * No-op; always throws <code>NoSuchPrincipalException</code>.
+ * @param loginName the login name
+ * @param newName the proposed new login name
+ * @throws DuplicateUserException never...
+ * @throws WikiSecurityException never...
+ */
+ public void rename( String loginName, String newName ) throws DuplicateUserException, WikiSecurityException
+ {
+ throw new NoSuchPrincipalException("No user profiles available");
+ }
+
+ /**
+ * No-op.
+ * @param profile the user profile
+ * @throws WikiSecurityException never...
+ */
+ public void save( UserProfile profile ) throws WikiSecurityException
+ {
+ }
+
+ }
+
+ // workflow task inner classes....................................................
+
+ /**
+ * Inner class that handles the actual profile save action. Instances
+ * of this class are assumed to have been added to an approval workflow via
+ * {@link com.ecyrd.jspwiki.workflow.WorkflowBuilder#buildApprovalWorkflow(Principal, String, Task, String, com.ecyrd.jspwiki.workflow.Fact[], Task, String)};
+ * they will not function correctly otherwise.
+ *
+ * @author Andrew Jaquith
+ */
+ public static class SaveUserProfileTask extends Task
+ {
+ private final UserDatabase m_db;
+ private final WikiEngine m_engine;
+
+ /**
+ * Constructs a new Task for saving a user profile.
+ * @param engine the wiki engine
+ */
+ public SaveUserProfileTask( WikiEngine engine )
+ {
+ super( SAVE_TASK_MESSAGE_KEY );
+ m_engine = engine;
+ m_db = engine.getUserManager().getUserDatabase();
+ }
+
+ /**
+ * Saves the user profile to the user database.
+ * @return {@link com.ecyrd.jspwiki.workflow.Outcome#STEP_COMPLETE} if the
+ * task completed successfully
+ * @throws WikiException if the save did not complete for some reason
+ */
+ public Outcome execute() throws WikiException
+ {
+ // Retrieve user profile
+ UserProfile profile = (UserProfile) getWorkflow().getAttribute( SAVED_PROFILE );
+
+ // Save the profile (userdatabase will take care of timestamps for us)
+ m_db.save( profile );
+
+ // Send e-mail if user supplied an e-mail address
+ if ( profile.getEmail() != null )
+ {
+ try
+ {
+ String app = m_engine.getApplicationName();
+ String to = profile.getEmail();
+ String subject = "Welcome to " + app;
+ String content = "Congratulations! Your new profile on "
+ + app + " has been created. Your profile details are as follows: \n\n"
+ + "Login name: " + profile.getLoginName() + "\n"
+ + "Your name : " + profile.getFullname() + "\n"
+ + "E-mail : " + profile.getEmail() + "\n\n"
+ + "If you forget your password, you can reset it at "
+ + m_engine.getBaseURL() + LoginActionBean.class.getAnnotation(UrlBinding.class).value();
+ MailUtil.sendMessage( m_engine, to, subject, content);
+ }
+ catch ( AddressException e)
+ {
+ }
+ catch ( MessagingException e )
+ {
+ log.error( "Could not send registration confirmation e-mail. Is the e-mail server running?" );
+ }
+ }
+
+ return Outcome.STEP_COMPLETE;
+ }
+ }
+
+ // events processing .......................................................
+
+ /**
+ * Registers a WikiEventListener with this instance.
+ * This is a convenience method.
+ * @param listener the event listener
+ */
+ public final synchronized void addWikiEventListener( WikiEventListener listener )
+ {
+ WikiEventManager.addWikiEventListener( this, listener );
+ }
+
+ /**
+ * Un-registers a WikiEventListener with this instance.
+ * This is a convenience method.
+ * @param listener the event listener
+ */
+ public final synchronized void removeWikiEventListener( WikiEventListener listener )
+ {
+ WikiEventManager.removeWikiEventListener( this, listener );
+ }
+
+ /**
+ * Fires a WikiSecurityEvent of the provided type, Principal and target Object
+ * to all registered listeners.
+ *
+ * @see com.ecyrd.jspwiki.event.WikiSecurityEvent
+ * @param type the event type to be fired
+ * @param session the wiki session supporting the event
+ * @param profile the user profile (or array of user profiles), which may be <code>null</code>
+ */
+ protected final void fireEvent( int type, WikiSession session, Object profile )
+ {
+ if ( WikiEventManager.isListening(this) )
+ {
+ WikiEventManager.fireEvent(this,new WikiSecurityEvent(session,type,profile));
+ }
+ }
+
+ /**
+ * Implements the JSON API for usermanager.
+ *
+ * @author Janne Jalkanen
+ */
+ public final class JSONUserModule implements RPCCallable
+ {
+ /**
+ * Directly returns the UserProfile object attached to an uid.
+ *
+ * @param uid The user id (e.g. WikiName)
+ * @return A UserProfile object
+ * @throws NoSuchPrincipalException If such a name does not exist.
+ */
+ public UserProfile getUserInfo( String uid )
+ throws NoSuchPrincipalException
+ {
+ log.info("request "+uid);
+ UserProfile prof = getUserDatabase().find( uid );
+
+ log.info("answer "+prof);
+
+ return prof;
+ }
+ }
+}
\ No newline at end of file