This is an automated email from the ASF dual-hosted git repository. juanpablo pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/jspwiki.git
commit 999c443643847effa3e840532aa93e0bc9bfefde Author: juanpablo <[email protected]> AuthorDate: Mon Feb 24 17:20:24 2020 +0100 JSPWIKI-120: rename + extract interface from GroupManager --- .../wiki/auth/authorize/DefaultGroupManager.java | 398 +++++++++++++ .../apache/wiki/auth/authorize/GroupManager.java | 659 +++------------------ .../src/main/resources/ini/classmappings.xml | 2 +- 3 files changed, 485 insertions(+), 574 deletions(-) diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/authorize/DefaultGroupManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/authorize/DefaultGroupManager.java new file mode 100644 index 0000000..e24ba71 --- /dev/null +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/authorize/DefaultGroupManager.java @@ -0,0 +1,398 @@ +/* + 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.authorize; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.log4j.Logger; +import org.apache.wiki.WikiSession; +import org.apache.wiki.api.core.Engine; +import org.apache.wiki.api.exceptions.NoRequiredPropertyException; +import org.apache.wiki.api.exceptions.WikiException; +import org.apache.wiki.auth.AuthenticationManager; +import org.apache.wiki.auth.Authorizer; +import org.apache.wiki.auth.GroupPrincipal; +import org.apache.wiki.auth.NoSuchPrincipalException; +import org.apache.wiki.auth.UserManager; +import org.apache.wiki.auth.WikiPrincipal; +import org.apache.wiki.auth.WikiSecurityException; +import org.apache.wiki.auth.user.UserProfile; +import org.apache.wiki.event.WikiEvent; +import org.apache.wiki.event.WikiEventListener; +import org.apache.wiki.event.WikiEventManager; +import org.apache.wiki.event.WikiSecurityEvent; +import org.apache.wiki.ui.InputValidator; +import org.apache.wiki.util.ClassUtil; + +import java.security.Principal; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; + + +/** + * <p> + * Facade class for storing, retrieving and managing wiki groups on behalf of AuthorizationManager, JSPs and other presentation-layer + * classes. GroupManager works in collaboration with a back-end {@link GroupDatabase}, which persists groups to permanent storage. + * </p> + * <p> + * <em>Note: prior to JSPWiki 2.4.19, GroupManager was an interface; it is now a concrete, final class. The aspects of GroupManager + * which previously extracted group information from storage (e.g., wiki pages) have been refactored into the GroupDatabase interface.</em> + * </p> + * @since 2.4.19 + */ +public class DefaultGroupManager implements GroupManager, Authorizer, WikiEventListener { + + private static final Logger log = Logger.getLogger( DefaultGroupManager.class ); + + protected Engine m_engine; + + protected WikiEventListener m_groupListener; + + private GroupDatabase m_groupDatabase = null; + + /** Map with GroupPrincipals as keys, and Groups as values */ + private final Map< Principal, Group > m_groups = new HashMap<>(); + + /** {@inheritDoc} */ + @Override + public Principal findRole( final String name ) { + try { + final Group group = getGroup( name ); + return group.getPrincipal(); + } catch( final NoSuchPrincipalException e ) { + return null; + } + } + + /** {@inheritDoc} */ + @Override + public Group getGroup( final String name ) throws NoSuchPrincipalException { + final Group group = m_groups.get( new GroupPrincipal( name ) ); + if( group != null ) { + return group; + } + throw new NoSuchPrincipalException( "Group " + name + " not found." ); + } + + /** {@inheritDoc} */ + @Override + public GroupDatabase getGroupDatabase() throws WikiSecurityException { + if( m_groupDatabase != null ) { + return m_groupDatabase; + } + + String dbClassName = "<unknown>"; + String dbInstantiationError = null; + Throwable cause = null; + try { + final Properties props = m_engine.getWikiProperties(); + dbClassName = props.getProperty( PROP_GROUPDATABASE ); + if( dbClassName == null ) { + dbClassName = XMLGroupDatabase.class.getName(); + } + log.info( "Attempting to load group database class " + dbClassName ); + final Class< ? > dbClass = ClassUtil.findClass( "org.apache.wiki.auth.authorize", dbClassName ); + m_groupDatabase = ( GroupDatabase )dbClass.newInstance(); + m_groupDatabase.initialize( m_engine, m_engine.getWikiProperties() ); + log.info( "Group database initialized." ); + } catch( final ClassNotFoundException e ) { + log.error( "GroupDatabase class " + dbClassName + " cannot be found.", e ); + dbInstantiationError = "Failed to locate GroupDatabase class " + dbClassName; + cause = e; + } catch( final InstantiationException e ) { + log.error( "GroupDatabase class " + dbClassName + " cannot be created.", e ); + dbInstantiationError = "Failed to create GroupDatabase class " + dbClassName; + cause = e; + } catch( final IllegalAccessException e ) { + log.error( "You are not allowed to access group database class " + dbClassName + ".", e ); + dbInstantiationError = "Access GroupDatabase class " + dbClassName + " denied"; + cause = e; + } catch( final NoRequiredPropertyException e ) { + log.error( "Missing property: " + e.getMessage() + "." ); + dbInstantiationError = "Missing property: " + e.getMessage(); + cause = e; + } + + if( dbInstantiationError != null ) { + throw new WikiSecurityException( dbInstantiationError + " Cause: " + cause.getMessage(), cause ); + } + + return m_groupDatabase; + } + + /** {@inheritDoc} */ + @Override + public Principal[] getRoles() { + return m_groups.keySet().toArray( new Principal[ m_groups.size() ] ); + } + + /** {@inheritDoc} */ + @Override + public void initialize( final Engine engine, final Properties props ) throws WikiSecurityException { + m_engine = engine; + + try { + m_groupDatabase = getGroupDatabase(); + } catch( final WikiException e ) { + throw new WikiSecurityException( e.getMessage(), e ); + } + + // Load all groups from the database into the cache + final Group[] groups = m_groupDatabase.groups(); + synchronized( m_groups ) { + for( final Group group : groups ) { + // Add new group to cache; fire GROUP_ADD event + m_groups.put( group.getPrincipal(), group ); + fireEvent( WikiSecurityEvent.GROUP_ADD, group ); + } + } + + // Make the GroupManager listen for WikiEvents (WikiSecurityEvents for changed user profiles) + engine.getManager( UserManager.class ).addWikiEventListener( this ); + + // Success! + log.info( "Authorizer GroupManager initialized successfully; loaded " + groups.length + " group(s)." ); + } + + /** {@inheritDoc} */ + @Override + public boolean isUserInRole( final WikiSession session, final Principal role ) { + // Always return false if session/role is null, or if role isn't a GroupPrincipal + if ( session == null || !( role instanceof GroupPrincipal ) || !session.isAuthenticated() ) { + return false; + } + + // Get the group we're examining + final Group group = m_groups.get( role ); + if( group == null ) { + return false; + } + + // Check each user principal to see if it belongs to the group + for( final Principal principal : session.getPrincipals() ) { + if( AuthenticationManager.isUserPrincipal( principal ) && group.isMember( principal ) ) { + return true; + } + } + return false; + } + + /** {@inheritDoc} */ + @Override + public Group parseGroup( String name, String memberLine, final boolean create ) throws WikiSecurityException { + // If null name parameter, it's because someone's creating a new group + if( name == null ) { + if( create ) { + name = "MyGroup"; + } else { + throw new WikiSecurityException( "Group name cannot be blank." ); + } + } else if( ArrayUtils.contains( Group.RESTRICTED_GROUPNAMES, name ) ) { + // Certain names are forbidden + throw new WikiSecurityException( "Illegal group name: " + name ); + } + name = name.trim(); + + // Normalize the member line + if( InputValidator.isBlank( memberLine ) ) { + memberLine = ""; + } + memberLine = memberLine.trim(); + + // Create or retrieve the group (may have been previously cached) + final Group group = new Group( name, m_engine.getApplicationName() ); + try { + final Group existingGroup = getGroup( name ); + + // If existing, clone it + group.setCreator( existingGroup.getCreator() ); + group.setCreated( existingGroup.getCreated() ); + group.setModifier( existingGroup.getModifier() ); + group.setLastModified( existingGroup.getLastModified() ); + for( final Principal existingMember : existingGroup.members() ) { + group.add( existingMember ); + } + } catch( final NoSuchPrincipalException e ) { + // It's a new group.... throw error if we don't create new ones + if( !create ) { + throw new NoSuchPrincipalException( "Group '" + name + "' does not exist." ); + } + } + + // If passed members not empty, overwrite + final String[] members = extractMembers( memberLine ); + if( members.length > 0 ) { + group.clear(); + for( final String member : members ) { + group.add( new WikiPrincipal( member ) ); + } + } + + return group; + } + + /** {@inheritDoc} */ + @Override + public void removeGroup( final String index ) throws WikiSecurityException { + if( index == null ) { + throw new IllegalArgumentException( "Group cannot be null." ); + } + + final Group group = m_groups.get( new GroupPrincipal( index ) ); + if( group == null ) { + throw new NoSuchPrincipalException( "Group " + index + " not found" ); + } + + // Delete the group + // TODO: need rollback procedure + synchronized( m_groups ) { + m_groups.remove( group.getPrincipal() ); + } + m_groupDatabase.delete( group ); + fireEvent( WikiSecurityEvent.GROUP_REMOVE, group ); + } + + /** {@inheritDoc} */ + @Override + public void setGroup( final WikiSession session, final Group group ) throws WikiSecurityException { + // TODO: check for appropriate permissions + + // If group already exists, delete it; fire GROUP_REMOVE event + final Group oldGroup = m_groups.get( group.getPrincipal() ); + if( oldGroup != null ) { + fireEvent( WikiSecurityEvent.GROUP_REMOVE, oldGroup ); + synchronized( m_groups ) { + m_groups.remove( oldGroup.getPrincipal() ); + } + } + + // Copy existing modifier info & timestamps + if( oldGroup != null ) { + group.setCreator( oldGroup.getCreator() ); + group.setCreated( oldGroup.getCreated() ); + group.setModifier( oldGroup.getModifier() ); + group.setLastModified( oldGroup.getLastModified() ); + } + + // Add new group to cache; announce GROUP_ADD event + synchronized( m_groups ) { + m_groups.put( group.getPrincipal(), group ); + } + fireEvent( WikiSecurityEvent.GROUP_ADD, group ); + + // Save the group to back-end database; if it fails, roll back to previous state. Note that the back-end + // MUST timestammp the create/modify fields in the Group. + try { + m_groupDatabase.save( group, session.getUserPrincipal() ); + } + + // We got an exception! Roll back... + catch( final WikiSecurityException e ) { + if( oldGroup != null ) { + // Restore previous version, re-throw... + fireEvent( WikiSecurityEvent.GROUP_REMOVE, group ); + fireEvent( WikiSecurityEvent.GROUP_ADD, oldGroup ); + synchronized( m_groups ) { + m_groups.put( oldGroup.getPrincipal(), oldGroup ); + } + throw new WikiSecurityException( e.getMessage() + " (rolled back to previous version).", e ); + } + // Re-throw security exception + throw new WikiSecurityException( e.getMessage(), e ); + } + } + + /** + * Extracts carriage-return separated members into a Set of String objects. + * + * @param memberLine the list of members + * @return the list of members + */ + protected String[] extractMembers( final String memberLine ) { + final Set< String > members = new HashSet<>(); + if( memberLine != null ) { + final StringTokenizer tok = new StringTokenizer( memberLine, "\n" ); + while( tok.hasMoreTokens() ) { + final String uid = tok.nextToken().trim(); + if( uid.length() > 0 ) { + members.add( uid ); + } + } + } + return members.toArray( new String[ members.size() ] ); + } + + // events processing ....................................................... + + /** {@inheritDoc} */ + @Override + public synchronized void addWikiEventListener( final WikiEventListener listener ) { + WikiEventManager.addWikiEventListener( this, listener ); + } + + /** {@inheritDoc} */ + @Override + public synchronized void removeWikiEventListener( final WikiEventListener listener ) { + WikiEventManager.removeWikiEventListener( this, listener ); + } + + /** {@inheritDoc} */ + @Override + public void actionPerformed( final WikiEvent event ) { + if( !( event instanceof WikiSecurityEvent ) ) { + return; + } + + final WikiSecurityEvent se = ( WikiSecurityEvent )event; + if( se.getType() == WikiSecurityEvent.PROFILE_NAME_CHANGED ) { + final WikiSession session = se.getSrc(); + final UserProfile[] profiles = ( UserProfile[] )se.getTarget(); + final Principal[] oldPrincipals = new Principal[] { new WikiPrincipal( profiles[ 0 ].getLoginName() ), + new WikiPrincipal( profiles[ 0 ].getFullname() ), new WikiPrincipal( profiles[ 0 ].getWikiName() ) }; + final Principal newPrincipal = new WikiPrincipal( profiles[ 1 ].getFullname() ); + + // Examine each group + int groupsChanged = 0; + try { + for( final Group group : m_groupDatabase.groups() ) { + boolean groupChanged = false; + for( final Principal oldPrincipal : oldPrincipals ) { + if( group.isMember( oldPrincipal ) ) { + group.remove( oldPrincipal ); + group.add( newPrincipal ); + groupChanged = true; + } + } + if( groupChanged ) { + setGroup( session, group ); + groupsChanged++; + } + } + } catch( final WikiException e ) { + // Oooo! This is really bad... + log.error( "Could not change user name in Group lists because of GroupDatabase error:" + e.getMessage() ); + } + log.info( "Profile name change for '" + newPrincipal.toString() + "' caused " + groupsChanged + " groups to change also." ); + } + } + +} diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/authorize/GroupManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/authorize/GroupManager.java index 37e3c33..7c38ac5 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/authorize/GroupManager.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/authorize/GroupManager.java @@ -19,400 +19,103 @@ package org.apache.wiki.auth.authorize; import org.apache.commons.lang3.ArrayUtils; -import org.apache.log4j.Logger; import org.apache.wiki.WikiContext; import org.apache.wiki.WikiSession; -import org.apache.wiki.api.core.Engine; -import org.apache.wiki.api.exceptions.NoRequiredPropertyException; -import org.apache.wiki.api.exceptions.WikiException; -import org.apache.wiki.auth.AuthenticationManager; import org.apache.wiki.auth.Authorizer; -import org.apache.wiki.auth.GroupPrincipal; import org.apache.wiki.auth.NoSuchPrincipalException; -import org.apache.wiki.auth.UserManager; -import org.apache.wiki.auth.WikiPrincipal; import org.apache.wiki.auth.WikiSecurityException; -import org.apache.wiki.auth.user.UserProfile; -import org.apache.wiki.event.WikiEvent; import org.apache.wiki.event.WikiEventListener; import org.apache.wiki.event.WikiEventManager; import org.apache.wiki.event.WikiSecurityEvent; import org.apache.wiki.ui.InputValidator; -import org.apache.wiki.util.ClassUtil; import javax.servlet.http.HttpServletRequest; import java.security.Principal; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.StringTokenizer; /** * <p> - * Facade class for storing, retrieving and managing wiki groups on behalf of - * AuthorizationManager, JSPs and other presentation-layer classes. GroupManager - * works in collaboration with a back-end {@link GroupDatabase}, which persists - * groups to permanent storage. + * Facade class for storing, retrieving and managing wiki groups on behalf of AuthorizationManager, JSPs and other presentation-layer + * classes. GroupManager works in collaboration with a back-end {@link GroupDatabase}, which persists groups to permanent storage. * </p> * <p> - * <em>Note: prior to JSPWiki 2.4.19, GroupManager was an interface; it - * is now a concrete, final class. The aspects of GroupManager which previously - * extracted group information from storage (e.g., wiki pages) have been - * refactored into the GroupDatabase interface.</em> + * <em>Note: prior to JSPWiki 2.4.19, GroupManager was an interface; it is now a concrete, final class. The aspects of GroupManager + * which previously extracted group information from storage (e.g., wiki pages) have been refactored into the GroupDatabase interface.</em> * </p> * @since 2.4.19 */ -public class GroupManager implements Authorizer, WikiEventListener { +public interface GroupManager extends Authorizer, WikiEventListener { /** Key used for adding UI messages to a user's WikiSession. */ - public static final String MESSAGES_KEY = "group"; + String MESSAGES_KEY = "group"; - private static final String PROP_GROUPDATABASE = "jspwiki.groupdatabase"; - - private static final Logger log = Logger.getLogger( GroupManager.class ); - - protected Engine m_engine; - - protected WikiEventListener m_groupListener; - - private GroupDatabase m_groupDatabase = null; - - /** Map with GroupPrincipals as keys, and Groups as values */ - private final Map< Principal, Group > m_groups = new HashMap<>(); - - /** - * <p> - * Returns a GroupPrincipal matching a given name. If a group cannot be found, return <code>null</code>. - * </p> - * @param name Name of the group. This is case-sensitive. - * @return A DefaultGroup instance. - */ - @Override public Principal findRole( final String name ) { - try { - final Group group = getGroup( name ); - return group.getPrincipal(); - } catch( final NoSuchPrincipalException e ) { - return null; - } - } + String PROP_GROUPDATABASE = "jspwiki.groupdatabase"; /** - * Returns the Group matching a given name. If the group cannot be found, - * this method throws a <code>NoSuchPrincipalException</code>. + * Returns the Group matching a given name. If the group cannot be found, this method throws a <code>NoSuchPrincipalException</code>. + * * @param name the name of the group to find * @return the group * @throws NoSuchPrincipalException if the group cannot be found */ - public Group getGroup( final String name ) throws NoSuchPrincipalException - { - final Group group = m_groups.get( new GroupPrincipal( name ) ); - if ( group != null ) - { - return group; - } - throw new NoSuchPrincipalException( "Group " + name + " not found." ); - } + Group getGroup( final String name ) throws NoSuchPrincipalException; /** - * Returns the current external {@link GroupDatabase} in use. This method - * is guaranteed to return a properly-initialized GroupDatabase, unless - * it could not be initialized. In that case, this method throws - * a {@link org.apache.wiki.api.exceptions.WikiException}. The GroupDatabase - * is lazily initialized. - * @throws org.apache.wiki.auth.WikiSecurityException if the GroupDatabase could - * not be initialized + * Returns the current external {@link GroupDatabase} in use. This method is guaranteed to return a properly-initialized GroupDatabase, + * unless it could not be initialized. In that case, this method throws a {@link org.apache.wiki.api.exceptions.WikiException}. The + * GroupDatabase is lazily initialized. + * + * @throws org.apache.wiki.auth.WikiSecurityException if the GroupDatabase could not be initialized * @return the current GroupDatabase * @since 2.3 */ - public GroupDatabase getGroupDatabase() throws WikiSecurityException - { - if ( m_groupDatabase != null ) - { - return m_groupDatabase; - } - - String dbClassName = "<unknown>"; - String dbInstantiationError = null; - Throwable cause = null; - try - { - final Properties props = m_engine.getWikiProperties(); - dbClassName = props.getProperty( PROP_GROUPDATABASE ); - if ( dbClassName == null ) - { - dbClassName = XMLGroupDatabase.class.getName(); - } - log.info( "Attempting to load group database class " + dbClassName ); - final Class<?> dbClass = ClassUtil.findClass( "org.apache.wiki.auth.authorize", dbClassName ); - m_groupDatabase = (GroupDatabase) dbClass.newInstance(); - m_groupDatabase.initialize( m_engine, m_engine.getWikiProperties() ); - log.info( "Group database initialized." ); - } - catch( final ClassNotFoundException e ) - { - log.error( "GroupDatabase class " + dbClassName + " cannot be found.", e ); - dbInstantiationError = "Failed to locate GroupDatabase class " + dbClassName; - cause = e; - } - catch( final InstantiationException e ) - { - log.error( "GroupDatabase class " + dbClassName + " cannot be created.", e ); - dbInstantiationError = "Failed to create GroupDatabase class " + dbClassName; - cause = e; - } - catch( final IllegalAccessException e ) - { - log.error( "You are not allowed to access group database class " + dbClassName + ".", e ); - dbInstantiationError = "Access GroupDatabase class " + dbClassName + " denied"; - cause = e; - } - catch( final NoRequiredPropertyException e ) - { - log.error( "Missing property: " + e.getMessage() + "." ); - dbInstantiationError = "Missing property: " + e.getMessage(); - cause = e; - } - - if( dbInstantiationError != null ) - { - throw new WikiSecurityException( dbInstantiationError + " Cause: " + (cause != null ? cause.getMessage() : ""), cause ); - } - - return m_groupDatabase; - } - - /** - * Returns an array of GroupPrincipals this GroupManager knows about. This - * method will return an array of GroupPrincipal objects corresponding to - * the wiki groups managed by this class. This method actually returns a - * defensive copy of an internally stored hashmap. - * @return an array of Principals representing the roles - */ - @Override public Principal[] getRoles() - { - return m_groups.keySet().toArray( new Principal[m_groups.size()] ); - } - - /** - * Initializes the group cache by initializing the group database and - * obtaining a list of all of the groups it stores. - * @param engine the wiki engine - * @param props the properties used to initialize the wiki engine - * @see GroupDatabase#initialize(org.apache.wiki.api.core.Engine, java.util.Properties) - * @see GroupDatabase#groups() - * @throws WikiSecurityException if GroupManager cannot be initialized - */ - @Override public void initialize( final Engine engine, final Properties props ) throws WikiSecurityException - { - m_engine = engine; - - try - { - m_groupDatabase = getGroupDatabase(); - } - catch ( final WikiException e ) - { - throw new WikiSecurityException( e.getMessage(), e ); - } - - // Load all groups from the database into the cache - final Group[] groups = m_groupDatabase.groups(); - synchronized( m_groups ) - { - for( final Group group : groups ) - { - // Add new group to cache; fire GROUP_ADD event - m_groups.put( group.getPrincipal(), group ); - fireEvent( WikiSecurityEvent.GROUP_ADD, group ); - } - } - - // Make the GroupManager listen for WikiEvents (WikiSecurityEvents for changed user profiles) - engine.getManager( UserManager.class ).addWikiEventListener( this ); - - // Success! - log.info( "Authorizer GroupManager initialized successfully; loaded " + groups.length + " group(s)." ); - - } + GroupDatabase getGroupDatabase() throws WikiSecurityException; /** * <p> - * Determines whether the Subject associated with a WikiSession is in a - * particular role. This method takes two parameters: the WikiSession - * containing the subject and the desired role ( which may be a Role or a - * Group). If either parameter is <code>null</code>, or if the user is - * not authenticated, this method returns <code>false</code>. - * </p> - * <p> - * With respect to this implementation, the supplied Principal must be a - * GroupPrincipal. The Subject posesses the "role" if it the session is - * authenticated <em>and</em> a Subject's principal is a member of the - * corresponding Group. This method simply finds the Group in question, then - * delegates to {@link Group#isMember(Principal)} for each of the principals - * in the Subject's principal set. + * Extracts group name and members from passed parameters and populates an existing Group with them. The Group will either be a copy of + * an existing Group (if one can be found), or a new, unregistered Group (if not). Optionally, this method can throw a + * WikiSecurityException if the Group does not yet exist in the GroupManager cache. * </p> - * @param session the current WikiSession - * @param role the role to check - * @return <code>true</code> if the user is considered to be in the role, - * <code>false</code> otherwise - */ - @Override public boolean isUserInRole( final WikiSession session, final Principal role ) - { - // Always return false if session/role is null, or if - // role isn't a GroupPrincipal - if ( session == null || role == null || !( role instanceof GroupPrincipal ) || !session.isAuthenticated() ) - { - return false; - } - - // Get the group we're examining - final Group group = m_groups.get( role ); - if ( group == null ) - { - return false; - } - - // Check each user principal to see if it belongs to the group - for ( final Principal principal : session.getPrincipals() ) - { - if ( AuthenticationManager.isUserPrincipal( principal ) && group.isMember( principal ) ) - { - return true; - } - } - return false; - } - - /** * <p> - * Extracts group name and members from passed parameters and populates an - * existing Group with them. The Group will either be a copy of an existing - * Group (if one can be found), or a new, unregistered Group (if not). - * Optionally, this method can throw a WikiSecurityException if the Group - * does not yet exist in the GroupManager cache. + * The <code>group</code> parameter in the HTTP request contains the Group name to look up and populate. The <code>members</code> + * parameter contains the member list. If these differ from those in the existing group, the passed values override the old values. * </p> * <p> - * The <code>group</code> parameter in the HTTP request contains the Group - * name to look up and populate. The <code>members</code> parameter - * contains the member list. If these differ from those in the existing - * group, the passed values override the old values. - * </p> - * <p> - * This method does not commit the new Group to the GroupManager cache. To - * do that, use {@link #setGroup(WikiSession, Group)}. + * This method does not commit the new Group to the GroupManager cache. To do that, use {@link #setGroup(WikiSession, Group)}. * </p> * @param name the name of the group to construct * @param memberLine the line of text containing the group membership list - * @param create whether this method should create a new, empty Group if one - * with the requested name is not found. If <code>false</code>, - * groups that do not exist will cause a - * <code>NoSuchPrincipalException</code> to be thrown + * @param create whether this method should create a new, empty Group if one with the requested name is not found. If <code>false</code>, + * groups that do not exist will cause a <code>NoSuchPrincipalException</code> to be thrown * @return a new, populated group * @see org.apache.wiki.auth.authorize.Group#RESTRICTED_GROUPNAMES - * @throws WikiSecurityException if the group name isn't allowed, or if - * <code>create</code> is <code>false</code> - * and the Group named <code>name</code> does not exist + * @throws WikiSecurityException if the group name isn't allowed, or if <code>create</code> is <code>false</code> + * and the Group named <code>name</code> does not exist */ - public Group parseGroup( String name, String memberLine, final boolean create ) throws WikiSecurityException - { - // If null name parameter, it's because someone's creating a new group - if ( name == null ) - { - if ( create ) - { - name = "MyGroup"; - } - else - { - throw new WikiSecurityException( "Group name cannot be blank." ); - } - } - else if ( ArrayUtils.contains( Group.RESTRICTED_GROUPNAMES, name ) ) - { - // Certain names are forbidden - throw new WikiSecurityException( "Illegal group name: " + name ); - } - name = name.trim(); - - // Normalize the member line - if ( InputValidator.isBlank( memberLine ) ) - { - memberLine = ""; - } - memberLine = memberLine.trim(); - - // Create or retrieve the group (may have been previously cached) - final Group group = new Group( name, m_engine.getApplicationName() ); - try - { - final Group existingGroup = getGroup( name ); - - // If existing, clone it - group.setCreator( existingGroup.getCreator() ); - group.setCreated( existingGroup.getCreated() ); - group.setModifier( existingGroup.getModifier() ); - group.setLastModified( existingGroup.getLastModified() ); - for( final Principal existingMember : existingGroup.members() ) - { - group.add( existingMember ); - } - } - catch( final NoSuchPrincipalException e ) - { - // It's a new group.... throw error if we don't create new ones - if ( !create ) - { - throw new NoSuchPrincipalException( "Group '" + name + "' does not exist." ); - } - } - - // If passed members not empty, overwrite - final String[] members = extractMembers( memberLine ); - if ( members.length > 0 ) - { - group.clear(); - for( final String member : members ) - { - group.add( new WikiPrincipal( member ) ); - } - } - - return group; - } + Group parseGroup( String name, String memberLine, boolean create ) throws WikiSecurityException; /** * <p> - * Extracts group name and members from the HTTP request and populates an - * existing Group with them. The Group will either be a copy of an existing - * Group (if one can be found), or a new, unregistered Group (if not). - * Optionally, this method can throw a WikiSecurityException if the Group - * does not yet exist in the GroupManager cache. + * Extracts group name and members from the HTTP request and populates an existing Group with them. The Group will either be a copy of + * an existing Group (if one can be found), or a new, unregistered Group (if not). Optionally, this method can throw a + * WikiSecurityException if the Group does not yet exist in the GroupManager cache. * </p> * <p> - * The <code>group</code> parameter in the HTTP request contains the Group - * name to look up and populate. The <code>members</code> parameter - * contains the member list. If these differ from those in the existing - * group, the passed values override the old values. + * The <code>group</code> parameter in the HTTP request contains the Group name to look up and populate. The <code>members</code> + * parameter contains the member list. If these differ from those in the existing group, the passed values override the old values. * </p> * <p> - * This method does not commit the new Group to the GroupManager cache. To - * do that, use {@link #setGroup(WikiSession, Group)}. + * This method does not commit the new Group to the GroupManager cache. To do that, use {@link #setGroup(WikiSession, Group)}. * </p> * @param context the current wiki context - * @param create whether this method should create a new, empty Group if one - * with the requested name is not found. If <code>false</code>, - * groups that do not exist will cause a - * <code>NoSuchPrincipalException</code> to be thrown + * @param create whether this method should create a new, empty Group if one with the requested name is not found. If <code>false</code>, + * groups that do not exist will cause a <code>NoSuchPrincipalException</code> to be thrown * @return a new, populated group - * @throws WikiSecurityException if the group name isn't allowed, or if - * <code>create</code> is <code>false</code> - * and the Group does not exist + * @throws WikiSecurityException if the group name isn't allowed, or if <code>create</code> is <code>false</code> + * and the Group does not exist */ - public Group parseGroup( final WikiContext context, final boolean create ) throws WikiSecurityException - { + default Group parseGroup( final WikiContext context, final boolean create ) throws WikiSecurityException { // Extract parameters final HttpServletRequest request = context.getHttpRequest(); final String name = request.getParameter( "group" ); @@ -423,8 +126,7 @@ public class GroupManager implements Authorizer, WikiEventListener { final Group group = parseGroup( name, memberLine, create ); // If no members, add the current user by default - if ( group.members().length == 0 ) - { + if( group.members().length == 0 ) { group.add( context.getWikiSession().getUserPrincipal() ); } @@ -432,310 +134,121 @@ public class GroupManager implements Authorizer, WikiEventListener { } /** - * Removes a named Group from the group database. If not found, throws a - * <code>NoSuchPrincipalException</code>. After removal, this method will - * commit the delete to the back-end group database. It will also fire a - * {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_REMOVE} event with - * the GroupManager instance as the source and the Group as target. - * If <code>index</code> is <code>null</code>, this method throws - * an {@link IllegalArgumentException}. + * Removes a named Group from the group database. If not found, throws a <code>NoSuchPrincipalException</code>. After removal, this + * method will commit the delete to the back-end group database. It will also fire a + * {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_REMOVE} event with the GroupManager instance as the source and the Group as target. + * If <code>index</code> is <code>null</code>, this method throws an {@link IllegalArgumentException}. + * * @param index the group to remove - * @throws WikiSecurityException if the Group cannot be removed by - * the back-end + * @throws WikiSecurityException if the Group cannot be removed by the back-end * @see org.apache.wiki.auth.authorize.GroupDatabase#delete(Group) */ - public void removeGroup( final String index ) throws WikiSecurityException - { - if ( index == null ) - { - throw new IllegalArgumentException( "Group cannot be null." ); - } - - final Group group = m_groups.get( new GroupPrincipal( index ) ); - if ( group == null ) - { - throw new NoSuchPrincipalException( "Group " + index + " not found" ); - } - - // Delete the group - // TODO: need rollback procedure - synchronized( m_groups ) - { - m_groups.remove( group.getPrincipal() ); - } - m_groupDatabase.delete( group ); - fireEvent( WikiSecurityEvent.GROUP_REMOVE, group ); - } + void removeGroup( final String index ) throws WikiSecurityException; /** * <p> - * Saves the {@link Group} created by a user in a wiki session. This method - * registers the Group with the GroupManager and saves it to the back-end - * database. If an existing Group with the same name already exists, the new - * group will overwrite it. After saving the Group, the group database - * changes are committed. + * Saves the {@link Group} created by a user in a wiki session. This method registers the Group with the GroupManager and saves it to + * the back-end database. If an existing Group with the same name already exists, the new group will overwrite it. After saving the + * Group, the group database changes are committed. * </p> * <p> * This method fires the following events: * </p> * <ul> - * <li><strong>When creating a new Group</strong>, this method fires a - * {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_ADD} with the - * GroupManager instance as its source and the new Group as the target.</li> - * <li><strong>When overwriting an existing Group</strong>, this method - * fires a new {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_REMOVE} - * with this GroupManager instance as the source, and the new Group as the - * target. It then fires a - * {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_ADD} event with the - * same source and target.</li> + * <li><strong>When creating a new Group</strong>, this method fires a {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_ADD} with + * the GroupManager instance as its source and the new Group as the target.</li> + * <li><strong>When overwriting an existing Group</strong>, this method fires a new + * {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_REMOVE} with this GroupManager instance as the source, and the new Group as the + * target. It then fires a {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_ADD} event with the same source and target.</li> * </ul> * <p> - * In addition, if the save or commit actions fail, this method will attempt - * to restore the older version of the wiki group if it exists. This will - * result in a <code>GROUP_REMOVE</code> event (for the new version of the - * Group) followed by a <code>GROUP_ADD</code> event (to indicate - * restoration of the old version). + * In addition, if the save or commit actions fail, this method will attempt to restore the older version of the wiki group if it + * exists. This will result in a <code>GROUP_REMOVE</code> event (for the new version of the Group) followed by a <code>GROUP_ADD</code> + * event (to indicate restoration of the old version). * </p> * <p> - * This method will register the new Group with the GroupManager. For example, - * {@link org.apache.wiki.auth.AuthenticationManager} attaches each - * WikiSession as a GroupManager listener. Thus, the act of registering a - * Group with <code>setGroup</code> means that all WikiSessions will - * automatically receive group add/change/delete events immediately. + * This method will register the new Group with the GroupManager. For example, {@link org.apache.wiki.auth.AuthenticationManager} + * attaches each WikiSession as a GroupManager listener. Thus, the act of registering a Group with <code>setGroup</code> means that + * all WikiSessions will automatically receive group add/change/delete events immediately. * </p> + * * @param session the wiki session, which may not be <code>null</code> * @param group the Group, which may not be <code>null</code> * @throws WikiSecurityException if the Group cannot be saved by the back-end */ - public void setGroup( final WikiSession session, final Group group ) throws WikiSecurityException - { - // TODO: check for appropriate permissions - - // If group already exists, delete it; fire GROUP_REMOVE event - final Group oldGroup = m_groups.get( group.getPrincipal() ); - if ( oldGroup != null ) - { - fireEvent( WikiSecurityEvent.GROUP_REMOVE, oldGroup ); - synchronized( m_groups ) - { - m_groups.remove( oldGroup.getPrincipal() ); - } - } - - // Copy existing modifier info & timestamps - if ( oldGroup != null ) - { - group.setCreator( oldGroup.getCreator() ); - group.setCreated( oldGroup.getCreated() ); - group.setModifier( oldGroup.getModifier() ); - group.setLastModified( oldGroup.getLastModified() ); - } - - // Add new group to cache; announce GROUP_ADD event - synchronized( m_groups ) - { - m_groups.put( group.getPrincipal(), group ); - } - fireEvent( WikiSecurityEvent.GROUP_ADD, group ); - - // Save the group to back-end database; if it fails, - // roll back to previous state. Note that the back-end - // MUST timestammp the create/modify fields in the Group. - try - { - m_groupDatabase.save( group, session.getUserPrincipal() ); - } - - // We got an exception! Roll back... - catch( final WikiSecurityException e ) - { - if ( oldGroup != null ) - { - // Restore previous version, re-throw... - fireEvent( WikiSecurityEvent.GROUP_REMOVE, group ); - fireEvent( WikiSecurityEvent.GROUP_ADD, oldGroup ); - synchronized( m_groups ) - { - m_groups.put( oldGroup.getPrincipal(), oldGroup ); - } - throw new WikiSecurityException( e.getMessage() + " (rolled back to previous version).", e ); - } - // Re-throw security exception - throw new WikiSecurityException( e.getMessage(), e ); - } - } + void setGroup( final WikiSession session, final Group group ) throws WikiSecurityException; /** - * Validates a Group, and appends any errors to the session errors list. Any - * validation errors are added to the wiki session's messages collection - * (see {@link WikiSession#getMessages()}. + * Validates a Group, and appends any errors to the session errors list. Any validation errors are added to the wiki session's messages + * collection (see {@link WikiSession#getMessages()}. + * * @param context the current wiki context * @param group the supplied Group */ - public void validateGroup( final WikiContext context, final Group group ) - { + default void validateGroup( final WikiContext context, final Group group ) { final InputValidator validator = new InputValidator( MESSAGES_KEY, context ); // Name cannot be null or one of the restricted names - try - { + try { checkGroupName( context, group.getName() ); - } - catch( final WikiSecurityException e ) - { - + } catch( final WikiSecurityException e ) { } // Member names must be "safe" strings final Principal[] members = group.members(); - for( int i = 0; i < members.length; i++ ) - { - validator.validateNotNull( members[i].getName(), "Full name", InputValidator.ID ); + for( final Principal member : members ) { + validator.validateNotNull( member.getName(), "Full name", InputValidator.ID ); } } - /** - * Extracts carriage-return separated members into a Set of String objects. - * @param memberLine the list of members - * @return the list of members - */ - protected String[] extractMembers( final String memberLine ) - { - final Set<String> members = new HashSet<>(); - if ( memberLine != null ) - { - final StringTokenizer tok = new StringTokenizer( memberLine, "\n" ); - while( tok.hasMoreTokens() ) - { - final String uid = tok.nextToken().trim(); - if ( uid != null && uid.length() > 0 ) - { - members.add( uid ); - } - } - } - return members.toArray( new String[members.size()] ); - } /** - * Checks if a String is blank or a restricted Group name, and if it is, - * appends an error to the WikiSession's message list. + * Checks if a String is blank or a restricted Group name, and if it is, appends an error to the WikiSession's message list. + * * @param context the wiki context * @param name the Group name to test - * @throws WikiSecurityException if <code>session</code> is - * <code>null</code> or the Group name is illegal + * @throws WikiSecurityException if <code>session</code> is <code>null</code> or the Group name is illegal * @see Group#RESTRICTED_GROUPNAMES */ - protected void checkGroupName( final WikiContext context, final String name ) throws WikiSecurityException - { - //TODO: groups cannot have the same name as a user + default void checkGroupName( final WikiContext context, final String name ) throws WikiSecurityException { + // TODO: groups cannot have the same name as a user // Name cannot be null final InputValidator validator = new InputValidator( MESSAGES_KEY, context ); validator.validateNotNull( name, "Group name" ); // Name cannot be one of the restricted names either - if( ArrayUtils.contains( Group.RESTRICTED_GROUPNAMES, name ) ) - { + if( ArrayUtils.contains( Group.RESTRICTED_GROUPNAMES, name ) ) { throw new WikiSecurityException( "The group name '" + name + "' is illegal. Choose another." ); } } - // events processing ....................................................... /** - * Registers a WikiEventListener with this instance. - * This is a convenience method. + * Registers a WikiEventListener with this instance. This is a convenience method. + * * @param listener the event listener */ - public synchronized void addWikiEventListener( final WikiEventListener listener ) - { - WikiEventManager.addWikiEventListener( this, listener ); - } + void addWikiEventListener( WikiEventListener listener ); /** - * Un-registers a WikiEventListener with this instance. - * This is a convenience method. + * Un-registers a WikiEventListener with this instance. This is a convenience method. + * * @param listener the event listener */ - public synchronized void removeWikiEventListener( final WikiEventListener listener ) - { - WikiEventManager.removeWikiEventListener( this, listener ); - } + void removeWikiEventListener( WikiEventListener listener ); /** - * Fires a WikiSecurityEvent of the provided type, Principal and target Object - * to all registered listeners. + * Fires a WikiSecurityEvent of the provided type, Principal and target Object to all registered listeners. * * @see org.apache.wiki.event.WikiSecurityEvent * @param type the event type to be fired * @param target the changed Object, which may be <code>null</code> */ - protected void fireEvent( final int type, final Object target ) - { - if ( WikiEventManager.isListening(this) ) - { - WikiEventManager.fireEvent(this,new WikiSecurityEvent(this,type,target)); - } - } - - /** - * Listens for {@link org.apache.wiki.event.WikiSecurityEvent#PROFILE_NAME_CHANGED} - * events. If a user profile's name changes, each group is inspected. If an entry contains - * a name that has changed, it is replaced with the new one. No group events are emitted - * as a consequence of this method, because the group memberships are still the same; it is - * only the representations of the names within that are changing. - * @param event the incoming event - */ - @Override public void actionPerformed( final WikiEvent event) - { - if (! ( event instanceof WikiSecurityEvent ) ) - { - return; - } - - final WikiSecurityEvent se = (WikiSecurityEvent)event; - if ( se.getType() == WikiSecurityEvent.PROFILE_NAME_CHANGED ) - { - final WikiSession session = se.getSrc(); - final UserProfile[] profiles = (UserProfile[])se.getTarget(); - final Principal[] oldPrincipals = new Principal[] { - new WikiPrincipal( profiles[0].getLoginName() ), - new WikiPrincipal( profiles[0].getFullname() ), - new WikiPrincipal( profiles[0].getWikiName() ) }; - final Principal newPrincipal = new WikiPrincipal( profiles[1].getFullname() ); - - // Examine each group - int groupsChanged = 0; - try - { - for ( final Group group : m_groupDatabase.groups() ) - { - boolean groupChanged = false; - for ( final Principal oldPrincipal : oldPrincipals ) - { - if ( group.isMember( oldPrincipal ) ) - { - group.remove( oldPrincipal ); - group.add( newPrincipal ); - groupChanged = true; - } - } - if ( groupChanged ) - { - setGroup( session, group ); - groupsChanged++; - } - } - } - catch ( final WikiException e ) - { - // Oooo! This is really bad... - log.error( "Could not change user name in Group lists because of GroupDatabase error:" + e.getMessage() ); - } - log.info( "Profile name change for '" + newPrincipal.toString() + - "' caused " + groupsChanged + " groups to change also." ); + default void fireEvent( final int type, final Object target ) { + if( WikiEventManager.isListening( this ) ) { + WikiEventManager.fireEvent( this, new WikiSecurityEvent( this, type, target ) ); } } diff --git a/jspwiki-main/src/main/resources/ini/classmappings.xml b/jspwiki-main/src/main/resources/ini/classmappings.xml index d572965..da8d88f 100644 --- a/jspwiki-main/src/main/resources/ini/classmappings.xml +++ b/jspwiki-main/src/main/resources/ini/classmappings.xml @@ -85,7 +85,7 @@ </mapping> <mapping> <requestedClass>org.apache.wiki.auth.authorize.GroupManager</requestedClass> - <mappedClass>org.apache.wiki.auth.authorize.GroupManager</mappedClass> + <mappedClass>org.apache.wiki.auth.authorize.DefaultGroupManager</mappedClass> </mapping> <mapping> <requestedClass>org.apache.wiki.content.PageRenamer</requestedClass>
