You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by en...@apache.org on 2022/09/26 18:32:43 UTC

[sling-org-apache-sling-auth-core] branch master updated: SLING-11446 Move the DefaultLoginsHealthCheck to the auth core bundle (#13)

This is an automated email from the ASF dual-hosted git repository.

enorman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-core.git


The following commit(s) were added to refs/heads/master by this push:
     new e8d6ba1  SLING-11446 Move the DefaultLoginsHealthCheck to the auth core bundle (#13)
e8d6ba1 is described below

commit e8d6ba1837a52f39f6d83ea77d37ac6193bb0091
Author: Eric Norman <en...@apache.org>
AuthorDate: Mon Sep 26 11:32:39 2022 -0700

    SLING-11446 Move the DefaultLoginsHealthCheck to the auth core bundle (#13)
---
 bnd.bnd                                            |   6 +-
 pom.xml                                            |  22 +++-
 .../core/impl/hc/DefaultLoginsHealthCheck.java     | 125 +++++++++++++++++++++
 .../core/impl/hc/DefaultLoginsHealthCheckTest.java |  98 ++++++++++++++++
 .../apache/sling/auth/core/impl/hc/SetField.java   |  30 +++++
 5 files changed, 275 insertions(+), 6 deletions(-)

diff --git a/bnd.bnd b/bnd.bnd
index c910ed7..f3533ac 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -1,3 +1,7 @@
-Import-Package: !javax.jcr,*
+# SLING-11446 - keep hc related imports optional
+Import-Package: !javax.jcr,\
+    org.apache.felix.hc.api;resolution:=optional,\
+    org.apache.sling.jcr.api;resolution:=optional,\
+    *
 DynamicImport-Package: javax.jcr
 Bundle-DocURL: http://sling.apache.org/site/authentication.html
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 7284c34..947453f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -135,11 +135,23 @@
             <artifactId>org.apache.sling.commons.metrics</artifactId>
             <version>1.2.8</version>
         </dependency>
-      <dependency>
-          <groupId>org.jetbrains</groupId>
-          <artifactId>annotations</artifactId>
-          <scope>provided</scope>
-      </dependency>
+       <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.healthcheck.api</artifactId>
+            <version>2.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.api</artifactId>
+            <version>2.0.4</version>
+            <scope>provided</scope>
+        </dependency>
 
         <!-- Test Dependencies -->
         <dependency>
diff --git a/src/main/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheck.java b/src/main/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheck.java
new file mode 100644
index 0000000..a02f373
--- /dev/null
+++ b/src/main/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheck.java
@@ -0,0 +1,125 @@
+/*
+ * 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 SF 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.auth.core.impl.hc;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.Credentials;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.felix.hc.api.FormattingResultLog;
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** {@link HealthCheck} that runs an arbitrary script. */
+@Component(service = HealthCheck.class, name = "org.apache.sling.auth.core.DefaultLoginsHealthCheck", configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = DefaultLoginsHealthCheck.Config.class, factory = true)
+public class DefaultLoginsHealthCheck implements HealthCheck {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DefaultLoginsHealthCheck.class);
+
+    public static final String HC_LABEL = "Health Check: Default Logins";
+
+    @ObjectClassDefinition(name = HC_LABEL, description = "Expects default logins to fail, used to verify that they are disabled on production systems")
+    @interface Config {
+
+        @AttributeDefinition(name = "Name", description = "Name of this health check.")
+        String hc_name() default "Default Logins Check"; // NOSONAR
+
+        @AttributeDefinition(name = "Tags", description = "List of tags for this health check, used to select subsets of health checks for execution e.g. by a composite health check.")
+        String[] hc_tags() default {}; // NOSONAR
+
+        @AttributeDefinition(name = "Default Logins", description = "Which credentials to check. Each one is in the format"
+                + " \"user:password\" like \"admin:admin\" for example. Do *not* put any confidential passwords here, the goal "
+                + "is just to check that the default/demo logins, which passwords are known anyway, are disabled.")
+        String[] logins() default "logins";
+
+        @AttributeDefinition
+        String webconsole_configurationFactory_nameHint() default "Default Logins Check: {logins}"; // NOSONAR
+    }
+
+    private List<String> logins;
+
+    @Reference
+    private SlingRepository repository;
+
+    @Activate
+    protected void activate(Config config) {
+        this.logins = Arrays.asList(config.logins());
+        LOG.info("Activated, logins={}", logins);
+    }
+
+    @Override
+    public Result execute() {
+        FormattingResultLog resultLog = new FormattingResultLog();
+        int checked = 0;
+        int failures = 0;
+
+        for (String login : logins) {
+            final String[] parts = login.split(":");
+            if (parts.length != 2) {
+                resultLog.warn("Expected login in the form username:password, got [{}]", login);
+                continue;
+            }
+            checked++;
+            final String username = parts[0].trim();
+            final String password = parts[1].trim();
+            final Credentials creds = new SimpleCredentials(username, password.toCharArray());
+            Session s = null;
+            try {
+                s = repository.login(creds);
+                if (s != null) {
+                    failures++;
+                    resultLog.warn("Login as [{}] succeeded, was expecting it to fail", username);
+                } else {
+                    resultLog.debug("Login as [{}] didn't throw an Exception but returned null Session", username);
+                }
+            } catch (RepositoryException re) {
+                resultLog.debug("Login as [{}] failed, as expected", username);
+            } finally {
+                if (s != null) {
+                    s.logout();
+                }
+            }
+        }
+
+        if (checked == 0) {
+            resultLog.warn("Did not check any logins, configured logins={}", logins);
+        } else if (failures != 0) {
+            resultLog.warn("Checked {} logins, {} failures", checked, failures);
+        } else {
+            resultLog.debug("Checked {} logins, all successful", checked, failures);
+        }
+
+        return new Result(resultLog);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheckTest.java b/src/test/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheckTest.java
new file mode 100644
index 0000000..0327b6b
--- /dev/null
+++ b/src/test/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheckTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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 SF 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.auth.core.impl.hc;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.felix.hc.api.Result;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class DefaultLoginsHealthCheckTest {
+
+    private Result getTestResult(String login) throws Exception {
+        final DefaultLoginsHealthCheck c = new DefaultLoginsHealthCheck();
+        if (login == null) {
+            SetField.set(c, "logins", Arrays.asList(new String[0]));
+        } else {
+            SetField.set(c, "logins", Arrays.asList(new String[] { login }));
+        }
+
+        final SlingRepository repo = Mockito.mock(SlingRepository.class);
+        SetField.set(c, "repository", repo);
+        final Session s = Mockito.mock(Session.class);
+        Mockito.when(repo.login(ArgumentMatchers.any(Credentials.class))).thenAnswer(new Answer<Session>() {
+            @Override
+            public Session answer(InvocationOnMock invocation) throws LoginException {
+                final SimpleCredentials c = (SimpleCredentials)invocation.getArguments()[0];
+                if ("admin".equals(c.getUserID())) {
+                    return s;
+                } else if ("throw".equals(c.getUserID())) {
+                    throw new LoginException("Login Failed");
+                }
+                return null;
+            }
+        });
+        
+        return c.execute();
+    }
+
+    @Test
+    public void testHealthCheckFails() throws Exception {
+        assertFalse("Expecting failed check", getTestResult("admin:admin").isOk());
+    }
+
+    @Test
+    public void testHealthCheckSucceeds() throws Exception {
+        assertTrue("Expecting successful check", getTestResult("FOO:bar").isOk());
+    }
+
+    @Test
+    public void testHealthCheckInvalidLogins() throws Exception {
+        Result testResult = getTestResult("FOO");
+        assertFalse("Expecting successful check", testResult.isOk());
+        assertTrue("Expected warning in the ResultLog", testResult.toString().contains("WARN Expected login in the form username:password, got [FOO]"));
+    }
+
+    @Test
+    public void testHealthCheckEmptyLogins() throws Exception {
+        Result testResult = getTestResult(null);
+        assertFalse("Expecting failed check", testResult.isOk());
+        assertTrue("Expected warning in the ResultLog", testResult.toString().contains("WARN Did not check any logins, configured logins=[]"));
+    }
+
+    @Test
+    public void testHealthCheckSucceedsWithLoginException() throws Exception {
+        Result testResult = getTestResult("throw:loginexception");
+        assertTrue("Expecting successful check", testResult.isOk());
+        assertTrue("Expected debug in the ResultLog", testResult.toString().contains("DEBUG Login as [throw] failed, as expected"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/auth/core/impl/hc/SetField.java b/src/test/java/org/apache/sling/auth/core/impl/hc/SetField.java
new file mode 100644
index 0000000..6df721e
--- /dev/null
+++ b/src/test/java/org/apache/sling/auth/core/impl/hc/SetField.java
@@ -0,0 +1,30 @@
+/*
+ * 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 SF 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.auth.core.impl.hc;
+
+import java.lang.reflect.Field;
+
+public class SetField {
+    
+    public static void set(Object o, String name, Object value) throws Exception {
+        final Field f = o.getClass().getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(o, value);
+    }
+    
+}
\ No newline at end of file