001    /* 
002        Licensed to the Apache Software Foundation (ASF) under one
003        or more contributor license agreements.  See the NOTICE file
004        distributed with this work for additional information
005        regarding copyright ownership.  The ASF licenses this file
006        to you under the Apache License, Version 2.0 (the
007        "License"); you may not use this file except in compliance
008        with the License.  You may obtain a copy of the License at
009    
010           http://www.apache.org/licenses/LICENSE-2.0
011    
012        Unless required by applicable law or agreed to in writing,
013        software distributed under the License is distributed on an
014        "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015        KIND, either express or implied.  See the License for the
016        specific language governing permissions and limitations
017        under the License.  
018     */
019    package org.apache.wiki.auth.authorize;
020    
021    import java.security.Principal;
022    import java.util.HashMap;
023    import java.util.HashSet;
024    import java.util.Map;
025    import java.util.Properties;
026    import java.util.Set;
027    import java.util.StringTokenizer;
028    
029    import javax.servlet.http.HttpServletRequest;
030    
031    import org.apache.commons.lang.ArrayUtils;
032    import org.apache.log4j.Logger;
033    import org.apache.wiki.WikiContext;
034    import org.apache.wiki.WikiEngine;
035    import org.apache.wiki.WikiSession;
036    import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
037    import org.apache.wiki.api.exceptions.WikiException;
038    import org.apache.wiki.auth.AuthenticationManager;
039    import org.apache.wiki.auth.Authorizer;
040    import org.apache.wiki.auth.GroupPrincipal;
041    import org.apache.wiki.auth.NoSuchPrincipalException;
042    import org.apache.wiki.auth.WikiPrincipal;
043    import org.apache.wiki.auth.WikiSecurityException;
044    import org.apache.wiki.auth.user.UserProfile;
045    import org.apache.wiki.event.WikiEvent;
046    import org.apache.wiki.event.WikiEventListener;
047    import org.apache.wiki.event.WikiEventManager;
048    import org.apache.wiki.event.WikiSecurityEvent;
049    import org.apache.wiki.ui.InputValidator;
050    import org.apache.wiki.util.ClassUtil;
051    
052    
053    /**
054     * <p>
055     * Facade class for storing, retrieving and managing wiki groups on behalf of
056     * AuthorizationManager, JSPs and other presentation-layer classes. GroupManager
057     * works in collaboration with a back-end {@link GroupDatabase}, which persists
058     * groups to permanent storage.
059     * </p>
060     * <p>
061     * <em>Note: prior to JSPWiki 2.4.19, GroupManager was an interface; it
062     * is now a concrete, final class. The aspects of GroupManager which previously
063     * extracted group information from storage (e.g., wiki pages) have been
064     * refactored into the GroupDatabase interface.</em>
065     * </p>
066     * @since 2.4.19
067     */
068    public class GroupManager implements Authorizer, WikiEventListener {
069    
070        /** Key used for adding UI messages to a user's WikiSession. */
071        public static final String  MESSAGES_KEY       = "group";
072    
073        private static final String PROP_GROUPDATABASE = "jspwiki.groupdatabase";
074    
075        static final Logger         log                = Logger.getLogger( GroupManager.class );
076    
077        protected WikiEngine        m_engine;
078    
079        protected WikiEventListener m_groupListener;
080    
081        private GroupDatabase       m_groupDatabase    = null;
082    
083        /** Map with GroupPrincipals as keys, and Groups as values */
084        private final Map<Principal, Group>           m_groups           = new HashMap<Principal, Group>();
085    
086        /**
087         * <p>
088         * Returns a GroupPrincipal matching a given name. If a group cannot be
089         * found, return <code>null</code>.
090         * </p>
091         * @param name Name of the group. This is case-sensitive.
092         * @return A DefaultGroup instance.
093         */
094        public Principal findRole( String name )
095        {
096            try
097            {
098                Group group = getGroup( name );
099                return group.getPrincipal();
100            }
101            catch( NoSuchPrincipalException e )
102            {
103                return null;
104            }
105        }
106    
107        /**
108         * Returns the Group matching a given name. If the group cannot be found,
109         * this method throws a <code>NoSuchPrincipalException</code>.
110         * @param name the name of the group to find
111         * @return the group
112         * @throws NoSuchPrincipalException if the group cannot be found
113         */
114        public Group getGroup( String name ) throws NoSuchPrincipalException
115        {
116            Group group = m_groups.get( new GroupPrincipal( name ) );
117            if ( group != null )
118            {
119                return group;
120            }
121            throw new NoSuchPrincipalException( "Group " + name + " not found." );
122        }
123    
124        /**
125         * Returns the current external {@link GroupDatabase} in use. This method
126         * is guaranteed to return a properly-initialized GroupDatabase, unless
127         * it could not be initialized. In that case, this method throws
128         * a {@link org.apache.wiki.api.exceptions.WikiException}. The GroupDatabase
129         * is lazily initialized.
130         * @throws org.apache.wiki.auth.WikiSecurityException if the GroupDatabase could
131         * not be initialized
132         * @return the current GroupDatabase
133         * @since 2.3
134         */
135        public GroupDatabase getGroupDatabase() throws WikiSecurityException
136        {
137            if ( m_groupDatabase != null )
138            {
139                return m_groupDatabase;
140            }
141    
142            String dbClassName = "<unknown>";
143            String dbInstantiationError = null;
144            Throwable cause = null;
145            try
146            {
147                Properties props = m_engine.getWikiProperties();
148                dbClassName = props.getProperty( PROP_GROUPDATABASE );
149                if ( dbClassName == null )
150                {
151                    dbClassName = XMLGroupDatabase.class.getName();
152                }
153                log.info( "Attempting to load group database class " + dbClassName );
154                Class<?> dbClass = ClassUtil.findClass( "org.apache.wiki.auth.authorize", dbClassName );
155                m_groupDatabase = (GroupDatabase) dbClass.newInstance();
156                m_groupDatabase.initialize( m_engine, m_engine.getWikiProperties() );
157                log.info( "Group database initialized." );
158            }
159            catch( ClassNotFoundException e )
160            {
161                log.error( "GroupDatabase class " + dbClassName + " cannot be found.", e );
162                dbInstantiationError = "Failed to locate GroupDatabase class " + dbClassName;
163                cause = e;
164            }
165            catch( InstantiationException e )
166            {
167                log.error( "GroupDatabase class " + dbClassName + " cannot be created.", e );
168                dbInstantiationError = "Failed to create GroupDatabase class " + dbClassName;
169                cause = e;
170            }
171            catch( IllegalAccessException e )
172            {
173                log.error( "You are not allowed to access group database class " + dbClassName + ".", e );
174                dbInstantiationError = "Access GroupDatabase class " + dbClassName + " denied";
175                cause = e;
176            }
177            catch( NoRequiredPropertyException e )
178            {
179                log.error( "Missing property: " + e.getMessage() + "." );
180                dbInstantiationError = "Missing property: " + e.getMessage();
181                cause = e;
182            }
183    
184            if( dbInstantiationError != null )
185            {
186                throw new WikiSecurityException( dbInstantiationError + " Cause: " + (cause != null ? cause.getMessage() : ""), cause );
187            }
188    
189            return m_groupDatabase;
190        }
191    
192        /**
193         * Returns an array of GroupPrincipals this GroupManager knows about. This
194         * method will return an array of GroupPrincipal objects corresponding to
195         * the wiki groups managed by this class. This method actually returns a
196         * defensive copy of an internally stored hashmap.
197         * @return an array of Principals representing the roles
198         */
199        public Principal[] getRoles()
200        {
201            return m_groups.keySet().toArray( new Principal[m_groups.size()] );
202        }
203    
204        /**
205         * Initializes the group cache by initializing the group database and
206         * obtaining a list of all of the groups it stores.
207         * @param engine the wiki engine
208         * @param props the properties used to initialize the wiki engine
209         * @see GroupDatabase#initialize(org.apache.wiki.WikiEngine,
210         *      java.util.Properties)
211         * @see GroupDatabase#groups()
212         * @throws WikiSecurityException if GroupManager cannot be initialized
213         */
214        public void initialize( WikiEngine engine, Properties props ) throws WikiSecurityException
215        {
216            m_engine = engine;
217    
218            try
219            {
220                m_groupDatabase = getGroupDatabase();
221            }
222            catch ( WikiException e )
223            {
224                throw new WikiSecurityException( e.getMessage(), e );
225            }
226    
227            // Load all groups from the database into the cache
228            Group[] groups = m_groupDatabase.groups();
229            synchronized( m_groups )
230            {
231                for( Group group : groups )
232                {
233                    // Add new group to cache; fire GROUP_ADD event
234                    m_groups.put( group.getPrincipal(), group );
235                    fireEvent( WikiSecurityEvent.GROUP_ADD, group );
236                }
237            }
238    
239            // Make the GroupManager listen for WikiEvents (WikiSecurityEvents for changed user profiles)
240            engine.getUserManager().addWikiEventListener( this );
241    
242            // Success!
243            log.info( "Authorizer GroupManager initialized successfully; loaded " + groups.length + " group(s)." );
244    
245        }
246    
247        /**
248         * <p>
249         * Determines whether the Subject associated with a WikiSession is in a
250         * particular role. This method takes two parameters: the WikiSession
251         * containing the subject and the desired role ( which may be a Role or a
252         * Group). If either parameter is <code>null</code>, or if the user is
253         * not authenticated, this method returns <code>false</code>.
254         * </p>
255         * <p>
256         * With respect to this implementation, the supplied Principal must be a
257         * GroupPrincipal. The Subject posesses the "role" if it the session is
258         * authenticated <em>and</em> a Subject's principal is a member of the
259         * corresponding Group. This method simply finds the Group in question, then
260         * delegates to {@link Group#isMember(Principal)} for each of the principals
261         * in the Subject's principal set.
262         * </p>
263         * @param session the current WikiSession
264         * @param role the role to check
265         * @return <code>true</code> if the user is considered to be in the role,
266         *         <code>false</code> otherwise
267         */
268        public boolean isUserInRole( WikiSession session, Principal role )
269        {
270            // Always return false if session/role is null, or if
271            // role isn't a GroupPrincipal
272            if ( session == null || role == null || !( role instanceof GroupPrincipal ) || !session.isAuthenticated() )
273            {
274                return false;
275            }
276    
277            // Get the group we're examining
278            Group group = m_groups.get( role );
279            if ( group == null )
280            {
281                return false;
282            }
283    
284            // Check each user principal to see if it belongs to the group
285            for ( Principal principal : session.getPrincipals() )
286            {
287                if ( AuthenticationManager.isUserPrincipal( principal ) && group.isMember( principal ) )
288                {
289                    return true;
290                }
291            }
292            return false;
293        }
294    
295        /**
296         * <p>
297         * Extracts group name and members from passed parameters and populates an
298         * existing Group with them. The Group will either be a copy of an existing
299         * Group (if one can be found), or a new, unregistered Group (if not).
300         * Optionally, this method can throw a WikiSecurityException if the Group
301         * does not yet exist in the GroupManager cache.
302         * </p>
303         * <p>
304         * The <code>group</code> parameter in the HTTP request contains the Group
305         * name to look up and populate. The <code>members</code> parameter
306         * contains the member list. If these differ from those in the existing
307         * group, the passed values override the old values.
308         * </p>
309         * <p>
310         * This method does not commit the new Group to the GroupManager cache. To
311         * do that, use {@link #setGroup(WikiSession, Group)}.
312         * </p>
313         * @param name the name of the group to construct
314         * @param memberLine the line of text containing the group membership list
315         * @param create whether this method should create a new, empty Group if one
316         *            with the requested name is not found. If <code>false</code>,
317         *            groups that do not exist will cause a
318         *            <code>NoSuchPrincipalException</code> to be thrown
319         * @return a new, populated group
320         * @see org.apache.wiki.auth.authorize.Group#RESTRICTED_GROUPNAMES
321         * @throws WikiSecurityException if the group name isn't allowed, or if
322         * <code>create</code> is <code>false</code>
323         * and the Group named <code>name</code> does not exist
324         */
325        public Group parseGroup( String name, String memberLine, boolean create ) throws WikiSecurityException
326        {
327            // If null name parameter, it's because someone's creating a new group
328            if ( name == null )
329            {
330                if ( create )
331                {
332                    name = "MyGroup";
333                }
334                else
335                {
336                    throw new WikiSecurityException( "Group name cannot be blank." );
337                }
338            }
339            else if ( ArrayUtils.contains( Group.RESTRICTED_GROUPNAMES, name ) )
340            {
341                // Certain names are forbidden
342                throw new WikiSecurityException( "Illegal group name: " + name );
343            }
344            name = name.trim();
345    
346            // Normalize the member line
347            if ( InputValidator.isBlank( memberLine ) )
348            {
349                memberLine = "";
350            }
351            memberLine = memberLine.trim();
352    
353            // Create or retrieve the group (may have been previously cached)
354            Group group = new Group( name, m_engine.getApplicationName() );
355            try
356            {
357                Group existingGroup = getGroup( name );
358    
359                // If existing, clone it
360                group.setCreator( existingGroup.getCreator() );
361                group.setCreated( existingGroup.getCreated() );
362                group.setModifier( existingGroup.getModifier() );
363                group.setLastModified( existingGroup.getLastModified() );
364                for( Principal existingMember : existingGroup.members() )
365                {
366                    group.add( existingMember );
367                }
368            }
369            catch( NoSuchPrincipalException e )
370            {
371                // It's a new group.... throw error if we don't create new ones
372                if ( !create )
373                {
374                    throw new NoSuchPrincipalException( "Group '" + name + "' does not exist." );
375                }
376            }
377    
378            // If passed members not empty, overwrite
379            String[] members = extractMembers( memberLine );
380            if ( members.length > 0 )
381            {
382                group.clear();
383                for( String member : members )
384                {
385                    group.add( new WikiPrincipal( member ) );
386                }
387            }
388    
389            return group;
390        }
391    
392        /**
393         * <p>
394         * Extracts group name and members from the HTTP request and populates an
395         * existing Group with them. The Group will either be a copy of an existing
396         * Group (if one can be found), or a new, unregistered Group (if not).
397         * Optionally, this method can throw a WikiSecurityException if the Group
398         * does not yet exist in the GroupManager cache.
399         * </p>
400         * <p>
401         * The <code>group</code> parameter in the HTTP request contains the Group
402         * name to look up and populate. The <code>members</code> parameter
403         * contains the member list. If these differ from those in the existing
404         * group, the passed values override the old values.
405         * </p>
406         * <p>
407         * This method does not commit the new Group to the GroupManager cache. To
408         * do that, use {@link #setGroup(WikiSession, Group)}.
409         * </p>
410         * @param context the current wiki context
411         * @param create whether this method should create a new, empty Group if one
412         *            with the requested name is not found. If <code>false</code>,
413         *            groups that do not exist will cause a
414         *            <code>NoSuchPrincipalException</code> to be thrown
415         * @return a new, populated group
416         * @throws WikiSecurityException if the group name isn't allowed, or if
417         * <code>create</code> is <code>false</code>
418         * and the Group does not exist
419         */
420        public Group parseGroup( WikiContext context, boolean create ) throws WikiSecurityException
421        {
422            // Extract parameters
423            HttpServletRequest request = context.getHttpRequest();
424            String name = request.getParameter( "group" );
425            String memberLine = request.getParameter( "members" );
426    
427            // Create the named group; we pass on any NoSuchPrincipalExceptions
428            // that may be thrown if create == false, or WikiSecurityExceptions
429            Group group = parseGroup( name, memberLine, create );
430    
431            // If no members, add the current user by default
432            if ( group.members().length == 0 )
433            {
434                group.add( context.getWikiSession().getUserPrincipal() );
435            }
436    
437            return group;
438        }
439    
440        /**
441         * Removes a named Group from the group database. If not found, throws a
442         * <code>NoSuchPrincipalException</code>. After removal, this method will
443         * commit the delete to the back-end group database. It will also fire a
444         * {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_REMOVE} event with
445         * the GroupManager instance as the source and the Group as target.
446         * If <code>index</code> is <code>null</code>, this method throws
447         * an {@link IllegalArgumentException}.
448         * @param index the group to remove
449         * @throws WikiSecurityException if the Group cannot be removed by
450         * the back-end
451         * @see org.apache.wiki.auth.authorize.GroupDatabase#delete(Group)
452         */
453        public void removeGroup( String index ) throws WikiSecurityException
454        {
455            if ( index == null )
456            {
457                throw new IllegalArgumentException( "Group cannot be null." );
458            }
459    
460            Group group = m_groups.get( new GroupPrincipal( index ) );
461            if ( group == null )
462            {
463                throw new NoSuchPrincipalException( "Group " + index + " not found" );
464            }
465    
466            // Delete the group
467            // TODO: need rollback procedure
468            synchronized( m_groups )
469            {
470                m_groups.remove( group.getPrincipal() );
471            }
472            m_groupDatabase.delete( group );
473            fireEvent( WikiSecurityEvent.GROUP_REMOVE, group );
474        }
475    
476        /**
477         * <p>
478         * Saves the {@link Group} created by a user in a wiki session. This method
479         * registers the Group with the GroupManager and saves it to the back-end
480         * database. If an existing Group with the same name already exists, the new
481         * group will overwrite it. After saving the Group, the group database
482         * changes are committed.
483         * </p>
484         * <p>
485         * This method fires the following events:
486         * </p>
487         * <ul>
488         * <li><strong>When creating a new Group</strong>, this method fires a
489         * {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_ADD} with the
490         * GroupManager instance as its source and the new Group as the target.</li>
491         * <li><strong>When overwriting an existing Group</strong>, this method
492         * fires a new {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_REMOVE}
493         * with this GroupManager instance as the source, and the new Group as the
494         * target. It then fires a
495         * {@link org.apache.wiki.event.WikiSecurityEvent#GROUP_ADD} event with the
496         * same source and target.</li>
497         * </ul>
498         * <p>
499         * In addition, if the save or commit actions fail, this method will attempt
500         * to restore the older version of the wiki group if it exists. This will
501         * result in a <code>GROUP_REMOVE</code> event (for the new version of the
502         * Group) followed by a <code>GROUP_ADD</code> event (to indicate
503         * restoration of the old version).
504         * </p>
505         * <p>
506         * This method will register the new Group with the GroupManager. For example,
507         * {@link org.apache.wiki.auth.AuthenticationManager} attaches each
508         * WikiSession as a GroupManager listener. Thus, the act of registering a
509         * Group with <code>setGroup</code> means that all WikiSessions will
510         * automatically receive group add/change/delete events immediately.
511         * </p>
512         * @param session the wiki session, which may not be <code>null</code>
513         * @param group the Group, which may not be <code>null</code>
514         * @throws WikiSecurityException if the Group cannot be saved by the back-end
515         */
516        public void setGroup( WikiSession session, Group group ) throws WikiSecurityException
517        {
518            // TODO: check for appropriate permissions
519    
520            // If group already exists, delete it; fire GROUP_REMOVE event
521            Group oldGroup = m_groups.get( group.getPrincipal() );
522            if ( oldGroup != null )
523            {
524                fireEvent( WikiSecurityEvent.GROUP_REMOVE, oldGroup );
525                synchronized( m_groups )
526                {
527                    m_groups.remove( oldGroup.getPrincipal() );
528                }
529            }
530    
531            // Copy existing modifier info & timestamps
532            if ( oldGroup != null )
533            {
534                group.setCreator( oldGroup.getCreator() );
535                group.setCreated( oldGroup.getCreated() );
536                group.setModifier( oldGroup.getModifier() );
537                group.setLastModified( oldGroup.getLastModified() );
538            }
539    
540            // Add new group to cache; announce GROUP_ADD event
541            synchronized( m_groups )
542            {
543                m_groups.put( group.getPrincipal(), group );
544            }
545            fireEvent( WikiSecurityEvent.GROUP_ADD, group );
546    
547            // Save the group to back-end database; if it fails,
548            // roll back to previous state. Note that the back-end
549            // MUST timestammp the create/modify fields in the Group.
550            try
551            {
552                m_groupDatabase.save( group, session.getUserPrincipal() );
553            }
554    
555            // We got an exception! Roll back...
556            catch( WikiSecurityException e )
557            {
558                if ( oldGroup != null )
559                {
560                    // Restore previous version, re-throw...
561                    fireEvent( WikiSecurityEvent.GROUP_REMOVE, group );
562                    fireEvent( WikiSecurityEvent.GROUP_ADD, oldGroup );
563                    synchronized( m_groups )
564                    {
565                        m_groups.put( oldGroup.getPrincipal(), oldGroup );
566                    }
567                    throw new WikiSecurityException( e.getMessage() + " (rolled back to previous version).", e );
568                }
569                // Re-throw security exception
570                throw new WikiSecurityException( e.getMessage(), e );
571            }
572        }
573    
574        /**
575         * Validates a Group, and appends any errors to the session errors list. Any
576         * validation errors are added to the wiki session's messages collection
577         * (see {@link WikiSession#getMessages()}.
578         * @param context the current wiki context
579         * @param group the supplied Group
580         */
581        public void validateGroup( WikiContext context, Group group )
582        {
583            InputValidator validator = new InputValidator( MESSAGES_KEY, context );
584    
585            // Name cannot be null or one of the restricted names
586            try
587            {
588                checkGroupName( context, group.getName() );
589            }
590            catch( WikiSecurityException e )
591            {
592    
593            }
594    
595            // Member names must be "safe" strings
596            Principal[] members = group.members();
597            for( int i = 0; i < members.length; i++ )
598            {
599                validator.validateNotNull( members[i].getName(), "Full name", InputValidator.ID );
600            }
601        }
602    
603        /**
604         * Extracts carriage-return separated members into a Set of String objects.
605         * @param memberLine the list of members
606         * @return the list of members
607         */
608        protected String[] extractMembers( String memberLine )
609        {
610            Set<String> members = new HashSet<String>();
611            if ( memberLine != null )
612            {
613                StringTokenizer tok = new StringTokenizer( memberLine, "\n" );
614                while( tok.hasMoreTokens() )
615                {
616                    String uid = tok.nextToken().trim();
617                    if ( uid != null && uid.length() > 0 )
618                    {
619                        members.add( uid );
620                    }
621                }
622            }
623            return members.toArray( new String[members.size()] );
624        }
625    
626        /**
627         * Checks if a String is blank or a restricted Group name, and if it is,
628         * appends an error to the WikiSession's message list.
629         * @param context the wiki context
630         * @param name the Group name to test
631         * @throws WikiSecurityException if <code>session</code> is
632         * <code>null</code> or the Group name is illegal
633         * @see Group#RESTRICTED_GROUPNAMES
634         */
635        protected void checkGroupName( WikiContext context, String name ) throws WikiSecurityException
636        {
637            //TODO: groups cannot have the same name as a user
638    
639            // Name cannot be null
640            InputValidator validator = new InputValidator( MESSAGES_KEY, context );
641            validator.validateNotNull( name, "Group name" );
642    
643            // Name cannot be one of the restricted names either
644            if( ArrayUtils.contains( Group.RESTRICTED_GROUPNAMES, name ) )
645            {
646                throw new WikiSecurityException( "The group name '" + name + "' is illegal. Choose another." );
647            }
648        }
649    
650    
651        // events processing .......................................................
652    
653        /**
654         * Registers a WikiEventListener with this instance.
655         * This is a convenience method.
656         * @param listener the event listener
657         */
658        public synchronized void addWikiEventListener( WikiEventListener listener )
659        {
660            WikiEventManager.addWikiEventListener( this, listener );
661        }
662    
663        /**
664         * Un-registers a WikiEventListener with this instance.
665         * This is a convenience method.
666         * @param listener the event listener
667         */
668        public synchronized void removeWikiEventListener( WikiEventListener listener )
669        {
670            WikiEventManager.removeWikiEventListener( this, listener );
671        }
672    
673        /**
674         *  Fires a WikiSecurityEvent of the provided type, Principal and target Object
675         *  to all registered listeners.
676         *
677         * @see org.apache.wiki.event.WikiSecurityEvent
678         * @param type       the event type to be fired
679         * @param target     the changed Object, which may be <code>null</code>
680         */
681        protected void fireEvent( int type, Object target )
682        {
683            if ( WikiEventManager.isListening(this) )
684            {
685                WikiEventManager.fireEvent(this,new WikiSecurityEvent(this,type,target));
686            }
687        }
688    
689        /**
690         * Listens for {@link org.apache.wiki.event.WikiSecurityEvent#PROFILE_NAME_CHANGED}
691         * events. If a user profile's name changes, each group is inspected. If an entry contains
692         * a name that has changed, it is replaced with the new one. No group events are emitted
693         * as a consequence of this method, because the group memberships are still the same; it is
694         * only the representations of the names within that are changing.
695         * @param event the incoming event
696         */
697        public void actionPerformed(WikiEvent event)
698        {
699            if (! ( event instanceof WikiSecurityEvent ) )
700            {
701                return;
702            }
703    
704            WikiSecurityEvent se = (WikiSecurityEvent)event;
705            if ( se.getType() == WikiSecurityEvent.PROFILE_NAME_CHANGED )
706            {
707                WikiSession session = se.getSrc();
708                UserProfile[] profiles = (UserProfile[])se.getTarget();
709                Principal[] oldPrincipals = new Principal[] {
710                    new WikiPrincipal( profiles[0].getLoginName() ),
711                    new WikiPrincipal( profiles[0].getFullname() ),
712                    new WikiPrincipal( profiles[0].getWikiName() ) };
713                Principal newPrincipal = new WikiPrincipal( profiles[1].getFullname() );
714    
715                // Examine each group
716                int groupsChanged = 0;
717                try
718                {
719                    for ( Group group : m_groupDatabase.groups() )
720                    {
721                        boolean groupChanged = false;
722                        for ( Principal oldPrincipal : oldPrincipals )
723                        {
724                            if ( group.isMember( oldPrincipal ) )
725                            {
726                                group.remove( oldPrincipal );
727                                group.add( newPrincipal );
728                                groupChanged = true;
729                            }
730                        }
731                        if ( groupChanged )
732                        {
733                            setGroup( session, group );
734                            groupsChanged++;
735                        }
736                    }
737                }
738                catch ( WikiException e )
739                {
740                    // Oooo! This is really bad...
741                    log.error( "Could not change user name in Group lists because of GroupDatabase error:" + e.getMessage() );
742                }
743                log.info( "Profile name change for '" + newPrincipal.toString() +
744                          "' caused " + groupsChanged + " groups to change also." );
745            }
746        }
747    
748    }