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 */ 020package org.apache.directory.server.core.referral; 021 022 023import javax.naming.Context; 024 025import org.apache.directory.api.ldap.model.constants.SchemaConstants; 026import org.apache.directory.api.ldap.model.entry.Attribute; 027import org.apache.directory.api.ldap.model.entry.Entry; 028import org.apache.directory.api.ldap.model.entry.Value; 029import org.apache.directory.api.ldap.model.exception.LdapException; 030import org.apache.directory.api.ldap.model.exception.LdapURLEncodingException; 031import org.apache.directory.api.ldap.model.message.SearchScope; 032import org.apache.directory.api.ldap.model.name.Dn; 033import org.apache.directory.api.ldap.model.url.LdapUrl; 034import org.apache.directory.api.util.Strings; 035import org.apache.directory.server.core.api.DirectoryService; 036import org.apache.directory.server.core.api.InterceptorEnum; 037import org.apache.directory.server.core.api.ReferralManager; 038import org.apache.directory.server.core.api.entry.ClonedServerEntry; 039import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 040import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 041import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 042import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 043import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 044import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 045import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 046import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 047import org.apache.directory.server.core.api.partition.PartitionNexus; 048import org.apache.directory.server.core.shared.ReferralManagerImpl; 049import org.apache.directory.server.i18n.I18n; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053 054/** 055 * An service which is responsible referral handling behaviors. It manages 056 * referral handling behavior when the {@link Context#REFERRAL} is implicitly 057 * or explicitly set to "ignore", when set to "throw" and when set to "follow". 058 * 059 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 060 */ 061public class ReferralInterceptor extends BaseInterceptor 062{ 063 private static final Logger LOG = LoggerFactory.getLogger( ReferralInterceptor.class ); 064 065 private PartitionNexus nexus; 066 067 /** The referralManager */ 068 private ReferralManager referralManager; 069 070 /** A normalized form for the SubschemaSubentry Dn */ 071 private Dn subschemaSubentryDn; 072 073 074 /** 075 * Creates a new instance of a ReferralInterceptor. 076 */ 077 public ReferralInterceptor() 078 { 079 super( InterceptorEnum.REFERRAL_INTERCEPTOR ); 080 } 081 082 083 private static void checkRefAttributeValue( Value value ) throws LdapException 084 { 085 String refVal = value.getString(); 086 087 LdapUrl ldapUrl = new LdapUrl( refVal ); 088 089 // We have a LDAP URL, we have to check that : 090 // - we don't have scope specifier 091 // - we don't have filters 092 // - we don't have attribute description list 093 // - we don't have extensions 094 // - the Dn is not empty 095 096 if ( ldapUrl.getScope() != SearchScope.OBJECT ) 097 { 098 // This is the default value if we don't have any scope 099 // Let's assume that it's incorrect if we get something 100 // else in the LdapURL 101 String message = I18n.err( I18n.ERR_36 ); 102 LOG.error( message ); 103 throw new LdapException( message ); 104 } 105 106 if ( !Strings.isEmpty( ldapUrl.getFilter() ) ) 107 { 108 String message = I18n.err( I18n.ERR_37 ); 109 LOG.error( message ); 110 throw new LdapException( message ); 111 } 112 113 if ( ( ldapUrl.getAttributes() != null ) && !ldapUrl.getAttributes().isEmpty() ) 114 { 115 String message = I18n.err( I18n.ERR_38 ); 116 LOG.error( message ); 117 throw new LdapException( message ); 118 } 119 120 if ( ( ldapUrl.getExtensions() != null ) && !ldapUrl.getExtensions().isEmpty() ) 121 { 122 String message = I18n.err( I18n.ERR_39 ); 123 LOG.error( message ); 124 throw new LdapException( message ); 125 } 126 127 if ( ( ldapUrl.getExtensions() != null ) && !ldapUrl.getExtensions().isEmpty() ) 128 { 129 String message = I18n.err( I18n.ERR_40 ); 130 LOG.error( message ); 131 throw new LdapException( message ); 132 } 133 134 Dn dn = ldapUrl.getDn(); 135 136 if ( ( dn == null ) || dn.isEmpty() ) 137 { 138 String message = I18n.err( I18n.ERR_41 ); 139 LOG.error( message ); 140 throw new LdapException( message ); 141 } 142 } 143 144 145 // This will suppress PMD.EmptyCatchBlock warnings in this method 146 @SuppressWarnings("PMD.EmptyCatchBlock") 147 private boolean isReferral( Entry entry ) throws LdapException 148 { 149 // Check that the entry is not null, otherwise return FALSE. 150 // This is typically to cover the case where the entry has not 151 // been added into the context because it does not exists. 152 if ( entry == null ) 153 { 154 return false; 155 } 156 157 if ( !entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.REFERRAL_OC ) ) 158 { 159 return false; 160 } 161 else 162 { 163 // We have a referral ObjectClass, let's check that the ref is 164 // valid, accordingly to the RFC 165 166 // Get the 'ref' attributeType 167 Attribute refAttr = entry.get( SchemaConstants.REF_AT ); 168 169 if ( refAttr == null ) 170 { 171 // very unlikely, as we have already checked the entry in SchemaInterceptor 172 String message = I18n.err( I18n.ERR_42 ); 173 LOG.error( message ); 174 throw new LdapException( message ); 175 } 176 177 for ( Value value : refAttr ) 178 { 179 try 180 { 181 checkRefAttributeValue( value ); 182 } 183 catch ( LdapURLEncodingException luee ) 184 { 185 // Either the URL is invalid, or it's not a LDAP URL. 186 // we will just ignore this LdapURL. 187 } 188 } 189 190 return true; 191 } 192 } 193 194 195 @Override 196 public void init( DirectoryService directoryService ) throws LdapException 197 { 198 super.init( directoryService ); 199 200 nexus = directoryService.getPartitionNexus(); 201 202 // Initialize the referralManager 203 referralManager = new ReferralManagerImpl( directoryService ); 204 directoryService.setReferralManager( referralManager ); 205 206 Value subschemaSubentry = nexus.getRootDseValue( directoryService.getAtProvider().getSubschemaSubentry() ); 207 subschemaSubentryDn = dnFactory.create( subschemaSubentry.getString() ); 208 } 209 210 211 /** 212 * Add an entry into the server. We have 3 cases : 213 * (1) The entry does not have any parent referral and is not a referral itself 214 * (2) The entry does not have any parent referral and is a referral itself 215 * (3) The entry has a parent referral 216 * 217 * Case (1) is easy : we inject the entry into the server and we are done. 218 * Case (2) is the same as case (1), but we have to update the referral manager. 219 * Case (3) is handled by the LdapProcotol handler, as we have to return a 220 * LdapResult containing a list of this entry's parent's referrals URL, if the 221 * ManageDSAIT control is not present, or the parent's entry if the control 222 * is present. 223 * 224 * Of course, if the entry already exists, nothing will be done, as we will get an 225 * entryAlreadyExists error. 226 * 227 */ 228 /** 229 * {@inheritDoc} 230 */ 231 @Override 232 public void add( AddOperationContext addContext ) throws LdapException 233 { 234 Entry entry = addContext.getEntry(); 235 236 // Check if the entry is a referral itself 237 boolean isReferral = isReferral( entry ); 238 239 // We add the entry into the server 240 next( addContext ); 241 242 // If the addition is successful, we update the referralManager 243 if ( isReferral ) 244 { 245 // We have to add it to the referralManager 246 referralManager.lockWrite(); 247 248 try 249 { 250 referralManager.addReferral( entry ); 251 } 252 finally 253 { 254 referralManager.unlock(); 255 } 256 } 257 } 258 259 260 /** 261 * Delete an entry in the server. We have 4 cases : 262 * (1) the entry is not a referral and does not have a parent referral 263 * (2) the entry is not a referral but has a parent referral 264 * (3) the entry is a referral 265 * 266 * Case (1) is handled by removing the entry from the server 267 * In case (2), we return an exception build using the parent referral 268 * For case(3), we remove the entry from the server and remove the referral 269 * from the referral manager. 270 * 271 * If the entry does not exist in the server, we will get a NoSuchObject error 272 */ 273 /** 274 * {@inheritDoc} 275 */ 276 @Override 277 public void delete( DeleteOperationContext deleteContext ) throws LdapException 278 { 279 // First delete the entry into the server 280 next( deleteContext ); 281 282 Entry entry = deleteContext.getEntry(); 283 284 // Check if the entry exists and is a referral itself 285 // If so, we have to update the referralManager 286 if ( ( entry != null ) && isReferral( entry ) ) 287 { 288 // We have to remove it from the referralManager 289 referralManager.lockWrite(); 290 291 try 292 { 293 referralManager.removeReferral( entry ); 294 } 295 finally 296 { 297 referralManager.unlock(); 298 } 299 } 300 } 301 302 303 /** 304 * {@inheritDoc} 305 */ 306 @Override 307 public void modify( ModifyOperationContext modifyContext ) throws LdapException 308 { 309 Dn dn = modifyContext.getDn(); 310 311 // handle a normal modify without following referrals 312 next( modifyContext ); 313 314 // Check if we are trying to modify the schema or the rootDSE, 315 // if so, we don't modify the referralManager 316 if ( dn.isEmpty() || dn.equals( subschemaSubentryDn ) ) 317 { 318 // Do nothing 319 return; 320 } 321 322 // Update the referralManager. We have to read the entry again 323 // as it has been modified, before updating the ReferralManager 324 // TODO: this can be spare, as we already have the altered entry 325 // into the opContext, but for an unknow reason, this will fail 326 // on eferral tests... 327 LookupOperationContext lookupContext = 328 new LookupOperationContext( modifyContext.getSession(), dn, SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 329 lookupContext.setPartition( modifyContext.getPartition() ); 330 lookupContext.setTransaction( modifyContext.getTransaction() ); 331 332 Entry newEntry = nexus.lookup( lookupContext ); 333 334 // Update the referralManager. 335 // Check that we have the entry, just in case 336 // TODO : entries should be locked until the operation is done on it. 337 if ( newEntry != null ) 338 { 339 referralManager.lockWrite(); 340 341 try 342 { 343 if ( referralManager.isReferral( newEntry.getDn() ) ) 344 { 345 referralManager.removeReferral( modifyContext.getEntry() ); 346 referralManager.addReferral( newEntry ); 347 } 348 } 349 finally 350 { 351 referralManager.unlock(); 352 } 353 } 354 } 355 356 357 /** 358 * {@inheritDoc} 359 **/ 360 @Override 361 public void move( MoveOperationContext moveContext ) throws LdapException 362 { 363 // Check if the entry is a referral itself 364 boolean isReferral = isReferral( moveContext.getOriginalEntry() ); 365 366 next( moveContext ); 367 368 if ( isReferral ) 369 { 370 // Update the referralManager 371 referralManager.lockWrite(); 372 373 try 374 { 375 referralManager.addReferral( moveContext.getModifiedEntry() ); 376 referralManager.removeReferral( moveContext.getOriginalEntry() ); 377 } 378 finally 379 { 380 referralManager.unlock(); 381 } 382 } 383 } 384 385 386 /** 387 * {@inheritDoc} 388 **/ 389 @Override 390 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 391 { 392 // Check if the entry is a referral itself 393 boolean isReferral = isReferral( moveAndRenameContext.getOriginalEntry() ); 394 395 next( moveAndRenameContext ); 396 397 if ( isReferral ) 398 { 399 // Update the referralManager 400 Entry newEntry = moveAndRenameContext.getModifiedEntry(); 401 402 referralManager.lockWrite(); 403 404 try 405 { 406 referralManager.addReferral( newEntry ); 407 referralManager.removeReferral( moveAndRenameContext.getOriginalEntry() ); 408 } 409 finally 410 { 411 referralManager.unlock(); 412 } 413 } 414 } 415 416 417 /** 418 * {@inheritDoc} 419 **/ 420 @Override 421 public void rename( RenameOperationContext renameContext ) throws LdapException 422 { 423 // Check if the entry is a referral itself 424 boolean isReferral = isReferral( renameContext.getOriginalEntry() ); 425 426 next( renameContext ); 427 428 if ( isReferral ) 429 { 430 // Update the referralManager 431 LookupOperationContext lookupContext = new LookupOperationContext( renameContext.getSession(), 432 renameContext.getNewDn(), SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 433 lookupContext.setPartition( renameContext.getPartition() ); 434 lookupContext.setTransaction( renameContext.getTransaction() ); 435 436 Entry newEntry = nexus.lookup( lookupContext ); 437 438 referralManager.lockWrite(); 439 440 try 441 { 442 referralManager.addReferral( newEntry ); 443 referralManager.removeReferral( ( ( ClonedServerEntry ) renameContext.getEntry() ).getOriginalEntry() ); 444 } 445 finally 446 { 447 referralManager.unlock(); 448 } 449 } 450 } 451}