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 */
020
021package org.apache.directory.ldap.client.api;
022
023
024import java.io.BufferedReader;
025import java.io.File;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.InputStreamReader;
029import java.io.PrintStream;
030import java.io.Writer;
031import java.nio.charset.Charset;
032import java.nio.file.Files;
033import java.nio.file.Paths;
034import java.util.ArrayList;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.List;
038import java.util.Map;
039import java.util.Set;
040
041import org.apache.directory.api.ldap.model.constants.SchemaConstants;
042import org.apache.directory.api.ldap.model.entry.Attribute;
043import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
044import org.apache.directory.api.ldap.model.entry.DefaultEntry;
045import org.apache.directory.api.ldap.model.entry.DefaultModification;
046import org.apache.directory.api.ldap.model.entry.Entry;
047import org.apache.directory.api.ldap.model.entry.Modification;
048import org.apache.directory.api.ldap.model.entry.StringValue;
049import org.apache.directory.api.ldap.model.entry.Value;
050import org.apache.directory.api.ldap.model.exception.LdapException;
051import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
052import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
053import org.apache.directory.api.ldap.model.ldif.ChangeType;
054import org.apache.directory.api.ldap.model.ldif.LdifEntry;
055import org.apache.directory.api.ldap.model.ldif.LdifReader;
056import org.apache.directory.api.ldap.model.ldif.LdifUtils;
057import org.apache.directory.api.ldap.model.ldif.anonymizer.Anonymizer;
058import org.apache.directory.api.ldap.model.ldif.anonymizer.BinaryAnonymizer;
059import org.apache.directory.api.ldap.model.ldif.anonymizer.CaseSensitiveStringAnonymizer;
060import org.apache.directory.api.ldap.model.ldif.anonymizer.IntegerAnonymizer;
061import org.apache.directory.api.ldap.model.ldif.anonymizer.StringAnonymizer;
062import org.apache.directory.api.ldap.model.ldif.anonymizer.TelephoneNumberAnonymizer;
063import org.apache.directory.api.ldap.model.name.Ava;
064import org.apache.directory.api.ldap.model.name.Dn;
065import org.apache.directory.api.ldap.model.name.Rdn;
066import org.apache.directory.api.ldap.model.schema.AttributeType;
067import org.apache.directory.api.ldap.model.schema.LdapSyntax;
068import org.apache.directory.api.ldap.model.schema.SchemaManager;
069import org.apache.directory.api.ldap.model.schema.syntaxCheckers.DnSyntaxChecker;
070import org.apache.directory.api.ldap.model.schema.syntaxCheckers.NameAndOptionalUIDSyntaxChecker;
071import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager;
072
073
074/**
075 * Anonymize the content of a LDIF file.
076 * 
077 * We will replace the values of the defined attributes with random chars. There are a default
078 * list of attributes that are going to be anonymized :
079 * <ul>
080 * <li>userPassword</li>
081 * <li>displayName</li>
082 * <li>givenName</li>
083 * <li>surName</li>
084 * <li>homePhone</li>
085 * <li>homePostalAddress</li>
086 * <li>jpegPhoto</li>
087 * <li>labeledURI</li>
088 * <li>mail</li>
089 * <li>manager</li>
090 * <li>mobile</li>
091 * <li>organizationName</li>
092 * <li>pager</li>
093 * <li>photo</li>
094 * <li>secretary</li>
095 * <li>uid</li>
096 * <li>userCertificate</li>
097 * <li>userPKCS12</li>
098 * <li>userSMIMECertificate</li>
099 * <li>x500UniqueIdentifier</li>
100 * <li>carLicense</li>
101 * <li>host</li>
102 * <li>locality</li>
103 * <li>organizationName</li>
104 * <li>organizationalUnitName</li>
105 * <li>seelAlso</li>
106 * <li>homeDirectory</li>
107 * <li>uidNumber</li>
108 * <li>gidNumber</li>
109 * <li>commonName</li>
110 * <li>gecos</li>
111 * <li>description</li>
112 * <li>memberUid</li>
113 * </ul>
114 *
115 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
116 */
117public class LdifAnonymizer
118{
119    /** The map that stores the anonymized values associated to the original value */
120    private Map<Value<?>, Value<?>> valueMap = new HashMap<>();
121    
122    /** The set that contains all the values we already have anonymized */
123    private Set<Value<?>> valueSet = new HashSet<>();
124    
125    /** The latest anonymized String value Map */
126    private Map<Integer, String> latestStringMap;
127    
128    /** The latest anonymized byte[] value Map */
129    private Map<Integer, byte[]> latestBytesMap;
130    
131    /** The map of AttributeType'sOID we want to anonymize. They are all associated with anonymizers */
132    private Map<String, Anonymizer> attributeAnonymizers = new HashMap<>();
133    
134    /** The list of existing NamingContexts */
135    private Set<Dn> namingContexts = new HashSet<>();
136
137    /** The schemaManager */
138    private SchemaManager schemaManager;
139    
140    /** The PrintStream used to write informations about the processing */
141    private PrintStream out = null;
142
143    /**
144     * Creates a default instance of LdifAnonymizer. The list of anonymized attribute
145     * is set to a default value.
146     *
147     */
148    public LdifAnonymizer()
149    {
150        try
151        {
152            schemaManager = new DefaultSchemaManager();
153        }
154        catch ( Exception e )
155        {
156            // Todo : we need a schemaManager
157            println( "Missing a SchemaManager !" );
158            System.exit( -1 );
159        }
160
161        init( null, null, null, null );
162    }
163    
164
165    /**
166     * Creates a default instance of LdifAnonymizer. The list of anonymized attribute
167     * is set to a default value.
168     * 
169     * @param schemaManager The SchemaManager instance we will use
170     */
171    public LdifAnonymizer( SchemaManager schemaManager )
172    {
173        this.schemaManager = schemaManager;
174
175        init( null, null, null, null );
176    }
177    
178    
179    /**
180     * Set the PrintStream to use to print information about the processing
181     * 
182     * @param out The PrintStream to use
183     */
184    public void setOut( PrintStream out )
185    {
186        this.out = out;
187    }
188    
189    
190    /**
191     * Print the string into the PrintStream
192     */
193    private void print( String str )
194    {
195        if ( out != null )
196        {
197            out.print( str );
198        }
199    }
200    
201    
202    /**
203     * Print the string into the PrintStream, with a NL at the end
204     */
205    private void println( String str )
206    {
207        if ( out != null )
208        {
209            out.println( str );
210        }
211    }
212    
213    
214    /**
215     * Print a nl into the PrintStream
216     */
217    private void println()
218    {
219        if ( out != null )
220        {
221            out.println();
222        }
223    }
224    
225    
226    /**
227     * Initialize the anonymizer, filling the maps we use.
228     */
229    private void init( Map<Integer, String> stringLatestValueMap, Map<Integer, byte[]> binaryLatestValueMap, 
230        Map<Integer, String> integerLatestValueMap, Map<Integer, String> telephoneNumberLatestValueMap )
231    {
232        // Load the anonymizers
233        attributeAnonymizers.put( SchemaConstants.CAR_LICENSE_AT_OID,
234            new StringAnonymizer( stringLatestValueMap ) );
235        attributeAnonymizers.put( SchemaConstants.DOMAIN_COMPONENT_AT_OID,
236            new StringAnonymizer( stringLatestValueMap ) );
237        attributeAnonymizers.put( SchemaConstants.CN_AT_OID, new StringAnonymizer( stringLatestValueMap ) );
238        attributeAnonymizers.put( SchemaConstants.DESCRIPTION_AT_OID,
239            new StringAnonymizer( stringLatestValueMap ) );
240        attributeAnonymizers.put( SchemaConstants.DISPLAY_NAME_AT_OID,
241            new StringAnonymizer( stringLatestValueMap ) );
242        attributeAnonymizers.put( SchemaConstants.GECOS_AT_OID, new StringAnonymizer( stringLatestValueMap ) );
243        attributeAnonymizers.put( SchemaConstants.GID_NUMBER_AT_OID,
244            new IntegerAnonymizer( integerLatestValueMap ) );
245        attributeAnonymizers.put( SchemaConstants.GIVENNAME_AT_OID,
246            new StringAnonymizer( stringLatestValueMap ) );
247        attributeAnonymizers.put( SchemaConstants.HOME_DIRECTORY_AT_OID,
248            new CaseSensitiveStringAnonymizer( stringLatestValueMap ) );
249        attributeAnonymizers.put( SchemaConstants.HOME_PHONE_AT_OID,
250            new TelephoneNumberAnonymizer() );
251        attributeAnonymizers.put( SchemaConstants.HOME_POSTAL_ADDRESS_AT_OID,
252            new StringAnonymizer( stringLatestValueMap ) );
253        attributeAnonymizers.put( SchemaConstants.HOST_AT_OID, new StringAnonymizer( stringLatestValueMap ) );
254        attributeAnonymizers.put( SchemaConstants.HOUSE_IDENTIFIER_AT_OID,
255            new StringAnonymizer( stringLatestValueMap ) );
256        attributeAnonymizers.put( SchemaConstants.JPEG_PHOTO_AT_OID,
257            new BinaryAnonymizer( binaryLatestValueMap ) );
258        attributeAnonymizers.put( SchemaConstants.LABELED_URI_AT_OID,
259            new CaseSensitiveStringAnonymizer( stringLatestValueMap ) );
260        attributeAnonymizers.put( SchemaConstants.LOCALITY_NAME_AT_OID,
261            new StringAnonymizer( stringLatestValueMap ) );
262        attributeAnonymizers.put( SchemaConstants.MAIL_AT_OID, new StringAnonymizer( stringLatestValueMap ) );
263        attributeAnonymizers.put( SchemaConstants.MANAGER_AT_OID, new StringAnonymizer( stringLatestValueMap ) );
264        attributeAnonymizers.put( SchemaConstants.MEMBER_UID_AT_OID,
265            new StringAnonymizer( stringLatestValueMap ) );
266        attributeAnonymizers.put( SchemaConstants.MOBILE_AT_OID, new TelephoneNumberAnonymizer() );
267        attributeAnonymizers.put( SchemaConstants.ORGANIZATION_NAME_AT_OID,
268            new StringAnonymizer( stringLatestValueMap ) );
269        attributeAnonymizers.put( SchemaConstants.ORGANIZATIONAL_UNIT_NAME_AT_OID,
270            new StringAnonymizer( stringLatestValueMap ) );
271        attributeAnonymizers.put( SchemaConstants.PAGER_AT_OID, new TelephoneNumberAnonymizer() );
272        attributeAnonymizers.put( SchemaConstants.POSTAL_ADDRESS_AT_OID,
273            new StringAnonymizer( stringLatestValueMap ) );
274        attributeAnonymizers.put( SchemaConstants.PHOTO_AT_OID, new BinaryAnonymizer( binaryLatestValueMap ) );
275        attributeAnonymizers.put( SchemaConstants.SECRETARY_AT_OID,
276            new StringAnonymizer( stringLatestValueMap ) );
277        attributeAnonymizers
278            .put( SchemaConstants.SEE_ALSO_AT_OID, new StringAnonymizer( stringLatestValueMap ) );
279        attributeAnonymizers.put( SchemaConstants.SN_AT_OID, new StringAnonymizer( stringLatestValueMap ) );
280        attributeAnonymizers.put( SchemaConstants.TELEPHONE_NUMBER_AT_OID,
281            new TelephoneNumberAnonymizer( telephoneNumberLatestValueMap ) );
282        attributeAnonymizers.put( SchemaConstants.UID_AT_OID, new StringAnonymizer( stringLatestValueMap ) );
283        attributeAnonymizers.put( SchemaConstants.UID_NUMBER_AT_OID,
284            new IntegerAnonymizer( integerLatestValueMap ) );
285        attributeAnonymizers.put( SchemaConstants.USER_CERTIFICATE_AT_OID,
286            new BinaryAnonymizer( binaryLatestValueMap ) );
287        attributeAnonymizers.put( SchemaConstants.USER_PASSWORD_AT_OID,
288            new BinaryAnonymizer( binaryLatestValueMap ) );
289        attributeAnonymizers.put( SchemaConstants.USER_PKCS12_AT_OID,
290            new BinaryAnonymizer( binaryLatestValueMap ) );
291        attributeAnonymizers.put( SchemaConstants.USER_SMIME_CERTIFICATE_AT_OID,
292            new BinaryAnonymizer( binaryLatestValueMap ) );
293        attributeAnonymizers.put( SchemaConstants.X500_UNIQUE_IDENTIFIER_AT_OID,
294            new BinaryAnonymizer( binaryLatestValueMap ) );
295        attributeAnonymizers.put( SchemaConstants.FACSIMILE_TELEPHONE_NUMBER_AT_OID,
296            new TelephoneNumberAnonymizer( telephoneNumberLatestValueMap ) );
297    }
298    
299    
300    /**
301     * Set the latest value map to a defined anonymizer - if it exists -.
302     *
303     * @param attributeType The AttributeType we are targetting
304     * @param latestValueMap The latest value map for this attribute
305     */
306    public void setAttributeLatestValueMap( AttributeType attributeType, Map<Integer, ?> latestValueMap )
307    {
308        Anonymizer anonymizer = attributeAnonymizers.get( attributeType.getOid() );
309        
310        if ( anonymizer != null )
311        {
312            if ( attributeType.getSyntax().isHumanReadable() )
313            {
314                anonymizer.setLatestStringMap( latestValueMap );
315            }
316            else
317            {
318                anonymizer.setLatestBytesMap( latestValueMap );
319            }
320        }
321    }
322    
323    
324    /**
325     * Add an attributeType that has to be anonymized
326     *
327     * @param attributeType the AttributeType that has to be anonymized
328     * @throws LdapException If the attributeType cannot be added
329     */
330    public void addAnonAttributeType( AttributeType attributeType ) throws LdapException
331    {
332        schemaManager.add( attributeType );
333        LdapSyntax syntax = attributeType.getSyntax();
334        
335        if ( syntax.isHumanReadable() )
336        {
337            if ( syntax.getOid().equals( SchemaConstants.INTEGER_SYNTAX ) )
338            {
339                attributeAnonymizers.put( attributeType.getOid(), new IntegerAnonymizer() );
340            }
341            else if ( syntax.getOid().equals( SchemaConstants.DIRECTORY_STRING_SYNTAX ) )
342            {
343                attributeAnonymizers.put( attributeType.getOid(), new StringAnonymizer() );
344            }
345            else if ( syntax.getOid().equals( SchemaConstants.TELEPHONE_NUMBER_SYNTAX ) )
346            {
347                attributeAnonymizers.put( attributeType.getOid(), new TelephoneNumberAnonymizer() );
348            }
349        }
350        else
351        {
352            attributeAnonymizers.put( attributeType.getOid(), new BinaryAnonymizer() );
353        }
354    }
355    
356    
357    /**
358     * Add an attributeType that has to be anonymized, with its associated anonymizer.
359     *
360     * @param attributeType the AttributeType that has to be anonymized
361     * @param anonymizer the instance of anonymizer to use with this AttributeType
362     * @throws LdapException If the attributeType cannot be added
363     */
364    public void addAnonAttributeType( AttributeType attributeType, Anonymizer<?> anonymizer ) throws LdapException
365    {
366        schemaManager.add( attributeType );
367        attributeAnonymizers.put( attributeType.getOid(), anonymizer );
368    }
369    
370    
371    /**
372     * Remove an attributeType that has to be anonymized
373     *
374     * @param attributeType the AttributeType that we don't want to be anonymized
375     * @throws LdapException If the attributeType cannot be removed
376     */
377    public void removeAnonAttributeType( AttributeType attributeType ) throws LdapException
378    {
379        attributeAnonymizers.remove( attributeType );
380    }
381    
382    
383    /**
384     * @return The list of configured anonymizers
385     */
386    public Map<String, Anonymizer> getAttributeAnonymizers()
387    {
388        return attributeAnonymizers;
389    }
390    
391    /**
392     * Add a new NamingContext
393     *
394     * @param dn The naming context to add
395     * @throws LdapInvalidDnException if it's an invalid naming context
396     */
397    public void addNamingContext( String dn ) throws LdapInvalidDnException
398    {
399        Dn namingContext = new Dn( schemaManager, dn );
400        namingContexts.add( namingContext );
401    }
402
403    
404    /**
405     * Anonymize an AVA
406     */
407    private Ava anonymizeAva( Ava ava ) throws LdapInvalidDnException, LdapInvalidAttributeValueException
408    {
409        Value<?> value = ava.getValue();
410        AttributeType attributeType = ava.getAttributeType();
411        Value<?> anonymizedValue = valueMap.get( value );
412        Ava anonymizedAva;
413        
414        if ( anonymizedValue == null )
415        {
416            Attribute attribute = new DefaultAttribute( attributeType );
417            attribute.add( value );
418            Anonymizer anonymizer = attributeAnonymizers.get( attribute.getAttributeType().getOid() );
419
420            if ( value.isHumanReadable() )
421            {
422                if ( anonymizer == null )
423                {
424                    anonymizedAva = new Ava( schemaManager, ava.getType(), value.getString() );
425                }
426                else
427                {
428                    Attribute anonymizedAttribute = anonymizer.anonymize( valueMap, valueSet, attribute );
429                    anonymizedAva = new Ava( schemaManager, ava.getType(), anonymizedAttribute.getString() );
430                }
431            }
432            else
433            {
434                if ( anonymizer == null )
435                {
436                    anonymizedAva = new Ava( schemaManager, ava.getType(), value.getBytes() );
437                }
438                else
439                {
440                    Attribute anonymizedAttribute = anonymizer.anonymize( valueMap, valueSet, attribute );
441
442                    anonymizedAva = new Ava( schemaManager, ava.getType(), anonymizedAttribute.getBytes() );
443                }
444            }
445        }
446        else
447        {
448            if ( value.isHumanReadable() )
449            {
450                anonymizedAva = new Ava( schemaManager, ava.getType(), anonymizedValue.getString() );
451            }
452            else
453            {
454                anonymizedAva = new Ava( schemaManager, ava.getType(), anonymizedValue.getBytes() );
455            }
456        }
457
458        return anonymizedAva;
459    }
460    
461    
462    /**
463     * Anonymize the entry's DN
464     */
465    private Dn anonymizeDn( Dn entryDn ) throws LdapException
466    {
467        // Search for the naming context
468        Dn descendant = entryDn;
469        Dn namingContext = null;
470        
471        for ( Dn nc : namingContexts )
472        {
473            if ( entryDn.isDescendantOf( nc ) )
474            { 
475                descendant = entryDn.getDescendantOf( nc );
476                namingContext = nc;
477                break;
478            }
479        }
480        
481        Rdn[] anonymizedRdns = new Rdn[entryDn.size()];
482        int rdnPos = entryDn.size() - 1;
483
484        if ( namingContext != null )
485        {
486            // Copy the naming contex
487            for ( Rdn ncRdn : namingContext )
488            {
489                anonymizedRdns[rdnPos] = ncRdn;
490                rdnPos--;
491            }
492        }
493        
494        // Iterate on all the RDN
495        for ( Rdn rdn : descendant )
496        {
497            Ava[] anonymizedAvas = new Ava[rdn.size()];
498            int pos = 0;
499            
500            // Iterate on the AVAs
501            for ( Ava ava : rdn )
502            {
503                Ava anonymizedAva = anonymizeAva( ava );
504                anonymizedAvas[pos] = anonymizedAva;
505                pos++;
506            }
507
508            Rdn anonymizedRdn = new Rdn( schemaManager, anonymizedAvas );
509            anonymizedRdns[rdnPos] = anonymizedRdn;
510            rdnPos--;
511        }
512        
513        return new Dn( schemaManager, anonymizedRdns );
514    }
515
516
517    /**
518     * Anonymize a LDIF 
519     * 
520     * @param ldifFile The ldif file to anonymize
521     * @param writer The Writer to use to write the result
522     * @throws LdapException If we got some LDAP related exception
523     * @throws IOException If we had some issue during some IO operations
524     */
525    public void anonymizeFile( String ldifFile, Writer writer ) throws LdapException, IOException
526    {
527        File inputFile = new File( ldifFile );
528        
529        if ( !inputFile.exists() )
530        {
531            println( "Cannot open file " + ldifFile );
532            return;
533        }
534        
535        LdifReader ldifReader = new LdifReader( inputFile, schemaManager );
536        int count = 0;
537        List<LdifEntry> errors = new ArrayList<>();
538        List<String> errorTexts = new ArrayList<>();
539
540        try
541        {
542            for ( LdifEntry ldifEntry : ldifReader )
543            {
544                count++;
545                
546                try
547                {
548                    if ( ldifEntry.isEntry() && !ldifEntry.isChangeAdd() )
549                    {
550                        // process a full entry. Add changes aren't processed here.
551                        Entry newEntry = anonymizeEntry( ldifEntry );
552                        
553                        writer.write( LdifUtils.convertToLdif( newEntry ) );
554                        writer.write( "\n" );
555                    }
556                    else if ( ldifEntry.isChangeDelete() )
557                    {
558                        // A Delete operation
559                        LdifEntry newLdifEntry = anonymizeChangeDelete( ldifEntry );
560
561                        if ( ldifEntry != null )
562                        {
563                            writer.write( newLdifEntry.toString() );
564                            writer.write( "\n" );
565                        }
566                    }
567                    else if ( ldifEntry.isChangeAdd() )
568                    {
569                        // A Add operation
570                        LdifEntry newLdifEntry = anonymizeChangeAdd( ldifEntry );
571
572                        if ( ldifEntry != null )
573                        {
574                            writer.write( newLdifEntry.toString() );
575                            writer.write( "\n" );
576                        }
577                    }
578                    else if ( ldifEntry.isChangeModify() )
579                    {
580                        // A Modify operation
581                        LdifEntry newLdifEntry = anonymizeChangeModify( ldifEntry );
582
583                        if ( ldifEntry != null )
584                        {
585                            writer.write( newLdifEntry.toString() );
586                            writer.write( "\n" );
587                        }
588                    }
589                    else if ( ldifEntry.isChangeModDn() ||  ldifEntry.isChangeModRdn() )
590                    {
591                        // A MODDN operation
592                        LdifEntry newLdifEntry = anonymizeChangeModDn( ldifEntry );
593
594                        if ( ldifEntry != null )
595                        {
596                            writer.write( newLdifEntry.toString() );
597                            writer.write( "\n" );
598                        }
599                    }
600                    
601                    System.out.print( '.' );
602                    
603                    if ( count % 100  == 0 )
604                    {
605                        println();
606                    }
607                }
608                catch ( Exception e )
609                {
610                    e.printStackTrace();
611                    System.out.print( '*' );
612
613                    if ( count % 100  == 0 )
614                    {
615                        println();
616                    }
617                    
618                    errors.add( ldifEntry );
619                    errorTexts.add( e.getMessage() );
620                }
621            }
622
623            println();
624            
625            if ( !errors.isEmpty() )
626            {
627                println( "There are " + errors.size() + " bad entries" );
628                int i = 0;
629                
630                for ( LdifEntry ldifEntry : errors )
631                {
632                    println( "---------------------------------------------------" );
633                    println( "error : " + errorTexts.get( i ) );
634                    println( ldifEntry.getDn().toString() );
635                    i++;
636                }
637            }
638        }
639        finally
640        {
641            println();
642
643            if ( !errors.isEmpty() )
644            {
645                println( "There are " + errors.size() + " bad entries" );
646            }
647                
648            println( "Nb entries : " + count ); 
649            ldifReader.close();
650        }
651    }
652    
653    
654    /**
655     * Anonymize a Modify change
656     */
657    private LdifEntry anonymizeChangeModify( LdifEntry ldifEntry ) throws LdapException
658    {
659        Dn entryDn = ldifEntry.getDn();
660        LdifEntry newLdifEntry = new LdifEntry( schemaManager );
661        newLdifEntry.setChangeType( ChangeType.Modify );
662
663        // Process the DN first
664        Dn anonymizedDn = anonymizeDn( entryDn );
665        
666        newLdifEntry.setDn( anonymizedDn );
667        
668        // Now, process the entry's attributes
669        for ( Modification modification : ldifEntry.getModifications() )
670        {
671            Attribute attribute = modification.getAttribute();
672            AttributeType attributeType = schemaManager.getAttributeType( attribute.getId() );
673            
674            if ( attributeType == null )
675            {
676                System.out.println( "\nUnknown AttributeType : " + attribute.getId() + " for entry " + entryDn );
677                
678                return null;
679            }
680            
681            attribute.apply( attributeType );
682            
683            // Deal with the special case of a DN syntax
684            if ( attributeType.getSyntax().getSyntaxChecker() instanceof DnSyntaxChecker )
685            {
686                Value<?>[] anonymizedValues = new Value<?>[ attribute.size()];
687                int pos = 0;
688                
689                for ( Value<?> dnValue : modification.getAttribute() )
690                {
691                    Dn dn = new Dn( schemaManager, dnValue.getString() );
692                    Dn newdDn = anonymizeDn( dn );
693                    anonymizedValues[pos++] = new StringValue( newdDn.toString() );
694                }
695                
696                Modification anonymizedModification = new DefaultModification( modification.getOperation(), attributeType, anonymizedValues );
697                newLdifEntry.addModification( anonymizedModification );
698            }
699            else
700            {
701                Anonymizer anonymizer = attributeAnonymizers.get( attributeType.getOid() );
702
703                if ( anonymizer == null )
704                {
705                    newLdifEntry.addModification( modification );
706                }
707                else
708                {
709                    Attribute anonymizedAttribute = anonymizer.anonymize( valueMap, valueSet, attribute );
710                    
711                    Modification anonymizedModification = new DefaultModification( modification.getOperation(), anonymizedAttribute );
712                    newLdifEntry.addModification( anonymizedModification );
713                }
714            }
715        }
716
717        return newLdifEntry;
718    }
719
720    
721    /**
722     * Anonymize a Add change
723     */
724    private LdifEntry anonymizeChangeAdd( LdifEntry ldifEntry ) throws LdapException
725    {
726        Dn entryDn = ldifEntry.getDn();
727        LdifEntry newLdifEntry = new LdifEntry( schemaManager );
728        newLdifEntry.setChangeType( ChangeType.Add );
729
730        // Process the DN first
731        Dn anonymizedDn = anonymizeDn( entryDn );
732        
733        newLdifEntry.setDn( anonymizedDn );
734        
735        // Now, process the entry's attributes
736        for ( Attribute attribute : ldifEntry )
737        {
738            AttributeType attributeType = attribute.getAttributeType();
739            Attribute anonymizedAttribute = new DefaultAttribute( attributeType );
740            
741            // Deal with the special case of a DN syntax
742            
743            if ( attributeType.getSyntax().getSyntaxChecker() instanceof DnSyntaxChecker )
744            {
745                for ( Value<?> dnValue : attribute )
746                {
747                    Dn dn = new Dn( schemaManager, dnValue.getString() );
748                    Dn newdDn = anonymizeDn( dn );
749                    anonymizedAttribute.add( newdDn.toString() );
750                }
751                
752                newLdifEntry.addAttribute( attribute );
753            }
754            else
755            {
756                Anonymizer anonymizer = attributeAnonymizers.get( attribute.getAttributeType().getOid() );
757
758                if ( anonymizer == null )
759                {
760                    newLdifEntry.addAttribute( attribute );
761                }
762                else
763                {
764                    anonymizedAttribute = anonymizer.anonymize( valueMap, valueSet, attribute );
765                    
766                    if ( anonymizedAttribute != null )
767                    {
768                        newLdifEntry.addAttribute( anonymizedAttribute );
769                    }
770                }
771            }
772        }
773
774        return newLdifEntry;
775    }
776    
777    
778    /**
779     * Anonymize a Delete change
780     */
781    private LdifEntry anonymizeChangeDelete( LdifEntry ldifEntry ) throws LdapException
782    {
783        Dn entryDn = ldifEntry.getDn();
784
785        // Process the DN, there is nothing more in the entry
786        Dn anonymizedDn = anonymizeDn( entryDn );
787        
788        ldifEntry.setDn( anonymizedDn );
789        
790        return ldifEntry;
791    }
792    
793    
794    /**
795     * Anonymize a Delete change
796     */
797    private LdifEntry anonymizeChangeModDn( LdifEntry ldifEntry ) throws LdapException
798    {
799        Dn entryDn = ldifEntry.getDn();
800
801        // Process the DN
802        Dn anonymizedDn = anonymizeDn( entryDn );
803        
804        ldifEntry.setDn( anonymizedDn );
805        
806        // Anonymize the newRdn if any
807        String newRdnStr = ldifEntry.getNewRdn();
808        
809        if ( newRdnStr != null )
810        {
811            Dn newRdn = new Dn( schemaManager, newRdnStr );
812            Dn anonymizedRdn = anonymizeDn( newRdn );
813            
814            ldifEntry.setNewRdn( anonymizedRdn.toString() );
815        }
816        
817        // Anonymize the neSuperior if any
818        String newSuperiorStr = ldifEntry.getNewSuperior();
819        
820        if ( newSuperiorStr != null )
821        {
822            Dn newSuperior = new Dn( schemaManager, newSuperiorStr );
823            
824            Dn anonymizedSuperior = anonymizeDn( newSuperior );
825            
826            ldifEntry.setNewSuperior( anonymizedSuperior.toString() );
827        }
828
829        return ldifEntry;
830    }
831    
832    
833    /**
834     * Anonymize the full entry
835     */
836    private Entry anonymizeEntry( LdifEntry ldifEntry ) throws LdapException
837    {
838        Entry entry = ldifEntry.getEntry();
839        Entry newEntry = new DefaultEntry( schemaManager );
840
841        // Process the DN first
842        Dn entryDn = entry.getDn();
843        
844        Dn anonymizedDn = anonymizeDn( entryDn );
845        
846        // Now, process the entry's attributes
847        for ( Attribute attribute : entry )
848        {
849            AttributeType attributeType = attribute.getAttributeType();
850            
851            // Deal with the special case of DN
852            if ( attributeType.getSyntax().getSyntaxChecker() instanceof DnSyntaxChecker )
853            {
854                for ( Value<?> dnValue : attribute )
855                {
856                    Dn dn = new Dn( schemaManager, dnValue.getString() );
857                    Dn newdDn = anonymizeDn( dn );
858                    newEntry.add( attributeType, newdDn.toString() );
859                }
860            }
861            // Deal with the special case of a NameAndOptionalUID
862            else if ( attributeType.getSyntax().getSyntaxChecker() instanceof NameAndOptionalUIDSyntaxChecker )
863            {
864                for ( Value<?> dnValue : attribute )
865                {
866                    // Get rid of the # part (UID)
867                    String valueStr = dnValue.getString();
868                    int uidPos = valueStr.indexOf( '#' );
869                    String uid = null;
870                    
871                    if ( uidPos != -1 )
872                    {
873                        uid = valueStr.substring( uidPos + 1 );
874                        valueStr = valueStr.substring( 0, uidPos ); 
875                    }
876                    
877                    Dn dn = new Dn( schemaManager, valueStr );
878                    Dn newDn = anonymizeDn( dn );
879                    String newDnStr = newDn.toString();
880                    
881                    if ( uid != null )
882                    {
883                        newDnStr = newDnStr + '#' + uid;
884                    }
885                    
886                    newEntry.add( attributeType, newDnStr );
887                }
888            }
889            else
890            {
891                Anonymizer anonymizer = attributeAnonymizers.get( attribute.getAttributeType().getOid() );
892
893                if ( anonymizer == null )
894                {
895                    newEntry.add( attribute );
896                }
897                else
898                {
899                    Attribute anonymizedAttribute = anonymizer.anonymize( valueMap, valueSet, attribute );
900                    
901                    if ( anonymizedAttribute != null )
902                    {
903                        newEntry.add( anonymizedAttribute );
904                    }
905                }
906            }
907        }
908
909        newEntry.setDn( anonymizedDn );
910
911        return newEntry;
912    }
913
914
915    /**
916     * Anonymize a LDIF 
917     * 
918     * @param ldif The ldif content to anonymize
919     * @return an anonymized version of the given ldif
920     * @throws LdapException If we got some LDAP related exception
921     * @throws IOException If we had some issue during some IO operations
922     */
923    public String anonymize( String ldif ) throws LdapException, IOException
924    {
925        LdifReader ldifReader = new LdifReader( schemaManager );
926
927        try
928        {
929            List<LdifEntry> entries = ldifReader.parseLdif( ldif );
930            StringBuilder result = new StringBuilder();
931
932            for ( LdifEntry ldifEntry : entries )
933            {
934                if ( ldifEntry.isEntry() && !ldifEntry.isChangeAdd() )
935                {
936                    // process a full entry. Add changes aren't preocessed ghere.
937                    Entry newEntry = anonymizeEntry( ldifEntry );
938                    
939                    result.append( LdifUtils.convertToLdif( newEntry ) );
940                    result.append( "\n" );
941                }
942                else if ( ldifEntry.isChangeDelete() )
943                {
944                    // A Delete operation
945                    LdifEntry newLdifEntry = anonymizeChangeDelete( ldifEntry );
946
947                    if ( newLdifEntry != null )
948                    {
949                        result.append( newLdifEntry );
950                        result.append( "\n" );
951                    }
952                }
953                else if ( ldifEntry.isChangeAdd() )
954                {
955                    // A Add operation
956                    LdifEntry newLdifEntry = anonymizeChangeAdd( ldifEntry );
957
958                    if ( newLdifEntry != null )
959                    {
960                        result.append( newLdifEntry );
961                        result.append( "\n" );
962                    }
963                }
964                else if ( ldifEntry.isChangeModify() )
965                {
966                    // A Modify operation
967                    LdifEntry newLdifEntry = anonymizeChangeModify( ldifEntry );
968
969                    if ( newLdifEntry != null )
970                    {
971                        result.append( newLdifEntry );
972                        result.append( "\n" );
973                    }
974                }
975                else if ( ldifEntry.isChangeModDn() ||  ldifEntry.isChangeModRdn() )
976                {
977                    // A MODDN operation
978                    LdifEntry newLdifEntry = anonymizeChangeModDn( ldifEntry );
979
980                    if ( newLdifEntry != null )
981                    {
982                        result.append( newLdifEntry );
983                        result.append( "\n" );
984                    }
985                }
986            }
987
988            return result.toString();
989        }
990        catch ( Exception e )
991        {
992            println( "Error :"  + e.getMessage() );
993            return null;
994        }
995        finally
996        {
997            ldifReader.close();
998        }
999    }
1000
1001
1002    /**
1003     * @return the valueMap
1004     */
1005    public Map<Value<?>, Value<?>> getValueMap()
1006    {
1007        return valueMap;
1008    }
1009
1010
1011    /**
1012     * @param valueMap the valueMap to set
1013     */
1014    public void setValueMap( Map<Value<?>, Value<?>> valueMap )
1015    {
1016        this.valueMap = valueMap;
1017    }
1018
1019
1020    /**
1021     * @return the latest String Value Map
1022     */
1023    public Map<Integer, String> getLatestStringMap()
1024    {
1025        return latestStringMap;
1026    }
1027
1028
1029    /**
1030     * @param latestStringMap the latest String Value Map to set
1031     */
1032    public void setLatestStringMap( Map<Integer, String> latestStringMap )
1033    {
1034        this.latestStringMap = latestStringMap;
1035    }
1036
1037
1038    /**
1039     * @return the latest byte[] Value Map
1040     */
1041    public Map<Integer, byte[]> getLatestBytesMap()
1042    {
1043        return latestBytesMap;
1044    }
1045
1046
1047    /**
1048     * @param latestBytesMap the latest byte[] Value Map to set
1049     */
1050    public void setLatestBytesMap( Map<Integer, byte[]> latestBytesMap )
1051    {
1052        this.latestBytesMap = latestBytesMap;
1053    }
1054
1055
1056    /**
1057     * The entry point, when used as a standalone application.
1058     *
1059     * @param args Contains the arguments : the file to convert. The anonymized 
1060     * LDIF will be printed on stdout
1061     * @throws IOException If we had an issue opening the file to anonymise ot writing the result
1062     * @throws LdapException If we had some issue while processing the LDAP data
1063     */
1064    public static void main( String[] args ) throws IOException, LdapException
1065    {
1066        if ( ( args == null ) || ( args.length < 1 ) )
1067        {
1068            System.out.println( "No file to anonymize" );
1069            return;
1070        }
1071
1072        LdifAnonymizer anonymizer = new LdifAnonymizer();
1073
1074        String ldifString = null;
1075
1076        try ( InputStream fis = Files.newInputStream( Paths.get( args[0] ) ) )
1077        {
1078    
1079            try ( BufferedReader br = new BufferedReader( new InputStreamReader( fis, Charset.defaultCharset() ) ) )
1080            {
1081                StringBuilder sb = new StringBuilder();
1082                String line = br.readLine();
1083    
1084                while ( line != null )
1085                {
1086                    sb.append( line );
1087                    sb.append( System.lineSeparator() );
1088                    line = br.readLine();
1089                }
1090    
1091                ldifString = sb.toString();
1092            }
1093        }
1094
1095        String result = anonymizer.anonymize( ldifString );
1096
1097        System.out.println( result );
1098    }
1099}