001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.server.core.referral; 021 022 import javax.naming.Context; 023 import javax.naming.NamingException; 024 025 import org.apache.directory.server.core.DirectoryService; 026 import org.apache.directory.server.core.ReferralManager; 027 import org.apache.directory.server.core.ReferralManagerImpl; 028 import org.apache.directory.server.core.interceptor.BaseInterceptor; 029 import org.apache.directory.server.core.interceptor.NextInterceptor; 030 import org.apache.directory.server.core.interceptor.context.AddOperationContext; 031 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext; 032 import org.apache.directory.server.core.interceptor.context.LookupOperationContext; 033 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; 034 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; 035 import org.apache.directory.server.core.interceptor.context.MoveOperationContext; 036 import org.apache.directory.server.core.interceptor.context.RenameOperationContext; 037 import org.apache.directory.server.core.partition.PartitionNexus; 038 import org.apache.directory.server.i18n.I18n; 039 import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException; 040 import org.apache.directory.shared.ldap.constants.SchemaConstants; 041 import org.apache.directory.shared.ldap.entry.StringValue; 042 import org.apache.directory.shared.ldap.entry.EntryAttribute; 043 import org.apache.directory.shared.ldap.entry.ServerEntry; 044 import org.apache.directory.shared.ldap.entry.Value; 045 import org.apache.directory.shared.ldap.filter.SearchScope; 046 import org.apache.directory.shared.ldap.name.DN; 047 import org.apache.directory.shared.ldap.schema.SchemaManager; 048 import org.apache.directory.shared.ldap.util.LdapURL; 049 import org.apache.directory.shared.ldap.util.StringTools; 050 import org.slf4j.Logger; 051 import org.slf4j.LoggerFactory; 052 053 054 /** 055 * An service which is responsible referral handling behavoirs. It manages 056 * referral handling behavoir when the {@link Context#REFERRAL} is implicitly 057 * or explicitly set to "ignore", when set to "throw" and when set to "follow". 058 * 059 * @org.apache.xbean.XBean 060 * 061 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 062 * @version $Rev$ 063 */ 064 public class ReferralInterceptor extends BaseInterceptor 065 { 066 private static final Logger LOG = LoggerFactory.getLogger( ReferralInterceptor.class ); 067 068 private PartitionNexus nexus; 069 070 /** The global schemaManager */ 071 private SchemaManager schemaManager; 072 073 /** The referralManager */ 074 private ReferralManager referralManager; 075 076 /** A normalized form for the SubschemaSubentry DN */ 077 private String subschemaSubentryDnNorm; 078 079 080 static private void checkRefAttributeValue( Value<?> value ) throws NamingException, LdapURLEncodingException 081 { 082 StringValue ref = ( StringValue ) value; 083 084 String refVal = ref.getString(); 085 086 LdapURL ldapUrl = new LdapURL( refVal ); 087 088 // We have a LDAP URL, we have to check that : 089 // - we don't have scope specifier 090 // - we don't have filters 091 // - we don't have attribute description list 092 // - we don't have extensions 093 // - the DN is not empty 094 095 if ( ldapUrl.getScope() != SearchScope.OBJECT ) 096 { 097 // This is the default value if we don't have any scope 098 // Let's assume that it's incorrect if we get something 099 // else in the LdapURL 100 String message = I18n.err( I18n.ERR_36 ); 101 LOG.error( message ); 102 throw new NamingException( message ); 103 } 104 105 if ( !StringTools.isEmpty( ldapUrl.getFilter() ) ) 106 { 107 String message = I18n.err( I18n.ERR_37 ); 108 LOG.error( message ); 109 throw new NamingException( message ); 110 } 111 112 if ( ( ldapUrl.getAttributes() != null ) && ( ldapUrl.getAttributes().size() != 0 ) ) 113 { 114 String message = I18n.err( I18n.ERR_38 ); 115 LOG.error( message ); 116 throw new NamingException( message ); 117 } 118 119 if ( ( ldapUrl.getExtensions() != null ) && ( ldapUrl.getExtensions().size() != 0 ) ) 120 { 121 String message = I18n.err( I18n.ERR_39 ); 122 LOG.error( message ); 123 throw new NamingException( message ); 124 } 125 126 if ( ( ldapUrl.getExtensions() != null ) && ( ldapUrl.getExtensions().size() != 0 ) ) 127 { 128 String message = I18n.err( I18n.ERR_40 ); 129 LOG.error( message ); 130 throw new NamingException( message ); 131 } 132 133 DN dn = ldapUrl.getDn(); 134 135 if ( ( dn == null ) || dn.isEmpty() ) 136 { 137 String message = I18n.err( I18n.ERR_41 ); 138 LOG.error( message ); 139 throw new NamingException( message ); 140 } 141 } 142 143 144 static private boolean isReferral( ServerEntry entry ) throws NamingException 145 { 146 // Check that the entry is not null, otherwise return FALSE. 147 // This is typically to cover the case where the entry has not 148 // been added into the context because it does not exists. 149 if ( entry == null ) 150 { 151 return false; 152 } 153 154 EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT ); 155 156 if ( oc == null ) 157 { 158 LOG.warn( "could not find objectClass attribute in entry: " + entry ); 159 return false; 160 } 161 162 if ( !oc.contains( SchemaConstants.REFERRAL_OC ) ) 163 { 164 return false; 165 } 166 else 167 { 168 // We have a referral ObjectClass, let's check that the ref is 169 // valid, accordingly to the RFC 170 171 // Get the 'ref' attributeType 172 EntryAttribute refAttr = entry.get( SchemaConstants.REF_AT ); 173 174 if ( refAttr == null ) 175 { 176 // very unlikely, as we have already checked the entry in SchemaInterceptor 177 String message = I18n.err( I18n.ERR_42 ); 178 LOG.error( message ); 179 throw new NamingException( message ); 180 } 181 182 for ( Value<?> value : refAttr ) 183 { 184 try 185 { 186 checkRefAttributeValue( value ); 187 } 188 catch ( LdapURLEncodingException luee ) 189 { 190 // Either the URL is invalid, or it's not a LDAP URL. 191 // we will just ignore this LdapURL. 192 } 193 } 194 195 return true; 196 } 197 } 198 199 200 public void init( DirectoryService directoryService ) throws Exception 201 { 202 nexus = directoryService.getPartitionNexus(); 203 schemaManager = directoryService.getSchemaManager(); 204 205 // Initialize the referralManager 206 referralManager = new ReferralManagerImpl( directoryService ); 207 directoryService.setReferralManager( referralManager ); 208 209 Value<?> subschemaSubentry = nexus.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get(); 210 DN subschemaSubentryDn = new DN( subschemaSubentry.getString() ); 211 subschemaSubentryDn.normalize( schemaManager.getNormalizerMapping() ); 212 subschemaSubentryDnNorm = subschemaSubentryDn.getNormName(); 213 } 214 215 216 /** 217 * Add an entry into the server. We have 3 cases : 218 * (1) The entry does not have any parent referral and is not a referral itself 219 * (2) The entry does not have any parent referral and is a referral itself 220 * (3) The entry has a parent referral 221 * 222 * Case (1) is easy : we inject the entry into the server and we are done. 223 * Case (2) is the same as case (1), but we have to update the referral manager. 224 * Case (3) is handled by the LdapProcotol handler, as we have to return a 225 * LdapResult containing a list of this entry's parent's referrals URL, if the 226 * ManageDSAIT control is not present, or the parent's entry if the control 227 * is present. 228 * 229 * Of course, if the entry already exists, nothing will be done, as we will get an 230 * entryAlreadyExists error. 231 * 232 */ 233 public void add( NextInterceptor next, AddOperationContext opContext ) throws Exception 234 { 235 ServerEntry entry = opContext.getEntry(); 236 237 // Check if the entry is a referral itself 238 boolean isReferral = isReferral( entry ); 239 240 // We add the entry into the server 241 next.add( opContext ); 242 243 // If the addition is successful, we update the referralManager 244 if ( isReferral ) 245 { 246 // We have to add it to the referralManager 247 referralManager.lockWrite(); 248 249 referralManager.addReferral( entry ); 250 251 referralManager.unlock(); 252 } 253 254 } 255 256 257 /** 258 * Delete an entry in the server. We have 4 cases : 259 * (1) the entry is not a referral and does not have a parent referral 260 * (2) the entry is not a referral but has a parent referral 261 * (3) the entry is a referral 262 * 263 * Case (1) is handled by removing the entry from the server 264 * In case (2), we return an exception build using the parent referral 265 * For case(3), we remove the entry from the server and remove the referral 266 * from the referral manager. 267 * 268 * If the entry does not exist in the server, we will get a NoSuchObject error 269 */ 270 public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws Exception 271 { 272 ServerEntry entry = opContext.getEntry(); 273 274 // First delete the entry into the server 275 next.delete( opContext ); 276 277 // Check if the entry exists and is a referral itself 278 // If so, we have to update the referralManager 279 if ( ( entry != null ) && isReferral( entry ) ) 280 { 281 // We have to remove it from the referralManager 282 referralManager.lockWrite(); 283 284 referralManager.removeReferral( entry ); 285 286 referralManager.unlock(); 287 } 288 } 289 290 291 /** 292 * 293 **/ 294 public void move( NextInterceptor next, MoveOperationContext opContext ) throws Exception 295 { 296 DN oldName = opContext.getDn(); 297 298 DN newName = ( DN ) opContext.getParent().clone(); 299 newName.add( oldName.get( oldName.size() - 1 ) ); 300 301 // Check if the entry is a referral itself 302 boolean isReferral = isReferral( opContext.getEntry() ); 303 304 next.move( opContext ); 305 306 307 if ( isReferral ) 308 { 309 // Update the referralManager 310 LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), newName ); 311 312 ServerEntry newEntry = nexus.lookup( lookupContext ); 313 314 referralManager.lockWrite(); 315 316 referralManager.addReferral( newEntry ); 317 referralManager.removeReferral( opContext.getEntry() ); 318 319 referralManager.unlock(); 320 } 321 } 322 323 324 /** 325 * 326 **/ 327 public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext ) throws Exception 328 { 329 DN newName = ( DN ) opContext.getParent().clone(); 330 newName.add( opContext.getNewRdn() ); 331 332 // Check if the entry is a referral itself 333 boolean isReferral = isReferral( opContext.getEntry() ); 334 335 next.moveAndRename( opContext ); 336 337 if ( isReferral ) 338 { 339 // Update the referralManager 340 LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), newName ); 341 342 ServerEntry newEntry = nexus.lookup( lookupContext ); 343 344 referralManager.lockWrite(); 345 346 referralManager.addReferral( newEntry ); 347 referralManager.removeReferral( opContext.getEntry() ); 348 349 referralManager.unlock(); 350 } 351 } 352 353 354 public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception 355 { 356 // Check if the entry is a referral itself 357 boolean isReferral = isReferral( opContext.getEntry() ); 358 359 next.rename( opContext ); 360 361 if ( isReferral ) 362 { 363 // Update the referralManager 364 LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), opContext.getNewDn() ); 365 366 ServerEntry newEntry = nexus.lookup( lookupContext ); 367 368 referralManager.lockWrite(); 369 370 referralManager.addReferral( newEntry ); 371 referralManager.removeReferral( opContext.getEntry().getOriginalEntry() ); 372 373 referralManager.unlock(); 374 } 375 } 376 377 378 /** 379 * Modify an entry in the server. 380 */ 381 public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception 382 { 383 DN name = opContext.getDn(); 384 385 // handle a normal modify without following referrals 386 next.modify( opContext ); 387 388 // Check if we are trying to modify the schema or the rootDSE, 389 // if so, we don't modify the referralManager 390 if ( ( name == DN.EMPTY_DN ) || ( subschemaSubentryDnNorm.equals( name.getNormName() ) ) ) 391 { 392 // Do nothing 393 return; 394 } 395 396 // Update the referralManager. We have to read the entry again 397 // as it has been modified, before updating the ReferralManager 398 // TODO: this can be spare, as we build the entry later. 399 // But we will have to store the modified entry into the opContext 400 LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), name ); 401 402 ServerEntry newEntry = nexus.lookup( lookupContext ); 403 404 // Check that we have the entry, just in case 405 // TODO : entries should be locked until the operation is done on it. 406 if ( newEntry != null ) 407 { 408 referralManager.lockWrite(); 409 410 if ( referralManager.isReferral( newEntry.getDn() ) ) 411 { 412 referralManager.removeReferral( opContext.getEntry() ); 413 referralManager.addReferral( newEntry ); 414 } 415 416 referralManager.unlock(); 417 } 418 } 419 420 421 /** 422 * When adding a new context partition, we have to update the referralManager 423 * by injecting all the new referrals into it. This is done using the init() 424 * method of the referralManager. 425 * 426 public void addContextPartition( NextInterceptor next, AddContextPartitionOperationContext opContext ) 427 throws Exception 428 { 429 // First, inject the partition 430 next.addContextPartition( opContext ); 431 432 Partition partition = opContext.getPartition(); 433 DN suffix = partition.getSuffixDn(); 434 435 // add referrals immediately after adding the new partition 436 referralManager.init( directoryService, new String[]{ suffix.getNormName() } ); 437 } 438 439 440 /** 441 * Remove a partion's referrals from the server. We have to first 442 * clear the referrals manager from all of this partition's referrals, 443 * then we can delete the partition. 444 * 445 public void removeContextPartition( NextInterceptor next, RemoveContextPartitionOperationContext opContext ) 446 throws Exception 447 { 448 // get the partition suffix 449 DN suffix = opContext.getDn(); 450 451 // remove referrals immediately before removing the partition 452 referralManager.remove( directoryService, suffix ); 453 454 // And remove the partition from the server 455 next.removeContextPartition( opContext ); 456 }*/ 457 }