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   *     https://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.Value;
49  import org.apache.directory.api.ldap.model.exception.LdapException;
50  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
51  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
52  import org.apache.directory.api.ldap.model.ldif.ChangeType;
53  import org.apache.directory.api.ldap.model.ldif.LdifEntry;
54  import org.apache.directory.api.ldap.model.ldif.LdifReader;
55  import org.apache.directory.api.ldap.model.ldif.LdifUtils;
56  import org.apache.directory.api.ldap.model.ldif.anonymizer.Anonymizer;
57  import org.apache.directory.api.ldap.model.ldif.anonymizer.BinaryAnonymizer;
58  import org.apache.directory.api.ldap.model.ldif.anonymizer.CaseSensitiveStringAnonymizer;
59  import org.apache.directory.api.ldap.model.ldif.anonymizer.IntegerAnonymizer;
60  import org.apache.directory.api.ldap.model.ldif.anonymizer.StringAnonymizer;
61  import org.apache.directory.api.ldap.model.ldif.anonymizer.TelephoneNumberAnonymizer;
62  import org.apache.directory.api.ldap.model.name.Ava;
63  import org.apache.directory.api.ldap.model.name.Dn;
64  import org.apache.directory.api.ldap.model.name.Rdn;
65  import org.apache.directory.api.ldap.model.schema.AttributeType;
66  import org.apache.directory.api.ldap.model.schema.LdapSyntax;
67  import org.apache.directory.api.ldap.model.schema.SchemaManager;
68  import org.apache.directory.api.ldap.model.schema.syntaxCheckers.DnSyntaxChecker;
69  import org.apache.directory.api.ldap.model.schema.syntaxCheckers.NameAndOptionalUIDSyntaxChecker;
70  import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager;
71  
72  
73  /**
74   * Anonymize the content of a LDIF file.
75   * 
76   * We will replace the values of the defined attributes with random chars. There are a default
77   * list of attributes that are going to be anonymized :
78   * <ul>
79   * <li>userPassword</li>
80   * <li>displayName</li>
81   * <li>givenName</li>
82   * <li>surName</li>
83   * <li>homePhone</li>
84   * <li>homePostalAddress</li>
85   * <li>jpegPhoto</li>
86   * <li>labeledURI</li>
87   * <li>mail</li>
88   * <li>manager</li>
89   * <li>mobile</li>
90   * <li>organizationName</li>
91   * <li>pager</li>
92   * <li>photo</li>
93   * <li>secretary</li>
94   * <li>uid</li>
95   * <li>userCertificate</li>
96   * <li>userPKCS12</li>
97   * <li>userSMIMECertificate</li>
98   * <li>x500UniqueIdentifier</li>
99   * <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  */
116 public 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 }