org.pentaho.di.trans.steps.ldapinput.LDAPConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kettle-engine Show documentation
Show all versions of kettle-engine Show documentation
Container pom for Pentaho Data Integration modules
The newest version!
/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2018 by Hitachi Vantara : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed 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.pentaho.di.trans.steps.ldapinput;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl;
import javax.naming.ldap.SortControl;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.core.row.RowMeta;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.row.value.ValueMetaFactory;
import org.pentaho.di.core.variables.VariableSpace;
import org.pentaho.di.i18n.BaseMessages;
public class LDAPConnection {
private static Class> PKG = LDAPInputMeta.class; // for i18n purposes, needed by Translator2!!
public static final int SEARCH_SCOPE_OBJECT_SCOPE = 0;
public static final int SEARCH_SCOPE_ONELEVEL_SCOPE = 1;
public static final int SEARCH_SCOPE_SUBTREE_SCOPE = 2;
public static final int DEFAULT_PORT = 389;
public static final String DEFAUL_FILTER_STRING = "objectclass=*";
public static final int STATUS_SKIPPED = 0;
public static final int STATUS_INSERTED = 1;
public static final int STATUS_UPDATED = 2;
public static final int STATUS_DELETED = 3;
public static final int STATUS_ADDED = 4;
private final LogChannelInterface log;
private String searchBase;
private String filter;
private SearchControls controls;
private int timeLimit;
private int pagingSize;
private NamingEnumeration results;
private List sortingAttributes;
private String[] sortingAttributesKeys;
private LdapProtocol protocol;
/**
* Construct a new LDAP Connection
*/
public LDAPConnection( LogChannelInterface logInterface, VariableSpace variableSpace, LdapMeta meta,
Collection binaryAttributes ) throws KettleException {
this.log = logInterface;
protocol = new LdapProtocolFactory( logInterface ).createLdapProtocol( variableSpace, meta, binaryAttributes );
this.sortingAttributes = new ArrayList();
}
/**
* Connect to LDAP server
*
* @throws KettleException
*/
public void connect() throws KettleException {
connect( null, null );
}
/**
* Connect to LDAP server
*
* @param username
* : username
* @param password
* : password
* @throws KettleException
*/
public void connect( String username, String password ) throws KettleException {
protocol.connect( username, password );
}
public void setSortingAttributesKeys( String[] value ) {
this.sortingAttributesKeys = value;
}
private String[] getSortingAttributesKeys() {
return this.sortingAttributesKeys;
}
public void addSortingAttributes( String value ) {
this.sortingAttributes.add( value );
}
public List getSortingAttributes() {
return this.sortingAttributes;
}
private boolean isSortingAttributes() {
return ( !this.sortingAttributes.isEmpty() );
}
private void setFilter( String filter ) {
this.filter = filter;
}
private String getFilter() {
return this.filter;
}
private void setSearchBase( String searchBase ) {
this.searchBase = searchBase;
}
private String getSearchBase() {
return this.searchBase;
}
public void setTimeLimit( int timeLimit ) {
this.timeLimit = timeLimit;
}
public int getTimeLimit() {
return this.timeLimit;
}
public void SetPagingSize( int value ) {
this.pagingSize = value;
}
private int GetPagingSize() {
return this.pagingSize;
}
private boolean isPagingUsed() {
return ( GetPagingSize() > 0 );
}
public void search( String searchBase, String filter, int limitRows, String[] attributeReturned, int searchScope ) throws KettleException {
// Set the Search base.This is the place where the search will
setSearchBase( searchBase );
setFilter( Const.NVL( correctFilter( filter ), DEFAUL_FILTER_STRING ) );
try {
if ( Utils.isEmpty( getSearchBase() ) ) {
// get Search Base
Attributes attrs = getInitialContext().getAttributes( "", new String[] { "namingContexts" } );
Attribute attr = attrs.get( "namingContexts" );
setSearchBase( attr.get().toString() );
if ( log.isDetailed() ) {
log.logDetailed( BaseMessages.getString( PKG, "LDAPInput.SearchBaseFound", getSearchBase() ) );
}
}
this.controls = new SearchControls();
if ( limitRows > 0 ) {
this.controls.setCountLimit( limitRows );
}
// Time Limit
if ( getTimeLimit() > 0 ) {
this.controls.setTimeLimit( getTimeLimit() * 1000 );
}
// Specify the attributes to return
if ( attributeReturned != null ) {
this.controls.setReturningAttributes( attributeReturned );
}
// Specify the search scope
switch ( searchScope ) {
case SEARCH_SCOPE_OBJECT_SCOPE:
this.controls.setSearchScope( SearchControls.OBJECT_SCOPE );
break;
case SEARCH_SCOPE_ONELEVEL_SCOPE:
this.controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
break;
default:
this.controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
break;
}
Control ctlp = null;
Control ctlk = null;
int nrCtl = 0;
// Set the sort search?
if ( isSortingAttributes() ) {
// Create a sort control that sorts based on attributes
setSortingAttributesKeys( getSortingAttributes().toArray( new String[getSortingAttributes().size()] ) );
ctlk = new SortControl( getSortingAttributesKeys(), Control.NONCRITICAL );
nrCtl++;
if ( log.isDebug() ) {
log.logDebug( BaseMessages
.getString( "LDAPInput.Log.SortingKeys", Arrays.toString( getSortingAttributesKeys() ) ) );
}
}
// Set the page size?
if ( isPagingUsed() ) {
// paging is activated
// Request the paged results control
ctlp = new PagedResultsControl( GetPagingSize(), Control.CRITICAL );
nrCtl++;
if ( log.isDebug() ) {
log.logDebug( BaseMessages.getString( "LDAPInput.Log.PageSize", String.valueOf( GetPagingSize() ) ) );
}
}
if ( nrCtl > 0 ) {
Control[] ctls = new Control[nrCtl];
int index = 0;
if ( ctlk != null ) {
ctls[index++] = ctlk;
}
if ( ctlp != null ) {
ctls[index++] = ctlp;
}
getInitialContext().setRequestControls( ctls );
}
// Search for objects using the filter
this.results = getInitialContext().search( getSearchBase(), getFilter(), getSearchControls() );
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( "LDAPConnection.Error.Search" ), e );
}
}
public int delete( String dn, boolean checkEntry ) throws KettleException {
try {
if ( checkEntry ) {
// First Check entry
getInitialContext().lookup( dn );
}
// The entry exists
getInitialContext().destroySubcontext( dn );
if ( log.isDebug() ) {
log.logDebug( BaseMessages.getString( PKG, "LDAPinput.Exception.Deleted", dn ) );
}
return STATUS_DELETED;
} catch ( NameNotFoundException n ) {
// The entry is not found
if ( checkEntry ) {
throw new KettleException(
BaseMessages.getString( PKG, "LDAPConnection.Error.Deleting.NameNotFound", dn ), n );
}
return STATUS_SKIPPED;
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "LDAPConnection.Error.Delete", dn ), e );
}
}
public int update( String dn, String[] attributes, String[] values, boolean checkEntry ) throws KettleException {
try {
int nrAttributes = attributes.length;
ModificationItem[] mods = new ModificationItem[nrAttributes];
for ( int i = 0; i < nrAttributes; i++ ) {
// Define attribute
Attribute mod = new BasicAttribute( attributes[i], values[i] );
if ( log.isDebug() ) {
log
.logDebug( BaseMessages.getString( PKG, "LDAPConnection.Update.Attribute", attributes[i], values[i] ) );
}
// Save update action on attribute
mods[i] = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, mod );
}
// We have all requested attribute
// let's update now
getInitialContext().modifyAttributes( dn, mods );
return STATUS_UPDATED;
} catch ( NameNotFoundException n ) {
// The entry is not found
if ( checkEntry ) {
throw new KettleException(
BaseMessages.getString( PKG, "LDAPConnection.Error.Deleting.NameNotFound", dn ), n );
}
return STATUS_SKIPPED;
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "LDAPConnection.Error.Update", dn ), e );
}
}
public int add( String dn, String[] attributes, String[] values, String multValuedSeparator, boolean checkEntry ) throws KettleException {
try {
Attributes attrs = buildAttributes( dn, attributes, values, multValuedSeparator );
// We had all attributes
getInitialContext().modifyAttributes( dn, DirContext.ADD_ATTRIBUTE, attrs );
return STATUS_ADDED;
} catch ( NameNotFoundException n ) {
// The entry is not found
if ( checkEntry ) {
throw new KettleException(
BaseMessages.getString( PKG, "LDAPConnection.Error.Deleting.NameNotFound", dn ), n );
}
return STATUS_SKIPPED;
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "LDAPConnection.Error.Add", dn ), e );
}
}
/**
* Insert record in LDAP based on DN
*
* @param dn
* : Distinguished Name (Key for lookup)
* @param attributes
* : contains all the attributes to set for insert
* @param values
* : contains all the values for attributes
* @param multValuedSeparator
* : multi-valued attributes separator
* @throws KettleException
*/
public void insert( String dn, String[] attributes, String[] values, String multValuedSeparator ) throws KettleException {
try {
Attributes attrs = buildAttributes( dn, attributes, values, multValuedSeparator );
// We had all attributes
// Let's insert now
getInitialContext().createSubcontext( dn, attrs );
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "LDAPConnection.Error.Insert", dn ), e );
}
}
/**
* Upsert record in LDAP First we will check if the entry exist based on DN If we can not find it, we will create it
* otherwise, we will perform an update
*
* @param dn
* : Distinguished Name (Key for lookup)
* @param attributes
* : contains all the attributes to set for insert
* @param values
* : contains all the values for attributes
* @param attributesToUpdate
* : contains attributes to update
* @param valuesToUpdate
* : contains values for attributes to update
* @param multValuedSeparator
* : multi-valued attributes separator
* @return status : STATUS_INSERTED, STATUS_UPDATED or STATUS_SKIPPED
* @throws KettleException
*/
public int upsert( String dn, String[] attributes, String[] values, String[] attributesToUpdate,
String[] valuesToUpdate, String multValuedSeparator ) throws KettleException {
try {
boolean found = false;
try {
getInitialContext().getAttributes( dn );
found = true;
} catch ( NameNotFoundException n ) {
Attributes attrs = buildAttributes( dn, attributes, values, multValuedSeparator );
getInitialContext().createSubcontext( dn, attrs );
return STATUS_INSERTED;
}
if ( found && attributesToUpdate != null && attributesToUpdate.length > 0 ) {
// The entry already exist
// let's update
Attributes attrs = buildAttributes( dn, attributesToUpdate, valuesToUpdate, multValuedSeparator );
getInitialContext().modifyAttributes( dn, DirContext.REPLACE_ATTRIBUTE, attrs );
return STATUS_UPDATED;
}
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "LDAPConnection.Error.Upsert", dn ), e );
}
return STATUS_SKIPPED;
}
private Attributes buildAttributes( String dn, String[] attributes, String[] values, String multValuedSeparator ) {
Attributes attrs = new javax.naming.directory.BasicAttributes( true );
int nrAttributes = attributes.length;
for ( int i = 0; i < nrAttributes; i++ ) {
if ( !Utils.isEmpty( values[i] ) ) {
// We have a value
String value = values[i].trim();
if ( multValuedSeparator != null && value.indexOf( multValuedSeparator ) > 0 ) {
Attribute attr = new javax.naming.directory.BasicAttribute( attributes[i] );
for ( String attribute : value.split( multValuedSeparator ) ) {
attr.add( attribute );
}
attrs.put( attr );
} else {
attrs.put( attributes[i], value );
}
}
}
return attrs;
}
/**
* Rename an entry
*
* @param oldDn
* Distinguished name of the entry to rename
* @param newDn
* target Distinguished name (new)
* @param deleteRDN
* To specify whether you want to keep the old name attribute when you use rename entry true : do not keep
* the old value (defaut) false : keep the old value as an attribute
* @throws KettleException
*/
public void rename( String oldDn, String newDn, boolean deleteRDN ) throws KettleException {
try {
if ( !deleteRDN ) {
// Keep the old dn as attribute
getInitialContext().removeFromEnvironment( "java.naming.ldap.deleteRDN" );
}
Map childs = new java.util.HashMap();
List paths = new ArrayList();
getPaths( oldDn, childs, paths );
// Destroy sub contexts
for ( String childName : paths ) {
getInitialContext().destroySubcontext( childName );
}
// Rename entry
try {
getInitialContext().rename( oldDn, newDn );
} catch ( Exception e ) {
// something goes wrong
// re attached removed sub contexts
for ( int i = paths.size(); i > 0; i-- ) {
getInitialContext().createSubcontext( paths.get( i - 1 ), childs.get( paths.get( i - 1 ) ) );
}
throw e;
}
// attach sub context
List newpaths = new ArrayList();
for ( String childName : paths ) {
newpaths.add( childName.replaceAll( oldDn, newDn ) );
}
for ( int i = newpaths.size(); i > 0; i-- ) {
getInitialContext().createSubcontext( newpaths.get( i - 1 ), childs.get( paths.get( i - 1 ) ) );
}
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "LDAPConnection.Error.Renaming", oldDn, newDn ), e );
} finally {
try {
if ( !deleteRDN ) {
// Delete the old dn as attribute
// switch back to default value
getInitialContext().addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
}
} catch ( Exception e ) {
// Ignore errors
}
}
}
@SuppressWarnings( "rawtypes" )
private void getPaths( String rootName, Map childs, List paths ) throws Exception {
NamingEnumeration ne = getInitialContext().list( rootName );
while ( ne.hasMore() ) {
NameClassPair nameCP = (NameClassPair) ne.next();
childs.put( nameCP.getName() + "," + rootName, getInitialContext().getAttributes(
nameCP.getName() + "," + rootName ) );
getPaths( nameCP.getName() + "," + rootName, childs, paths );
paths.add( nameCP.getName() + "," + rootName );
}
}
/**
* Close the LDAP connection
*
* @throws KettleException
*/
public void close() throws KettleException {
if ( protocol != null ) {
try {
protocol.close();
} catch ( KettleException e ) {
throw e;
} finally {
protocol = null;
if ( results != null ) {
results = null;
}
}
}
}
public Attributes getAttributes() throws KettleException {
byte[] cookie = null;
while ( !getSearchResult().hasMoreElements() ) {
if ( isPagingUsed() ) {
// we are using paging...
// we need here to check the response controls
// and pass back cookie to next page
try {
// examine response controls
Control[] rc = getInitialContext().getResponseControls();
if ( rc != null ) {
for ( int i = 0; i < rc.length; i++ ) {
if ( rc[i] instanceof PagedResultsResponseControl ) {
PagedResultsResponseControl prc = (PagedResultsResponseControl) rc[i];
cookie = prc.getCookie();
}
}
}
// pass the cookie back for the next page
if ( isSortingAttributes() ) {
getInitialContext().setRequestControls(
new Control[] {
new SortControl( getSortingAttributesKeys(), Control.NONCRITICAL ),
new PagedResultsControl( GetPagingSize(), cookie, Control.CRITICAL ) } );
} else {
getInitialContext().setRequestControls(
new Control[] { new PagedResultsControl( GetPagingSize(), cookie, Control.CRITICAL ) } );
}
if ( ( cookie != null ) && ( cookie.length != 0 ) ) {
// get search result for the page
this.results = getInitialContext().search( getSearchBase(), getFilter(), getSearchControls() );
} else {
return null;
}
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "LDAPInput.Exception.ErrorPaging" ), e );
}
while ( !getSearchResult().hasMoreElements() ) {
return null;
}
} else {
// User do not want to use paging
// we have already returned all the result
return null;
}
}
try {
SearchResult searchResult = getSearchResult().next();
Attributes results = searchResult.getAttributes();
results.put( "dn", searchResult.getNameInNamespace() );
return results;
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "LDAPConnection.Exception.GettingAttributes" ), e );
}
}
private InitialLdapContext getInitialContext() {
return protocol.getCtx();
}
private SearchControls getSearchControls() {
return this.controls;
}
private NamingEnumeration getSearchResult() {
return this.results;
}
/**
* Remove CR and LF from filter string
*
* @param filter
* @return corrected filter
*/
private static String correctFilter( String filter ) {
return Utils.isEmpty( filter ) ? "" : filter.replaceAll( "(\\r|\\n)", "" );
}
public static String extractBytesAndConvertToString( Attribute attr, boolean isSID ) throws Exception {
byte[] b;
try {
b = (byte[]) attr.get();
} catch ( Exception e ) {
// Get bytes from String
b = attr.get().toString().getBytes();
}
if ( isSID ) {
return getSIDAsString( b );
} else {
return byteToHexEncode( b );
}
}
/**
* Convert the SID into string format
*
* @param SID
* @return String representation of SID
*/
private static String getSIDAsString( byte[] SID ) {
long version;
long authority;
long count;
long rid;
String strSID;
strSID = "S";
version = SID[0];
strSID = strSID + "-" + Long.toString( version );
authority = SID[4];
for ( int i = 0; i < 4; i++ ) {
authority <<= 8;
authority += SID[4 + i] & 0xFF;
}
strSID = strSID + "-" + Long.toString( authority );
count = SID[2];
count <<= 8;
count += SID[1] & 0xFF;
for ( int j = 0; j < count; j++ ) {
rid = SID[11 + ( j * 4 )] & 0xFF;
for ( int k = 1; k < 4; k++ ) {
rid <<= 8;
rid += SID[11 - k + ( j * 4 )] & 0xFF;
}
strSID = strSID + "-" + Long.toString( rid );
}
return strSID;
}
/**
* Converts the GUID to a readable string format
*
* @param inArr
* @return the formatted GUID
*/
private static String byteToHexEncode( byte[] inArr ) {
StringBuilder guid = new StringBuilder();
for ( int i = 0; i < inArr.length; i++ ) {
StringBuilder dblByte = new StringBuilder( Integer.toHexString( inArr[i] & 0xff ) );
if ( dblByte.length() == 1 ) {
guid.append( "0" );
}
guid.append( dblByte );
}
return guid.toString();
}
public RowMeta getFields( String searchBase ) throws KettleException {
RowMeta fields = new RowMeta();
List fieldsl = new ArrayList();
try {
search( searchBase, null, 0, null, SEARCH_SCOPE_SUBTREE_SCOPE );
Attributes attributes = null;
fieldsl = new ArrayList();
while ( ( attributes = getAttributes() ) != null ) {
NamingEnumeration extends Attribute> ne = attributes.getAll();
while ( ne.hasMore() ) {
Attribute attr = ne.next();
String fieldName = attr.getID();
if ( !fieldsl.contains( fieldName ) ) {
fieldsl.add( fieldName );
String attributeValue = attr.get().toString();
int valueType;
// Try to determine the data type
//
if ( IsDate( attributeValue ) ) {
valueType = ValueMetaInterface.TYPE_DATE;
} else if ( IsInteger( attributeValue ) ) {
valueType = ValueMetaInterface.TYPE_INTEGER;
} else if ( IsNumber( attributeValue ) ) {
valueType = ValueMetaInterface.TYPE_NUMBER;
} else {
valueType = ValueMetaInterface.TYPE_STRING;
}
ValueMetaInterface value = ValueMetaFactory.createValueMeta( fieldName, valueType );
fields.addValueMeta( value );
}
}
}
return fields;
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "LDAPConnection.Error.RetrievingFields" ) );
} finally {
fieldsl = null;
}
}
private boolean IsNumber( String str ) {
try {
Float.parseFloat( str );
} catch ( Exception e ) {
return false;
}
return true;
}
private boolean IsDate( String str ) {
// TODO: What about other dates? Maybe something for a CRQ
try {
SimpleDateFormat fdate = new SimpleDateFormat( "yy-mm-dd" );
fdate.parse( str );
} catch ( Exception e ) {
return false;
}
return true;
}
private boolean IsInteger( String str ) {
try {
Integer.parseInt( str );
} catch ( NumberFormatException e ) {
return false;
}
return true;
}
}