LdaptiveSambaTemplate.java
/*
* Copyright 2024 the original author or authors.
*
* Licensed 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.bremersee.ldaptive;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import org.ldaptive.AttributeModification;
import org.ldaptive.AttributeModification.Type;
import org.ldaptive.ConnectionFactory;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapException;
import org.ldaptive.ModifyRequest;
import org.ldaptive.ResultCode;
import org.ldaptive.SimpleBindRequest;
import org.passay.CharacterData;
import org.passay.CharacterRule;
import org.passay.EnglishCharacterData;
import org.passay.PasswordGenerator;
/**
* The ldaptive samba template.
*
* @author Christian Bremer
*/
public class LdaptiveSambaTemplate extends LdaptiveTemplate {
private Supplier<String> passwordGenerator;
/**
* Instantiates a new ldaptive samba template.
*
* @param connectionFactory the connection factory
*/
public LdaptiveSambaTemplate(ConnectionFactory connectionFactory) {
super(connectionFactory);
this.passwordGenerator = getDefaultPasswordGenerator();
}
/**
* Sets password generator.
*
* @param passwordGenerator the password generator
*/
public void setPasswordGenerator(Supplier<String> passwordGenerator) {
if (Objects.nonNull(passwordGenerator)) {
this.passwordGenerator = passwordGenerator;
}
}
@Override
public LdaptiveSambaTemplate copy(LdaptiveErrorHandler errorHandler) {
LdaptiveSambaTemplate template = new LdaptiveSambaTemplate(getConnectionFactory());
template.setErrorHandler(errorHandler);
return template;
}
@Override
public String generateUserPassword(String dn) {
String newPassword = passwordGenerator.get();
setUserPassword(dn, newPassword);
return newPassword;
}
private void setUserPassword(String dn, String newPassword) {
String quotedPassword = "\"" + newPassword + "\"";
char[] unicodePwd = quotedPassword.toCharArray();
byte[] pwdArray = new byte[unicodePwd.length * 2];
for (int i = 0; i < unicodePwd.length; i++) {
pwdArray[i * 2 + 1] = (byte) (unicodePwd[i] >>> 8);
pwdArray[i * 2] = (byte) (unicodePwd[i] & 0xff);
}
LdapAttribute ldapAttribute = new LdapAttribute();
ldapAttribute.setName("unicodePwd");
ldapAttribute.setBinary(true);
ldapAttribute.addBinaryValues(pwdArray);
AttributeModification attributeModification = new AttributeModification(
Type.REPLACE, ldapAttribute);
ModifyRequest modifyRequest = ModifyRequest.builder()
.dn(dn)
.modifications(attributeModification)
.build();
this.copy(new AbstractLdaptiveErrorHandler() {
@Override
public LdaptiveException map(LdapException ldapException) {
int httpStatus;
String errorCode;
String reason;
if (ldapException.getResultCode() == ResultCode.CONSTRAINT_VIOLATION
&& ldapException.getMessage().contains("check_password_restrictions")) {
httpStatus = 400;
errorCode = "check_password_restrictions";
reason = "New password restrictions were violated.";
} else if (ldapException.getResultCode() == ResultCode.NO_SUCH_OBJECT) {
httpStatus = 404;
reason = "No such object found.";
errorCode = "org.bremersee.ldaptive:26cfda51-0fde-443c-87b1-502fc165bb94";
} else {
httpStatus = 500;
reason = "Internal error.";
errorCode = "org.bremersee.ldaptive:a70939fb-2c94-412f-80c0-00a7d5dcf4a6";
}
return LdaptiveException.builder()
.httpStatus(httpStatus)
.errorCode(errorCode)
.reason(reason)
.cause(ldapException)
.build();
}
})
.modify(modifyRequest);
}
@Override
public void modifyUserPassword(String dn, String oldPass, String newPass) {
if (bind(new SimpleBindRequest(dn, oldPass))) {
setUserPassword(dn, newPass);
} else {
throw LdaptiveException.builder()
.reason("Bind failed.")
.errorCode("bind_failed")
.httpStatus(400)
.build();
}
}
private static Supplier<String> getDefaultPasswordGenerator() {
PasswordGenerator pwGen = new PasswordGenerator();
CharacterData special = new CharacterData() {
@Override
public String getErrorCode() {
return "INSUFFICIENT_SPECIAL";
}
@Override
public String getCharacters() {
return "!#$%&'()*+,-./:;<=>?@[]_{|}~";
}
};
List<CharacterRule> rules = List.of(
// at least five upper-case character
new CharacterRule(EnglishCharacterData.UpperCase, 5),
// at least five lower-case character
new CharacterRule(EnglishCharacterData.LowerCase, 5),
// at least three digit character
new CharacterRule(EnglishCharacterData.Digit, 3),
// at least one special character
new CharacterRule(special, 1));
return () -> pwGen.generatePassword(16, rules);
}
}