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