Revert "SLING-7805 - NPE in Oak SessionImpl when starting up"
Restore the fix for SLING-3524
diff --git a/pom.xml b/pom.xml
index 6a44844..fea1412 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,7 @@
<plugin>
<groupId>org.apache.sling</groupId>
<artifactId>maven-sling-plugin</artifactId>
+ <version>2.3.2</version>
<executions>
<execution>
<id>generate-adapter-metadata</id>
@@ -176,7 +177,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.api</artifactId>
- <version>2.16.4</version>
+ <version>2.18.2</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 8cc6a55..064f095 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
@@ -144,7 +144,12 @@
@NotNull 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);
@@ -166,25 +171,35 @@
* @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)) {
- try {
- final SimpleCredentials creds = new SimpleCredentials(sudoUser, new char[0]);
+ // Do we need session.impersonate() because we are asked to impersonate another user?
+ boolean needsSudo = (sudoUser != null) && !session.getUserID().equals(sudoUser);
+ // Do we need session.impersonate() to get an independent copy of the session we were given in the auth info?
+ boolean needsCloning = !needsSudo && explicitSessionUsed && authenticationInfo.containsKey(ResourceProvider.AUTH_CLONE);
+ try {
+ if (needsSudo) {
+ SimpleCredentials creds = new SimpleCredentials(sudoUser, new char[0]);
copyAttributes(creds, authenticationInfo);
creds.setAttribute(ResourceResolver.USER_IMPERSONATOR, session.getUserID());
return session.impersonate(creds);
- } catch (final RepositoryException re) {
- throw getLoginException(re);
- } finally {
- if (logoutSession) {
- session.logout();
- }
+ } else if (needsCloning) {
+ SimpleCredentials creds = new SimpleCredentials(session.getUserID(), new char[0]);
+ copyAttributes(creds, authenticationInfo);
+ return session.impersonate(creds);
+ }
+ } catch (final RepositoryException re) {
+ throw getLoginException(re);
+ } finally {
+ if (logoutSession) {
+ session.logout();
}
}
return session;
diff --git a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java
new file mode 100644
index 0000000..a471446
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.sling.jcr.resource.internal.helper.jcr;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.testing.jcr.RepositoryProvider;
+import org.apache.sling.jcr.api.SlingRepository;
+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.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.Mockito;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+
+@RunWith(Parameterized.class)
+public class JcrResourceProviderSessionHandlingTest {
+
+ private enum LoginStyle {USER, SESSION};
+
+ private static final String AUTH_USER = "admin";
+ private static final char[] AUTH_PASSWORD = "admin".toCharArray();
+ private static final String SUDO_USER = "anonymous";
+
+ @Parameters(name = "loginStyle= {0}, sudo = {1}, clone = {2}")
+ public static List<Object[]> data() {
+
+ LoginStyle[] loginStyles = LoginStyle.values();
+ boolean[] sudoOptions = new boolean[] {false, true};
+ boolean[] cloneOptions = new boolean[] {false, true};
+
+ // Generate all possible combinations into data.
+ List<Object[]> data = new ArrayList<>();
+ Object[] dataPoint = new Object[3];
+ for (LoginStyle loginStyle : loginStyles) {
+ dataPoint[0] = loginStyle;
+ for (boolean sudo : sudoOptions) {
+ dataPoint[1] = sudo;
+ for (boolean clone : cloneOptions) {
+ dataPoint[2] = clone;
+ data.add(dataPoint.clone());
+ }
+ }
+ }
+ return data;
+ }
+
+ @Parameter(0)
+ public LoginStyle loginStyle;
+
+ @Parameter(1)
+ public boolean useSudo;
+
+ @Parameter(2)
+ public boolean doClone;
+
+ // Session we're using when loginStyle == SESSION, null otherwise.
+ private Session explicitSession;
+
+ private JcrResourceProvider jcrResourceProvider;
+ private JcrProviderState jcrProviderState;
+
+ @Before
+ public void setUp() throws Exception {
+ SlingRepository repo = RepositoryProvider.instance().getRepository();
+ Map<String, Object> authInfo = new HashMap<>();
+ switch (loginStyle) {
+ case USER:
+ authInfo.put(ResourceResolverFactory.USER, AUTH_USER);
+ authInfo.put(ResourceResolverFactory.PASSWORD, AUTH_PASSWORD);
+ break;
+ case SESSION:
+ explicitSession = repo.loginAdministrative(null);
+ authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, explicitSession);
+ break;
+ }
+
+ if (useSudo) {
+ authInfo.put(ResourceResolverFactory.USER_IMPERSONATION, SUDO_USER);
+ }
+
+ if (doClone) {
+ authInfo.put(ResourceProvider.AUTH_CLONE, true);
+ }
+
+ ComponentContext ctx = mock(ComponentContext.class);
+ when(ctx.locateService(anyString(), Mockito.<ServiceReference<Object>>any())).thenReturn(repo);
+
+ jcrResourceProvider = new JcrResourceProvider();
+ jcrResourceProvider.activate(ctx);
+
+ jcrProviderState = jcrResourceProvider.authenticate(authInfo);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ // Some tests do a logout, so check for liveness before trying to log out.
+ if (jcrProviderState.getSession().isLive()) {
+ jcrResourceProvider.logout(jcrProviderState);
+ }
+
+ jcrResourceProvider.deactivate();
+
+ if (explicitSession != null) {
+ explicitSession.logout();
+ }
+ }
+
+ @Test
+ public void sessionUsesCorrectUser() {
+ String expectedUser = useSudo ? SUDO_USER : AUTH_USER;
+ assertEquals(expectedUser, jcrProviderState.getSession().getUserID());
+ }
+
+ @Test
+ public void explicitSessionNotClosedOnLogout() {
+ assumeTrue(loginStyle == LoginStyle.SESSION);
+
+ jcrResourceProvider.logout(jcrProviderState);
+
+ assertTrue(explicitSession.isLive());
+ }
+
+ @Test
+ public void sessionsDoNotLeak() {
+ // This test is only valid if we either didn't pass an explicit session,
+ // or the provider had to clone it. Sessions created by the provider
+ // must be closed by the provider, or we have a session leak.
+ assumeThat(jcrProviderState.getSession(), is(not(sameInstance(explicitSession))));
+
+ jcrResourceProvider.logout(jcrProviderState);
+
+ assertFalse(jcrProviderState.getSession().isLive());
+ }
+
+ @Test
+ public void impersonatorIsReportedCorrectly() {
+ assumeTrue(useSudo);
+
+ @SuppressWarnings("unchecked")
+ ResolveContext<JcrProviderState> mockContext = mock(ResolveContext.class);
+ when(mockContext.getProviderState()).thenReturn(jcrProviderState);
+ Object reportedImpersonator = jcrResourceProvider.getAttribute(mockContext, ResourceResolver.USER_IMPERSONATOR);
+
+ assertEquals(AUTH_USER, reportedImpersonator);
+ }
+
+ @Test
+ public void clonesAreIndependent() {
+ assumeTrue(loginStyle == LoginStyle.SESSION && doClone);
+
+ assertNotSame(explicitSession, jcrProviderState.getSession());
+ }
+
+}
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..32c6bfb 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
@@ -19,18 +19,11 @@
package org.apache.sling.jcr.resource.internal.helper.jcr;
import java.security.Principal;
-import java.util.HashMap;
-import java.util.Map;
import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
import javax.jcr.Session;
-import javax.naming.NamingException;
-import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.ResourceResolverFactory;
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.junit.Assert;
import org.mockito.Mockito;
@@ -47,33 +40,23 @@
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);
- Map<String, Object> authInfo = new HashMap<String, Object>();
- authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, session);
- authInfo.put(ResourceResolverFactory.USER_IMPERSONATION, "anonymous");
- JcrProviderState providerState = jcrResourceProvider.authenticate(authInfo);
- Assert.assertNotEquals("Impersonation didn't start new session", session, providerState.getSession());
- jcrResourceProvider.logout(providerState);
- assertFalse("Impersonated session wasn't closed.", providerState.getSession().isLive());
+ }
+
+ @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));
}
}