All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wiki.auth.authorize.DefaultGroupManager Maven / Gradle / Ivy

/* 
    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.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.wiki.api.core.Context;
import org.apache.wiki.api.core.Engine;
import org.apache.wiki.api.core.Session;
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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;


/**
 * 

* 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. *

*

* 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. *

* @since 2.4.19 */ public class DefaultGroupManager implements GroupManager, Authorizer, WikiEventListener { private static final Logger LOG = LogManager.getLogger( DefaultGroupManager.class ); protected Engine m_engine; protected WikiEventListener m_groupListener; private GroupDatabase m_groupDatabase; /** 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 = ""; 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 ); m_groupDatabase = ClassUtil.buildInstance( "org.apache.wiki.auth.authorize", dbClassName ); m_groupDatabase.initialize( m_engine, m_engine.getWikiProperties() ); LOG.info( "Group database initialized." ); } catch( final ReflectiveOperationException e ) { LOG.error( "UserDatabase {} cannot be instantiated", 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[0] ); } /** {@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 Session 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 return Arrays.stream(session.getPrincipals()).anyMatch(principal -> AuthenticationManager.isUserPrincipal(principal) && group.isMember(principal)); } /** {@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 Session 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 ); } } /** {@inheritDoc} */ @Override public void validateGroup( final Context context, final Group group ) { final InputValidator validator = new InputValidator( MESSAGES_KEY, context ); // Name cannot be null or one of the restricted names try { checkGroupName( context, group.getName() ); } catch( final WikiSecurityException e ) { } // Member names must be "safe" strings final Principal[] members = group.members(); for( final Principal member : members ) { validator.validateNotNull( member.getName(), "Full name", InputValidator.ID ); } } /** {@inheritDoc} */ @Override public void checkGroupName( final Context 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 ) ) { throw new WikiSecurityException( "The group name '" + name + "' is illegal. Choose another." ); } } /** * 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.isEmpty() ) { members.add( uid ); } } } return members.toArray( new String[0] ); } // 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 Session 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 + "' caused " + groupsChanged + " groups to change also." ); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy