OAK-9868 : Introduce interface to extract tree from users (#660)
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/DynamicGroupUtil.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/DynamicGroupUtil.java
index d49479b..8612c64 100644
--- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/DynamicGroupUtil.java
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/DynamicGroupUtil.java
@@ -22,6 +22,7 @@
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.plugins.tree.TreeAware;
import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
@@ -65,7 +66,7 @@
@NotNull
static Tree getTree(@NotNull Authorizable authorizable, @NotNull Root root) throws RepositoryException {
- return root.getTree(authorizable.getPath());
+ return (authorizable instanceof TreeAware) ? ((TreeAware) authorizable).getTree() : root.getTree(authorizable.getPath());
}
static boolean hasStoredMemberInfo(@NotNull Group group, @NotNull Root root) {
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.java
index c6e6329..4925a1b 100644
--- a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.java
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.java
@@ -30,6 +30,7 @@
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.plugins.tree.TreeAware;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
@@ -70,6 +71,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
public class ExternalGroupPrincipalProviderTest extends AbstractPrincipalTest {
@@ -401,8 +403,10 @@
@Test
public void testGetPrincipalsNonExistingUserTree() throws Exception {
- Authorizable a = spy(getUserManager(root).getAuthorizable(USER_ID));
+ Authorizable a = mock(Authorizable.class, withSettings().extraInterfaces(User.class));
+ when(a.getID()).thenReturn(USER_ID);
when(a.getPath()).thenReturn("/path/to/non/existing/item");
+
UserManager um = when(mock(UserManager.class).getAuthorizable(USER_ID)).thenReturn(a).getMock();
UserConfiguration uc = when(mock(UserConfiguration.class).getUserManager(root, getNamePathMapper())).thenReturn(um).getMock();
@@ -415,6 +419,9 @@
Authorizable group = getUserManager(root).createGroup("testGroup");
Authorizable a = spy(getUserManager(root).getAuthorizable(USER_ID));
when(a.getPath()).thenReturn(group.getPath());
+ if (a instanceof TreeAware && group instanceof TreeAware) {
+ when(((TreeAware) a).getTree()).thenReturn(((TreeAware)group).getTree());
+ }
UserManager um = when(mock(UserManager.class).getAuthorizable(USER_ID)).thenReturn(a).getMock();
UserConfiguration uc = when(mock(UserConfiguration.class).getUserManager(root, getNamePathMapper())).thenReturn(um).getMock();
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
index 82b2e56..0ea39ff 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
@@ -42,7 +42,9 @@
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.namepath.PathMapper;
import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager;
+import org.apache.jackrabbit.oak.plugins.tree.TreeAware;
import org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
@@ -381,7 +383,7 @@
String userPath = user.getPath();
parentPath = userPath + '/' + TOKENS_NODE_NAME;
- Tree userNode = root.getTree(userPath);
+ Tree userNode = getTree(user, userPath);
tokenParent = TreeUtil.getOrAddChild(userNode, TOKENS_NODE_NAME, TOKENS_NT_NAME);
root.commit();
@@ -403,6 +405,14 @@
return tokenParent;
}
+ public @NotNull Tree getTree(@NotNull User user, @NotNull String userPath) {
+ if (user instanceof TreeAware) {
+ return ((TreeAware) user).getTree();
+ } else {
+ return root.getTree(userPath);
+ }
+ }
+
/**
* Create a new token node below the specified {@code parent}.
*
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/AuthorizableImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/AuthorizableImpl.java
index 1369abb..7e44186 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/AuthorizableImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/AuthorizableImpl.java
@@ -23,6 +23,7 @@
import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.plugins.tree.TreeAware;
import org.apache.jackrabbit.oak.security.user.monitor.UserMonitor;
import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;
import org.apache.jackrabbit.oak.spi.security.user.DynamicMembershipProvider;
@@ -43,7 +44,7 @@
/**
* Base class for {@code User} and {@code Group} implementations.
*/
-abstract class AuthorizableImpl implements Authorizable, UserConstants {
+abstract class AuthorizableImpl implements Authorizable, UserConstants, TreeAware {
/**
* logger instance
@@ -179,10 +180,11 @@
String typeStr = (isGroup()) ? "Group '" : "User '";
return new StringBuilder().append(typeStr).append(id).append('\'').toString();
}
-
- //--------------------------------------------------------------------------
+
+ //----------------------------------------------------------< TreeAware >---
+ @Override
@NotNull
- Tree getTree() {
+ public Tree getTree() {
if (tree.exists()) {
return tree;
} else {
@@ -190,6 +192,8 @@
}
}
+ //--------------------------------------------------------------------------
+
@Nullable
String getPrincipalNameOrNull() {
if (principalName == null) {
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
index 8d0828d..09b35cf 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
@@ -239,12 +239,7 @@
@Nullable
private Long getPasswordLastModified(@NotNull User user) throws RepositoryException {
- Tree userTree;
- if (user instanceof UserImpl) {
- userTree = ((UserImpl) user).getTree();
- } else {
- userTree = root.getTree(user.getPath());
- }
+ Tree userTree = Utils.getTree(user, root);
PropertyState property = userTree.getChild(REP_PWD).getProperty(REP_PASSWORD_LAST_MODIFIED);
return (property != null) ? property.getValue(Type.LONG) : null;
}
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java
index 1b36403..90a0af8 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java
@@ -640,7 +640,7 @@
if (!nonExisting.isEmpty()) {
Stopwatch watch = Stopwatch.createStarted();
log.debug("ImportBehavior.BESTEFFORT: Found {} entries of rep:members pointing to non-existing authorizables. Adding to rep:members.", nonExisting.size());
- Tree groupTree = root.getTree(gr.getPath());
+ Tree groupTree = Utils.getTree(gr, root);
MembershipProvider membershipProvider = userManager.getMembershipProvider();
@@ -741,7 +741,7 @@
// 2. adjust set of impersonators
List<String> nonExisting = updateImpersonators(a, imp, toRemove, toAdd);
if (!nonExisting.isEmpty()) {
- Tree userTree = checkNotNull(root.getTree(a.getPath()));
+ Tree userTree = Utils.getTree(a, root);
// copy over all existing impersonators to the nonExisting list
PropertyState impersonators = userTree.getProperty(REP_IMPERSONATORS);
if (impersonators != null) {
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/Utils.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/Utils.java
index f0f1903..197f661 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/Utils.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/Utils.java
@@ -18,8 +18,10 @@
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.tree.TreeAware;
import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
@@ -103,4 +105,13 @@
return null;
}
}
+
+ @NotNull
+ static Tree getTree(@NotNull Authorizable authorizable, @NotNull Root root) throws RepositoryException {
+ if (authorizable instanceof TreeAware) {
+ return ((TreeAware) authorizable).getTree();
+ } else {
+ return root.getTree(authorizable.getPath());
+ }
+ }
}
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UtilsTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UtilsTest.java
index 362d782..6462817 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UtilsTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UtilsTest.java
@@ -21,8 +21,10 @@
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.tree.TreeAware;
import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
import org.jetbrains.annotations.NotNull;
@@ -35,8 +37,13 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
public class UtilsTest extends AbstractSecurityTest {
@@ -149,4 +156,34 @@
when(a.isGroup()).thenReturn(true);
assertFalse(Utils.isEveryone(a));
}
+
+ @Test
+ public void testGetTreeFromTreeAware() throws Exception {
+ Tree t = mock(Tree.class);
+ Root r = mock(Root.class);
+
+ Authorizable a = mock(Authorizable.class, withSettings().extraInterfaces(TreeAware.class));
+ when(((TreeAware) a).getTree()).thenReturn(t);
+
+ assertSame(t, Utils.getTree(a, r));
+
+ verifyNoInteractions(r);
+ verify((TreeAware) a).getTree();
+ verifyNoMoreInteractions(a);
+ }
+
+ @Test
+ public void testGetTree() throws Exception {
+ Tree t = mock(Tree.class);
+ Root r = when(mock(Root.class).getTree("/user/path")).thenReturn(t).getMock();
+
+ Authorizable a = mock(Authorizable.class);
+ when(a.getPath()).thenReturn("/user/path");
+
+ assertSame(t, Utils.getTree(a, r));
+
+ verify(r).getTree(anyString());
+ verify(a).getPath();
+ verifyNoMoreInteractions(a, r);
+ }
}
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/TreeAware.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/TreeAware.java
new file mode 100644
index 0000000..db24b0f
--- /dev/null
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/TreeAware.java
@@ -0,0 +1,32 @@
+/*
+ * 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.plugins.tree;
+
+import org.apache.jackrabbit.oak.api.Tree;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Oak internal utility interface to avoid repeated retrieval of an underlying {@link Tree}.
+ * Note, that is is the responsibility of the caller to make user that the content session and root
+ * used to retrieve {@link TreeAware} object remains the same.
+ */
+public interface TreeAware {
+
+ @NotNull
+ Tree getTree();
+
+}
\ No newline at end of file
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/package-info.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/package-info.java
index 221dffa..6141ac0 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/package-info.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/package-info.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("3.2.1")
+@Version("3.3.0")
package org.apache.jackrabbit.oak.plugins.tree;
import org.osgi.annotation.versioning.Version;
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeAction.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeAction.java
index 3efcbad..014e953 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeAction.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeAction.java
@@ -21,7 +21,9 @@
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.plugins.tree.TreeAware;
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
@@ -55,7 +57,8 @@
//------------------------------------------------------------< private >---
@Nullable
- private String getPasswordHash(@NotNull Root root, @NotNull User user) throws RepositoryException {
- return TreeUtil.getString(root.getTree(user.getPath()), UserConstants.REP_PASSWORD);
+ private static String getPasswordHash(@NotNull Root root, @NotNull User user) throws RepositoryException {
+ Tree tree = (user instanceof TreeAware) ? ((TreeAware)user).getTree() : root.getTree(user.getPath());
+ return TreeUtil.getString(tree, UserConstants.REP_PASSWORD);
}
}
diff --git a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeActionTest.java b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeActionTest.java
index 784712e..aaf0567 100644
--- a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeActionTest.java
+++ b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeActionTest.java
@@ -21,6 +21,7 @@
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
+import org.apache.jackrabbit.oak.plugins.tree.TreeAware;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
@@ -28,19 +29,23 @@
import org.jetbrains.annotations.Nullable;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
import javax.jcr.nodetype.ConstraintViolationException;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
public class PasswordChangeActionTest {
private static final String USER_PATH = "/userpath";
- private final NamePathMapper namePathMapper = Mockito.mock(NamePathMapper.class);
+ private final NamePathMapper namePathMapper = mock(NamePathMapper.class);
private PasswordChangeAction pwChangeAction;
@@ -49,19 +54,19 @@
@Before
public void before() throws Exception {
pwChangeAction = new PasswordChangeAction();
- pwChangeAction.init(Mockito.mock(SecurityProvider.class), ConfigurationParameters.EMPTY);
+ pwChangeAction.init(mock(SecurityProvider.class), ConfigurationParameters.EMPTY);
- user = Mockito.mock(User.class);
+ user = mock(User.class);
when(user.getPath()).thenReturn(USER_PATH);
}
private static Root createRoot(@Nullable String pw) throws Exception {
- Tree userTree = Mockito.mock(Tree.class);
+ Tree userTree = mock(Tree.class);
if (pw != null) {
String pwHash = PasswordUtil.buildPasswordHash(pw);
when(userTree.getProperty(UserConstants.REP_PASSWORD)).thenReturn(PropertyStates.createProperty(UserConstants.REP_PASSWORD, pwHash));
}
- Root root = Mockito.mock(Root.class);
+ Root root = mock(Root.class);
when(root.getTree(USER_PATH)).thenReturn(userTree);
return root;
}
@@ -89,4 +94,20 @@
verify(user).getPath();
verifyNoMoreInteractions(user);
}
+
+ @Test
+ public void testUserIsTreeAware() throws Exception {
+ Root r = createRoot("pw");
+ Tree t = r.getTree(USER_PATH);
+
+ User u = mock(User.class, withSettings().extraInterfaces(TreeAware.class));
+ when(((TreeAware) u).getTree()).thenReturn(t);
+
+ pwChangeAction.onPasswordChange(u, "changedPassword", r, namePathMapper);
+
+ verify(u, never()).getPath();
+ verify(((TreeAware) u)).getTree();
+ verify(r).getTree(USER_PATH);
+ verifyNoMoreInteractions(r, u);
+ }
}