SLING-3524: Clone JCR session when a resource resolver is cloned.
Detect when a session-based resource resolver is being cloned, and clone
the underlying JCR session using the self-impersonation trick. This
requires slight API changes in order to detect cloning.
diff --git a/pom.xml b/pom.xml
index 0ba0132..ee5ac67 100644
--- a/pom.xml
+++ b/pom.xml
@@ -176,7 +176,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.api</artifactId>
- <version>2.16.4</version>
+ <version>2.18.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrProviderStateFactory.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrProviderStateFactory.java
index 8767fa9..1c3649e 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrProviderStateFactory.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrProviderStateFactory.java
@@ -145,7 +145,12 @@
@Nonnull final Map<String, Object> authenticationInfo,
@Nullable final BundleContext ctx
) throws LoginException {
- final Session impersonatedSession = handleImpersonation(session, authenticationInfo, logoutSession);
+ boolean explicitSessionUsed = (getSession(authenticationInfo) != null);
+ final Session impersonatedSession = handleImpersonation(session, authenticationInfo, logoutSession, explicitSessionUsed);
+ if (impersonatedSession != session && explicitSessionUsed) {
+ // update the session in the auth info map in case the resolver gets cloned in the future
+ authenticationInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, impersonatedSession);
+ }
// if we're actually impersonating, we're responsible for closing the session we've created, regardless
// of what the original logoutSession value was.
boolean doLogoutSession = logoutSession || (impersonatedSession != session);
@@ -167,18 +172,26 @@
* @param logoutSession
* whether to logout the <code>session</code> after impersonation
* or not.
+ * @param explicitSessionUsed
+ * whether the JCR session was explicitly given in the auth info or not.
* @return The original session or impersonated session.
* @throws LoginException
* If something goes wrong.
*/
private static Session handleImpersonation(final Session session, final Map<String, Object> authenticationInfo,
- final boolean logoutSession) throws LoginException {
+ final boolean logoutSession, boolean explicitSessionUsed) throws LoginException {
final String sudoUser = getSudoUser(authenticationInfo);
- if (sudoUser != null && !session.getUserID().equals(sudoUser)) {
+ boolean needsSudo = (sudoUser != null) && !session.getUserID().equals(sudoUser);
+ boolean needsCloning = !needsSudo && explicitSessionUsed && authenticationInfo.containsKey(ResourceProvider.AUTH_CLONE);
+ if (needsCloning || needsSudo) {
+ // If we just need to clone the session, we impersonate with the same user ID and not set an impersonator attribute.
+ // In all other cases, it's a "proper" sudo to the given user.
try {
- final SimpleCredentials creds = new SimpleCredentials(sudoUser, new char[0]);
+ final SimpleCredentials creds = new SimpleCredentials(needsSudo ? sudoUser : session.getUserID(), new char[0]);
copyAttributes(creds, authenticationInfo);
- creds.setAttribute(ResourceResolver.USER_IMPERSONATOR, session.getUserID());
+ if (needsSudo) {
+ creds.setAttribute(ResourceResolver.USER_IMPERSONATOR, session.getUserID());
+ }
return session.impersonate(creds);
} catch (final RepositoryException re) {
throw getLoginException(re);
diff --git a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
index aa40162..a520dc1 100644
--- a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
+++ b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
@@ -32,6 +32,7 @@
import org.apache.sling.commons.testing.jcr.RepositoryTestBase;
import org.apache.sling.jcr.resource.api.JcrResourceConstants;
import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.junit.Assert;
import org.mockito.Mockito;
import org.osgi.framework.ServiceReference;
@@ -47,26 +48,26 @@
super.setUp();
// create the session
session = getSession();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
- public void testAdaptTo_Principal() {
- jcrResourceProvider = new JcrResourceProvider();
- ResolveContext ctx = Mockito.mock(ResolveContext.class);
- Mockito.when(ctx.getProviderState()).thenReturn(new JcrProviderState(session, null, false));
- Assert.assertNotNull(jcrResourceProvider.adaptTo(ctx, Principal.class));
- }
-
- public void testLeakOnSudo() throws LoginException, RepositoryException, NamingException {
Repository repo = getRepository();
ComponentContext ctx = Mockito.mock(ComponentContext.class);
Mockito.when(ctx.locateService(Mockito.anyString(), Mockito.any(ServiceReference.class))).thenReturn(repo);
jcrResourceProvider = new JcrResourceProvider();
jcrResourceProvider.activate(ctx);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ jcrResourceProvider.deactivate();
+ super.tearDown();
+ }
+
+ public void testAdaptTo_Principal() {
+ ResolveContext ctx = Mockito.mock(ResolveContext.class);
+ Mockito.when(ctx.getProviderState()).thenReturn(new JcrProviderState(session, null, false));
+ Assert.assertNotNull(jcrResourceProvider.adaptTo(ctx, Principal.class));
+ }
+
+ public void testLeakOnSudo() throws LoginException, RepositoryException, NamingException {
Map<String, Object> authInfo = new HashMap<String, Object>();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, session);
authInfo.put(ResourceResolverFactory.USER_IMPERSONATION, "anonymous");
@@ -75,6 +76,15 @@
jcrResourceProvider.logout(providerState);
assertFalse("Impersonated session wasn't closed.", providerState.getSession().isLive());
}
+
+ public void testNoSessionSharing() throws LoginException {
+ Map<String, Object> authInfo = new HashMap<String, Object>();
+ authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, session);
+ authInfo.put(ResourceProvider.AUTH_CLONE, true);
+ JcrProviderState providerState = jcrResourceProvider.authenticate(authInfo);
+ Assert.assertNotEquals("Cloned resolver didn't clone session.", session, providerState.getSession());
+ jcrResourceProvider.logout(providerState);
+ }
}