SLING-11503 option to authenticate webconsole only against JCR (#2)
add an option to authenticate webconsole only against JCR; to achieve this, set the framework property "sling.webconsole.authType" to the value "jcrAuth". The default is authentication against Sling with a fallback to JCR if the Sling authentication is not available.
Co-authored-by: Carsten Ziegeler <cziegeler@apache.org>
diff --git a/.gitignore b/.gitignore
index 5b783ed..19758c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@
.DS_Store
jcr.log
atlassian-ide-plugin.xml
+.vscode
diff --git a/pom.xml b/pom.xml
index 333c9f5..6f1f20b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>sling-bundle-parent</artifactId>
<groupId>org.apache.sling</groupId>
- <version>35</version>
+ <version>48</version>
<relativePath />
</parent>
@@ -105,5 +105,23 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.osgi-mock.junit4</artifactId>
+ <version>3.3.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>4.6.1</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/src/main/java/org/apache/sling/extensions/webconsolesecurityprovider/internal/ServicesListener.java b/src/main/java/org/apache/sling/extensions/webconsolesecurityprovider/internal/ServicesListener.java
index e52daad..5c60215 100644
--- a/src/main/java/org/apache/sling/extensions/webconsolesecurityprovider/internal/ServicesListener.java
+++ b/src/main/java/org/apache/sling/extensions/webconsolesecurityprovider/internal/ServicesListener.java
@@ -18,7 +18,6 @@
* under the License.
*/
-
import java.util.Dictionary;
import java.util.Hashtable;
@@ -31,16 +30,30 @@
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ManagedService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* The <code>ServicesListener</code> listens for the required services
- * and registers the security provider when required services are available
+ * and registers the security provider when required services are available.
+ *
+ * It supports 3 modes, which can be forced by the value of the framework property "sling.webconsole.authType"
+ * <ul>
+ * <li> "jcrAuth": always authenticate against the JCR repository even if Sling Authentication is possible.</li>
+ * <li> "slingAuth": always use SlingAuthentication
+ * <li> no value (default) : Use SlingAuthentication if available, fallback to JCR repository
+ * <li> If an invalid value is specifed, the value is ignored and the default is used
+ * </ul>
*/
public class ServicesListener {
private static final String AUTH_SUPPORT_CLASS = "org.apache.sling.auth.core.AuthenticationSupport";
private static final String AUTHENTICATOR_CLASS = "org.apache.sling.api.auth.Authenticator";
private static final String REPO_CLASS = "javax.jcr.Repository";
+
+ protected static final String WEBCONSOLE_AUTH_TYPE = "sling.webconsole.authType";
+ protected static final String JCR_AUTH = "jcrAuth";
+ protected static final String SLING_AUTH = "slingAuth";
/** The bundle context. */
private final BundleContext bundleContext;
@@ -54,10 +67,16 @@
/** The listener for the authenticator. */
private final Listener authListener;
- private enum State {
+ enum State {
NONE,
- PROVIDER,
- PROVIDER2
+ PROVIDER_JCR,
+ PROVIDER_SLING
+ }
+
+ enum AuthType {
+ DEFAULT,
+ JCR,
+ SLING
}
/** State */
@@ -68,12 +87,19 @@
/** The registration for the provider2 */
private ServiceRegistration<?> provider2Reg;
+
+ /** Auth type */
+ final AuthType authType;
+
+ /** Logger */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* Start listeners
*/
public ServicesListener(final BundleContext bundleContext) {
this.bundleContext = bundleContext;
+ this.authType = getAuthType();
this.authSupportListener = new Listener(AUTH_SUPPORT_CLASS);
this.repositoryListener = new Listener(REPO_CLASS);
this.authListener = new Listener(AUTHENTICATOR_CLASS);
@@ -82,56 +108,77 @@
this.authListener.start();
}
+ AuthType getAuthType() {
+ final String webConsoleAuthType = bundleContext.getProperty(WEBCONSOLE_AUTH_TYPE);
+ if ( webConsoleAuthType != null ) {
+ if ( webConsoleAuthType.equals(JCR_AUTH) ) {
+ return AuthType.JCR;
+ } else if ( webConsoleAuthType.equals(SLING_AUTH) ) {
+ return AuthType.SLING;
+ }
+ logger.error("Ignoring invalid auth type for webconsole security provider {}", this.authType);
+ }
+ return AuthType.DEFAULT;
+ }
+
+ State getTargetState(final boolean slingAvailable, final boolean jcrAvailable) {
+ if ( !slingAvailable && !jcrAvailable ) {
+ return State.NONE;
+ }
+ if ( this.authType == AuthType.JCR && jcrAvailable ) {
+ return State.PROVIDER_JCR;
+ }
+ if ( this.authType == AuthType.SLING && slingAvailable ) {
+ return State.PROVIDER_SLING;
+ }
+ if ( this.authType == AuthType.DEFAULT ) {
+ return slingAvailable ? State.PROVIDER_SLING : State.PROVIDER_JCR;
+ }
+ return State.NONE;
+ }
+
/**
* Notify of service changes from the listeners.
*/
public synchronized void notifyChange() {
// check if all services are available
+
final Object authSupport = this.authSupportListener.getService();
final Object authenticator = this.authListener.getService();
- final boolean hasAuthServices = authSupport != null && authenticator != null;
final Object repository = this.repositoryListener.getService();
- if ( registrationState == State.NONE ) {
- if ( hasAuthServices ) {
- registerProvider2(authSupport, authenticator);
- } else if ( repository != null ) {
- registerProvider(repository);
+
+ final State targetState = this.getTargetState(authSupport != null && authenticator != null, repository != null);
+ if ( this.registrationState != targetState ) {
+ if ( targetState != State.PROVIDER_JCR ) {
+ this.unregisterProviderJcr();
+ }
+ if ( targetState != State.PROVIDER_SLING ) {
+ this.unregisterProviderSling();
}
- } else if ( registrationState == State.PROVIDER ) {
- if ( hasAuthServices ) {
- registerProvider2(authSupport, authenticator);
- unregisterProvider();
- } else if ( repository == null ) {
- unregisterProvider();
- this.registrationState = State.NONE;
+ if ( targetState == State.PROVIDER_JCR ) {
+ this.registerProviderJcr(repository);
+ } else if ( targetState == State.PROVIDER_SLING ) {
+ this.registerProviderSling(authSupport, authenticator);
}
- } else {
- if ( authSupport == null ) {
- if ( repository != null ) {
- registerProvider(repository);
- } else {
- this.registrationState = State.NONE;
- }
- unregisterProvider2();
- }
+ this.registrationState = targetState;
}
}
- private void unregisterProvider2() {
+ private void unregisterProviderSling() {
if ( this.provider2Reg != null ) {
this.provider2Reg.unregister();
this.provider2Reg = null;
}
}
- private void unregisterProvider() {
+ private void unregisterProviderJcr() {
if ( this.providerReg != null ) {
this.providerReg.unregister();
this.providerReg = null;
}
}
- private void registerProvider2(final Object authSupport, final Object authenticator) {
+ private void registerProviderSling(final Object authSupport, final Object authenticator) {
final Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put(Constants.SERVICE_PID, SlingWebConsoleSecurityProvider.class.getName());
props.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Web Console Security Provider 2");
@@ -140,10 +187,9 @@
this.provider2Reg = this.bundleContext.registerService(
new String[] {ManagedService.class.getName(), WebConsoleSecurityProvider.class.getName()},
new SlingWebConsoleSecurityProvider2(authSupport, authenticator), props);
- this.registrationState = State.PROVIDER2;
}
- private void registerProvider(final Object repository) {
+ private void registerProviderJcr(final Object repository) {
final Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put(Constants.SERVICE_PID, SlingWebConsoleSecurityProvider.class.getName());
props.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Web Console Security Provider");
@@ -151,7 +197,6 @@
props.put("webconsole.security.provider.id", "org.apache.sling.extensions.webconsolesecurityprovider");
this.providerReg = this.bundleContext.registerService(
new String[] {ManagedService.class.getName(), WebConsoleSecurityProvider.class.getName()}, new SlingWebConsoleSecurityProvider(repository), props);
- this.registrationState = State.PROVIDER;
}
/**
@@ -161,8 +206,8 @@
this.repositoryListener.deactivate();
this.authSupportListener.deactivate();
this.authListener.deactivate();
- this.unregisterProvider();
- this.unregisterProvider2();
+ this.unregisterProviderJcr();
+ this.unregisterProviderSling();
}
/**
diff --git a/src/test/java/org/apache/sling/extensions/webconsolesecurityprovider/internal/ServiceListenerTest.java b/src/test/java/org/apache/sling/extensions/webconsolesecurityprovider/internal/ServiceListenerTest.java
new file mode 100644
index 0000000..b59b190
--- /dev/null
+++ b/src/test/java/org/apache/sling/extensions/webconsolesecurityprovider/internal/ServiceListenerTest.java
@@ -0,0 +1,216 @@
+package org.apache.sling.extensions.webconsolesecurityprovider.internal;
+/*
+ * 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.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import javax.jcr.Repository;
+
+import org.apache.felix.webconsole.WebConsoleSecurityProvider;
+import org.apache.sling.api.auth.Authenticator;
+import org.apache.sling.auth.core.AuthenticationSupport;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.osgi.framework.BundleContext;
+
+public class ServiceListenerTest {
+
+ @Rule
+ public OsgiContext context = new OsgiContext();
+
+ @Mock
+ Repository repository;
+
+ @Mock
+ AuthenticationSupport authenticationSupport;
+
+ @Mock
+ Authenticator authenticator;
+
+
+ ServicesListener listener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.openMocks(this);
+
+ }
+
+ @After
+ public void shutdown() {
+ listener.deactivate();
+ }
+
+ @Test
+ public void testDefaultAuth() {
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertNoSecurityProviderRegistered();
+
+ context.registerService(Repository.class,repository);
+ listener.notifyChange();
+ assertRepositoryRegistered();
+
+ context.registerService(AuthenticationSupport.class, authenticationSupport);
+ listener.notifyChange();
+ assertRepositoryRegistered();
+
+ context.registerService(Authenticator.class, authenticator);
+ listener.notifyChange();
+ assertSlingAuthRegistered();
+ }
+
+ @Test
+ public void testWithSlingAuth() {
+ try {
+ System.setProperty(ServicesListener.WEBCONSOLE_AUTH_TYPE, ServicesListener.SLING_AUTH);
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertNoSecurityProviderRegistered();
+
+ context.registerService(Repository.class,repository);
+ listener.notifyChange();
+ assertNoSecurityProviderRegistered();
+
+ context.registerService(AuthenticationSupport.class, authenticationSupport);
+ listener.notifyChange();
+ assertNoSecurityProviderRegistered();
+
+ context.registerService(Authenticator.class, authenticator);
+ listener.notifyChange();
+ assertSlingAuthRegistered();
+ } finally {
+ System.getProperties().remove(ServicesListener.WEBCONSOLE_AUTH_TYPE);
+ }
+ }
+
+ @Test
+ public void testWithForcedJcrAuth() {
+ try {
+ System.setProperty(ServicesListener.WEBCONSOLE_AUTH_TYPE, ServicesListener.JCR_AUTH);
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertNoSecurityProviderRegistered();
+
+ // no matter what is registered, always the auth against the repo needs to be there
+
+ context.registerService(Repository.class,repository);
+ listener.notifyChange();
+ assertRepositoryRegistered();
+
+ context.registerService(AuthenticationSupport.class, authenticationSupport);
+ listener.notifyChange();
+ assertRepositoryRegistered();
+
+ context.registerService(Authenticator.class, authenticator);
+ listener.notifyChange();
+ assertRepositoryRegistered();
+ } finally {
+ System.getProperties().remove(ServicesListener.WEBCONSOLE_AUTH_TYPE);
+ }
+ }
+
+ @Test
+ public void testGetAuthType() {
+ try {
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertEquals(ServicesListener.AuthType.DEFAULT, listener.getAuthType());
+
+ System.setProperty(ServicesListener.WEBCONSOLE_AUTH_TYPE, ServicesListener.JCR_AUTH);
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertEquals(ServicesListener.AuthType.JCR, listener.getAuthType());
+
+ System.setProperty(ServicesListener.WEBCONSOLE_AUTH_TYPE, ServicesListener.SLING_AUTH);
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertEquals(ServicesListener.AuthType.SLING, listener.getAuthType());
+
+ System.setProperty(ServicesListener.WEBCONSOLE_AUTH_TYPE, "invalid");
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertEquals(ServicesListener.AuthType.DEFAULT, listener.getAuthType());
+ } finally {
+ System.getProperties().remove(ServicesListener.WEBCONSOLE_AUTH_TYPE);
+ }
+ }
+
+ @Test
+ public void testGetTargetState() {
+ try {
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertEquals(ServicesListener.State.NONE, listener.getTargetState(false, false));
+ assertEquals(ServicesListener.State.PROVIDER_JCR, listener.getTargetState(false, true));
+ assertEquals(ServicesListener.State.PROVIDER_SLING, listener.getTargetState(true, false));
+ assertEquals(ServicesListener.State.PROVIDER_SLING, listener.getTargetState(true, true));
+
+ System.setProperty(ServicesListener.WEBCONSOLE_AUTH_TYPE, ServicesListener.JCR_AUTH);
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertEquals(ServicesListener.State.NONE, listener.getTargetState(false, false));
+ assertEquals(ServicesListener.State.PROVIDER_JCR, listener.getTargetState(false, true));
+ assertEquals(ServicesListener.State.NONE, listener.getTargetState(true, false));
+ assertEquals(ServicesListener.State.PROVIDER_JCR, listener.getTargetState(true, true));
+
+ System.setProperty(ServicesListener.WEBCONSOLE_AUTH_TYPE, ServicesListener.SLING_AUTH);
+ listener = new ServicesListener(wrapForValidProperties(context.bundleContext()));
+ assertEquals(ServicesListener.State.NONE, listener.getTargetState(false, false));
+ assertEquals(ServicesListener.State.NONE, listener.getTargetState(false, true));
+ assertEquals(ServicesListener.State.PROVIDER_SLING, listener.getTargetState(true, false));
+ assertEquals(ServicesListener.State.PROVIDER_SLING, listener.getTargetState(true, true));
+ } finally {
+ System.getProperties().remove(ServicesListener.WEBCONSOLE_AUTH_TYPE);
+ }
+ }
+
+ // until https://issues.apache.org/jira/browse/SLING-11505 is implemented
+ private BundleContext wrapForValidProperties(BundleContext bc) {
+ BundleContext spy = Mockito.spy(bc);
+ Mockito.when(spy.getProperty(Mockito.anyString())).thenAnswer(invocation -> {
+ String key = (String) invocation.getArguments()[0];
+ return System.getProperty(key);
+ });
+ return spy;
+ }
+
+ // Helpers
+
+ private void assertRepositoryRegistered() {
+ assertTrue("Expected to have the repository registered",getSecurityProvider() instanceof SlingWebConsoleSecurityProvider);
+ }
+
+ private void assertSlingAuthRegistered() {
+ assertTrue("Expected to have SlingAuth registered",getSecurityProvider() instanceof SlingWebConsoleSecurityProvider2);
+ }
+
+ private void assertNoSecurityProviderRegistered () {
+ assertNull(getSecurityProvider());
+ }
+
+ private WebConsoleSecurityProvider getSecurityProvider() {
+ return context.getService(WebConsoleSecurityProvider.class);
+ }
+
+
+
+
+
+
+}