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.authz; 021 022 023import java.text.ParseException; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import javax.naming.directory.SearchControls; 032 033import org.apache.directory.api.ldap.aci.ACIItem; 034import org.apache.directory.api.ldap.aci.ACIItemParser; 035import org.apache.directory.api.ldap.aci.ACITuple; 036import org.apache.directory.api.ldap.model.constants.SchemaConstants; 037import org.apache.directory.api.ldap.model.entry.Attribute; 038import org.apache.directory.api.ldap.model.entry.Entry; 039import org.apache.directory.api.ldap.model.entry.Modification; 040import org.apache.directory.api.ldap.model.entry.Value; 041import org.apache.directory.api.ldap.model.exception.LdapException; 042import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException; 043import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException; 044import org.apache.directory.api.ldap.model.filter.EqualityNode; 045import org.apache.directory.api.ldap.model.filter.ExprNode; 046import org.apache.directory.api.ldap.model.message.AliasDerefMode; 047import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 048import org.apache.directory.api.ldap.model.name.Dn; 049import org.apache.directory.api.ldap.model.schema.AttributeType; 050import org.apache.directory.api.ldap.model.schema.SchemaManager; 051import org.apache.directory.api.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer; 052import org.apache.directory.api.ldap.model.schema.normalizers.NameComponentNormalizer; 053import org.apache.directory.server.core.api.CoreSession; 054import org.apache.directory.server.core.api.DirectoryService; 055import org.apache.directory.server.core.api.DnFactory; 056import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 057import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 058import org.apache.directory.server.core.api.partition.Partition; 059import org.apache.directory.server.core.api.partition.PartitionNexus; 060import org.apache.directory.server.i18n.I18n; 061import org.slf4j.Logger; 062import org.slf4j.LoggerFactory; 063 064 065/** 066 * A cache for tuple sets which responds to specific events to perform 067 * cache house keeping as access control subentries are added, deleted 068 * and modified. 069 * 070 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 071 */ 072public class TupleCache 073{ 074 /** the logger for this class */ 075 private static final Logger LOG = LoggerFactory.getLogger( TupleCache.class ); 076 077 /** a map of strings to ACITuple collections */ 078 private final Map<String, List<ACITuple>> tuples = new HashMap<>(); 079 080 /** the directory service */ 081 private final DirectoryService directoryService; 082 083 /** the Dn factory */ 084 private final DnFactory dnFactory; 085 086 /** a handle on the partition nexus */ 087 private final PartitionNexus nexus; 088 089 /** a normalizing ACIItem parser */ 090 private final ACIItemParser aciParser; 091 092 093 /** 094 * Creates a ACITuple cache. 095 * 096 * @param session the session with the directory core services 097 * @throws LdapException if initialization fails 098 */ 099 public TupleCache( CoreSession session ) throws LdapException 100 { 101 this.directoryService = session.getDirectoryService(); 102 SchemaManager schemaManager = directoryService.getSchemaManager(); 103 this.dnFactory = directoryService.getDnFactory(); 104 this.nexus = directoryService.getPartitionNexus(); 105 NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( schemaManager ); 106 aciParser = new ACIItemParser( ncn, schemaManager ); 107 initialize( session ); 108 } 109 110 111 private void initialize( CoreSession session ) throws LdapException 112 { 113 // search all naming contexts for access control subentenries 114 // generate ACITuple Arrays for each subentry 115 // add that subentry to the hash 116 Set<String> suffixes = nexus.listSuffixes(); 117 118 for ( String suffix : suffixes ) 119 { 120 AttributeType ocAt = directoryService.getAtProvider().getObjectClass(); 121 122 ExprNode filter = new EqualityNode<String>( ocAt, 123 new Value( ocAt, SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) ); 124 SearchControls ctls = new SearchControls(); 125 ctls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 126 ctls.setReturningAttributes( new String[] 127 { SchemaConstants.ALL_USER_ATTRIBUTES, SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES } ); 128 129 Dn baseDn = dnFactory.create( suffix ); 130 Partition partition = nexus.getPartition( baseDn ); 131 132 SearchOperationContext searchOperationContext = new SearchOperationContext( session, 133 baseDn, filter, ctls ); 134 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 135 searchOperationContext.setPartition( partition ); 136 searchOperationContext.setTransaction( partition.beginReadTransaction() ); 137 138 EntryFilteringCursor results = nexus.search( searchOperationContext ); 139 140 try 141 { 142 while ( results.next() ) 143 { 144 Entry result = results.get(); 145 146 Dn subentryDn = result.getDn(); 147 148 if ( !subentryDn.isSchemaAware() ) 149 { 150 subentryDn = new Dn( session.getDirectoryService().getSchemaManager(), subentryDn ); 151 } 152 153 Attribute aci = result.get( directoryService.getAtProvider().getPrescriptiveACI() ); 154 155 if ( aci == null ) 156 { 157 LOG.warn( "Found accessControlSubentry '{}' without any {}", subentryDn, SchemaConstants.PRESCRIPTIVE_ACI_AT ); 158 continue; 159 } 160 161 subentryAdded( subentryDn, result ); 162 } 163 164 results.close(); 165 } 166 catch ( Exception e ) 167 { 168 throw new LdapOperationErrorException( e.getMessage(), e ); 169 } 170 } 171 } 172 173 174 /** 175 * Check if the Entry contains a prescriptiveACI 176 */ 177 private boolean hasPrescriptiveACI( Entry entry ) throws LdapException 178 { 179 // only do something if the entry contains prescriptiveACI 180 Attribute aci = entry.get( directoryService.getAtProvider().getPrescriptiveACI() ); 181 182 if ( aci == null ) 183 { 184 if ( entry.contains( directoryService.getAtProvider().getObjectClass(), 185 SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) ) 186 { 187 // should not be necessary because of schema interceptor but schema checking 188 // can be turned off and in this case we must protect against being able to 189 // add access control information to anything other than an AC subentry 190 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, "" ); 191 } 192 else 193 { 194 return false; 195 } 196 } 197 198 return true; 199 } 200 201 202 public void subentryAdded( Dn dn, Entry entry ) throws LdapException 203 { 204 // only do something if the entry contains a prescriptiveACI 205 if ( !hasPrescriptiveACI( entry ) ) 206 { 207 return; 208 } 209 210 // Get the prescriptiveACI 211 Attribute prescriptiveAci = entry.get( directoryService.getAtProvider().getPrescriptiveACI() ); 212 213 List<ACITuple> entryTuples = new ArrayList<>(); 214 215 // Loop on all the ACI, parse each of them and 216 // store the associated tuples into the cache 217 for ( Value value : prescriptiveAci ) 218 { 219 String aci = value.getString(); 220 ACIItem item = null; 221 222 try 223 { 224 item = aciParser.parse( aci ); 225 entryTuples.addAll( item.toTuples() ); 226 } 227 catch ( ParseException e ) 228 { 229 String msg = I18n.err( I18n.ERR_28, item ); 230 LOG.error( msg, e ); 231 232 // do not process this ACI Item because it will be null 233 // continue on to process the next ACI item in the entry 234 } 235 } 236 237 tuples.put( dn.getNormName(), entryTuples ); 238 } 239 240 241 public void subentryDeleted( Dn dn, Entry entry ) throws LdapException 242 { 243 if ( !hasPrescriptiveACI( entry ) ) 244 { 245 return; 246 } 247 248 tuples.remove( dn.getNormName() ); 249 } 250 251 252 public void subentryModified( Dn normName, List<Modification> mods, Entry entry ) throws LdapException 253 { 254 if ( !hasPrescriptiveACI( entry ) ) 255 { 256 return; 257 } 258 259 for ( Modification mod : mods ) 260 { 261 if ( mod.getAttribute().isInstanceOf( directoryService.getAtProvider().getPrescriptiveACI() ) ) 262 { 263 subentryDeleted( normName, entry ); 264 subentryAdded( normName, entry ); 265 } 266 } 267 } 268 269 270 public void subentryModified( Dn normName, Entry mods, Entry entry ) throws LdapException 271 { 272 if ( !hasPrescriptiveACI( entry ) ) 273 { 274 return; 275 } 276 277 if ( mods.get( directoryService.getAtProvider().getPrescriptiveACI() ) != null ) 278 { 279 subentryDeleted( normName, entry ); 280 subentryAdded( normName, entry ); 281 } 282 } 283 284 285 public List<ACITuple> getACITuples( String subentryDn ) 286 { 287 List<ACITuple> aciTuples = tuples.get( subentryDn ); 288 289 if ( aciTuples == null ) 290 { 291 return Collections.emptyList(); 292 } 293 294 return Collections.unmodifiableList( aciTuples ); 295 } 296 297 298 public void subentryRenamed( Dn oldName, Dn newName ) 299 { 300 tuples.put( newName.getNormName(), tuples.remove( oldName.getNormName() ) ); 301 } 302}