Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtil.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtil.java (revision 1605678)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtil.java (revision )
@@ -56,6 +56,8 @@
public static final String DEFAULT_ALGORITHM = "SHA-256";
public static final int DEFAULT_SALT_SIZE = 8;
public static final int DEFAULT_ITERATIONS = 1000;
+ public static final int DEFAULT_PASSWORD_MAX_AGE = 0;
+ public static final boolean DEFAULT_PASSWORD_INITIAL_CHANGE = false;
/**
* Avoid instantiation
@@ -184,7 +186,7 @@
}
return false;
}
-
+
//------------------------------------------------------------< private >---
/**
* Compare two strings. The comparison is constant time: it will always loop
\ No newline at end of file
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/user/UserAuthenticationTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/user/UserAuthenticationTest.java (revision 1605678)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationTest.java (revision )
@@ -14,24 +14,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jackrabbit.oak.security.authentication.user;
+package org.apache.jackrabbit.oak.security.user;
import java.security.Principal;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.jcr.Credentials;
import javax.jcr.GuestCredentials;
import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.AccountNotFoundException;
+import javax.security.auth.login.CredentialExpiredException;
+import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
+import com.google.common.collect.ImmutableMap;
import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.oak.AbstractSecurityTest;
import org.apache.jackrabbit.oak.api.AuthInfo;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.security.SecurityProviderImpl;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.junit.Before;
import org.junit.Test;
@@ -45,24 +56,20 @@
public class UserAuthenticationTest extends AbstractSecurityTest {
private String userId;
- private UserAuthentication authentication;
+ private Authentication authentication;
+ private UserAuthenticationFactoryImpl userAuthenticationFactory = new UserAuthenticationFactoryImpl((UserConfigurationImpl) getUserConfiguration());
@Before
public void before() throws Exception {
super.before();
userId = getTestUser().getID();
- authentication = new UserAuthentication(userId, getUserManager(root));
+ userAuthenticationFactory = new UserAuthenticationFactoryImpl((UserConfigurationImpl) getUserConfiguration());
+ authentication = userAuthenticationFactory.getAuthentication(root, userId);
}
@Test
- public void testAuthenticateWithoutUserManager() throws Exception {
- UserAuthentication authentication = new UserAuthentication(userId, null);
- assertFalse(authentication.authenticate(new SimpleCredentials(userId, userId.toCharArray())));
- }
-
- @Test
public void testAuthenticateWithoutUserId() throws Exception {
- UserAuthentication authentication = new UserAuthentication(null, getUserManager(root));
+ Authentication authentication = userAuthenticationFactory.getAuthentication(root, null);
assertFalse(authentication.authenticate(new SimpleCredentials(userId, userId.toCharArray())));
}
@@ -80,29 +87,31 @@
@Test
public void testAuthenticateCannotResolveUser() throws Exception {
SimpleCredentials sc = new SimpleCredentials("unknownUser", "pw".toCharArray());
- Authentication a = new UserAuthentication(sc.getUserID(), getUserManager(root));
-
- assertFalse(a.authenticate(sc));
+ Authentication a = userAuthenticationFactory.getAuthentication(root, sc.getUserID());
+ try {
+ a.authenticate(sc);
+ fail("expected account not found exception");
+ } catch (AccountNotFoundException e) {
+ // success
- }
+ }
+ }
@Test
public void testAuthenticateResolvesToGroup() throws Exception {
Group g = getUserManager(root).createGroup("g1");
SimpleCredentials sc = new SimpleCredentials(g.getID(), "pw".toCharArray());
- Authentication a = new UserAuthentication(sc.getUserID(), getUserManager(root));
+ Authentication a = userAuthenticationFactory.getAuthentication(root, sc.getUserID());
try {
a.authenticate(sc);
fail("Authenticating Group should fail");
- } catch (LoginException e) {
+ } catch (AccountNotFoundException e) {
// success
} finally {
- if (g != null) {
- g.remove();
- root.commit();
- }
- }
+ g.remove();
+ root.commit();
+ }
+ }
- }
@Test
public void testAuthenticateInvalidSimpleCredentials() throws Exception {
@@ -115,7 +124,7 @@
try {
authentication.authenticate(creds);
fail("LoginException expected");
- } catch (LoginException e) {
+ } catch (FailedLoginException e) {
// success
}
}
@@ -166,7 +175,114 @@
assertTrue(authentication.authenticate(new ImpersonationCredentials(sc, new TestAuthInfo())));
}
+ @Test
+ public void testAuthenticatePasswordExpiredNewUser() throws Exception {
+ UserConfigurationImpl c = setCustomConfiguration(
+ ConfigurationParameters.of(new HashMap
*/
String PARAM_SUPPORT_AUTOSAVE = "supportAutoSave";
+
+ /**
+ * Optional configuration parameter indicating the maximum age in days a password may have
+ * before it expires. If the value specified is > 0, password expiry is implicitly enabled.
+ */
+ String PARAM_PASSWORD_MAX_AGE = "passwordMaxAge";
+
+ /**
+ * Optional configuration parameter indicating whether users must change their passwords
+ * on first login. If enabled, passwords are immediately expired upon user creation.
+ */
+ String PARAM_PASSWORD_INITIAL_CHANGE = "initialPasswordChange";
}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java (revision )
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java (revision )
@@ -0,0 +1,38 @@
+/*
+ * 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.jackrabbit.oak.security.user;
+
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
+import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory;
+
+import javax.annotation.Nonnull;
+
+public class UserAuthenticationFactoryImpl implements UserAuthenticationFactory {
+
+ private UserConfigurationImpl config;
+
+ public UserAuthenticationFactoryImpl(UserConfigurationImpl config) {
+ this.config = config;
+ }
+
+ @Nonnull
+ @Override
+ public Authentication getAuthentication(@Nonnull Root root, String userId) {
+ return new UserAuthentication(config, root, userId);
+ }
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java (revision 1605678)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java (revision )
@@ -18,6 +18,7 @@
import java.security.Principal;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -90,20 +91,40 @@
@Property(name = UserConstants.PARAM_SUPPORT_AUTOSAVE,
label = "Autosave Support",
description = "Configuration option to enable autosave behavior. Note: this config option is present for backwards compatibility with Jackrabbit 2.x and should only be used for broken code that doesn't properly verify the autosave behavior (see Jackrabbit API). If this option is turned on autosave will be enabled by default; otherwise autosave is not supported.",
- boolValue = false)
+ boolValue = false),
+ @Property(name = UserConstants.PARAM_PASSWORD_MAX_AGE,
+ label = "Maximum Password Age",
+ description = "Maximum age in days a password may have. Values greater 0 will implicitly enable password expiry. A value of 0 indicates unlimited password age.",
+ intValue = PasswordUtil.DEFAULT_PASSWORD_MAX_AGE),
+ @Property(name = UserConstants.PARAM_PASSWORD_INITIAL_CHANGE,
+ label = "Change Password On First Login",
+ description = "When enabled, forces users to change their password upon first login.",
+ boolValue = PasswordUtil.DEFAULT_PASSWORD_INITIAL_CHANGE)
})
public class UserConfigurationImpl extends ConfigurationBase implements UserConfiguration, SecurityConfiguration {
+ private final UserAuthenticationFactoryImpl factory = new UserAuthenticationFactoryImpl(this);
+
public UserConfigurationImpl() {
super();
+ ConfigurationParameters params = ConfigurationParameters.of(new HashMap() {{
+ put(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, factory);
+ }});
+ setParameters(params);
}
- public UserConfigurationImpl(SecurityProvider securityProvider) {
+ public UserConfigurationImpl(final SecurityProvider securityProvider) {
super(securityProvider, securityProvider.getParameters(NAME));
+ ConfigurationParameters params = ConfigurationParameters.of(new HashMap() {{
+ putAll(securityProvider.getParameters(NAME));
+ put(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, factory);
+ }});
+ setParameters(params);
}
@Activate
private void activate(Map properties) {
+ properties.put(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, factory);
setParameters(ConfigurationParameters.of(properties));
}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java (revision 1605678)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java (revision )
@@ -39,7 +39,9 @@
import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
import org.apache.jackrabbit.oak.spi.security.authentication.PreAuthenticatedLogin;
+import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory;
import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -97,6 +99,8 @@
private static final Logger log = LoggerFactory.getLogger(LoginModuleImpl.class);
+ private static final Credentials PRE_AUTHENTICATED = new Credentials() { };
+
protected static final Set SUPPORTED_CREDENTIALS = new HashSet(3);
static {
SUPPORTED_CREDENTIALS.add(SimpleCredentials.class);
@@ -112,32 +116,40 @@
@Override
public boolean login() throws LoginException {
- final boolean success;
+ boolean success = false;
credentials = getCredentials();
+ UserAuthenticationFactory factory = getUserAuthenticationFactory();
+ if (factory != null) {
+
- // check if we have a pre authenticated login from a previous login module
- PreAuthenticatedLogin preAuthLogin = getSharedPreAuthLogin();
+ // check if we have a pre authenticated login from a previous login module
+ PreAuthenticatedLogin preAuthLogin = getSharedPreAuthLogin();
- if (preAuthLogin != null) {
- userId = preAuthLogin.getUserId();
- Authentication authentication = new UserAuthentication(userId, getUserManager());
- success = authentication.authenticate(UserAuthentication.PRE_AUTHENTICATED);
- } else {
- userId = getUserId();
- Authentication authentication = new UserAuthentication(userId, getUserManager());
- success = authentication.authenticate(credentials);
- }
+ userId = (preAuthLogin != null)
+ ? preAuthLogin.getUserId()
+ : getUserId();
+ Credentials creds = (preAuthLogin != null)
+ ? PRE_AUTHENTICATED
+ : credentials;
+ Authentication authentication = factory.getAuthentication(getRoot(), userId);
+ success = authentication.authenticate(creds);
+
- if (success) {
- principals = getPrincipals(userId);
+ if (success) {
+ principals = getPrincipals(userId);
- log.debug("Adding Credentials to shared state.");
- //noinspection unchecked
- sharedState.put(SHARED_KEY_CREDENTIALS, credentials);
+ log.debug("Adding Credentials to shared state.");
+ //noinspection unchecked
+ sharedState.put(SHARED_KEY_CREDENTIALS, credentials);
- log.debug("Adding login name to shared state.");
- //noinspection unchecked
- sharedState.put(SHARED_KEY_LOGIN_NAME, userId);
- }
+ log.debug("Adding login name to shared state.");
+ //noinspection unchecked
+ sharedState.put(SHARED_KEY_LOGIN_NAME, userId);
+ }
+
+ } else {
+ log.error("No user authentication factory configured in user configuration.");
+ }
+
return success;
}
@@ -215,6 +227,13 @@
ConfigurationParameters params = sp.getConfiguration(UserConfiguration.class).getParameters();
return UserUtil.getAnonymousId(params);
}
+ }
+
+ private UserAuthenticationFactory getUserAuthenticationFactory() {
+ return getSecurityProvider()
+ .getConfiguration(UserConfiguration.class)
+ .getParameters()
+ .getConfigValue(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, null, UserAuthenticationFactory.class);
}
private AuthInfo createAuthInfo() {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactory.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactory.java (revision )
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactory.java (revision )
@@ -0,0 +1,38 @@
+/*
+ * 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.jackrabbit.oak.spi.security.user;
+
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Provides {@link org.apache.jackrabbit.oak.security.user.UserAuthentication}s to {@link javax.security.auth.spi.LoginModule}s, as a means of preventing the need for implementation casting across package boundaries.
+ */
+public interface UserAuthenticationFactory {
+
+ /**
+ * Returns the implementation's {@link org.apache.jackrabbit.oak.security.user.UserAuthentication}.
+ *
+ * @param root The {@link org.apache.jackrabbit.oak.api.Root} that provides repository access.
+ * @param userId The userId for which a user authentication is provided.
+ * @return The {@link org.apache.jackrabbit.oak.security.user.UserAuthentication} specific to the provided user.
+ */
+ @Nonnull
+ Authentication getAuthentication(@Nonnull Root root, String userId);
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java (revision 1605678)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java (revision )
@@ -19,6 +19,7 @@
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
+import java.util.Calendar;
import java.util.Iterator;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
@@ -51,6 +52,7 @@
import org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider;
import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
+import org.apache.jackrabbit.oak.util.NodeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -155,7 +157,11 @@
setPrincipal(userTree, principal);
if (password != null) {
setPassword(userTree, password, true);
+
+ if (forceInitialPasswordChange()) {
+ userTree.getChild(UserConstants.NN_REP_PW).removeProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
- }
+ }
+ }
User user = new UserImpl(userID, userTree, this);
onCreate(user, password);
@@ -372,6 +378,8 @@
pwHash = password;
}
userTree.setProperty(UserConstants.REP_PASSWORD, pwHash);
+
+ setPasswordLastModified(userTree, Calendar.getInstance().getTimeInMillis());
}
private UserQueryManager getQueryManager() {
@@ -379,5 +387,17 @@
queryManager = new UserQueryManager(this, namePathMapper, config, root);
}
return queryManager;
+ }
+
+ private boolean forceInitialPasswordChange() {
+ return config.getConfigValue(UserConstants.PARAM_PASSWORD_INITIAL_CHANGE, false);
+ }
+
+ private void setPasswordLastModified(Tree userTree, long lastModified) throws RepositoryException {
+ NodeUtil parent = new NodeUtil(userTree);
+ NodeUtil pwNode = (parent.hasChild(UserConstants.NN_REP_PW))
+ ? parent.getChild(UserConstants.NN_REP_PW)
+ : parent.addChild(UserConstants.NN_REP_PW, UserConstants.NT_REP_PASSWORD);
+ pwNode.setDate(UserConstants.REP_PASSWORD_LAST_MODIFIED, lastModified);
}
}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/UserAuthentication.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/UserAuthentication.java (revision 1605678)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java (revision )
@@ -14,23 +14,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jackrabbit.oak.security.authentication.user;
+package org.apache.jackrabbit.oak.security.user;
+import java.util.Calendar;
import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
import javax.jcr.Credentials;
import javax.jcr.GuestCredentials;
import javax.jcr.RepositoryException;
import javax.jcr.SimpleCredentials;
import javax.security.auth.Subject;
+import javax.security.auth.login.AccountLockedException;
+import javax.security.auth.login.AccountNotFoundException;
+import javax.security.auth.login.CredentialExpiredException;
+import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.oak.api.AuthInfo;
-import org.apache.jackrabbit.oak.security.user.CredentialsImpl;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -62,44 +76,55 @@
private static final Logger log = LoggerFactory.getLogger(UserAuthentication.class);
+ private final UserConfiguration config;
+ private final Root root;
private final String userId;
- private final UserManager userManager;
- UserAuthentication(String userId, UserManager userManager) {
+ UserAuthentication(@Nonnull UserConfigurationImpl config, @Nonnull Root root, String userId) {
+ this.config = config;
+ this.root = root;
this.userId = userId;
- this.userManager = userManager;
}
//-----------------------------------------------------< Authentication >---
@Override
public boolean authenticate(Credentials credentials) throws LoginException {
- if (userId == null || userManager == null || credentials == null) {
+
+ UserManager userManager = config.getUserManager(root, NamePathMapper.DEFAULT);
+
+ if (credentials == null || userId == null) {
return false;
}
- boolean success = false;
+ boolean success;
try {
Authorizable authorizable = userManager.getAuthorizable(userId);
if (authorizable == null) {
- return false;
+ throw new AccountNotFoundException("Could not find user " + userId);
}
if (authorizable.isGroup()) {
- throw new LoginException("Not a user " + userId);
+ throw new AccountNotFoundException("Not a user " + userId);
}
User user = (User) authorizable;
if (user.isDisabled()) {
- throw new LoginException("User with ID " + userId + " has been disabled: "+ user.getDisabledReason());
+ throw new AccountLockedException("User with ID " + userId + " has been disabled: "+ user.getDisabledReason());
}
if (credentials instanceof SimpleCredentials) {
- SimpleCredentials creds = (SimpleCredentials) credentials;
+
+ if (isPasswordExpired(user)) {
+ throw new CredentialExpiredException("User password has expired");
+ }
+
Credentials userCreds = user.getCredentials();
- if (userId.equals(creds.getUserID()) && userCreds instanceof CredentialsImpl) {
- success = PasswordUtil.isSame(((CredentialsImpl) userCreds).getPasswordHash(), creds.getPassword());
+ if (!PasswordUtil.isSame(((CredentialsImpl) userCreds).getPasswordHash(), ((SimpleCredentials) credentials).getPassword())) {
+ throw new FailedLoginException("UserId/Password mismatch.");
}
- checkSuccess(success, "UserId/Password mismatch.");
+
+ success = true;
+
} else if (credentials instanceof ImpersonationCredentials) {
ImpersonationCredentials ipCreds = (ImpersonationCredentials) credentials;
AuthInfo info = ipCreds.getImpersonatorInfo();
@@ -141,5 +166,46 @@
log.debug("Error while validating impersonation", e.getMessage());
}
return false;
+ }
+
+ @CheckForNull
+ private Long getPasswordLastModified(Tree userTree) {
+ PropertyState property = userTree.getChild(UserConstants.NN_REP_PW).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
+ return null != property ? property.getValue(Type.LONG, 0) : null;
+ }
+
+ private boolean isPasswordExpired(@Nonnull User user) throws RepositoryException {
+
+ Tree userTree = root.getTree(user.getPath());
+
+ // the password of the "admin" user never expires
+ String adminId = config.getParameters().getConfigValue(UserConstants.PARAM_ADMIN_ID, UserConstants.DEFAULT_ADMIN_ID);
+ if (adminId.equalsIgnoreCase(user.getID())) {
+ return false;
+ }
+
+ final boolean expired;
+
+ Long passwordLastModified = getPasswordLastModified(userTree);
+ int maxAge = config.getParameters().getConfigValue(UserConstants.PARAM_PASSWORD_MAX_AGE, 0);
+ boolean forceInitialPasswordChange = config.getParameters().getConfigValue(UserConstants.PARAM_PASSWORD_INITIAL_CHANGE, false);
+
+ if (maxAge > 0) {
+ if (passwordLastModified == null) {
+ // password expiry is enabled, but no expiry property exists (yet) => expire!
+ expired = true;
+ } else {
+ // password expiry is enabled, calculate expiry time (pw last mod + pw max age) and compare
+ long expiryTime = passwordLastModified + TimeUnit.MILLISECONDS.convert(maxAge, TimeUnit.DAYS);
+ expired = expiryTime < Calendar.getInstance().getTimeInMillis();
+ }
+
+ } else {
+ // a password is defined as never having changed (after initial setting, usually during user
+ // creation) when no pw last modified property exists.
+ expired = forceInitialPasswordChange && passwordLastModified == null;
+ }
+
+ return expired;
}
}
\ No newline at end of file
Index: oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd (revision 1605678)
+++ oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd (revision )
@@ -707,7 +707,12 @@
mixin
- rep:impersonators (STRING) protected multiple
+[rep:Password]
+ - * (UNDEFINED) protected
+ - * (UNDEFINED) protected multiple
+
[rep:User] > rep:Authorizable, rep:Impersonatable
+ + rep:pw (rep:Password) = rep:Password protected
- rep:password (STRING) protected
- rep:disabled (STRING) protected