mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 15:02:05 -03:30
Password modification time attribute as an operational and read-only attribute
Closes #40270 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
a8a455486d
commit
9412e339a8
@ -1228,20 +1228,16 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||
}
|
||||
|
||||
private long getPasswordChangedTime(LDAPObject ldapObject) {
|
||||
String value = ldapObject.getAttributeAsString(getAttributeName());
|
||||
String attributeName = getLdapIdentityStore().getPasswordModificationTimeAttributeName();
|
||||
String value = ldapObject.getAttributeAsString(attributeName);
|
||||
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return -1L;
|
||||
}
|
||||
|
||||
if (LDAPConstants.PWD_LAST_SET.equals(getAttributeName())) {
|
||||
if (LDAPConstants.PWD_LAST_SET.equals(attributeName)) {
|
||||
return (Long.parseLong(value) / 10000L) - 11644473600000L;
|
||||
}
|
||||
return LDAPUtils.generalizedTimeToDate(value).getTime();
|
||||
}
|
||||
|
||||
private String getAttributeName() {
|
||||
LDAPConfig config = getLdapIdentityStore().getConfig();
|
||||
return config.isActiveDirectory() ? LDAPConstants.PWD_LAST_SET : LDAPConstants.PWD_CHANGED_TIME;
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,6 +113,12 @@ public class LDAPUtils {
|
||||
.collect(Collectors.toSet());
|
||||
mandatoryAttrs.add(ldapConfig.getRdnLdapAttribute());
|
||||
|
||||
String passwordModifiedTimeAttributeName = ldapStore.getPasswordModificationTimeAttributeName();
|
||||
String passwordModifiedTime = user.getFirstAttribute(passwordModifiedTimeAttributeName);
|
||||
if (passwordModifiedTime != null) {
|
||||
ldapUser.setSingleAttribute(passwordModifiedTimeAttributeName, passwordModifiedTime);
|
||||
}
|
||||
|
||||
ldapUser.executeOnMandatoryAttributesComplete(mandatoryAttrs, ldapObject -> {
|
||||
LDAPUtils.computeAndSetDn(ldapConfig, ldapObject);
|
||||
ldapStore.add(ldapObject);
|
||||
|
||||
@ -524,6 +524,13 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (!isCreate) {
|
||||
// for updates, assume the PWD_CHANGED_TIME attribute is an operational attribute and read-only
|
||||
// otherwise, updates will fail when trying to modify the attribute
|
||||
// vendors like AD, support the same type of attribute differently and using a mapper
|
||||
ldapObject.addReadOnlyAttributeName(LDAPConstants.PWD_CHANGED_TIME);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Set<String>> attrEntry : ldapObject.getAttributes().entrySet()) {
|
||||
String attrName = attrEntry.getKey();
|
||||
Set<String> attrValue = attrEntry.getValue();
|
||||
@ -602,4 +609,8 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||
return attr;
|
||||
}
|
||||
|
||||
public String getPasswordModificationTimeAttributeName() {
|
||||
return getConfig().isActiveDirectory() ? LDAPConstants.PWD_LAST_SET : LDAPConstants.PWD_CHANGED_TIME;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -65,6 +65,9 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
|
||||
public static final Set<String> PASSWORD_UPDATE_MSAD_ERROR_CODES = Set.of("52D");
|
||||
|
||||
private final Function<LDAPObject, UserAccountControl> GET_USER_ACCOUNT_CONTROL = ldapUser -> {
|
||||
if (ldapUser == null) {
|
||||
return UserAccountControl.empty();
|
||||
}
|
||||
String userAccountControl = ldapUser.getAttributeAsString(LDAPConstants.USER_ACCOUNT_CONTROL);
|
||||
return UserAccountControl.of(userAccountControl);
|
||||
};
|
||||
|
||||
@ -49,9 +49,13 @@ public class UserAccountControl {
|
||||
|
||||
private static final UserAccountControl EMPTY = new UserAccountControl(0);
|
||||
|
||||
public static UserAccountControl empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
public static UserAccountControl of(String userAccountControl) {
|
||||
if (userAccountControl == null) {
|
||||
return EMPTY;
|
||||
return empty();
|
||||
}
|
||||
return new UserAccountControl(Long.parseLong(userAccountControl));
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ public class LDAPTestUtils {
|
||||
} else if (otherAttrs.containsKey(name)) {
|
||||
return otherAttrs.getFirst(name);
|
||||
}
|
||||
return super.getFirstAttribute(name);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -307,4 +307,8 @@ public class LDAPRule extends ExternalResource {
|
||||
STARTTLS
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmbeddedServer() {
|
||||
return ldapTestConfiguration.isStartEmbeddedLdapServer();
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.admin.client.resource.UserProfileResource;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.federation.kerberos.KerberosFederationProvider;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
@ -90,6 +91,7 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
|
||||
|
||||
@Override
|
||||
protected void afterImportTestRealm() {
|
||||
boolean isEmbeddedServer = ldapRule.isEmbeddedServer();
|
||||
testingClient.server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
@ -97,10 +99,17 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
|
||||
// Delete all LDAP users and add some new for testing
|
||||
LDAPTestUtils.removeAllLDAPUsers(ctx.getLdapProvider(), appRealm);
|
||||
|
||||
LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
|
||||
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
|
||||
john.setSingleAttribute(LDAPConstants.PWD_CHANGED_TIME, "22000101000000Z");
|
||||
ctx.getLdapProvider().getLdapIdentityStore().update(john);
|
||||
if (isEmbeddedServer) {
|
||||
MultivaluedHashMap<String, String> otherAttrs = new MultivaluedHashMap<>();
|
||||
|
||||
otherAttrs.putSingle(LDAPConstants.PWD_CHANGED_TIME, "22000101000000Z");
|
||||
|
||||
LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", otherAttrs);
|
||||
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
|
||||
} else {
|
||||
LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
|
||||
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -246,7 +246,7 @@ public class LDAPBinaryAttributesTest extends AbstractLDAPTest {
|
||||
} else if (UserModel.USERNAME.equals(name)) {
|
||||
return username;
|
||||
}
|
||||
return super.getFirstAttribute(name);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user