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.authz;
021    
022    
023    import java.util.HashSet;
024    import java.util.Set;
025    
026    import javax.naming.NoPermissionException;
027    
028    import org.apache.directory.server.constants.ServerDNConstants;
029    import org.apache.directory.server.core.CoreSession;
030    import org.apache.directory.server.core.DefaultCoreSession;
031    import org.apache.directory.server.core.DirectoryService;
032    import org.apache.directory.server.core.LdapPrincipal;
033    import org.apache.directory.server.core.entry.ClonedServerEntry;
034    import org.apache.directory.server.core.filtering.EntryFilter;
035    import org.apache.directory.server.core.filtering.EntryFilteringCursor;
036    import org.apache.directory.server.core.interceptor.BaseInterceptor;
037    import org.apache.directory.server.core.interceptor.Interceptor;
038    import org.apache.directory.server.core.interceptor.NextInterceptor;
039    import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
040    import org.apache.directory.server.core.interceptor.context.ListOperationContext;
041    import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
042    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
043    import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
044    import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
045    import org.apache.directory.server.core.interceptor.context.OperationContext;
046    import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
047    import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
048    import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
049    import org.apache.directory.server.core.partition.DefaultPartitionNexus;
050    import org.apache.directory.server.core.partition.PartitionNexus;
051    import org.apache.directory.server.i18n.I18n;
052    import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
053    import org.apache.directory.shared.ldap.constants.SchemaConstants;
054    import org.apache.directory.shared.ldap.entry.EntryAttribute;
055    import org.apache.directory.shared.ldap.entry.ServerEntry;
056    import org.apache.directory.shared.ldap.entry.Value;
057    import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
058    import org.apache.directory.shared.ldap.name.DN;
059    import org.apache.directory.shared.ldap.schema.AttributeType;
060    import org.apache.directory.shared.ldap.schema.SchemaManager;
061    import org.slf4j.Logger;
062    import org.slf4j.LoggerFactory;
063    
064    
065    /**
066     * An {@link Interceptor} that controls access to {@link DefaultPartitionNexus}.
067     * If a user tries to perform any operations that requires
068     * permission he or she doesn't have, {@link NoPermissionException} will be
069     * thrown and therefore the current invocation chain will terminate.
070     *
071     * @org.apache.xbean.XBean
072     *
073     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
074     * @version $Rev: 927146 $, $Date: 2010-03-24 19:39:54 +0100 (Wed, 24 Mar 2010) $
075     */
076    public class DefaultAuthorizationInterceptor extends BaseInterceptor
077    {
078        /** the logger for this class */
079        private static final Logger LOG = LoggerFactory.getLogger( DefaultAuthorizationInterceptor.class );
080    
081        /**
082         * the base distinguished {@link Name} for all users
083         */
084        private static DN USER_BASE_DN;
085    
086        /**
087         * the base distinguished {@link Name} for all groups
088         */
089        private static DN GROUP_BASE_DN;
090    
091        /**
092         * the distinguished {@link Name} for the administrator group
093         */
094        private static DN ADMIN_GROUP_DN;
095    
096        private Set<String> administrators = new HashSet<String>(2);
097        
098        private PartitionNexus nexus;
099    
100        /** A starage for the uniqueMember attributeType */
101        private AttributeType uniqueMemberAT;
102    
103    
104        /**
105         * Creates a new instance.
106         */
107        public DefaultAuthorizationInterceptor()
108        {
109            // Nothing to do
110        }
111    
112    
113        public void init( DirectoryService directoryService ) throws Exception
114        {
115            nexus = directoryService.getPartitionNexus();
116            SchemaManager schemaManager = directoryService.getSchemaManager();
117    
118            USER_BASE_DN = new DN( ServerDNConstants.ADMIN_SYSTEM_DN );
119            USER_BASE_DN.normalize( schemaManager.getNormalizerMapping() );
120            
121            GROUP_BASE_DN = new DN( ServerDNConstants.GROUPS_SYSTEM_DN );
122            GROUP_BASE_DN.normalize( schemaManager.getNormalizerMapping() );
123         
124            ADMIN_GROUP_DN = new DN( ServerDNConstants.ADMINISTRATORS_GROUP_DN );
125            ADMIN_GROUP_DN.normalize( schemaManager.getNormalizerMapping() );
126    
127            uniqueMemberAT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.UNIQUE_MEMBER_AT_OID );
128            
129            loadAdministrators( directoryService );
130        }
131        
132        
133        private void loadAdministrators( DirectoryService directoryService ) throws Exception
134        {
135            // read in the administrators and cache their normalized names
136            Set<String> newAdministrators = new HashSet<String>( 2 );
137            DN adminDn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
138            adminDn.normalize( directoryService.getSchemaManager().getNormalizerMapping() );
139            CoreSession adminSession = new DefaultCoreSession( 
140                new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
141    
142            ServerEntry adminGroup = nexus.lookup( new LookupOperationContext( adminSession, ADMIN_GROUP_DN ) );
143            
144            if ( adminGroup == null )
145            {
146                return;
147            }
148            
149            EntryAttribute uniqueMember = adminGroup.get( uniqueMemberAT );
150            
151            for ( Value<?> value:uniqueMember )
152            {
153                DN memberDn = new DN( value.getString() );
154                memberDn.normalize( directoryService.getSchemaManager().getNormalizerMapping() );
155                newAdministrators.add( memberDn.getNormName() );
156            }
157            
158            administrators = newAdministrators;
159        }
160    
161        
162        // Note:
163        //    Lookup, search and list operations need to be handled using a filter
164        // and so we need access to the filter service.
165    
166        public void delete( NextInterceptor nextInterceptor, DeleteOperationContext opContext ) throws Exception
167        {
168            DN name = opContext.getDn();
169            
170            if ( opContext.getSession().getDirectoryService().isAccessControlEnabled() )
171            {
172                nextInterceptor.delete( opContext );
173                return;
174            }
175    
176            DN principalDn = getPrincipal().getClonedName();
177    
178            if ( name.isEmpty() )
179            {
180                String msg = I18n.err( I18n.ERR_12 );
181                LOG.error( msg );
182                throw new LdapNoPermissionException( msg );
183            }
184    
185            if ( name.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) )
186            {
187                String msg = I18n.err( I18n.ERR_13 );
188                LOG.error( msg );
189                throw new LdapNoPermissionException( msg );
190            }
191    
192            if ( isTheAdministrator( name ) )
193            {
194                String msg = I18n.err( I18n.ERR_14, principalDn.getName() );
195                LOG.error( msg );
196                throw new LdapNoPermissionException( msg );
197            }
198    
199            if ( name.size() > 2 )
200            {
201                if ( !isAnAdministrator( principalDn ) )
202                {
203                    if ( name.isChildOf( USER_BASE_DN ) )
204                    {
205                        String msg = I18n.err( I18n.ERR_15, principalDn.getName(), name.getName() );
206                        LOG.error( msg );
207                        throw new LdapNoPermissionException( msg );
208                    }
209            
210                    if ( name.isChildOf( GROUP_BASE_DN ) )
211                    {
212                        String msg = I18n.err( I18n.ERR_16, principalDn.getName(), name.getName() );
213                        LOG.error( msg );
214                        throw new LdapNoPermissionException( msg );
215                    }
216                }
217            }
218    
219            nextInterceptor.delete( opContext );
220        }
221    
222        
223        private boolean isTheAdministrator( DN normalizedDn )
224        {
225            return normalizedDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
226        }
227        
228        
229        private boolean isAnAdministrator( DN normalizedDn )
230        {
231            return isTheAdministrator( normalizedDn ) || administrators.contains( normalizedDn.getNormName() );
232    
233        }
234        
235    
236        // ------------------------------------------------------------------------
237        // Entry Modification Operations
238        // ------------------------------------------------------------------------
239    
240        /**
241         * This policy needs to be really tight too because some attributes may take
242         * part in giving the user permissions to protected resources.  We do not want
243         * users to self access these resources.  As far as we're concerned no one but
244         * the admin needs access.
245         */
246        public void modify( NextInterceptor nextInterceptor, ModifyOperationContext opContext )
247            throws Exception
248        {
249            if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
250            {
251                DN dn = opContext.getDn();
252                
253                protectModifyAlterations( dn );
254                nextInterceptor.modify( opContext );
255    
256                // update administrators if we change administrators group
257                if ( dn.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) )
258                {
259                    loadAdministrators( opContext.getSession().getDirectoryService() );
260                }
261            }
262            else
263            {
264                nextInterceptor.modify( opContext );
265            }
266        }
267    
268    
269        private void protectModifyAlterations( DN dn ) throws Exception
270        {
271            DN principalDn = getPrincipal().getClonedName();
272    
273            if ( dn.isEmpty() )
274            {
275                String msg = I18n.err( I18n.ERR_17 );
276                LOG.error( msg );
277                throw new LdapNoPermissionException( msg );
278            }
279    
280            if ( ! isAnAdministrator( principalDn ) )
281            {
282                // allow self modifications 
283                if ( dn.getNormName().equals( getPrincipal().getName() ) )
284                {
285                    return;
286                }
287                
288                if ( dn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ) )
289                {
290                    String msg = I18n.err( I18n.ERR_18, principalDn.getName() );
291                    LOG.error( msg );
292                    throw new LdapNoPermissionException( msg );
293                }
294    
295                if ( dn.size() > 2 ) 
296                    {
297                    if ( dn.isChildOf( USER_BASE_DN ) )
298                    {
299                        String msg = I18n.err( I18n.ERR_19, principalDn.getName(),  dn.getName() );
300                        LOG.error( msg );
301                        throw new LdapNoPermissionException( msg );
302                    }
303        
304                    if ( dn.isChildOf( GROUP_BASE_DN ) )
305                    {
306                        String msg = I18n.err( I18n.ERR_20, principalDn.getName(), dn.getName() );
307                        LOG.error( msg );
308                        throw new LdapNoPermissionException( msg );
309                    }
310                }
311            }
312        }
313        
314        
315        // ------------------------------------------------------------------------
316        // DN altering operations are a no no for any user entry.  Basically here
317        // are the rules of conduct to follow:
318        //
319        //  o No user should have the ability to move or rename their entry
320        //  o Only the administrator can move or rename non-admin user entries
321        //  o The administrator entry cannot be moved or renamed by anyone
322        // ------------------------------------------------------------------------
323    
324        public void rename( NextInterceptor nextInterceptor, RenameOperationContext opContext )
325            throws Exception
326        {
327            if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
328            {
329                protectDnAlterations( opContext.getDn() );
330            }
331            
332            nextInterceptor.rename( opContext );
333        }
334    
335    
336        public void move( NextInterceptor nextInterceptor, MoveOperationContext opContext ) throws Exception
337        {
338            if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
339            {
340                protectDnAlterations( opContext.getDn() );
341            }
342            
343            nextInterceptor.move( opContext );
344        }
345    
346    
347        public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext opContext ) throws Exception
348        {
349            if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
350            {
351                protectDnAlterations( opContext.getDn() );
352            }
353            
354            nextInterceptor.moveAndRename( opContext );
355        }
356    
357    
358        private void protectDnAlterations( DN dn ) throws Exception
359        {
360            DN principalDn = getPrincipal().getClonedName();
361    
362            if ( dn.isEmpty() )
363            {
364                String msg = I18n.err( I18n.ERR_234 );
365                LOG.error( msg );
366                throw new LdapNoPermissionException( msg );
367            }
368    
369            if ( dn.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) )
370            {
371                String msg = I18n.err( I18n.ERR_21 );
372                LOG.error( msg );
373                throw new LdapNoPermissionException( msg );
374            }
375            
376            if ( isTheAdministrator( dn ) )
377            {
378                String msg = I18n.err( I18n.ERR_22, principalDn.getName(), dn.getName() );
379                LOG.error( msg );
380                throw new LdapNoPermissionException( msg );
381            }
382    
383            if ( dn.size() > 2 && dn.isChildOf( USER_BASE_DN ) && !isAnAdministrator( principalDn ) )
384            {
385                String msg = I18n.err( I18n.ERR_23, principalDn.getName(), dn.getName() );
386                LOG.error( msg );
387                throw new LdapNoPermissionException( msg );
388            }
389    
390            if ( dn.size() > 2 && dn.isChildOf( GROUP_BASE_DN ) && !isAnAdministrator( principalDn ) )
391            {
392                String msg = I18n.err( I18n.ERR_24, principalDn.getName(), dn.getName() );
393                LOG.error( msg );
394                throw new LdapNoPermissionException( msg );
395            }
396        }
397    
398    
399        public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) throws Exception
400        {
401            ClonedServerEntry serverEntry = nextInterceptor.lookup( opContext );
402            
403            if ( opContext.getSession().getDirectoryService().isAccessControlEnabled() || ( serverEntry == null ) )
404            {
405                return serverEntry;
406            }
407    
408            protectLookUp( opContext.getSession().getEffectivePrincipal().getClonedName(), opContext.getDn() );
409            return serverEntry;
410        }
411    
412    
413        private void protectLookUp( DN principalDn, DN normalizedDn ) throws Exception
414        {
415            if ( !isAnAdministrator( principalDn ) )
416            {
417                if ( normalizedDn.size() > 2 )
418                {
419                    if( normalizedDn.isChildOf( USER_BASE_DN ) )
420                    {
421                        // allow for self reads
422                        if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
423                        {
424                            return;
425                        }
426        
427                        String msg = I18n.err( I18n.ERR_25, normalizedDn.getName(), principalDn.getName() );
428                        LOG.error( msg );
429                        throw new LdapNoPermissionException( msg );
430                    }
431    
432                    if ( normalizedDn.isChildOf( GROUP_BASE_DN ) )
433                    {
434                        // allow for self reads
435                        if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
436                        {
437                            return;
438                        }
439        
440                        String msg = I18n.err( I18n.ERR_26, normalizedDn.getName(), principalDn.getName() );
441                        LOG.error( msg );
442                        throw new LdapNoPermissionException( msg );
443                    }
444                }
445    
446                if ( isTheAdministrator( normalizedDn ) )
447                {
448                    // allow for self reads
449                    if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
450                    {
451                        return;
452                    }
453    
454                    String msg = I18n.err( I18n.ERR_27,  principalDn.getName() );
455                    LOG.error( msg );
456                    throw new LdapNoPermissionException( msg );
457                }
458            }
459        }
460    
461    
462        public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws Exception
463        {
464            EntryFilteringCursor cursor = nextInterceptor.search( opContext );
465    
466            if ( opContext.getSession().getDirectoryService().isAccessControlEnabled() )
467            {
468                return cursor;
469            }
470    
471            cursor.addEntryFilter( new EntryFilter() {
472                public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception
473                {
474                    return DefaultAuthorizationInterceptor.this.isSearchable( operation, result );
475                }
476            } );
477            return cursor;
478        }
479    
480    
481        public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws Exception
482        {
483            EntryFilteringCursor cursor = nextInterceptor.list( opContext );
484            
485            if ( opContext.getSession().getDirectoryService().isAccessControlEnabled() )
486            {
487                return cursor;
488            }
489    
490            cursor.addEntryFilter( new EntryFilter()
491            {
492                public boolean accept( SearchingOperationContext operation, ClonedServerEntry entry ) throws Exception
493                {
494                    return DefaultAuthorizationInterceptor.this.isSearchable( operation, entry );
495                }
496            } );
497            return cursor;
498        }
499    
500    
501        private boolean isSearchable( OperationContext opContext, ClonedServerEntry result ) throws Exception
502        {
503            DN principalDn = opContext.getSession().getEffectivePrincipal().getClonedName();
504            DN dn = result.getDn();
505            
506            if ( !dn.isNormalized() )
507            {
508                dn.normalize( opContext.getSession().getDirectoryService().getSchemaManager().getNormalizerMapping() );
509            }
510    
511            // Admin users gets full access to all entries
512            if ( isAnAdministrator( principalDn ) )
513            {
514                return true;
515            }
516            
517            // Users reading their own entries should be allowed to see all
518            boolean isSelfRead = dn.getNormName().equals( principalDn.getNormName() );
519            
520            if ( isSelfRead )
521            {
522                return true;
523            }
524            
525            // Block off reads to anything under ou=users and ou=groups if not a self read
526            if ( dn.size() > 2 )
527            {
528                // stuff this if in here instead of up in outer if to prevent 
529                // constant needless reexecution for all entries in other depths
530                
531                if ( dn.getNormName().endsWith( USER_BASE_DN.getNormName() ) 
532                    || dn.getNormName().endsWith( GROUP_BASE_DN.getNormName() ) )
533                {
534                    return false;
535                }
536            }
537            
538            // Non-admin users cannot read the admin entry
539            return ! isTheAdministrator( dn );
540    
541        }
542    }