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