SLING-6285 - Implement LoginAdminWhitelist in JCR Base

- move implementation from o.a.s.jcr.oak.server to o.a.s.jcr.base
- move and refactor tests to work with moved implementation
- support @Modified callback on LoginAdminWhitelist to avoid restarting repositories on configuratin change
- improved error handling, i.e. set a message for LoginExceptions


git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1769962 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index a99a2a8..4c8ae29 100644
--- a/pom.xml
+++ b/pom.xml
@@ -177,7 +177,7 @@
     <dependency>
       <groupId>org.apache.sling</groupId>
       <artifactId>org.apache.sling.jcr.base</artifactId>
-      <version>2.4.2</version>
+      <version>2.4.3-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
diff --git a/src/main/java/org/apache/sling/jcr/oak/server/internal/LoginAdminWhitelist.java b/src/main/java/org/apache/sling/jcr/oak/server/internal/LoginAdminWhitelist.java
deleted file mode 100644
index 2ed8fe9..0000000
--- a/src/main/java/org/apache/sling/jcr/oak/server/internal/LoginAdminWhitelist.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.oak.server.internal;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-import org.apache.sling.jcr.api.SlingRepository;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.Constants;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.metatype.annotations.Designate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Whitelist that defines which bundles can use the
- * {@link SlingRepository#loginAdministrative} method.
- *
- * The default configuration lets a few trusted Sling bundles
- * use the loginAdministrative method.
- */
-@Component(
-        service = LoginAdminWhitelist.class,
-        property = {
-                Constants.SERVICE_DESCRIPTION + "=Apache Sling Login Admin Whitelist",
-                Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
-        }
-)
-@Designate(
-        ocd = LoginAdminWhitelistConfiguration.class
-)
-public class LoginAdminWhitelist {
-
-    private final Logger log = LoggerFactory.getLogger(getClass());
-
-    private boolean bypassWhitelist;
-
-    private Pattern whitelistRegexp;
-
-    private Set<String> whitelistedBsn;
-
-    @Activate
-    void activate(LoginAdminWhitelistConfiguration config) {
-        whitelistedBsn = new TreeSet<String>();
-
-        if (config.whitelist_bundles_default() != null) {
-            whitelistedBsn.addAll(Arrays.asList(config.whitelist_bundles_default()));
-        }
-        if (config.whitelist_bundles_additional() != null) { // null check due to FELIX-5404
-            whitelistedBsn.addAll(Arrays.asList(config.whitelist_bundles_additional()));
-        }
-
-        final String regexp = config.whitelist_bundles_regexp();
-        if(regexp.trim().length() > 0) {
-            whitelistRegexp = Pattern.compile(regexp);
-            log.warn("A whitelist.bundles.regexp is configured, this is NOT RECOMMENDED for production: {}", whitelistRegexp);
-        } else {
-            whitelistRegexp = null;
-        }
-
-        bypassWhitelist = config.whitelist_bypass();
-        if(bypassWhitelist) {
-            log.info("bypassWhitelist=true, whitelisted BSNs=<ALL>");
-            log.warn(
-                "All bundles are allowed to use loginAdministrative due to the 'bypass whitelist' configuration"
-                + " of this service. This is NOT RECOMMENDED, for security reasons."
-            );
-        } else {
-            log.info("bypassWhitelist=false, whitelisted BSNs({})={}", whitelistedBsn.size(), whitelistedBsn);
-        }
-    }
-
-    boolean allowLoginAdministrative(Bundle b) {
-        if(bypassWhitelist) {
-            log.debug("Whitelist is bypassed, all bundles allowed to use loginAdministrative");
-            return true;
-        }
-
-        final String bsn = b.getSymbolicName();
-        if(whitelistRegexp != null && whitelistRegexp.matcher(bsn).matches()) {
-            log.debug("{} is whitelisted to use loginAdministrative, by regexp", bsn);
-            return true;
-        } else if(whitelistedBsn.contains(bsn)) {
-            log.debug("{} is whitelisted to use loginAdministrative, by explicit whitelist", bsn);
-            return true;
-        }
-        log.debug("{} is not whitelisted to use loginAdministrative", bsn);
-        return false;
-    }
-}
diff --git a/src/main/java/org/apache/sling/jcr/oak/server/internal/LoginAdminWhitelistConfiguration.java b/src/main/java/org/apache/sling/jcr/oak/server/internal/LoginAdminWhitelistConfiguration.java
deleted file mode 100644
index 8810e35..0000000
--- a/src/main/java/org/apache/sling/jcr/oak/server/internal/LoginAdminWhitelistConfiguration.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.oak.server.internal;
-
-import org.osgi.service.metatype.annotations.AttributeDefinition;
-import org.osgi.service.metatype.annotations.ObjectClassDefinition;
-
-@ObjectClassDefinition(
-    name = "Apache Sling Login Admin Whitelist",
-    description = "Defines which bundles can use SlingRepository.loginAdministrative()"
-)
-@interface LoginAdminWhitelistConfiguration {
-
-    /** Need to allow for bypassing the whitelist, for backwards
-     *  compatibility with previous Sling versions which didn't
-     *  implement it. Setting this to true is not recommended
-     *  and logged as a warning.
-     */
-    @AttributeDefinition(
-        name = "Bypass the whitelist",
-        description = "Allow all bundles to use loginAdministrative(). Should ONLY be used " +
-                      "for backwards compatibility reasons and if you are aware of " +
-                      "the related security risks."
-    )
-    boolean whitelist_bypass() default false;
-
-    @AttributeDefinition(
-        name = "Whitelist regexp",
-        description = "Regular expression for bundle symbolic names for which loginAdministrative() " +
-                      "is allowed. NOT recommended for production use, but useful for testing with " +
-                      "generated bundles."
-    )
-    String whitelist_bundles_regexp() default "";
-
-    @AttributeDefinition(
-        name = "Default whitelisted BSNs",
-        description = "Default list of bundle symbolic names for which loginAdministrative() is allowed."
-    )
-    String[] whitelist_bundles_default() default {
-            // TODO: remove bundles as their dependency on admin login is fixed, see SLING-5355 for linked issues
-            "org.apache.sling.discovery.commons",
-            "org.apache.sling.discovery.base",
-            "org.apache.sling.discovery.oak",
-            "org.apache.sling.extensions.webconsolesecurityprovider",
-            "org.apache.sling.i18n",
-            "org.apache.sling.installer.provider.jcr",
-            "org.apache.sling.jcr.base",
-            "org.apache.sling.jcr.contentloader",
-            "org.apache.sling.jcr.davex",
-            "org.apache.sling.jcr.jackrabbit.usermanager",
-            "org.apache.sling.jcr.oak.server",
-            "org.apache.sling.jcr.repoinit",
-            "org.apache.sling.jcr.resource",
-            "org.apache.sling.jcr.webconsole",
-            "org.apache.sling.resourceresolver",
-            "org.apache.sling.servlets.post", // remove when 2.3.16 is released
-            "org.apache.sling.servlets.resolver"
-    };
-
-    @AttributeDefinition(
-        name = "Additional whitelisted BSNs",
-        description = "Additional list of bundle symbolic names for which loginAdministrative() is allowed."
-    )
-    String[] whitelist_bundles_additional() default {};
-}
diff --git a/src/main/java/org/apache/sling/jcr/oak/server/internal/OakSlingRepositoryManager.java b/src/main/java/org/apache/sling/jcr/oak/server/internal/OakSlingRepositoryManager.java
index 5292d8b..b042dae 100644
--- a/src/main/java/org/apache/sling/jcr/oak/server/internal/OakSlingRepositoryManager.java
+++ b/src/main/java/org/apache/sling/jcr/oak/server/internal/OakSlingRepositoryManager.java
@@ -99,9 +99,6 @@
     @Reference
     private ThreadPoolManager threadPoolManager = null;
 
-    @Reference
-    private LoginAdminWhitelist loginAdminWhitelist;
-
     private ThreadPool threadPool;
 
     private ServiceRegistration oakExecutorServiceReference;
@@ -122,9 +119,6 @@
 
     private ServiceRegistration nodeAggregatorRegistration;
 
-    public OakSlingRepositoryManager() {
-    }
-
     @Override
     protected ServiceUserMapper getServiceUserMapper() {
         return this.serviceUserMapper;
@@ -203,11 +197,6 @@
         ((JackrabbitRepository) repository).shutdown();
     }
 
-    @Override
-    protected boolean allowLoginAdministrativeForBundle(final Bundle bundle) {
-        return loginAdminWhitelist.allowLoginAdministrative(bundle);
-    }
-
     @Activate
     private void activate(final OakSlingRepositoryManagerConfiguration configuration, final ComponentContext componentContext) {
         this.configuration = configuration;
diff --git a/src/test/java/org/apache/sling/jcr/oak/server/internal/LoginAdminWhitelistTest.java b/src/test/java/org/apache/sling/jcr/oak/server/internal/LoginAdminWhitelistTest.java
deleted file mode 100644
index 527dd9c..0000000
--- a/src/test/java/org/apache/sling/jcr/oak/server/internal/LoginAdminWhitelistTest.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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.oak.server.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.when;
-
-import java.lang.annotation.Annotation;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.osgi.framework.Bundle;
-
-public class LoginAdminWhitelistTest {
-
-    private static final String TYPICAL_DEFAULT_ALLOWED_BSN = "org.apache.sling.jcr.base";
-
-    private LoginAdminWhitelist whitelist;
-
-    @Before
-    public void setup() {
-        whitelist = new LoginAdminWhitelist();
-    }
-    
-    private void assertAdminLogin(final String bundleSymbolicName, boolean expected) {
-        final Bundle b = Mockito.mock(Bundle.class);
-        when(b.getSymbolicName()).thenReturn(bundleSymbolicName);
-        final boolean actual = whitelist.allowLoginAdministrative(b);
-        assertEquals("For bundle " + bundleSymbolicName + ", expected admin login=" + expected, expected, actual);
-    }
-    
-    private List<String> randomBsn() {
-        final List<String> result = new ArrayList<String>();
-        for(int i=0; i < 5; i++) {
-            result.add("random.bsn." + UUID.randomUUID());
-        }
-        return result;
-    }
- 
-    @Test
-    public void testDefaultConfig() {
-        final LoginAdminWhitelistConfiguration config = config(null, null, null, null);
-        whitelist.activate(config);
-
-        for(String bsn : config.whitelist_bundles_default()) {
-            assertAdminLogin(bsn, true);
-        }
-        
-        assertAdminLogin(TYPICAL_DEFAULT_ALLOWED_BSN, true);
-        
-        for(String bsn : randomBsn()) {
-            assertAdminLogin(bsn, false);
-        }
-    }
-
-    @Test
-    public void testBypassWhitelist() {
-        whitelist.activate(config(true, null, null, null));
-        
-        for(String bsn : randomBsn()) {
-            assertAdminLogin(bsn, true);
-        }
-    }
-    
-    @Test
-    public void testDefaultConfigOnly() {
-        final String [] allowed = {
-                "bundle1", "bundle2"
-        };
-        whitelist.activate(config(null, null, allowed, null));
-        
-        assertAdminLogin("bundle1", true);
-        assertAdminLogin("bundle2", true);
-        assertAdminLogin("foo.1.bar", false);
-        assertAdminLogin(TYPICAL_DEFAULT_ALLOWED_BSN, false);
-        
-        for(String bsn : randomBsn()) {
-            assertAdminLogin(bsn, false);
-        }
-    }
-    
-    @Test
-    public void testAdditionalConfigOnly() {
-        final String [] allowed = {
-                "bundle5", "bundle6"
-        };
-        final LoginAdminWhitelistConfiguration config = config(null, null, null, allowed);
-        whitelist.activate(config);
-        
-        assertAdminLogin("bundle5", true);
-        assertAdminLogin("bundle6", true);
-        assertAdminLogin("foo.1.bar", false);
-        assertAdminLogin(TYPICAL_DEFAULT_ALLOWED_BSN, true);
-        
-        for(String bsn : config.whitelist_bundles_default()) {
-            assertAdminLogin(bsn, true);
-        }
-        
-        for(String bsn : randomBsn()) {
-            assertAdminLogin(bsn, false);
-        }
-    }
-    
-    @Test
-    public void testDefaultAndAdditionalConfig() {
-        whitelist.activate(config(null, null, new String [] { "defB"}, new String [] { "addB"}));
-        
-        assertAdminLogin("defB", true);
-        assertAdminLogin("addB", true);
-        assertAdminLogin("foo.1.bar", false);
-        assertAdminLogin(TYPICAL_DEFAULT_ALLOWED_BSN, false);
-        
-        for(String bsn : randomBsn()) {
-            assertAdminLogin(bsn, false);
-        }
-    }
-    
-    @Test
-    public void testRegexpWhitelist() {
-        final String [] allowed = {
-                "bundle3", "bundle4"
-        };
-        whitelist.activate(config(null, "foo.*bar", allowed, null));
-        
-        assertAdminLogin("bundle3", true);
-        assertAdminLogin("bundle4", true);
-        assertAdminLogin("foo.2.bar", true);
-        assertAdminLogin("foo.somethingElse.bar", true);
-        assertAdminLogin(TYPICAL_DEFAULT_ALLOWED_BSN, false);
-        
-        for(String bsn : randomBsn()) {
-            assertAdminLogin(bsn, false);
-        }
-    }
-
-
-    private LoginAdminWhitelistConfiguration config(final Boolean bypass, final String regexp, final String[] defaultBSNs, final String[] additionalBSNs) {
-        return new LoginAdminWhitelistConfiguration() {
-            @Override
-            public boolean whitelist_bypass() {
-                return defaultIfNull(bypass, "whitelist_bypass");
-            }
-
-            @Override
-            public String whitelist_bundles_regexp() {
-                return defaultIfNull(regexp, "whitelist_bundles_regexp");
-            }
-
-            @Override
-            public String[] whitelist_bundles_default() {
-                return defaultIfNull(defaultBSNs, "whitelist_bundles_default");
-            }
-
-            @Override
-            public String[] whitelist_bundles_additional() {
-                return defaultIfNull(additionalBSNs, "whitelist_bundles_additional");
-            }
-
-            @Override
-            public Class<? extends Annotation> annotationType() {
-                return LoginAdminWhitelistConfiguration.class;
-            }
-
-            private <T> T defaultIfNull(final T value, final String methodName) {
-                if (value != null) {
-                    return value;
-                }
-                try {
-                    return (T)this.annotationType().getMethod(methodName).getDefaultValue();
-                } catch (NoSuchMethodException e) {
-                    return null;
-                }
-            }
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/jcr/oak/server/it/OakServerTestSupport.java b/src/test/java/org/apache/sling/jcr/oak/server/it/OakServerTestSupport.java
index 3dc19b2..b816715 100644
--- a/src/test/java/org/apache/sling/jcr/oak/server/it/OakServerTestSupport.java
+++ b/src/test/java/org/apache/sling/jcr/oak/server/it/OakServerTestSupport.java
@@ -202,7 +202,7 @@
     }
 
     protected Option getWhitelistRegexpOption() {
-        return newConfiguration("org.apache.sling.jcr.oak.server.internal.LoginAdminWhitelist")
+        return newConfiguration("org.apache.sling.jcr.base.internal.LoginAdminWhitelist")
             .put("whitelist.bundles.regexp", "PAXEXAM-PROBE-.*")
             .asOption();
     }