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

Reply via email to