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
021package org.apache.directory.server.core.hash;
022
023
024import java.util.List;
025
026import org.apache.directory.api.ldap.model.constants.LdapSecurityConstants;
027import org.apache.directory.api.ldap.model.constants.SchemaConstants;
028import org.apache.directory.api.ldap.model.entry.Attribute;
029import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
030import org.apache.directory.api.ldap.model.entry.Entry;
031import org.apache.directory.api.ldap.model.entry.Modification;
032import org.apache.directory.api.ldap.model.entry.ModificationOperation;
033import org.apache.directory.api.ldap.model.entry.Value;
034import org.apache.directory.api.ldap.model.exception.LdapException;
035import org.apache.directory.api.ldap.model.password.PasswordUtil;
036import org.apache.directory.api.util.Strings;
037import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
038import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
039import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
040
041
042/**
043 * An interceptor to hash plain text password according to the configured
044 * hashing algorithm.
045 *
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 */
048public abstract class PasswordHashingInterceptor extends BaseInterceptor
049{
050
051    /** the hashing algorithm to be used, if null then the password won't be changed */
052    private LdapSecurityConstants algorithm;
053
054
055    /**
056     * 
057     * Creates a new instance of PasswordHashingInterceptor which hashes the
058     * incoming non-hashed password using the given algorithm.
059     * If the password is found already hashed then it will skip hashing it.
060     * 
061     * @param name The instance's name
062     * @param algorithm the name of the algorithm to be used
063     */
064    protected PasswordHashingInterceptor( String name, LdapSecurityConstants algorithm )
065    {
066        super( name );
067        this.algorithm = algorithm;
068    }
069
070
071    /**
072     * {@inheritDoc}
073     */
074    @Override
075    public void add( AddOperationContext addContext ) throws LdapException
076    {
077        if ( algorithm == null )
078        {
079            next( addContext );
080            return;
081        }
082
083        Entry entry = addContext.getEntry();
084
085        Attribute pwdAt = entry.get( SchemaConstants.USER_PASSWORD_AT );
086
087        Attribute hashedPwdAt = includeHashedPassword( pwdAt );
088        
089        if ( hashedPwdAt != null )
090        {
091            entry.remove( pwdAt );
092            entry.add( hashedPwdAt );
093        }
094
095        next( addContext );
096    }
097
098
099    /**
100     * {@inheritDoc}
101     */
102    @Override
103    public void modify( ModifyOperationContext modifyContext ) throws LdapException
104    {
105        if ( algorithm == null )
106        {
107            next( modifyContext );
108            return;
109        }
110
111        List<Modification> mods = modifyContext.getModItems();
112
113        for ( Modification mod : mods )
114        {
115            String oid = mod.getAttribute().getAttributeType().getOid();
116
117            // check for modification on 'userPassword' AT
118            if ( SchemaConstants.USER_PASSWORD_AT_OID.equals( oid ) )
119            {
120                if ( mod.getOperation() == ModificationOperation.REMOVE_ATTRIBUTE )
121                {
122                   continue; 
123                }
124                
125                Attribute newPwd = includeHashedPassword( mod.getAttribute() );
126
127                if ( newPwd != null )
128                {
129                    mod.setAttribute( newPwd );
130                }
131            }
132        }
133
134        next( modifyContext );
135    }
136
137
138    /**
139     * hash the password if it was <i>not</i> already hashed
140     *
141     * @param pwdAt the password attribute
142     */
143    private Attribute includeHashedPassword( Attribute pwdAt ) throws LdapException
144    {
145        if ( pwdAt == null )
146        {
147            return null;
148        }
149
150        Attribute newPwd = new DefaultAttribute( pwdAt.getAttributeType() );
151
152        // Special case : deal with a potential empty value. We may have more than one
153        for ( Value userPassword : pwdAt )
154        {
155            if ( Strings.isEmpty( userPassword.getString() ) )
156            {
157                continue;
158            }
159
160            // check if the given password is already hashed
161            LdapSecurityConstants existingAlgo = PasswordUtil.findAlgorithm( userPassword.getBytes() );
162
163            // if there exists NO algorithm, then hash the password
164            if ( existingAlgo == null )
165            {
166                byte[] hashedPassword = PasswordUtil.createStoragePassword( userPassword.getBytes(), algorithm );
167
168                newPwd.add( hashedPassword );
169            }
170            else
171            {
172                newPwd.add( userPassword.getBytes() );
173            }
174        }
175
176        return newPwd;
177    }
178}