This is an automated email from the ASF dual-hosted git repository.
kwin pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new 69ee5b5034 OAK-11498: Expose Session-bound principals via
JackrabbitSession (#2093)
69ee5b5034 is described below
commit 69ee5b5034275b981943b8eec871c27def94cb24
Author: Konrad Windszus <[email protected]>
AuthorDate: Tue Jul 15 09:04:36 2025 +0200
OAK-11498: Expose Session-bound principals via JackrabbitSession (#2093)
Co-authored-by: Alejandro Moratinos <[email protected]>
---
.../apache/jackrabbit/api/JackrabbitSession.java | 45 ++++++++-
.../org/apache/jackrabbit/api/package-info.java | 2 +-
.../jackrabbit/api/JackrabbitSessionTest.java | 112 +++++++++++++++++++--
.../jackrabbit/oak/jcr/session/SessionImpl.java | 14 ++-
.../oak/jcr/session/JackrabbitSessionTest.java | 72 +++++++++++--
5 files changed, 224 insertions(+), 21 deletions(-)
diff --git
a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java
b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java
index f7e1f71187..028ae34f24 100644
---
a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java
+++
b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java
@@ -16,19 +16,25 @@
*/
package org.apache.jackrabbit.api;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Set;
+import javax.jcr.AccessDeniedException;
import javax.jcr.Item;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.Session;
-import javax.jcr.AccessDeniedException;
import javax.jcr.NamespaceException;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
+import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.annotation.versioning.ProviderType;
@@ -304,4 +310,37 @@ public interface JackrabbitSession extends Session {
* @see <a
href="https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.2.5.1%20Expanded%20Form">JCR
2.0, 3.2.5.1 Expanded Form</a>
*/
@NotNull String getExpandedPath(@NotNull Item item) throws
RepositoryException;
+
+ /**
+ * Returns the set of principals associated with this session.
+ * @return the set of principals associated with this session. Usually
this set is unmodifiable.
+ * @throws RepositoryException in case principal information cannot be
retrieved.
+ * @throws IllegalStateException if user information is not available or
if the user is a system user.
+ * @since 1.84
+ */
+ @NotNull default Set<Principal> getBoundPrincipals() throws
RepositoryException {
+ String userId = getUserID();
+ if (userId == null) {
+ throw new IllegalStateException("No user ID associated with this
session.");
+ }
+
+ Authorizable authorizable = getUserManager().getAuthorizable(userId);
+ if (authorizable == null) {
+ throw new IllegalStateException("No authorizable found for user
ID: " + userId);
+ }
+
+ if (!authorizable.isGroup() && ((User) authorizable).isSystemUser()) {
+ throw new IllegalStateException("Unable to calculate effective set
of principals for system user " + userId);
+ }
+
+ Principal userPrincipal = authorizable.getPrincipal();
+ Set<Principal> principals = new java.util.HashSet<>();
+ principals.add(userPrincipal);
+ PrincipalIterator iterator =
getPrincipalManager().getGroupMembership(userPrincipal);
+ while (iterator.hasNext()) {
+ principals.add(iterator.nextPrincipal());
+ }
+ return Collections.unmodifiableSet(principals);
+ }
}
+
diff --git
a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/package-info.java
b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/package-info.java
index fdbb2c7688..0994258e1b 100644
---
a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/package-info.java
+++
b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/package-info.java
@@ -18,6 +18,6 @@
/**
* Jackrabbit extensions for JCR core interfaces
*/
[email protected]("2.10.0")
[email protected]("2.11.0")
package org.apache.jackrabbit.api;
diff --git
a/oak-jackrabbit-api/src/test/java/org/apache/jackrabbit/api/JackrabbitSessionTest.java
b/oak-jackrabbit-api/src/test/java/org/apache/jackrabbit/api/JackrabbitSessionTest.java
index e0c79d4bc3..fdd49b0065 100644
---
a/oak-jackrabbit-api/src/test/java/org/apache/jackrabbit/api/JackrabbitSessionTest.java
+++
b/oak-jackrabbit-api/src/test/java/org/apache/jackrabbit/api/JackrabbitSessionTest.java
@@ -16,21 +16,32 @@
*/
package org.apache.jackrabbit.api;
-import org.junit.Test;
-import org.mockito.Answers;
-
-import javax.jcr.AccessDeniedException;
-import javax.jcr.Item;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
+import java.security.Principal;
+import java.util.Collection;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+
+import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+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.junit.Test;
+import org.mockito.Answers;
+
public class JackrabbitSessionTest {
@Test
@@ -48,4 +59,89 @@ public class JackrabbitSessionTest {
doThrow(new ItemNotFoundException()).when(item).getParent();
assertNull(s.getParentOrNull(item));
}
+
+ @Test
+ public void testGetBoundPrincipals() throws Exception {
+ JackrabbitSession s = mock(JackrabbitSession.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
+ // no user id set
+ IllegalStateException ise = assertThrows(IllegalStateException.class,
s::getBoundPrincipals);
+ assertTrue(ise.getMessage().contains("user ID"));
+ when(s.getUserID()).thenReturn("admin");
+ UserManager um = mock(UserManager.class);
+ when(s.getUserManager()).thenReturn(um);
+ // no authorizable found for user id
+ ise = assertThrows(IllegalStateException.class, s::getBoundPrincipals);
+ assertTrue(ise.getMessage().contains("No authorizable found for user
ID"));
+ // mock user manager to return principals
+ Authorizable user = mock(User.class);
+ when(um.getAuthorizable("admin")).thenReturn(user);
+ PrincipalManager pm = mock(PrincipalManager.class);
+ when(s.getPrincipalManager()).thenReturn(pm);
+ Principal adminPrincipal = mock(Principal.class);
+ when(user.getPrincipal()).thenReturn(adminPrincipal);
+ when(adminPrincipal.getName()).thenReturn("admin");
+ Principal everyonePrincipal = mock(Principal.class);
+ when(everyonePrincipal.getName()).thenReturn("everyone");
+ PrincipalIterator pi = new
SingletonPrincipalIterator(everyonePrincipal);
+
when(pm.getPrincipals(PrincipalManager.SEARCH_TYPE_ALL)).thenReturn(pi);
+ when(pm.getGroupMembership(adminPrincipal)).thenReturn(pi);
+ assertImmutablePrincipals(s.getBoundPrincipals(), "admin",
"everyone"); // should not throw exception
+ }
+
+ private static final class SingletonPrincipalIterator implements
PrincipalIterator {
+ private final Principal principal;
+ boolean hasNext = true;
+
+ private SingletonPrincipalIterator(Principal principal) {
+ this.principal = principal;
+ }
+
+ @Override
+ public Principal nextPrincipal() {
+ if(!hasNext) {
+ throw new java.util.NoSuchElementException("No more principals
in SingletonPrincipalIterator");
+ } else {
+ hasNext = false;
+ }
+ return principal;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return hasNext;
+ }
+
+ @Override
+ public long getSize() {
+ return 1;
+ }
+
+ @Override
+ public void skip(long skipNum) {
+ throw new UnsupportedOperationException("skip not supported in
SingletonPrincipalIterator");
+ }
+
+ @Override
+ public long getPosition() {
+ if (hasNext) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public Object next() {
+ return nextPrincipal();
+ }
+ }
+
+ public static void assertImmutablePrincipals(Collection<Principal>
actualPrincipals, String... expectedPrincipalNames) {
+ assertNotNull(actualPrincipals);
+ for (String expectedPrincipalName : expectedPrincipalNames) {
+ assertTrue("Given collection did not contain expected principal
name \'" + expectedPrincipalName + "'", actualPrincipals.stream().anyMatch(p ->
p.getName().equals(expectedPrincipalName) ));
+ }
+ // make sure it is not modifiable
+ assertThrows(UnsupportedOperationException.class,
actualPrincipals::clear);
+ }
}
diff --git
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
index 6f37d57b0b..2eadf539aa 100644
---
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
+++
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessControlException;
+import java.security.Principal;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
@@ -287,7 +288,7 @@ public class SessionImpl implements JackrabbitSession {
@Override
public Object getAttribute(String name) {
if (RepositoryImpl.BOUND_PRINCIPALS.equals(name)) {
- return sd.getAuthInfo().getPrincipals();
+ return internalGetBoundPrincipals();
}
Object attribute = sd.getAuthInfo().getAttribute(name);
if (attribute == null) {
@@ -845,6 +846,17 @@ public class SessionImpl implements JackrabbitSession {
return sessionContext.getUserManager();
}
+ @Override
+ @NotNull
+ public Set<Principal> getBoundPrincipals() throws RepositoryException {
+ return internalGetBoundPrincipals();
+ }
+
+ @NotNull
+ private Set<Principal> internalGetBoundPrincipals() {
+ return sd.getAuthInfo().getPrincipals();
+ }
+
@Override
public String toString() {
if (isLive()) {
diff --git
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java
index 926502a382..8d3f02a272 100644
---
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java
+++
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java
@@ -16,11 +16,15 @@
*/
package org.apache.jackrabbit.oak.jcr.session;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.test.AbstractJCRTest;
-import org.apache.jackrabbit.test.NotExecutableException;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Set;
+import java.util.UUID;
import javax.jcr.GuestCredentials;
import javax.jcr.Item;
@@ -28,10 +32,18 @@ import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
+import javax.jcr.SimpleCredentials;
-import java.util.UUID;
-
-import static org.mockito.Mockito.mock;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.jcr.repository.RepositoryImpl;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
public class JackrabbitSessionTest extends AbstractJCRTest {
@@ -131,4 +143,48 @@ public class JackrabbitSessionTest extends AbstractJCRTest
{
s.setNamespacePrefix("test", "urn:foo");
assertEquals("/{}testroot/{http://www.apache.org/jackrabbit/test}bar/{internal}bar",
s.getExpandedPath(n));
}
+
+ public void testGetPrincipalsForAdminSession() throws RepositoryException {
+ Set<Principal> principals = s.getBoundPrincipals();
+ assertEquals("Principals returned via getBoundPrincipals and session
attribute must be equal", principals,
s.getAttribute(RepositoryImpl.BOUND_PRINCIPALS));
+ assertImmutablePrincipals(principals, "admin",
EveryonePrincipal.getInstance().getName());
+ }
+
+ public void testGetPrincipalsForCustomUser() throws RepositoryException {
+ // add test user being member of one group directly and another group
transitively
+ UserManager uMgr = s.getUserManager();
+ // create the testUser
+ String uid = generateId("testUser");
+ SimpleCredentials creds = new SimpleCredentials(uid,
uid.toCharArray());
+ User testUser = uMgr.createUser(uid, uid);
+ String gid = generateId("testGroup");
+ Group testGroup = uMgr.createGroup(gid);
+ testGroup.addMember(testUser);
+ String gid2 = generateId("testGroup2");
+ Group testGroup2 = uMgr.createGroup(gid2);
+ testGroup2.addMember(testGroup);
+ s.save();
+ JackrabbitSession guest = (JackrabbitSession)
getHelper().getRepository().login(creds);
+ try {
+ Set<Principal> principals = guest.getBoundPrincipals();
+ assertEquals("Principals returned via getBoundPrincipals and
session attribute must be equal", principals,
guest.getAttribute(RepositoryImpl.BOUND_PRINCIPALS));
+ assertImmutablePrincipals(principals,
EveryonePrincipal.getInstance().getName(), gid, uid, gid2);
+ assertFalse("Admin principal not expected",
principals.contains(s.getPrincipalManager().getPrincipal("admin")));
+ } finally {
+ guest.logout();
+ }
+ }
+
+ protected static String generateId(@NotNull String hint) {
+ return hint + UUID.randomUUID();
+ }
+
+ public static void assertImmutablePrincipals(Collection<Principal>
actualPrincipals, String... expectedPrincipalNames) {
+ assertNotNull(actualPrincipals);
+ for (String expectedPrincipalName : expectedPrincipalNames) {
+ assertTrue("Given collection did not contain expected principal
name \'" + expectedPrincipalName + "'", actualPrincipals.stream().anyMatch(p ->
p.getName().equals(expectedPrincipalName) ));
+ }
+ // make sure it is not modifiable
+ assertThrows(UnsupportedOperationException.class,
actualPrincipals::clear);
+ }
}
\ No newline at end of file