Skip to content

Commit 69ee5b5

Browse files
kwinAmoratinos
andauthored
OAK-11498: Expose Session-bound principals via JackrabbitSession (#2093)
Co-authored-by: Alejandro Moratinos <amoratinos@adobe.com>
1 parent 5347773 commit 69ee5b5

File tree

5 files changed

+224
-21
lines changed

5 files changed

+224
-21
lines changed

oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,25 @@
1616
*/
1717
package org.apache.jackrabbit.api;
1818

19-
import org.apache.jackrabbit.api.security.user.UserManager;
20-
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
19+
import java.security.Principal;
20+
import java.util.Collections;
21+
import java.util.Set;
2122

23+
import javax.jcr.AccessDeniedException;
2224
import javax.jcr.Item;
2325
import javax.jcr.ItemNotFoundException;
2426
import javax.jcr.Node;
2527
import javax.jcr.Property;
2628
import javax.jcr.Session;
27-
import javax.jcr.AccessDeniedException;
2829
import javax.jcr.NamespaceException;
2930
import javax.jcr.RepositoryException;
3031
import javax.jcr.UnsupportedRepositoryOperationException;
3132

33+
import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
34+
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
35+
import org.apache.jackrabbit.api.security.user.Authorizable;
36+
import org.apache.jackrabbit.api.security.user.User;
37+
import org.apache.jackrabbit.api.security.user.UserManager;
3238
import org.jetbrains.annotations.NotNull;
3339
import org.jetbrains.annotations.Nullable;
3440
import org.osgi.annotation.versioning.ProviderType;
@@ -304,4 +310,37 @@ default Node getParentOrNull(@NotNull Item item) throws RepositoryException {
304310
* @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>
305311
*/
306312
@NotNull String getExpandedPath(@NotNull Item item) throws RepositoryException;
313+
314+
/**
315+
* Returns the set of principals associated with this session.
316+
* @return the set of principals associated with this session. Usually this set is unmodifiable.
317+
* @throws RepositoryException in case principal information cannot be retrieved.
318+
* @throws IllegalStateException if user information is not available or if the user is a system user.
319+
* @since 1.84
320+
*/
321+
@NotNull default Set<Principal> getBoundPrincipals() throws RepositoryException {
322+
String userId = getUserID();
323+
if (userId == null) {
324+
throw new IllegalStateException("No user ID associated with this session.");
325+
}
326+
327+
Authorizable authorizable = getUserManager().getAuthorizable(userId);
328+
if (authorizable == null) {
329+
throw new IllegalStateException("No authorizable found for user ID: " + userId);
330+
}
331+
332+
if (!authorizable.isGroup() && ((User) authorizable).isSystemUser()) {
333+
throw new IllegalStateException("Unable to calculate effective set of principals for system user " + userId);
334+
}
335+
336+
Principal userPrincipal = authorizable.getPrincipal();
337+
Set<Principal> principals = new java.util.HashSet<>();
338+
principals.add(userPrincipal);
339+
PrincipalIterator iterator = getPrincipalManager().getGroupMembership(userPrincipal);
340+
while (iterator.hasNext()) {
341+
principals.add(iterator.nextPrincipal());
342+
}
343+
return Collections.unmodifiableSet(principals);
344+
}
307345
}
346+

oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
/**
1919
* Jackrabbit extensions for JCR core interfaces
2020
*/
21-
@org.osgi.annotation.versioning.Version("2.10.0")
21+
@org.osgi.annotation.versioning.Version("2.11.0")
2222
package org.apache.jackrabbit.api;
2323

oak-jackrabbit-api/src/test/java/org/apache/jackrabbit/api/JackrabbitSessionTest.java

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,32 @@
1616
*/
1717
package org.apache.jackrabbit.api;
1818

19-
import org.junit.Test;
20-
import org.mockito.Answers;
21-
22-
import javax.jcr.AccessDeniedException;
23-
import javax.jcr.Item;
24-
import javax.jcr.ItemNotFoundException;
25-
import javax.jcr.Node;
26-
19+
import static org.junit.Assert.assertNotNull;
2720
import static org.junit.Assert.assertNull;
2821
import static org.junit.Assert.assertSame;
22+
import static org.junit.Assert.assertThrows;
23+
import static org.junit.Assert.assertTrue;
2924
import static org.mockito.Mockito.doThrow;
3025
import static org.mockito.Mockito.mock;
3126
import static org.mockito.Mockito.when;
3227
import static org.mockito.Mockito.withSettings;
3328

29+
import java.security.Principal;
30+
import java.util.Collection;
31+
32+
import javax.jcr.AccessDeniedException;
33+
import javax.jcr.Item;
34+
import javax.jcr.ItemNotFoundException;
35+
import javax.jcr.Node;
36+
37+
import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
38+
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
39+
import org.apache.jackrabbit.api.security.user.Authorizable;
40+
import org.apache.jackrabbit.api.security.user.User;
41+
import org.apache.jackrabbit.api.security.user.UserManager;
42+
import org.junit.Test;
43+
import org.mockito.Answers;
44+
3445
public class JackrabbitSessionTest {
3546

3647
@Test
@@ -48,4 +59,89 @@ public void testGetParentOrNull() throws Exception {
4859
doThrow(new ItemNotFoundException()).when(item).getParent();
4960
assertNull(s.getParentOrNull(item));
5061
}
62+
63+
@Test
64+
public void testGetBoundPrincipals() throws Exception {
65+
JackrabbitSession s = mock(JackrabbitSession.class, withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
66+
// no user id set
67+
IllegalStateException ise = assertThrows(IllegalStateException.class, s::getBoundPrincipals);
68+
assertTrue(ise.getMessage().contains("user ID"));
69+
when(s.getUserID()).thenReturn("admin");
70+
UserManager um = mock(UserManager.class);
71+
when(s.getUserManager()).thenReturn(um);
72+
// no authorizable found for user id
73+
ise = assertThrows(IllegalStateException.class, s::getBoundPrincipals);
74+
assertTrue(ise.getMessage().contains("No authorizable found for user ID"));
75+
// mock user manager to return principals
76+
Authorizable user = mock(User.class);
77+
when(um.getAuthorizable("admin")).thenReturn(user);
78+
PrincipalManager pm = mock(PrincipalManager.class);
79+
when(s.getPrincipalManager()).thenReturn(pm);
80+
Principal adminPrincipal = mock(Principal.class);
81+
when(user.getPrincipal()).thenReturn(adminPrincipal);
82+
when(adminPrincipal.getName()).thenReturn("admin");
83+
Principal everyonePrincipal = mock(Principal.class);
84+
when(everyonePrincipal.getName()).thenReturn("everyone");
85+
PrincipalIterator pi = new SingletonPrincipalIterator(everyonePrincipal);
86+
when(pm.getPrincipals(PrincipalManager.SEARCH_TYPE_ALL)).thenReturn(pi);
87+
when(pm.getGroupMembership(adminPrincipal)).thenReturn(pi);
88+
assertImmutablePrincipals(s.getBoundPrincipals(), "admin", "everyone"); // should not throw exception
89+
}
90+
91+
private static final class SingletonPrincipalIterator implements PrincipalIterator {
92+
private final Principal principal;
93+
boolean hasNext = true;
94+
95+
private SingletonPrincipalIterator(Principal principal) {
96+
this.principal = principal;
97+
}
98+
99+
@Override
100+
public Principal nextPrincipal() {
101+
if(!hasNext) {
102+
throw new java.util.NoSuchElementException("No more principals in SingletonPrincipalIterator");
103+
} else {
104+
hasNext = false;
105+
}
106+
return principal;
107+
}
108+
109+
@Override
110+
public boolean hasNext() {
111+
return hasNext;
112+
}
113+
114+
@Override
115+
public long getSize() {
116+
return 1;
117+
}
118+
119+
@Override
120+
public void skip(long skipNum) {
121+
throw new UnsupportedOperationException("skip not supported in SingletonPrincipalIterator");
122+
}
123+
124+
@Override
125+
public long getPosition() {
126+
if (hasNext) {
127+
return 0;
128+
} else {
129+
return 1;
130+
}
131+
}
132+
133+
@Override
134+
public Object next() {
135+
return nextPrincipal();
136+
}
137+
}
138+
139+
public static void assertImmutablePrincipals(Collection<Principal> actualPrincipals, String... expectedPrincipalNames) {
140+
assertNotNull(actualPrincipals);
141+
for (String expectedPrincipalName : expectedPrincipalNames) {
142+
assertTrue("Given collection did not contain expected principal name \'" + expectedPrincipalName + "'", actualPrincipals.stream().anyMatch(p -> p.getName().equals(expectedPrincipalName) ));
143+
}
144+
// make sure it is not modifiable
145+
assertThrows(UnsupportedOperationException.class, actualPrincipals::clear);
146+
}
51147
}

oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.io.InputStream;
2525
import java.io.OutputStream;
2626
import java.security.AccessControlException;
27+
import java.security.Principal;
2728
import java.util.Collections;
2829
import java.util.Set;
2930
import java.util.TreeSet;
@@ -287,7 +288,7 @@ public String[] getAttributeNames() {
287288
@Override
288289
public Object getAttribute(String name) {
289290
if (RepositoryImpl.BOUND_PRINCIPALS.equals(name)) {
290-
return sd.getAuthInfo().getPrincipals();
291+
return internalGetBoundPrincipals();
291292
}
292293
Object attribute = sd.getAuthInfo().getAttribute(name);
293294
if (attribute == null) {
@@ -845,6 +846,17 @@ public UserManager getUserManager() throws RepositoryException {
845846
return sessionContext.getUserManager();
846847
}
847848

849+
@Override
850+
@NotNull
851+
public Set<Principal> getBoundPrincipals() throws RepositoryException {
852+
return internalGetBoundPrincipals();
853+
}
854+
855+
@NotNull
856+
private Set<Principal> internalGetBoundPrincipals() {
857+
return sd.getAuthInfo().getPrincipals();
858+
}
859+
848860
@Override
849861
public String toString() {
850862
if (isLive()) {

oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,34 @@
1616
*/
1717
package org.apache.jackrabbit.oak.jcr.session;
1818

19-
import org.apache.jackrabbit.api.JackrabbitSession;
20-
import org.apache.jackrabbit.test.AbstractJCRTest;
21-
import org.apache.jackrabbit.test.NotExecutableException;
22-
import org.jetbrains.annotations.NotNull;
23-
import org.jetbrains.annotations.Nullable;
19+
import static org.junit.Assert.assertNotNull;
20+
import static org.junit.Assert.assertThrows;
21+
import static org.junit.Assert.assertTrue;
22+
import static org.mockito.Mockito.mock;
23+
24+
import java.security.Principal;
25+
import java.util.Collection;
26+
import java.util.Set;
27+
import java.util.UUID;
2428

2529
import javax.jcr.GuestCredentials;
2630
import javax.jcr.Item;
2731
import javax.jcr.NamespaceException;
2832
import javax.jcr.Node;
2933
import javax.jcr.Property;
3034
import javax.jcr.RepositoryException;
35+
import javax.jcr.SimpleCredentials;
3136

32-
import java.util.UUID;
33-
34-
import static org.mockito.Mockito.mock;
37+
import org.apache.jackrabbit.api.JackrabbitSession;
38+
import org.apache.jackrabbit.api.security.user.Group;
39+
import org.apache.jackrabbit.api.security.user.User;
40+
import org.apache.jackrabbit.api.security.user.UserManager;
41+
import org.apache.jackrabbit.oak.jcr.repository.RepositoryImpl;
42+
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
43+
import org.apache.jackrabbit.test.AbstractJCRTest;
44+
import org.apache.jackrabbit.test.NotExecutableException;
45+
import org.jetbrains.annotations.NotNull;
46+
import org.jetbrains.annotations.Nullable;
3547

3648
public class JackrabbitSessionTest extends AbstractJCRTest {
3749

@@ -131,4 +143,48 @@ public void testGetExpandedPath() throws RepositoryException {
131143
s.setNamespacePrefix("test", "urn:foo");
132144
assertEquals("/{}testroot/{http://www.apache.org/jackrabbit/test}bar/{internal}bar", s.getExpandedPath(n));
133145
}
146+
147+
public void testGetPrincipalsForAdminSession() throws RepositoryException {
148+
Set<Principal> principals = s.getBoundPrincipals();
149+
assertEquals("Principals returned via getBoundPrincipals and session attribute must be equal", principals, s.getAttribute(RepositoryImpl.BOUND_PRINCIPALS));
150+
assertImmutablePrincipals(principals, "admin", EveryonePrincipal.getInstance().getName());
151+
}
152+
153+
public void testGetPrincipalsForCustomUser() throws RepositoryException {
154+
// add test user being member of one group directly and another group transitively
155+
UserManager uMgr = s.getUserManager();
156+
// create the testUser
157+
String uid = generateId("testUser");
158+
SimpleCredentials creds = new SimpleCredentials(uid, uid.toCharArray());
159+
User testUser = uMgr.createUser(uid, uid);
160+
String gid = generateId("testGroup");
161+
Group testGroup = uMgr.createGroup(gid);
162+
testGroup.addMember(testUser);
163+
String gid2 = generateId("testGroup2");
164+
Group testGroup2 = uMgr.createGroup(gid2);
165+
testGroup2.addMember(testGroup);
166+
s.save();
167+
JackrabbitSession guest = (JackrabbitSession) getHelper().getRepository().login(creds);
168+
try {
169+
Set<Principal> principals = guest.getBoundPrincipals();
170+
assertEquals("Principals returned via getBoundPrincipals and session attribute must be equal", principals, guest.getAttribute(RepositoryImpl.BOUND_PRINCIPALS));
171+
assertImmutablePrincipals(principals, EveryonePrincipal.getInstance().getName(), gid, uid, gid2);
172+
assertFalse("Admin principal not expected", principals.contains(s.getPrincipalManager().getPrincipal("admin")));
173+
} finally {
174+
guest.logout();
175+
}
176+
}
177+
178+
protected static String generateId(@NotNull String hint) {
179+
return hint + UUID.randomUUID();
180+
}
181+
182+
public static void assertImmutablePrincipals(Collection<Principal> actualPrincipals, String... expectedPrincipalNames) {
183+
assertNotNull(actualPrincipals);
184+
for (String expectedPrincipalName : expectedPrincipalNames) {
185+
assertTrue("Given collection did not contain expected principal name \'" + expectedPrincipalName + "'", actualPrincipals.stream().anyMatch(p -> p.getName().equals(expectedPrincipalName) ));
186+
}
187+
// make sure it is not modifiable
188+
assertThrows(UnsupportedOperationException.class, actualPrincipals::clear);
189+
}
134190
}

0 commit comments

Comments
 (0)