| /* |
| * 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.ldap.handlers.extended; |
| |
| |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyRequest; |
| import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyRequest; |
| import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyResponse; |
| import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyResponseImpl; |
| import org.apache.directory.api.ldap.model.constants.SchemaConstants; |
| import org.apache.directory.api.ldap.model.entry.Attribute; |
| import org.apache.directory.api.ldap.model.entry.DefaultAttribute; |
| import org.apache.directory.api.ldap.model.entry.DefaultModification; |
| import org.apache.directory.api.ldap.model.entry.Entry; |
| import org.apache.directory.api.ldap.model.entry.Modification; |
| import org.apache.directory.api.ldap.model.entry.ModificationOperation; |
| import org.apache.directory.api.ldap.model.entry.Value; |
| import org.apache.directory.api.ldap.model.exception.LdapException; |
| import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; |
| import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; |
| import org.apache.directory.api.ldap.model.exception.LdapOperationException; |
| import org.apache.directory.api.ldap.model.message.Control; |
| import org.apache.directory.api.ldap.model.message.ModifyRequest; |
| import org.apache.directory.api.ldap.model.message.ModifyRequestImpl; |
| import org.apache.directory.api.ldap.model.message.ResultCodeEnum; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.api.ldap.model.password.PasswordUtil; |
| import org.apache.directory.api.util.Strings; |
| import org.apache.directory.server.core.api.CoreSession; |
| import org.apache.directory.server.core.api.DirectoryService; |
| import org.apache.directory.server.core.api.interceptor.context.BindOperationContext; |
| import org.apache.directory.server.core.shared.DefaultCoreSession; |
| import org.apache.directory.server.ldap.ExtendedOperationHandler; |
| import org.apache.directory.server.ldap.LdapServer; |
| import org.apache.directory.server.ldap.LdapSession; |
| import org.apache.mina.core.session.IoSession; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * An handler to manage PwdModifyRequest. Users can send a pwdModify request |
| * for their own passwords, or for another people password. Only admin can |
| * change someone else password without having to provide the original password. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class PwdModifyHandler implements ExtendedOperationHandler<PasswordModifyRequest, PasswordModifyResponse> |
| { |
| private static final Logger LOG = LoggerFactory.getLogger( PwdModifyHandler.class ); |
| public static final Set<String> EXTENSION_OIDS; |
| |
| static |
| { |
| Set<String> set = new HashSet<>( 2 ); |
| set.add( PasswordModifyRequest.EXTENSION_OID ); |
| set.add( PasswordModifyResponse.EXTENSION_OID ); |
| EXTENSION_OIDS = Collections.unmodifiableSet( set ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getOid() |
| { |
| return PasswordModifyRequest.EXTENSION_OID; |
| } |
| |
| |
| /** |
| * Modify the user's credentials. |
| * |
| * We will need to modify the userPassword attribute, accordingly to a few rules: |
| * - if the old password is present, we should verify it's valid. if not, we return an error |
| * - if the old password is absent, we are modifying the password of the current used. |
| * - if the new password is absent, we will return an error. The RFC says that we could |
| * generate a random password, but that would be dangerous to do so. |
| * - if the new password already exists, we simply return not changing anything |
| * - otherwise, we just remove the old password from the list of passwords (we may have |
| * more than one) and add the new password. This is done with a REPLACE operation (Modify) |
| */ |
| private void modifyUserPassword( CoreSession userSession, Entry userEntry, Dn userDn, |
| byte[] oldPassword, byte[] newPassword, PasswordModifyRequest req ) |
| { |
| IoSession ioSession = ( ( DefaultCoreSession ) userSession ).getIoSession(); |
| |
| if ( newPassword == null ) |
| { |
| // We don't support password generation on ApacheDS |
| writeResult( ioSession, req, ResultCodeEnum.UNWILLING_TO_PERFORM, |
| "Cannot change a password for user " + userDn + ", exception : null new password" ); |
| |
| return; |
| } |
| |
| // Get the user password attribute |
| Attribute userPassword = userEntry.get( SchemaConstants.USER_PASSWORD_AT ); |
| |
| if ( userPassword == null ) |
| { |
| // We can't modify the password |
| writeResult( ioSession, req, ResultCodeEnum.UNWILLING_TO_PERFORM, |
| "Cannot change a password for user " + userDn + ", the user has no existing password" ); |
| |
| return; |
| } |
| |
| if ( userPassword.contains( newPassword ) ) |
| { |
| // Ok, we are done : just return success |
| PasswordModifyResponseImpl pmrl = new PasswordModifyResponseImpl( |
| req.getMessageId(), ResultCodeEnum.SUCCESS ); |
| |
| Control ppolicyControl = req.getControl( PasswordPolicyRequest.OID ); |
| |
| if ( ppolicyControl != null ) |
| { |
| pmrl.addControl( ppolicyControl ); |
| } |
| |
| ioSession.write( pmrl ); |
| |
| return; |
| } |
| |
| if ( oldPassword == null ) |
| { |
| // We are modifying the password on behalf of another user. ACI will |
| // protect such modification if it's not allowed. In any case, we just |
| // modify the existing userPassword attribute, adding the password |
| ModifyRequest modifyRequest = new ModifyRequestImpl(); |
| modifyRequest.setName( userDn ); |
| |
| Control ppolicyControl = req.getControl( PasswordPolicyRequest.OID ); |
| |
| if ( ppolicyControl != null ) |
| { |
| modifyRequest.addControl( ppolicyControl ); |
| } |
| |
| try |
| { |
| Modification modification = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, |
| userPassword.getAttributeType(), newPassword ); |
| |
| modifyRequest.addModification( modification ); |
| ResultCodeEnum errorCode = null; |
| String errorMessage = null; |
| |
| try |
| { |
| userSession.modify( modifyRequest ); |
| |
| if ( LOG.isDebugEnabled() ) |
| { |
| LOG.debug( "Password modified for user {}", userDn ); |
| } |
| |
| // Ok, all done |
| PasswordModifyResponseImpl pmrl = new PasswordModifyResponseImpl( |
| req.getMessageId(), ResultCodeEnum.SUCCESS ); |
| |
| ppolicyControl = modifyRequest.getResultResponse().getControl( PasswordPolicyRequest.OID ); |
| |
| if ( ppolicyControl != null ) |
| { |
| pmrl.addControl( ppolicyControl ); |
| } |
| |
| ioSession.write( pmrl ); |
| |
| return; |
| } |
| catch ( LdapOperationException loe ) |
| { |
| errorCode = loe.getResultCode(); |
| errorMessage = loe.getMessage(); |
| } |
| catch ( LdapException le ) |
| { |
| // this exception means something else must be wrong |
| errorCode = ResultCodeEnum.OTHER; |
| errorMessage = le.getMessage(); |
| } |
| |
| // We can't modify the password |
| LOG.error( "Cannot modify the password for user {}, exception : {}", userDn, errorMessage ); |
| PasswordModifyResponseImpl errorPmrl = new PasswordModifyResponseImpl( |
| req.getMessageId(), errorCode, "Cannot modify the password for user " |
| + userDn + ", exception : " + errorMessage ); |
| |
| ppolicyControl = modifyRequest.getResultResponse().getControl( PasswordPolicyRequest.OID ); |
| |
| if ( ppolicyControl != null ) |
| { |
| errorPmrl.addControl( ppolicyControl ); |
| } |
| |
| ioSession.write( errorPmrl ); |
| |
| return; |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| // Nothing to do, this will never be a problem |
| } |
| } |
| else |
| { |
| // We are changing the password of the current user, check the password |
| boolean valid = false; |
| Attribute modifiedPassword = new DefaultAttribute( userPassword.getAttributeType() ); |
| |
| for ( Value value : userPassword ) |
| { |
| if ( !valid ) |
| { |
| valid = PasswordUtil.compareCredentials( oldPassword, value.getBytes() ); |
| } |
| |
| try |
| { |
| if ( valid ) |
| { |
| modifiedPassword.add( newPassword ); |
| } |
| else |
| { |
| modifiedPassword.add( value ); |
| } |
| } |
| catch ( LdapInvalidAttributeValueException e ) |
| { |
| // Nothing to do, this will never be a problem |
| } |
| } |
| |
| // At this point, we have what is needed to modify the password, if the oldPassword |
| // was valid |
| if ( valid ) |
| { |
| ModifyRequest modifyRequest = new ModifyRequestImpl(); |
| modifyRequest.setName( userDn ); |
| |
| Control ppolicyControl = req.getControl( PasswordPolicyRequest.OID ); |
| |
| if ( ppolicyControl != null ) |
| { |
| modifyRequest.addControl( ppolicyControl ); |
| } |
| |
| Modification modification = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, |
| modifiedPassword ); |
| |
| modifyRequest.addModification( modification ); |
| |
| ResultCodeEnum errorCode = null; |
| String errorMessage = null; |
| |
| try |
| { |
| userSession.modify( modifyRequest ); |
| |
| if ( LOG.isDebugEnabled() ) |
| { |
| LOG.debug( "Password modified for user {}", userDn ); |
| } |
| |
| // Ok, all done |
| PasswordModifyResponseImpl pmrl = new PasswordModifyResponseImpl( |
| req.getMessageId(), ResultCodeEnum.SUCCESS ); |
| |
| ppolicyControl = modifyRequest.getResultResponse().getControl( PasswordPolicyRequest.OID ); |
| |
| if ( ppolicyControl != null ) |
| { |
| pmrl.addControl( ppolicyControl ); |
| } |
| |
| ioSession.write( pmrl ); |
| |
| return; |
| } |
| catch ( LdapOperationException loe ) |
| { |
| errorCode = loe.getResultCode(); |
| errorMessage = loe.getMessage(); |
| } |
| catch ( LdapException le ) |
| { |
| // this exception means something else must be wrong |
| errorCode = ResultCodeEnum.OTHER; |
| errorMessage = le.getMessage(); |
| } |
| |
| // We can't modify the password |
| LOG.error( "Cannot modify the password for user {}, exception : {}", userDn, errorMessage ); |
| PasswordModifyResponseImpl errorPmrl = new PasswordModifyResponseImpl( |
| req.getMessageId(), errorCode, "Cannot modify the password for user " |
| + userDn + ", exception : " + errorMessage ); |
| |
| ppolicyControl = modifyRequest.getResultResponse().getControl( PasswordPolicyRequest.OID ); |
| |
| if ( ppolicyControl != null ) |
| { |
| errorPmrl.addControl( ppolicyControl ); |
| } |
| |
| ioSession.write( errorPmrl ); |
| |
| return; |
| } |
| else |
| { |
| // Too bad, the old password is invalid |
| writeResult( ioSession, req, ResultCodeEnum.INVALID_CREDENTIALS, |
| "Cannot change a password for user " + userDn + ", invalid credentials" ); |
| |
| return; |
| } |
| } |
| } |
| |
| |
| private void writeResult( LdapSession requestor, PasswordModifyRequest req, ResultCodeEnum error, String errorMessage ) |
| { |
| writeResult( requestor.getIoSession(), req, error, errorMessage ); |
| |
| } |
| |
| |
| private void writeResult( IoSession ioSession, PasswordModifyRequest req, ResultCodeEnum error, String errorMessage ) |
| { |
| LOG.error( errorMessage ); |
| ioSession.write( new PasswordModifyResponseImpl( |
| req.getMessageId(), error, errorMessage ) ); |
| |
| } |
| |
| |
| private Entry getModifiedEntry( LdapSession requestor, PasswordModifyRequest req, Dn entryDn ) |
| { |
| try |
| { |
| Entry modifiedEntry = requestor.getCoreSession().lookup( entryDn, SchemaConstants.ALL_ATTRIBUTES_ARRAY ); |
| |
| if ( modifiedEntry == null ) |
| { |
| // The entry does not exist, we can't modify its password |
| writeResult( requestor, req, ResultCodeEnum.NO_SUCH_OBJECT, |
| "The entry does not exist, we can't modify its password" ); |
| return null; |
| } |
| else |
| { |
| return modifiedEntry; |
| } |
| } |
| catch ( Exception le ) |
| { |
| // The entry does not exist, we can't modify its password |
| writeResult( requestor, req, ResultCodeEnum.NO_SUCH_OBJECT, |
| "The entry does not exist, we can't modify its password" ); |
| return null; |
| } |
| } |
| |
| |
| private void processAuthenticatedPasswordModify( LdapSession requestor, PasswordModifyRequest req, |
| Dn userDn ) |
| { |
| byte[] oldPassword = req.getOldPassword(); |
| byte[] newPassword = req.getNewPassword(); |
| |
| // We are already bound. Fetch the entry which we want to modify |
| Entry modifiedEntry = null; |
| |
| Dn principalDn = requestor.getCoreSession().getEffectivePrincipal().getDn(); |
| |
| LOG.debug( "User {} trying to modify password of user {}", principalDn, userDn ); |
| |
| |
| // First, check that the userDn is null : we can't change the password of someone else |
| // except if we are admin |
| if ( ( userDn != null ) && ( !userDn.equals( principalDn ) ) ) |
| { |
| // Are we admin ? |
| if ( requestor.getCoreSession().isAdministrator() ) |
| { |
| modifiedEntry = getModifiedEntry( requestor, req, userDn ); |
| |
| if ( modifiedEntry == null ) |
| { |
| return; |
| } |
| |
| // We are administrator, we can try to modify the user's credentials |
| modifyUserPassword( requestor.getCoreSession(), modifiedEntry, userDn, oldPassword, newPassword, req ); |
| } |
| else |
| { |
| // No : error |
| writeResult( requestor, req, ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS, |
| "Non-admin user cannot access another user's password to modify it" ); |
| } |
| } |
| else |
| { |
| // We are trying to modify our own password |
| modifiedEntry = getModifiedEntry( requestor, req, principalDn ); |
| |
| if ( modifiedEntry == null ) |
| { |
| return; |
| } |
| |
| modifyUserPassword( requestor.getCoreSession(), modifiedEntry, principalDn, oldPassword, newPassword, req ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void handleExtendedOperation( LdapSession requestor, PasswordModifyRequest req ) throws Exception |
| { |
| LOG.debug( "Password modification requested" ); |
| |
| // Grab the adminSession, we might need it later |
| DirectoryService service = requestor.getLdapServer().getDirectoryService(); |
| CoreSession adminSession = service.getAdminSession(); |
| String userIdentity = Strings.utf8ToString( req.getUserIdentity() ); |
| Dn userDn = null; |
| |
| if ( !Strings.isEmpty( userIdentity ) ) |
| { |
| try |
| { |
| userDn = service.getDnFactory().create( userIdentity ); |
| } |
| catch ( LdapInvalidDnException lide ) |
| { |
| // The userIdentity is not a DN : return with an error code. |
| writeResult( requestor, req, ResultCodeEnum.INVALID_DN_SYNTAX, |
| "The user DN is invalid : " + userDn ); |
| |
| return; |
| } |
| } |
| |
| byte[] oldPassword = req.getOldPassword(); |
| byte[] newPassword = req.getNewPassword(); |
| |
| // First check if the user is bound or not |
| if ( requestor.isAuthenticated() ) |
| { |
| processAuthenticatedPasswordModify( requestor, req, userDn ); |
| } |
| else |
| { |
| // The user is not authenticated : we have to use the provided userIdentity |
| // and the oldPassword to check if the user is present |
| BindOperationContext bindContext = new BindOperationContext( adminSession ); |
| bindContext.setDn( userDn ); |
| bindContext.setCredentials( oldPassword ); |
| |
| try |
| { |
| service.getOperationManager().bind( bindContext ); |
| } |
| catch ( LdapException le ) |
| { |
| // We can't bind with the provided information : we thus can't |
| // change the password... |
| requestor.getIoSession().write( new PasswordModifyResponseImpl( |
| req.getMessageId(), ResultCodeEnum.INVALID_CREDENTIALS ) ); |
| |
| return; |
| } |
| |
| // Ok, we were able to bind using the userIdentity and the password. Let's |
| // modify the password now |
| modifyUserPassword( requestor.getCoreSession(), bindContext.getEntry(), userDn, oldPassword, newPassword, req ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Set<String> getExtensionOids() |
| { |
| return EXTENSION_OIDS; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void setLdapServer( LdapServer ldapServer ) |
| { |
| } |
| } |