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