/*************************************************************************
* Copyright 2009-2015 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
*
* This file may incorporate work covered under the following copyright
* and permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms,
* with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
* THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
* COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
* AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
* SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
* WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
* REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
* IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
* NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
************************************************************************/
package com.eucalyptus.auth.euare.ldap;
import java.util.Map;
import java.util.Set;
import javax.naming.InvalidNameException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapName;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.euare.Accounts;
import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.LdapException;
import com.eucalyptus.auth.euare.checker.ValueCheckerFactory;
import com.eucalyptus.auth.euare.principal.EuareAccount;
import com.eucalyptus.auth.euare.principal.EuareGroup;
import com.eucalyptus.auth.euare.principal.EuareUser;
import com.eucalyptus.auth.principal.AccountIdentifiers;
import com.eucalyptus.auth.principal.User;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.bootstrap.Hosts;
import com.eucalyptus.event.ClockTick;
import com.eucalyptus.event.Event;
import com.eucalyptus.event.EventListener;
import com.eucalyptus.event.ListenerRegistry;
import com.eucalyptus.event.SystemClock;
import com.eucalyptus.system.Threads;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Logic to perform LDAP sync.
*/
public class LdapSync {
private interface LdapEntryProcessor {
public void processLdapEntry( String dn, Attributes attrs ) throws NamingException;
}
private static final Logger LOG = Logger.getLogger( LdapSync.class );
private static final boolean VERBOSE = true;
private static final String LDAP_SYNC_THREAD = "LDAP sync";
private static final LdapIntegrationConfiguration DEFAULT_LIC = new LdapIntegrationConfiguration( );
private static LdapIntegrationConfiguration lic = DEFAULT_LIC;
private static boolean inSync = false;
private static long timeTillNextSync;
private static final ClockTickListener TIMER_LISTENER = new ClockTickListener( );
private static class ClockTickListener implements EventListener<Event> {
@Override
public void fireEvent( Event event ) {
if ( Bootstrap.isOperational( ) && Hosts.isCoordinator( ) && event instanceof ClockTick ) {
periodicSync( );
}
}
}
public static synchronized boolean inSync( ) {
return inSync;
}
public static synchronized void start( ) {
if ( lic.isSyncEnabled( ) ) {
if ( lic.isAutoSync( ) ) {
ListenerRegistry.getInstance( ).register( ClockTick.class, TIMER_LISTENER );
}
startSync( );
}
}
public static synchronized LdapIntegrationConfiguration getLic( ) {
return lic;
}
public static synchronized void setLic( LdapIntegrationConfiguration config ) {
LOG.debug( "A new LIC is being set: " + config );
lic = config;
if ( Bootstrap.isFinished( ) ) {
if ( lic.isSyncEnabled( ) ) {
if ( lic.isAutoSync( ) ) {
ListenerRegistry.getInstance( ).register( ClockTick.class, TIMER_LISTENER );
}
startSync( );
} else {
ListenerRegistry.getInstance( ).deregister( ClockTick.class, TIMER_LISTENER );
}
}
}
public static synchronized void forceSync( ) {
if ( lic.isSyncEnabled( ) ) {
startSync( );
}
}
public static synchronized boolean check( ) {
if ( !lic.isSyncEnabled( ) ) {
return true;
}
LdapClient ldap = null;
try {
ldap = LdapClient.authenticateClient( lic );
return true;
} catch ( LdapException e ) {
LOG.error( e, e );
LOG.warn( "Failed to connect to LDAP service", e );
return false;
} finally {
if ( ldap != null ) {
ldap.close( );
}
}
}
/**
* Authenticate an LDAP user by his login and password.
*
* @param login
* @param password
* @return
* @throws LdapException
*/
public static synchronized void authenticate( EuareUser user, String password ) throws LdapException {
if ( !lic.isSyncEnabled( ) ) {
throw new LdapException( "LDAP sync is not enabled" );
}
LdapClient ldap = null;
try {
// Get proper user LDAP principal based on authentication method
String login = null;
if ( LicParser.LDAP_AUTH_METHOD_SIMPLE.equals( lic.getRealUserAuthMethod( ) ) ) {
// Simple requires full user DN
login = user.getInfo( EuareUser.DN );
} else {
// SASL requires a different ID:
// Pick the specified ID first. If not exist, pick the user name.
// TODO(wenye): possible other formats of user ID, like "u:..." or "dn:..."
login = user.getInfo( EuareUser.SASLID );
if ( Strings.isNullOrEmpty( login ) ) {
login = user.getName( );
}
}
if ( Strings.isNullOrEmpty( login ) ) {
throw new LdapException( "Invalid login user" );
}
ldap = LdapClient.authenticateUser( lic, login, password );
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.debug( "Failed to get auth information for user " + user );
throw new LdapException( "Failed to get auth information", e );
} catch ( LdapException e ) {
LOG.error( e, e );
LOG.debug( "Failed to connect to LDAP service", e );
throw e;
} finally {
if ( ldap != null ) {
ldap.close( );
}
}
}
/**
* @return true if LDAP sync is enabled.
*/
public static synchronized boolean enabled( ) {
return lic.isSyncEnabled( );
}
private static synchronized boolean getAndSetSync( boolean newValue ) {
boolean old = inSync;
inSync = newValue;
return old;
}
private static synchronized void periodicSync( ) {
if ( lic.isSyncEnabled( ) && lic.isAutoSync( ) ) {
timeTillNextSync -= SystemClock.getRate( );
if ( timeTillNextSync <= 0 ) {
startSync( );
}
}
}
private static void startSync( ) {
LOG.debug( "A new sync initiated." );
timeTillNextSync = lic.getSyncInterval( );
if ( !getAndSetSync( true ) ) {
Threads.newThread( new Runnable( ) {
@Override
public void run( ) {
LOG.debug( "Sync started" );
sync( lic );
getAndSetSync( false );
LOG.debug( "Sync ended" );
}
}, LDAP_SYNC_THREAD ).start( );
}
}
public static void sync( final LdapIntegrationConfiguration lic ) {
// Get users/groups from LDAP
Map<String, Set<String>> accountingGroups = Maps.newHashMap( );
Map<String, String> groupDnToId = Maps.newHashMap( );
Map<String, Set<String>> groups = Maps.newHashMap( );
Map<String, String> userDnToId = Maps.newHashMap( );
Map<String, Map<String, String>> users = Maps.newHashMap( );
LdapClient ldap = null;
try {
ldap = LdapClient.authenticateClient( lic );
loadLdapUsers( ldap, lic, userDnToId, users );
loadLdapGroups( ldap, lic, userDnToId, groupDnToId, groups );
if ( lic.hasAccountingGroups( ) ) {
loadLdapAccountingGroups( ldap, lic, groupDnToId, accountingGroups );
} else {
accountingGroups = lic.getGroupsPartition( );
}
} catch ( Exception e ) {
LOG.error( e, e );
LOG.error( "Failed to sync with LDAP", e );
return;
} finally {
if ( ldap != null ) {
ldap.close( );
}
}
if ( VERBOSE ) {
LOG.debug( "Sync remote accounts: " + accountingGroups );
LOG.debug( "Sync remote groups: " + groups );
LOG.debug( "Sync remote users: " + users );
}
checkConflictingIdentities( accountingGroups, groups, users );
rebuildLocalAuthDatabase( lic, accountingGroups, groups, users );
}
private static void checkConflictingIdentities( Map<String, Set<String>> accountingGroups, Map<String, Set<String>> groups, Map<String, Map<String, String>> users ) {
if ( accountingGroups.containsKey( AccountIdentifiers.SYSTEM_ACCOUNT ) ) {
LOG.error( "Account " + AccountIdentifiers.SYSTEM_ACCOUNT + " is reserved for Eucalyptus only. Sync will skip this account from LDAP." );
accountingGroups.remove( AccountIdentifiers.SYSTEM_ACCOUNT );
}
if ( users.containsKey( User.ACCOUNT_ADMIN ) ) {
LOG.error( "User " + User.ACCOUNT_ADMIN + " is reserved for Eucalyptus only. Sync will skip this user from LDAP." );
users.remove( User.ACCOUNT_ADMIN );
}
for ( String group : groups.keySet( ) ) {
if ( group.startsWith( EuareUser.USER_GROUP_PREFIX ) ) {
LOG.error( "Group name starting with " + EuareUser.USER_GROUP_PREFIX + " is reserved for Eucalyptus only. Sync will skip this group " + group );
groups.remove( group );
}
}
}
private static void rebuildLocalAuthDatabase( LdapIntegrationConfiguration lic, Map<String, Set<String>> accountingGroups,
Map<String, Set<String>> groups, Map<String, Map<String, String>> users ) {
try {
Set<String> oldAccountSet = getLocalAccountSet( );
for ( Map.Entry<String, Set<String>> entry : accountingGroups.entrySet( ) ) {
String accountName = entry.getKey( );
Set<String> accountMembers = entry.getValue( );
if ( oldAccountSet.contains( accountName ) ) {
// Remove common elements from old account set
oldAccountSet.remove( accountName );
updateAccount( lic, accountName, accountMembers, groups, users );
} else {
addNewAccount( accountName, accountMembers, groups, users );
}
}
if ( lic.isCleanDeletion( ) ) {
// Remaining accounts are obsolete
removeObsoleteAccounts( oldAccountSet );
}
} catch ( Exception e ) {
LOG.error( e, e );
LOG.error( "Error in rebuilding local auth database", e );
}
}
private static void addNewAccount( String accountName, Set<String> accountMembers, Map<String, Set<String>> groups, Map<String, Map<String, String>> users ) {
LOG.debug( "Adding new account " + accountName );
try {
EuareAccount account = com.eucalyptus.auth.euare.Accounts.addAccount( accountName );
account.addUser( User.ACCOUNT_ADMIN, "/", true, null );
for ( String user : getAccountUserSet( accountMembers, groups ) ) {
try {
LOG.debug( "Adding new user " + user );
Map<String, String> info = users.get( user );
if ( info == null ) {
LOG.warn( "Empty user info for user " + user );
}
account.addUser( user, "/", true/* enabled */, info );
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.warn( "Failed add new user " + user, e );
}
}
for ( String group : accountMembers ) {
EuareGroup dbGroup = null;
try {
LOG.debug( "Adding new group " + group );
dbGroup = account.addGroup( group, "/" );
Set<String> groupUsers = groups.get( group );
if ( groupUsers == null ) {
LOG.error( "Empty user set for group " + group );
} else {
for ( String user : groupUsers ) {
LOG.debug( "Adding " + user + " to group " + group );
dbGroup.addUserByName( user );
}
}
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.warn( "Failed to add new group " + group + " in " + accountName, e );
}
}
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.error( "Failed to add new account " + accountName, e );
}
}
private static Set<String> getAccountUserSet( Set<String> members, Map<String, Set<String>> groups ) {
Set<String> userSet = Sets.newHashSet( );
for ( String member : members ) {
Set<String> groupMembers = groups.get( member );
if ( groupMembers == null ) {
LOG.error( "Empty user set of group " + member );
} else {
userSet.addAll( groupMembers );
}
}
return userSet;
}
private static void updateAccount( LdapIntegrationConfiguration lic, String accountName, Set<String> accountMembers, Map<String, Set<String>> groups, Map<String, Map<String, String>> users ) {
LOG.debug( "Updating account " + accountName );
EuareAccount account = null;
try {
account = Accounts.lookupAccountByName( accountName );
// Update users first
Set<String> newUserSet = getAccountUserSet( accountMembers, groups );
Set<String> oldUserSet = getLocalUserSet( account );
for ( String user : newUserSet ) {
if ( oldUserSet.contains( user ) ) {
oldUserSet.remove( user );
try {
updateUser( account, user, users.get( user ) );
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.warn( "Failed to update user " + user + " in " + accountName, e );
}
} else {
try {
addNewUser( account, user, users.get( user ) );
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.warn( "Failed to add new user " + user + " in " + accountName, e );
}
}
}
if ( lic.isCleanDeletion( ) ) {
removeObsoleteUsers( account, oldUserSet );
}
// Now update groups
Set<String> oldGroupSet = getLocalGroupSet( account );
for ( String group : accountMembers ) {
if ( oldGroupSet.contains( group ) ) {
oldGroupSet.remove( group );
updateGroup( account, group, groups.get( group ) );
} else {
addNewGroup( account, group, groups.get( group ) );
}
}
if ( lic.isCleanDeletion( ) ) {
removeObsoleteGroups( account, oldGroupSet );
}
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.error( "Failed to update account " + accountName, e );
}
}
private static void removeObsoleteGroups( EuareAccount account, Set<String> oldGroupSet ) {
for ( String group : oldGroupSet ) {
try {
account.deleteGroup( group, true/* recursive */ );
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.warn( "Failed to delete group " + group + " in " + account.getName( ), e );
}
}
}
private static void addNewGroup( EuareAccount account, String group, Set<String> users ) {
LOG.debug( "Adding new group " + group + " in account " + account.getName( ) );
if ( users == null ) {
LOG.error( "Empty new user set of group " + group );
return;
}
try {
EuareGroup g = account.addGroup( group, "/" );
for ( String user : users ) {
LOG.debug( "Adding " + user + " to " + group );
g.addUserByName( user );
}
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.warn( "Failed to add new group " + group + " in " + account.getName( ), e );
}
}
private static void updateGroup( EuareAccount account, String group, Set<String> users ) {
LOG.debug( "Updating group " + group + " in account " + account.getName( ) );
if ( users == null ) {
LOG.error( "Empty new user set of group " + group );
return;
}
try {
// Get local user set of the group
Set<String> localUserSet = Sets.newHashSet( );
EuareGroup g = account.lookupGroupByName( group );
for ( User u : g.getUsers( ) ) {
localUserSet.add( u.getName( ) );
}
// Update group by adding new users and remove obsolete users
for ( String user : users ) {
if ( localUserSet.contains( user ) ) {
localUserSet.remove( user );
} else {
LOG.debug( "Adding " + user + " to " + g.getName( ) );
g.addUserByName( user );
}
}
for ( String user : localUserSet ) {
LOG.debug( "Removing " + user + " from " + g.getName( ) );
g.removeUserByName( user );
}
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.warn( "Failed to update group " + group + " in " + account.getName( ), e );
}
}
private static Set<String> getLocalGroupSet( EuareAccount account ) throws AuthException {
Set<String> groupSet = Sets.newHashSet( );
for ( EuareGroup group : account.getGroups( ) ) {
groupSet.add( group.getName( ) );
}
return groupSet;
}
private static void removeObsoleteUsers( EuareAccount account, Set<String> oldUserSet ) {
// We don't want to remove account admin when updating an account
oldUserSet.remove( User.ACCOUNT_ADMIN );
LOG.debug( "Removing obsolete users: " + oldUserSet + ", in account " + account.getName( ) );
for ( String user : oldUserSet ) {
try {
account.deleteUser( user, true/* forceDeleteAdmin */, true /* recursive */ );
} catch ( AuthException e ) {
LOG.error( e, e );
LOG.warn( "Failed to delete user " + user + " in " + account.getName( ) );
}
}
}
private static void addNewUser( EuareAccount account, String user, Map<String, String> info ) throws AuthException {
LOG.debug( "Adding new user " + user + " in account " + account.getName( ) );
if ( info == null ) {
LOG.warn( "Empty user info for user " + user );
}
account.addUser( user, "/", true/* enabled */, info );
}
private static void updateUser( EuareAccount account, String user, Map<String, String> map ) throws AuthException {
LOG.debug( "Updating user " + user + " in account " + account.getName( ) );
if ( map == null ) {
LOG.error( "Empty info map of user " + user );
} else {
account.lookupUserByName( user ).setInfo( map );
}
}
private static Set<String> getLocalUserSet( EuareAccount account ) throws AuthException {
Set<String> userSet = Sets.newHashSet( );
for ( User user : account.getUsers( ) ) {
userSet.add( user.getName( ) );
}
return userSet;
}
private static void removeObsoleteAccounts( Set<String> oldAccountSet ) {
// We don't want to remove system account
oldAccountSet.remove( EuareAccount.SYSTEM_ACCOUNT );
LOG.debug( "Removing obsolete accounts: " + oldAccountSet );
for ( final String account : oldAccountSet ) {
try {
com.eucalyptus.auth.euare.Accounts.deleteAccount( account, false /* forceDeleteSystem */, true /* recursive */ );
} catch ( final AuthException e ) {
if ( !AuthException.DELETE_SYSTEM_ACCOUNT.equals( e.getMessage( ) ) ) {
LOG.error( e, e );
LOG.warn( "Failed to delete account " + account, e );
}
}
}
}
private static Set<String> getLocalAccountSet( ) throws AuthException {
Set<String> accountSet = Sets.newHashSet( );
for ( EuareAccount account : com.eucalyptus.auth.euare.Accounts.listAllAccounts() ) {
accountSet.add( account.getName( ) );
}
return accountSet;
}
/**
* Following RFC 2253
*
* @param dn
* @return the last RDN of a DN
*/
private static String parseIdFromDn( String dn ) {
if ( Strings.isNullOrEmpty( dn ) ) {
return null;
}
try {
LdapName ln = new LdapName( dn );
if ( ln.size( ) > 0 ) {
return ( String ) ln.getRdn( ln.size( ) - 1 ).getValue( );
}
} catch ( InvalidNameException e ) {
LOG.error( e, e );
LOG.warn( "Invalid DN " + dn, e );
}
return null;
}
/**
* Get the ID of an LDAP/AD entity, accounting group or group or user.
*
* If no id attribute name is specified, use the last RDN of the entity DN
* Otherwise, use the specified id attribute value.
*
* @param dn
* @param idAttrName
* @param attrs
* @return a valid ID
* @throws NamingException
*/
private static String getId( String dn, String idAttrName, Attributes attrs ) throws NamingException {
String id = null;
// If the id-attribute is not specified, by default, use the last RDN from DN
// Else use the value of the id-attribute
if ( Strings.isNullOrEmpty( idAttrName ) ) {
id = parseIdFromDn( dn );
} else {
id = getAttrWithNullCheck( attrs, idAttrName );
}
if ( Strings.isNullOrEmpty( id ) ) {
throw new NamingException( "Empty ID for " + attrs );
}
return id.toLowerCase( );
}
private static Set<String> getMembers( String memberAttrName, Attributes attrs, final Map<String, String> dnToId ) throws NamingException {
Set<String> members = Sets.newHashSet( );
String memberItemType = lic.getMembersItemType();
String memberId = null;
String memberDn = null;
Attribute membersAttr = attrs.get( memberAttrName );
if ( membersAttr != null ) {
NamingEnumeration<?> names = membersAttr.getAll( );
while ( names.hasMore( ) ) {
if ( "identity".equals( memberItemType ) ) {
memberId = sanitizeUserGroupId( ( String ) names.next( ) );
} else {
memberDn = ( String ) names.next( );
memberId = dnToId.get( memberDn.toLowerCase( ) );
}
if ( Strings.isNullOrEmpty( memberId ) ) {
LOG.warn( "Can not map member DN " + memberDn + " to ID for " + attrs + ". Check corresponding selection section in your LIC." );
} else {
members.add( memberId.toLowerCase( ) );
}
}
}
return members;
}
private static void retrieveSelection( LdapClient ldap, String baseDn, Selection selection, String[] attrNames, LdapEntryProcessor processor ) throws LdapException {
if ( VERBOSE ) {
LOG.debug( "Search users by: baseDn=" + baseDn + ", attributes=" + attrNames + ", selection=" + selection );
}
try {
// Search by filter first.
NamingEnumeration<SearchResult> results = ldap.search( baseDn, selection.getSearchFilter( ), attrNames );
while ( results.hasMore( ) ) {
SearchResult res = results.next( );
try {
if ( !selection.getNotSelected( ).contains( res.getNameInNamespace( ) ) ) {
processor.processLdapEntry( res.getNameInNamespace( ).toLowerCase( ), res.getAttributes( ) );
}
} catch ( NamingException e ) {
LOG.debug( "Failed to retrieve entry " + res );
LOG.error( e, e );
}
}
// Get one-off DNs
for ( String dn : selection.getSelected( ) ) {
Attributes attrs = null;
try {
attrs = ldap.getContext( ).getAttributes( dn, attrNames );
processor.processLdapEntry( dn.toLowerCase( ), attrs );
} catch ( NamingException e ) {
LOG.debug( "Failed to retrieve entry " + attrs );
LOG.error( e, e );
}
}
} catch ( NamingException e ) {
LOG.error( e, e );
throw new LdapException( e );
}
}
private static void loadLdapAccountingGroups( LdapClient ldap, final LdapIntegrationConfiguration lic, final Map<String, String> groupDnToId, final Map<String, Set<String>> accountingGroups ) throws LdapException {
if ( VERBOSE ) {
LOG.debug( "Loading accounting groups from LDAP/AD" );
}
Set<String> attrNames = Sets.newHashSet( );
attrNames.add( lic.getGroupsAttribute( ) );
if ( !Strings.isNullOrEmpty( lic.getAccountingGroupIdAttribute( ) ) ) {
attrNames.add( lic.getAccountingGroupIdAttribute( ) );
}
if ( VERBOSE ) {
LOG.debug( "Attributes to load for accounting groups: " + attrNames );
}
retrieveSelection( ldap, lic.getAccountingGroupBaseDn( ), lic.getAccountingGroupsSelection( ), attrNames.toArray( new String[0] ), new LdapEntryProcessor( ) {
@Override
public void processLdapEntry( String dn, Attributes attrs ) throws NamingException {
if ( VERBOSE ) {
LOG.debug( "Retrieved accounting group: " + dn + " -> " + attrs );
}
accountingGroups.put( sanitizeAccountId( getId( dn, lic.getAccountingGroupIdAttribute( ), attrs ) ),
getMembers( lic.getGroupsAttribute( ), attrs, groupDnToId ) );
}
} );
}
private static void loadLdapGroups( LdapClient ldap, final LdapIntegrationConfiguration lic, final Map<String, String> userDnToId, final Map<String, String> groupDnToId, final Map<String, Set<String>> groups ) throws LdapException {
if ( VERBOSE ) {
LOG.debug( "Loading groups from LDAP/AD" );
}
Set<String> attrNames = Sets.newHashSet( );
attrNames.add( lic.getUsersAttribute( ) );
if ( !Strings.isNullOrEmpty( lic.getGroupIdAttribute( ) ) ) {
attrNames.add( lic.getGroupIdAttribute( ) );
}
if ( VERBOSE ) {
LOG.debug( "Attributes to load for groups: " + attrNames );
}
retrieveSelection( ldap, lic.getGroupBaseDn( ), lic.getGroupsSelection( ), attrNames.toArray( new String[0] ), new LdapEntryProcessor( ) {
@Override
public void processLdapEntry( String dn, Attributes attrs ) throws NamingException {
if ( VERBOSE ) {
LOG.debug( "Retrieved group: " + dn + " -> " + attrs );
}
String id = sanitizeUserGroupId( getId( dn, lic.getGroupIdAttribute( ), attrs ) );
groupDnToId.put( dn, id );
groups.put( id, getMembers( lic.getUsersAttribute( ), attrs, userDnToId ) );
}
} );
}
private static void loadLdapUsers( LdapClient ldap, final LdapIntegrationConfiguration lic, final Map<String, String> userDnToId, final Map<String, Map<String, String>> users ) throws LdapException {
if ( VERBOSE ) {
LOG.debug( "Loading users from LDAP/AD" );
}
// Prepare the list of attributes to retrieve
Set<String> attrNames = Sets.newHashSet( );
attrNames.addAll( lic.getUserInfoAttributes( ).keySet( ) );
if ( !Strings.isNullOrEmpty( lic.getUserIdAttribute( ) ) ) {
attrNames.add( lic.getUserIdAttribute( ) );
}
if ( !Strings.isNullOrEmpty( lic.getUserSaslIdAttribute( ) ) ) {
attrNames.add( lic.getUserSaslIdAttribute( ) );
}
if ( VERBOSE ) {
LOG.debug( "Attributes to load for users: " + attrNames );
}
// Retrieving from LDAP using a search
retrieveSelection( ldap, lic.getUserBaseDn( ), lic.getUsersSelection( ), attrNames.toArray( new String[0] ), new LdapEntryProcessor( ) {
@Override
public void processLdapEntry( String dn, Attributes attrs ) throws NamingException {
if ( VERBOSE ) {
LOG.debug( "Retrieved user: " + dn + " -> " + attrs );
}
String id = sanitizeUserGroupId( getId( dn, lic.getUserIdAttribute( ), attrs ) );
userDnToId.put( dn, id );
Map<String, String> infoMap = Maps.newHashMap( );
for ( String attrName : lic.getUserInfoAttributes( ).keySet( ) ) {
String infoKey = lic.getUserInfoAttributes( ).get( attrName );
String infoVal = getAttrWithNullCheck( attrs, attrName );
if ( infoVal != null ) {
infoMap.put( infoKey, infoVal );
}
}
infoMap.put( EuareUser.DN, dn );
if ( !Strings.isNullOrEmpty( lic.getUserSaslIdAttribute( ) ) ) {
infoMap.put( EuareUser.SASLID, getAttrWithNullCheck( attrs, lic.getUserSaslIdAttribute( ) ) );
}
users.put( id, infoMap );
}
} );
}
private static String getAttrWithNullCheck( Attributes attrs, String attrName ) throws NamingException {
Attribute attr = attrs.get( attrName );
if ( attr != null ) {
return ( ( String ) attr.get( ) ).toLowerCase( );
}
return null;
}
private static String sanitizeUserGroupId( String id ) {
if ( id != null ) {
return id.replaceAll( ValueCheckerFactory.INVALID_USERGROUPNAME_CHARSET_REGEX, "-" ).replaceAll( "-{2,}", "-" );
}
return id;
}
private static String sanitizeAccountId( String id ) {
if ( id != null ) {
return id.replaceAll( ValueCheckerFactory.INVALID_ACCOUNTNAME_CHARSET_REGEX, "-" ).replaceAll( "-{2,}", "-" );
}
return id;
}
}