| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.server.core.authz; |
| |
| |
| import org.apache.directory.server.constants.ServerDNConstants; |
| import org.apache.directory.server.core.DirectoryService; |
| import org.apache.directory.server.core.enumeration.SearchResultFilter; |
| import org.apache.directory.server.core.enumeration.SearchResultFilteringEnumeration; |
| import org.apache.directory.server.core.interceptor.BaseInterceptor; |
| import org.apache.directory.server.core.interceptor.Interceptor; |
| import org.apache.directory.server.core.interceptor.NextInterceptor; |
| import org.apache.directory.server.core.interceptor.context.DeleteOperationContext; |
| import org.apache.directory.server.core.interceptor.context.ListOperationContext; |
| import org.apache.directory.server.core.interceptor.context.LookupOperationContext; |
| import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; |
| import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; |
| import org.apache.directory.server.core.interceptor.context.MoveOperationContext; |
| import org.apache.directory.server.core.interceptor.context.RenameOperationContext; |
| import org.apache.directory.server.core.interceptor.context.SearchOperationContext; |
| import org.apache.directory.server.core.invocation.Invocation; |
| import org.apache.directory.server.core.invocation.InvocationStack; |
| import org.apache.directory.server.core.jndi.ServerContext; |
| import org.apache.directory.server.core.partition.PartitionNexus; |
| import org.apache.directory.server.schema.registries.AttributeTypeRegistry; |
| import org.apache.directory.shared.ldap.constants.SchemaConstants; |
| import org.apache.directory.shared.ldap.exception.LdapNoPermissionException; |
| import org.apache.directory.shared.ldap.message.ServerSearchResult; |
| import org.apache.directory.shared.ldap.name.LdapDN; |
| import org.apache.directory.shared.ldap.schema.AttributeType; |
| import org.apache.directory.shared.ldap.schema.OidNormalizer; |
| import org.apache.directory.shared.ldap.util.AttributeUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.naming.Name; |
| import javax.naming.NamingEnumeration; |
| import javax.naming.NamingException; |
| import javax.naming.NoPermissionException; |
| import javax.naming.directory.Attribute; |
| import javax.naming.directory.Attributes; |
| import javax.naming.directory.SearchControls; |
| import javax.naming.directory.SearchResult; |
| import javax.naming.ldap.LdapContext; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| |
| /** |
| * An {@link Interceptor} that controls access to {@link PartitionNexus}. |
| * If a user tries to perform any operations that requires |
| * permission he or she doesn't have, {@link NoPermissionException} will be |
| * thrown and therefore the current invocation chain will terminate. |
| * |
| * @org.apache.xbean.XBean |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| * @version $Rev$, $Date$ |
| */ |
| public class DefaultAuthorizationInterceptor extends BaseInterceptor |
| { |
| /** the logger for this class */ |
| private static final Logger LOG = LoggerFactory.getLogger( DefaultAuthorizationInterceptor.class ); |
| |
| /** |
| * the base distinguished {@link Name} for all users |
| */ |
| private static LdapDN USER_BASE_DN; |
| |
| /** |
| * the base distinguished {@link Name} for all groups |
| */ |
| private static LdapDN GROUP_BASE_DN; |
| |
| /** |
| * the distinguished {@link Name} for the administrator group |
| */ |
| private static LdapDN ADMIN_GROUP_DN; |
| |
| /** |
| * the name parser used by this service |
| */ |
| private boolean enabled = true; |
| |
| private Set<String> administrators = new HashSet<String>(2); |
| |
| /** The normalizer mapping containing a relation between an OID and a normalizer */ |
| private Map<String, OidNormalizer> normalizerMapping; |
| |
| private PartitionNexus nexus; |
| |
| /** A starage for the uniqueMember attributeType */ |
| private AttributeType uniqueMemberAT; |
| |
| |
| /** |
| * Creates a new instance. |
| */ |
| public DefaultAuthorizationInterceptor() |
| { |
| } |
| |
| |
| public void init( DirectoryService directoryService ) throws NamingException |
| { |
| nexus = directoryService.getPartitionNexus(); |
| normalizerMapping = directoryService.getRegistries().getAttributeTypeRegistry().getNormalizerMapping(); |
| |
| // disable this static module if basic access control mechanisms are enabled |
| enabled = ! directoryService.isAccessControlEnabled(); |
| |
| USER_BASE_DN = PartitionNexus.getUsersBaseName(); |
| USER_BASE_DN.normalize( normalizerMapping ); |
| |
| GROUP_BASE_DN = PartitionNexus.getGroupsBaseName(); |
| GROUP_BASE_DN.normalize( normalizerMapping ); |
| |
| ADMIN_GROUP_DN = new LdapDN( ServerDNConstants.ADMINISTRATORS_GROUP_DN ); |
| ADMIN_GROUP_DN.normalize( normalizerMapping ); |
| |
| AttributeTypeRegistry attrRegistry = directoryService.getRegistries().getAttributeTypeRegistry(); |
| |
| uniqueMemberAT = attrRegistry.lookup( SchemaConstants.UNIQUE_MEMBER_AT_OID ); |
| |
| loadAdministrators(); |
| } |
| |
| |
| private void loadAdministrators() throws NamingException |
| { |
| // read in the administrators and cache their normalized names |
| Set<String> newAdministrators = new HashSet<String>( 2 ); |
| Attributes adminGroup = nexus.lookup( new LookupOperationContext( ADMIN_GROUP_DN ) ); |
| |
| if ( adminGroup == null ) |
| { |
| return; |
| } |
| |
| Attribute uniqueMember = AttributeUtils.getAttribute( adminGroup, uniqueMemberAT ); |
| |
| for ( int ii = 0; ii < uniqueMember.size(); ii++ ) |
| { |
| LdapDN memberDn = new LdapDN( ( String ) uniqueMember.get( ii ) ); |
| memberDn.normalize( normalizerMapping ); |
| newAdministrators.add( memberDn.getNormName() ); |
| } |
| |
| administrators = newAdministrators; |
| } |
| |
| |
| // Note: |
| // Lookup, search and list operations need to be handled using a filter |
| // and so we need access to the filter service. |
| |
| public void delete( NextInterceptor nextInterceptor, DeleteOperationContext opContext ) throws NamingException |
| { |
| LdapDN name = opContext.getDn(); |
| |
| if ( !enabled ) |
| { |
| nextInterceptor.delete( opContext ); |
| return; |
| } |
| |
| LdapDN principalDn = getPrincipal().getJndiName(); |
| |
| if ( name.isEmpty() ) |
| { |
| String msg = "The rootDSE cannot be deleted!"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( name.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) ) |
| { |
| String msg = "The Administrators group cannot be deleted!"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( isTheAdministrator( name ) ) |
| { |
| String msg = "User " + principalDn.getUpName(); |
| msg += " does not have permission to delete the admin account."; |
| msg += " No one not even the admin can delete this account!"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( name.size() > 2 ) |
| { |
| if ( !isAnAdministrator( principalDn ) ) |
| { |
| if ( name.startsWith( USER_BASE_DN ) ) |
| { |
| String msg = "User " + principalDn.getUpName(); |
| msg += " does not have permission to delete the user account: "; |
| msg += name.getUpName() + ". Only the admin can delete user accounts."; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( name.startsWith( GROUP_BASE_DN ) ) |
| { |
| String msg = "User " + principalDn.getUpName(); |
| msg += " does not have permission to delete the group entry: "; |
| msg += name.getUpName() + ". Only the admin can delete groups."; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| } |
| } |
| |
| nextInterceptor.delete( opContext ); |
| } |
| |
| |
| private boolean isTheAdministrator( LdapDN normalizedDn ) |
| { |
| return normalizedDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ); |
| } |
| |
| |
| private boolean isAnAdministrator( LdapDN normalizedDn ) |
| { |
| return isTheAdministrator( normalizedDn ) || administrators.contains( normalizedDn.getNormName() ); |
| |
| } |
| |
| |
| // ------------------------------------------------------------------------ |
| // Entry Modification Operations |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * This policy needs to be really tight too because some attributes may take |
| * part in giving the user permissions to protected resources. We do not want |
| * users to self access these resources. As far as we're concerned no one but |
| * the admin needs access. |
| */ |
| public void modify( NextInterceptor nextInterceptor, ModifyOperationContext opContext ) |
| throws NamingException |
| { |
| if ( enabled ) |
| { |
| LdapDN dn = opContext.getDn(); |
| |
| protectModifyAlterations( dn ); |
| nextInterceptor.modify( opContext ); |
| |
| // update administrators if we change administrators group |
| if ( dn.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) ) |
| { |
| loadAdministrators(); |
| } |
| } |
| else |
| { |
| nextInterceptor.modify( opContext ); |
| } |
| } |
| |
| |
| private void protectModifyAlterations( LdapDN dn ) throws NamingException |
| { |
| LdapDN principalDn = getPrincipal().getJndiName(); |
| |
| if ( dn.isEmpty() ) |
| { |
| String msg = "The rootDSE cannot be modified!"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( ! isAnAdministrator( principalDn ) ) |
| { |
| // allow self modifications |
| if ( dn.getNormName().equals( getPrincipal().getJndiName().getNormName() ) ) |
| { |
| return; |
| } |
| |
| if ( dn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ) ) |
| { |
| String msg = "User " + principalDn.getUpName(); |
| msg += " does not have permission to modify the account of the"; |
| msg += " admin user."; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( dn.size() > 2 ) |
| { |
| if ( dn.startsWith( USER_BASE_DN ) ) |
| { |
| String msg = "User " + principalDn.getUpName(); |
| msg += " does not have permission to modify the account of the"; |
| msg += " user " + dn.getUpName() + ".\nEven the owner of an account cannot"; |
| msg += " modify it.\nUser accounts can only be modified by the"; |
| msg += " administrator."; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( dn.startsWith( GROUP_BASE_DN ) ) |
| { |
| String msg = "User " + principalDn.getUpName(); |
| msg += " does not have permission to modify the group entry "; |
| msg += dn.getUpName() + ".\nGroups can only be modified by the admin."; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| } |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------ |
| // DN altering operations are a no no for any user entry. Basically here |
| // are the rules of conduct to follow: |
| // |
| // o No user should have the ability to move or rename their entry |
| // o Only the administrator can move or rename non-admin user entries |
| // o The administrator entry cannot be moved or renamed by anyone |
| // ------------------------------------------------------------------------ |
| |
| public void rename( NextInterceptor nextInterceptor, RenameOperationContext opContext ) |
| throws NamingException |
| { |
| if ( enabled ) |
| { |
| protectDnAlterations( opContext.getDn() ); |
| } |
| |
| nextInterceptor.rename( opContext ); |
| } |
| |
| |
| public void move( NextInterceptor nextInterceptor, MoveOperationContext opContext ) throws NamingException |
| { |
| if ( enabled ) |
| { |
| protectDnAlterations( opContext.getDn() ); |
| } |
| |
| nextInterceptor.move( opContext ); |
| } |
| |
| |
| public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext opContext ) throws NamingException |
| { |
| if ( enabled ) |
| { |
| protectDnAlterations( opContext.getDn() ); |
| } |
| |
| nextInterceptor.moveAndRename( opContext ); |
| } |
| |
| |
| private void protectDnAlterations( LdapDN dn ) throws NamingException |
| { |
| LdapDN principalDn = getPrincipal().getJndiName(); |
| |
| if ( dn.isEmpty() ) |
| { |
| String msg = "The rootDSE cannot be moved or renamed!"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( dn.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) ) |
| { |
| String msg = "The Administrators group cannot be moved or renamed!"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( isTheAdministrator( dn ) ) |
| { |
| String msg = "User '" + principalDn.getUpName(); |
| msg += "' does not have permission to move or rename the admin"; |
| msg += " account. No one not even the admin can move or"; |
| msg += " rename " + dn.getUpName() + "!"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( dn.size() > 2 && dn.startsWith( USER_BASE_DN ) && !isAnAdministrator( principalDn ) ) |
| { |
| String msg = "User '" + principalDn.getUpName(); |
| msg += "' does not have permission to move or rename the user"; |
| msg += " account: " + dn.getUpName() + ". Only the admin can move or"; |
| msg += " rename user accounts."; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( dn.size() > 2 && dn.startsWith( GROUP_BASE_DN ) && !isAnAdministrator( principalDn ) ) |
| { |
| String msg = "User " + principalDn.getUpName(); |
| msg += " does not have permission to move or rename the group entry "; |
| msg += dn.getUpName() + ".\nGroups can only be moved or renamed by the admin."; |
| throw new LdapNoPermissionException( msg ); |
| } |
| } |
| |
| |
| public Attributes lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) throws NamingException |
| { |
| Attributes attributes = nextInterceptor.lookup( opContext ); |
| |
| if ( !enabled || ( attributes == null ) ) |
| { |
| return attributes; |
| } |
| |
| protectLookUp( opContext.getDn() ); |
| return attributes; |
| } |
| |
| |
| private void protectLookUp( LdapDN normalizedDn ) throws NamingException |
| { |
| LdapContext ctx = ( LdapContext ) InvocationStack.getInstance().peek().getCaller(); |
| LdapDN principalDn = ( ( ServerContext ) ctx ).getPrincipal().getJndiName(); |
| |
| if ( !isAnAdministrator( principalDn ) ) |
| { |
| if ( normalizedDn.size() > 2 ) |
| { |
| if( normalizedDn.startsWith( USER_BASE_DN ) ) |
| { |
| // allow for self reads |
| if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) ) |
| { |
| return; |
| } |
| |
| String msg = "Access to user account '" + normalizedDn.getUpName() + "' not permitted"; |
| msg += " for user '" + principalDn.getUpName() + "'. Only the admin can"; |
| msg += " access user account information"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( normalizedDn.startsWith( GROUP_BASE_DN ) ) |
| { |
| // allow for self reads |
| if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) ) |
| { |
| return; |
| } |
| |
| String msg = "Access to group '" + normalizedDn.getUpName() + "' not permitted"; |
| msg += " for user '" + principalDn.getUpName() + "'. Only the admin can"; |
| msg += " access group information"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| } |
| |
| if ( isTheAdministrator( normalizedDn ) ) |
| { |
| // allow for self reads |
| if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) ) |
| { |
| return; |
| } |
| |
| String msg = "Access to admin account not permitted for user '"; |
| msg += principalDn.getUpName() + "'. Only the admin can"; |
| msg += " access admin account information"; |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| } |
| } |
| |
| |
| public NamingEnumeration<SearchResult> search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws NamingException |
| { |
| NamingEnumeration<SearchResult> e = nextInterceptor.search( opContext ); |
| |
| if ( !enabled ) |
| { |
| return e; |
| } |
| |
| Invocation invocation = InvocationStack.getInstance().peek(); |
| |
| return new SearchResultFilteringEnumeration( e, opContext.getSearchControls(), invocation, |
| new SearchResultFilter() |
| { |
| public boolean accept( Invocation invocation, SearchResult result, SearchControls controls ) |
| throws NamingException |
| { |
| return DefaultAuthorizationInterceptor.this.isSearchable( invocation, result ); |
| } |
| }, "Search Default Authorization filter" ); |
| } |
| |
| |
| public NamingEnumeration<SearchResult> list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws NamingException |
| { |
| NamingEnumeration<SearchResult> result = nextInterceptor.list( opContext ); |
| |
| if ( !enabled ) |
| { |
| return result; |
| } |
| |
| Invocation invocation = InvocationStack.getInstance().peek(); |
| |
| return new SearchResultFilteringEnumeration( result, null, invocation, new SearchResultFilter() |
| { |
| public boolean accept( Invocation invocation, SearchResult result, SearchControls controls ) |
| throws NamingException |
| { |
| return DefaultAuthorizationInterceptor.this.isSearchable( invocation, result ); |
| } |
| }, "List Default Authorization filter" ); |
| } |
| |
| |
| private boolean isSearchable( Invocation invocation, SearchResult result ) throws NamingException |
| { |
| LdapDN principalDn = ( ( ServerContext ) invocation.getCaller() ).getPrincipal().getJndiName(); |
| LdapDN dn = ((ServerSearchResult)result).getDn(); |
| |
| if ( !dn.isNormalized() ) |
| { |
| dn.normalize( normalizerMapping ); |
| } |
| |
| // Admin users gets full access to all entries |
| if ( isAnAdministrator( principalDn ) ) |
| { |
| return true; |
| } |
| |
| // Users reading their own entries should be allowed to see all |
| boolean isSelfRead = dn.getNormName().equals( principalDn.getNormName() ); |
| |
| if ( isSelfRead ) |
| { |
| return true; |
| } |
| |
| // Block off reads to anything under ou=users and ou=groups if not a self read |
| if ( dn.size() > 2 ) |
| { |
| // stuff this if in here instead of up in outer if to prevent |
| // constant needless reexecution for all entries in other depths |
| |
| if ( dn.getNormName().endsWith( USER_BASE_DN.getNormName() ) |
| || dn.getNormName().endsWith( GROUP_BASE_DN.getNormName() ) ) |
| { |
| return false; |
| } |
| } |
| |
| // Non-admin users cannot read the admin entry |
| return ! isTheAdministrator( dn ); |
| |
| } |
| } |