1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.ldap.server.schema;
18
19
20 import org.apache.ldap.common.util.NamespaceTools;
21 import org.apache.ldap.common.exception.LdapSchemaViolationException;
22 import org.apache.ldap.common.message.ResultCodeEnum;
23 import org.apache.ldap.common.schema.ObjectClass;
24 import org.apache.ldap.common.schema.ObjectClassTypeEnum;
25
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import javax.naming.NamingException;
30 import javax.naming.Name;
31 import javax.naming.NamingEnumeration;
32 import javax.naming.directory.Attributes;
33 import javax.naming.directory.DirContext;
34 import javax.naming.directory.Attribute;
35 import java.util.Set;
36 import java.util.HashSet;
37
38
39 /***
40 * Performs schema checks on behalf of the SchemaService.
41 *
42 * @todo we really need to refactor this code since there's much duplication
43 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
44 * @version $Rev$, $Date$
45 */
46 public class SchemaChecker
47 {
48 /*** the SLF4J logger for this class */
49 private static Logger log = LoggerFactory.getLogger( SchemaChecker.class );
50
51
52 /***
53 * Makes sure modify operations do not leave the entry without a STRUCTURAL
54 * objectClass. At least one STRUCTURAL objectClass must be specified for
55 * the entry after modifications take effect.
56 *
57 * @param registry the objectClass registry to lookup ObjectClass specifications
58 * @param name the name of the entry being modified
59 * @param mod the type of modification operation being performed (should be
60 * REMOVE_ATTRIBUTE)
61 * @param attribute the attribute being modified
62 * @throws NamingException if modify operations leave the entry inconsistent
63 * without a STRUCTURAL objectClass
64 */
65 public static void preventStructuralClassRemovalOnModifyReplace( ObjectClassRegistry registry, Name name, int mod,
66 Attribute attribute )
67 throws NamingException
68 {
69 if ( mod != DirContext.REPLACE_ATTRIBUTE )
70 {
71 return;
72 }
73
74 if ( ! "objectclass".equalsIgnoreCase( attribute.getID() ) )
75 {
76 return;
77 }
78
79
80
81 if ( attribute.size() == 0 )
82 {
83 String msg = "Modify operation leaves no structural objectClass for entry " + name;
84 if ( log.isInfoEnabled() )
85 {
86 log.info( msg + ". Raising LdapSchemaViolationException." );
87 }
88 throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
89 }
90
91
92 for ( int ii = 0; ii < attribute.size(); ii++ )
93 {
94 ObjectClass ocType = registry.lookup( ( String ) attribute.get( ii ) );
95 if ( ocType.getType() == ObjectClassTypeEnum.STRUCTURAL )
96 {
97 return;
98 }
99 }
100
101
102
103 String msg = "Modify operation leaves no structural objectClass for entry " + name;
104 if ( log.isInfoEnabled() )
105 {
106 log.info( msg + ". Raising LdapSchemaViolationException." );
107 }
108 throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
109 }
110
111
112 /***
113 * Makes sure modify operations do not leave the entry without a STRUCTURAL
114 * objectClass. At least one STRUCTURAL objectClass must be specified for
115 * the entry after modifications take effect.
116 *
117 * @param registry the objectClass registry to lookup ObjectClass specifications
118 * @param name the name of the entry being modified
119 * @param mod the type of modification operation being performed (should be
120 * REMOVE_ATTRIBUTE)
121 * @param attributes the attributes being modified
122 * @throws NamingException if modify operations leave the entry inconsistent
123 * without a STRUCTURAL objectClass
124 */
125 public static void preventStructuralClassRemovalOnModifyReplace( ObjectClassRegistry registry, Name name, int mod,
126 Attributes attributes )
127 throws NamingException
128 {
129 if ( mod != DirContext.REPLACE_ATTRIBUTE )
130 {
131 return;
132 }
133
134 Attribute objectClass = attributes.get( "objectClass" );
135 if ( objectClass == null )
136 {
137 return;
138 }
139
140
141
142 if ( objectClass.size() == 0 )
143 {
144 String msg = "Modify operation leaves no structural objectClass for entry " + name;
145 if ( log.isInfoEnabled() )
146 {
147 log.info( msg + ". Raising LdapSchemaViolationException." );
148 }
149 throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
150 }
151
152
153 for ( int ii = 0; ii < objectClass.size(); ii++ )
154 {
155 ObjectClass ocType = registry.lookup( ( String ) objectClass.get( ii ) );
156 if ( ocType.getType() == ObjectClassTypeEnum.STRUCTURAL )
157 {
158 return;
159 }
160 }
161
162
163
164 String msg = "Modify operation leaves no structural objectClass for entry " + name;
165 if ( log.isInfoEnabled() )
166 {
167 log.info( msg + ". Raising LdapSchemaViolationException." );
168 }
169 throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
170 }
171
172
173 /***
174 * Makes sure modify operations do not leave the entry without a STRUCTURAL
175 * objectClass. At least one STRUCTURAL objectClass must be specified for
176 * the entry after modifications take effect.
177 *
178 * @param registry the objectClass registry to lookup ObjectClass specifications
179 * @param name the name of the entry being modified
180 * @param mod the type of modification operation being performed (should be
181 * REMOVE_ATTRIBUTE)
182 * @param attribute the attribute being modified
183 * @param entryObjectClasses the entry being modified
184 * @throws NamingException if modify operations leave the entry inconsistent
185 * without a STRUCTURAL objectClass
186 */
187 public static void preventStructuralClassRemovalOnModifyRemove( ObjectClassRegistry registry, Name name, int mod,
188 Attribute attribute, Attribute entryObjectClasses )
189 throws NamingException
190 {
191 if ( mod != DirContext.REMOVE_ATTRIBUTE )
192 {
193 return;
194 }
195
196 if ( ! "objectclass".equalsIgnoreCase( attribute.getID() ) )
197 {
198 return;
199 }
200
201
202
203 if ( attribute.size() == 0 )
204 {
205 String msg = "Modify operation leaves no structural objectClass for entry " + name;
206 if ( log.isInfoEnabled() )
207 {
208 log.info( msg + ". Raising LdapSchemaViolationException." );
209 }
210 throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
211 }
212
213
214
215
216
217 Attribute cloned = ( Attribute ) entryObjectClasses.clone();
218 for ( int ii = 0; ii < attribute.size(); ii++ )
219 {
220 cloned.remove( attribute.get( ii ) );
221 }
222
223
224 for ( int ii = 0; ii < cloned.size(); ii++ )
225 {
226 ObjectClass ocType = registry.lookup( ( String ) cloned.get( ii ) );
227 if ( ocType.getType() == ObjectClassTypeEnum.STRUCTURAL )
228 {
229 return;
230 }
231 }
232
233
234
235 String msg = "Modify operation leaves no structural objectClass for entry " + name;
236 if ( log.isInfoEnabled() )
237 {
238 log.info( msg + ". Raising LdapSchemaViolationException." );
239 }
240 throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
241 }
242
243
244 /***
245 * Makes sure modify operations do not leave the entry without a STRUCTURAL
246 * objectClass. At least one STRUCTURAL objectClass must be specified for
247 * the entry after modifications take effect.
248 *
249 * @param registry the objectClass registry to lookup ObjectClass specifications
250 * @param name the name of the entry being modified
251 * @param mod the type of modification operation being performed (should be
252 * REMOVE_ATTRIBUTE)
253 * @param attributes the attributes being modified
254 * @param entryObjectClasses the entry being modified
255 * @throws NamingException if modify operations leave the entry inconsistent
256 * without a STRUCTURAL objectClass
257 */
258 public static void preventStructuralClassRemovalOnModifyRemove( ObjectClassRegistry registry, Name name, int mod,
259 Attributes attributes, Attribute entryObjectClasses )
260 throws NamingException
261 {
262 if ( mod != DirContext.REMOVE_ATTRIBUTE )
263 {
264 return;
265 }
266
267 Attribute objectClass = attributes.get( "objectClass" );
268 if ( objectClass == null )
269 {
270 return;
271 }
272
273
274
275 if ( objectClass.size() == 0 )
276 {
277 String msg = "Modify operation leaves no structural objectClass for entry " + name;
278 if ( log.isInfoEnabled() )
279 {
280 log.info( msg + ". Raising LdapSchemaViolationException." );
281 }
282 throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
283 }
284
285
286
287
288
289 Attribute cloned = ( Attribute ) entryObjectClasses.clone();
290 for ( int ii = 0; ii < objectClass.size(); ii++ )
291 {
292 cloned.remove( objectClass.get( ii ) );
293 }
294
295
296 for ( int ii = 0; ii < cloned.size(); ii++ )
297 {
298 ObjectClass ocType = registry.lookup( ( String ) cloned.get( ii ) );
299 if ( ocType.getType() == ObjectClassTypeEnum.STRUCTURAL )
300 {
301 return;
302 }
303 }
304
305
306
307 String msg = "Modify operation leaves no structural objectClass for entry " + name;
308 if ( log.isInfoEnabled() )
309 {
310 log.info( msg + ". Raising LdapSchemaViolationException." );
311 }
312 throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
313 }
314
315
316 /***
317 * Makes sure a modify operation does not replace RDN attributes or their value.
318 * According to section 4.6 of <a href="http://rfc.net/rfc2251.html#s4.6.">
319 * RFC 2251</a> a modify operation cannot be used to remove Rdn attributes as
320 * seen below:
321 * <p/>
322 * <pre>
323 * The Modify Operation cannot be used to remove from an entry any of
324 * its distinguished values, those values which form the entry's
325 * relative distinguished name. An attempt to do so will result in the
326 * server returning the error notAllowedOnRDN. The Modify DN Operation
327 * described in section 4.9 is used to rename an entry.
328 * </pre>
329 *
330 * @param name the distinguished name of the attribute being modified
331 * @param mod the modification operation being performed (should be REPLACE_ATTRIBUTE )
332 * @param attribute the attribute being modified
333 * @throws NamingException if the modify operation is removing an Rdn attribute
334 */
335 public static void preventRdnChangeOnModifyReplace( Name name, int mod, Attribute attribute )
336 throws NamingException
337 {
338 if ( mod != DirContext.REPLACE_ATTRIBUTE )
339 {
340 return;
341 }
342
343 Set rdnAttributes = getRdnAttributes( name );
344 String id = ( String ) attribute.getID();
345
346 if ( ! rdnAttributes.contains( id ) )
347 {
348 return;
349 }
350
351
352
353
354 if ( attribute.size() == 0 )
355 {
356 String msg = "Modify operation attempts to delete RDN attribute ";
357 msg += id + " on entry " + name + " violates schema constraints";
358
359 if ( log.isInfoEnabled() )
360 {
361 log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
362 }
363 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
364 }
365
366
367
368
369 String rdnValue = getRdnValue( id, name );
370 for ( int ii = 0; ii < attribute.size(); ii++ )
371 {
372
373
374 if ( ! attribute.contains( rdnValue ) )
375 {
376 String msg = "Modify operation attempts to delete RDN attribute values in use for ";
377 msg += id + " on entry " + name + " and violates schema constraints";
378
379 if ( log.isInfoEnabled() )
380 {
381 log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
382 }
383 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
384 }
385 }
386 }
387
388
389 /***
390 * Makes sure a modify operation does not replace RDN attributes or their value.
391 * According to section 4.6 of <a href="http://rfc.net/rfc2251.html#s4.6.">
392 * RFC 2251</a> a modify operation cannot be used to remove Rdn attributes as
393 * seen below:
394 * <p/>
395 * <pre>
396 * The Modify Operation cannot be used to remove from an entry any of
397 * its distinguished values, those values which form the entry's
398 * relative distinguished name. An attempt to do so will result in the
399 * server returning the error notAllowedOnRDN. The Modify DN Operation
400 * described in section 4.9 is used to rename an entry.
401 * </pre>
402 *
403 * @param name the distinguished name of the attribute being modified
404 * @param mod the modification operation being performed (should be REPLACE_ATTRIBUTE )
405 * @param attributes the attributes being modified
406 * @throws NamingException if the modify operation is removing an Rdn attribute
407 */
408 public static void preventRdnChangeOnModifyReplace( Name name, int mod, Attributes attributes )
409 throws NamingException
410 {
411 if ( mod != DirContext.REPLACE_ATTRIBUTE )
412 {
413 return;
414 }
415
416 Set rdnAttributes = getRdnAttributes( name );
417 NamingEnumeration list = attributes.getIDs();
418 while ( list.hasMore() )
419 {
420 String id = ( String ) list.next();
421
422 if ( rdnAttributes.contains( id ) )
423 {
424
425
426
427 if ( attributes.get( id ).size() == 0 )
428 {
429 String msg = "Modify operation attempts to delete RDN attribute ";
430 msg += id + " on entry " + name + " violates schema constraints";
431
432 if ( log.isInfoEnabled() )
433 {
434 log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
435 }
436 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
437 }
438
439
440
441
442 String rdnValue = getRdnValue( id, name );
443 Attribute rdnAttr = attributes.get( id );
444 for ( int ii = 0; ii < rdnAttr.size(); ii++ )
445 {
446
447
448 if ( ! rdnAttr.contains( rdnValue ) )
449 {
450 String msg = "Modify operation attempts to delete RDN attribute values in use for ";
451 msg += id + " on entry " + name + " and violates schema constraints";
452
453 if ( log.isInfoEnabled() )
454 {
455 log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
456 }
457 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
458 }
459 }
460 }
461 }
462 }
463
464
465 /***
466 * Makes sure a modify operation does not delete RDN attributes or their value.
467 * According to section 4.6 of <a href="http://rfc.net/rfc2251.html#s4.6.">
468 * RFC 2251</a> a modify operation cannot be used to remove Rdn attributes as
469 * seen below:
470 * <p/>
471 * <pre>
472 * The Modify Operation cannot be used to remove from an entry any of
473 * its distinguished values, those values which form the entry's
474 * relative distinguished name. An attempt to do so will result in the
475 * server returning the error notAllowedOnRDN. The Modify DN Operation
476 * described in section 4.9 is used to rename an entry.
477 * </pre>
478 *
479 * @param name the distinguished name of the attribute being modified
480 * @param mod the modification operation being performed (should be REMOVE_ATTRIBUTE )
481 * @param attribute the attribute being modified
482 * @throws NamingException if the modify operation is removing an Rdn attribute
483 */
484 public static void preventRdnChangeOnModifyRemove( Name name, int mod, Attribute attribute )
485 throws NamingException
486 {
487 if ( mod != DirContext.REMOVE_ATTRIBUTE )
488 {
489 return;
490 }
491
492 Set rdnAttributes = getRdnAttributes( name );
493 String id = attribute.getID();
494
495 if ( ! rdnAttributes.contains( id ) )
496 {
497 return;
498 }
499
500
501
502
503 if ( attribute.size() == 0 )
504 {
505 String msg = "Modify operation attempts to delete RDN attribute ";
506 msg += id + " on entry " + name + " violates schema constraints";
507
508 if ( log.isInfoEnabled() )
509 {
510 log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
511 }
512 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
513 }
514
515
516
517
518 String rdnValue = getRdnValue( id, name );
519 for ( int ii = 0; ii < attribute.size(); ii++ )
520 {
521 if ( rdnValue.equals( attribute.get( ii ) ) )
522 {
523 String msg = "Modify operation attempts to delete RDN attribute values in use for ";
524 msg += id + " on entry " + name + " and violates schema constraints";
525
526 if ( log.isInfoEnabled() )
527 {
528 log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
529 }
530 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
531 }
532 }
533 }
534
535
536 /***
537 * Makes sure a modify operation does not delete RDN attributes or their value.
538 * According to section 4.6 of <a href="http://rfc.net/rfc2251.html#s4.6.">
539 * RFC 2251</a> a modify operation cannot be used to remove Rdn attributes as
540 * seen below:
541 * <p/>
542 * <pre>
543 * The Modify Operation cannot be used to remove from an entry any of
544 * its distinguished values, those values which form the entry's
545 * relative distinguished name. An attempt to do so will result in the
546 * server returning the error notAllowedOnRDN. The Modify DN Operation
547 * described in section 4.9 is used to rename an entry.
548 * </pre>
549 *
550 * @param name the distinguished name of the attribute being modified
551 * @param mod the modification operation being performed (should be REMOVE_ATTRIBUTE )
552 * @param attributes the attributes being modified
553 * @throws NamingException if the modify operation is removing an Rdn attribute
554 */
555 public static void preventRdnChangeOnModifyRemove( Name name, int mod, Attributes attributes )
556 throws NamingException
557 {
558 if ( mod != DirContext.REMOVE_ATTRIBUTE )
559 {
560 return;
561 }
562
563 Set rdnAttributes = getRdnAttributes( name );
564 NamingEnumeration list = attributes.getIDs();
565 while ( list.hasMore() )
566 {
567 String id = ( String ) list.next();
568
569 if ( rdnAttributes.contains( id ) )
570 {
571
572
573
574 if ( attributes.get( id ).size() == 0 )
575 {
576 String msg = "Modify operation attempts to delete RDN attribute ";
577 msg += id + " on entry " + name + " violates schema constraints";
578
579 if ( log.isInfoEnabled() )
580 {
581 log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
582 }
583 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
584 }
585
586
587
588
589 String rdnValue = getRdnValue( id, name );
590 Attribute rdnAttr = attributes.get( id );
591 for ( int ii = 0; ii < rdnAttr.size(); ii++ )
592 {
593 if ( rdnValue.equals( rdnAttr.get( ii ) ) )
594 {
595 String msg = "Modify operation attempts to delete RDN attribute values in use for ";
596 msg += id + " on entry " + name + " and violates schema constraints";
597
598 if ( log.isInfoEnabled() )
599 {
600 log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
601 }
602 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
603 }
604 }
605 }
606 }
607 }
608
609
610 /***
611 * Gets the Rdn attribute value. This method works even if the Rdn is
612 * composed of multiple attributes.
613 *
614 * @param id the attribute id of the Rdn attribute to return
615 * @param name the distinguished name of the entry
616 * @return the Rdn attribute value corresponding to the id, or null if the
617 * attribute is not an rdn attribute
618 * @throws NamingException if the name is malformed in any way
619 */
620 private static String getRdnValue( String id, Name name ) throws NamingException
621 {
622 String [] comps = NamespaceTools.getCompositeComponents( name.get( name.size() - 1 ) );
623
624 for ( int ii = 0; ii < comps.length; ii++ )
625 {
626 String rdnAttrId = NamespaceTools.getRdnAttribute( comps[ii] );
627
628 if ( rdnAttrId.equalsIgnoreCase( id ) )
629 {
630 return NamespaceTools.getRdnValue( comps[ii] );
631 }
632 }
633
634 return null;
635 }
636
637
638 /***
639 * Collects the set of Rdn attributes whether or not the Rdn is based on a
640 * single attribute or multiple attributes.
641 *
642 * @param name the distinguished name of an entry
643 * @return the set of attributes composing the Rdn for the name
644 * @throws NamingException if the syntax of the Rdn is incorrect
645 */
646 private static Set getRdnAttributes( Name name ) throws NamingException
647 {
648 String [] comps = NamespaceTools.getCompositeComponents( name.get( name.size() - 1 ) );
649 Set attributes = new HashSet();
650
651 for ( int ii = 0; ii < comps.length; ii++ )
652 {
653 attributes.add( NamespaceTools.getRdnAttribute( comps[ii] ) );
654 }
655
656 return attributes;
657 }
658 }