View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  
21  package org.apache.directory.ldap.client.api;
22  
23  
24  import java.io.BufferedReader;
25  import java.io.File;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.InputStreamReader;
29  import java.io.PrintStream;
30  import java.io.Writer;
31  import java.nio.charset.Charset;
32  import java.nio.file.Files;
33  import java.nio.file.Paths;
34  import java.util.ArrayList;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  
41  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
42  import org.apache.directory.api.ldap.model.entry.Attribute;
43  import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
44  import org.apache.directory.api.ldap.model.entry.DefaultEntry;
45  import org.apache.directory.api.ldap.model.entry.DefaultModification;
46  import org.apache.directory.api.ldap.model.entry.Entry;
47  import org.apache.directory.api.ldap.model.entry.Modification;
48  import org.apache.directory.api.ldap.model.entry.StringValue;
49  import org.apache.directory.api.ldap.model.entry.Value;
50  import org.apache.directory.api.ldap.model.exception.LdapException;
51  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
52  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
53  import org.apache.directory.api.ldap.model.ldif.ChangeType;
54  import org.apache.directory.api.ldap.model.ldif.LdifEntry;
55  import org.apache.directory.api.ldap.model.ldif.LdifReader;
56  import org.apache.directory.api.ldap.model.ldif.LdifUtils;
57  import org.apache.directory.api.ldap.model.ldif.anonymizer.Anonymizer;
58  import org.apache.directory.api.ldap.model.ldif.anonymizer.BinaryAnonymizer;
59  import org.apache.directory.api.ldap.model.ldif.anonymizer.CaseSensitiveStringAnonymizer;
60  import org.apache.directory.api.ldap.model.ldif.anonymizer.IntegerAnonymizer;
61  import org.apache.directory.api.ldap.model.ldif.anonymizer.StringAnonymizer;
62  import org.apache.directory.api.ldap.model.ldif.anonymizer.TelephoneNumberAnonymizer;
63  import org.apache.directory.api.ldap.model.name.Ava;
64  import org.apache.directory.api.ldap.model.name.Dn;
65  import org.apache.directory.api.ldap.model.name.Rdn;
66  import org.apache.directory.api.ldap.model.schema.AttributeType;
67  import org.apache.directory.api.ldap.model.schema.LdapSyntax;
68  import org.apache.directory.api.ldap.model.schema.SchemaManager;
69  import org.apache.directory.api.ldap.model.schema.syntaxCheckers.DnSyntaxChecker;
70  import org.apache.directory.api.ldap.model.schema.syntaxCheckers.NameAndOptionalUIDSyntaxChecker;
71  import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager;
72  
73  
74  /**
75   * Anonymize the content of a LDIF file.
76   * 
77   * We will replace the values of the defined attributes with random chars. There are a default
78   * list of attributes that are going to be anonymized :
79   * <ul>
80   * <li>userPassword</li>
81   * <li>displayName</li>
82   * <li>givenName</li>
83   * <li>surName</li>
84   * <li>homePhone</li>
85   * <li>homePostalAddress</li>
86   * <li>jpegPhoto</li>
87   * <li>labeledURI</li>
88   * <li>mail</li>
89   * <li>manager</li>
90   * <li>mobile</li>
91   * <li>organizationName</li>
92   * <li>pager</li>
93   * <li>photo</li>
94   * <li>secretary</li>
95   * <li>uid</li>
96   * <li>userCertificate</li>
97   * <li>userPKCS12</li>
98   * <li>userSMIMECertificate</li>
99   * <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  */
117 public 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 }