blob: 5dbdf7c2506f3d335db492a8e87cb9e6973f7220 [file] [log] [blame]
/*
* 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.Credentials;
import javax.jcr.LoginException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
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.Matchers;
import org.mockito.Mockito;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
@RunWith(Parameterized.class)
public class JcrResourceProviderSessionHandlingTest {
private enum LoginStyle {USER, SESSION, SERVICE};
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;
// TransientRepository has a bug that makes it ignore sessions created
// by calling Session.impersonate(). To prevent the repo from closing
// prematurely, have this dummy session open during the whole lifetime
// of the test.
private Session footInDoor;
private JcrResourceProvider jcrResourceProvider;
private JcrProviderState jcrProviderState;
private static class SlingRepositoryWithDummyServiceUsers implements SlingRepository {
private final SlingRepository wrapped;
SlingRepositoryWithDummyServiceUsers(SlingRepository wrapped) {
this.wrapped = wrapped;
}
@SuppressWarnings("deprecation")
@Override
public Session loginService(String subServiceName, String workspace)
throws LoginException, RepositoryException {
// just fake service logins by doing administrative logins instead
return wrapped.loginAdministrative(workspace);
}
// the rest of the methods just delegate to wrapped
@Override
public String[] getDescriptorKeys() {
return wrapped.getDescriptorKeys();
}
@Override
public boolean isStandardDescriptor(String key) {
return wrapped.isStandardDescriptor(key);
}
@Override
public boolean isSingleValueDescriptor(String key) {
return wrapped.isSingleValueDescriptor(key);
}
@Override
public Value getDescriptorValue(String key) {
return wrapped.getDescriptorValue(key);
}
@Override
public Value[] getDescriptorValues(String key) {
return wrapped.getDescriptorValues(key);
}
@Override
public String getDescriptor(String key) {
return wrapped.getDescriptor(key);
}
@Override
public Session login(Credentials credentials, String workspaceName)
throws LoginException, NoSuchWorkspaceException, RepositoryException {
return wrapped.login(credentials, workspaceName);
}
@Override
public Session login(Credentials credentials) throws LoginException, RepositoryException {
return wrapped.login(credentials);
}
@Override
public Session login(String workspaceName)
throws LoginException, NoSuchWorkspaceException, RepositoryException {
return wrapped.login(workspaceName);
}
@Override
public Session login() throws LoginException, RepositoryException {
return wrapped.login();
}
@Override
public String getDefaultWorkspace() {
return wrapped.getDefaultWorkspace();
}
@SuppressWarnings("deprecation")
@Override
public Session loginAdministrative(String workspace) throws LoginException, RepositoryException {
return wrapped.loginAdministrative(workspace);
}
}
@Before
public void setUp() throws Exception {
final SlingRepository repo = new SlingRepositoryWithDummyServiceUsers(SlingRepositoryProvider.getRepository());
footInDoor = repo.loginAdministrative(null);
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;
case SERVICE:
Bundle mockBundle = mock(Bundle.class);
BundleContext mockBundleContext = mock(BundleContext.class);
when(mockBundle.getBundleContext()).thenReturn(mockBundleContext);
when(mockBundleContext.getService(Matchers.<ServiceReference<Object>>any())).thenReturn(repo);
authInfo.put(ResourceResolverFactory.SUBSERVICE, "dummy-service");
authInfo.put(ResourceProvider.AUTH_SERVICE_BUNDLE, mockBundle);
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();
}
footInDoor.logout();
}
@Test
public void returnedSessionIsLive() {
assertTrue(jcrProviderState.getSession().isLive());
}
@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());
}
}