You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by jo...@apache.org on 2023/07/02 13:20:13 UTC

[sling-org-apache-sling-models-jacksonexporter] branch SLING-11924-prevent-rr-serialization created (now e47008d)

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

joerghoh pushed a change to branch SLING-11924-prevent-rr-serialization
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-jacksonexporter.git


      at e47008d  SLING-11924 disallow the serialization of a ResourceResolver

This branch includes the following new commits:

     new e47008d  SLING-11924 disallow the serialization of a ResourceResolver

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sling-org-apache-sling-models-jacksonexporter] 01/01: SLING-11924 disallow the serialization of a ResourceResolver

Posted by jo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

joerghoh pushed a commit to branch SLING-11924-prevent-rr-serialization
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-jacksonexporter.git

commit e47008dd1af6fcfcfd47648e6d0e7365092b30d7
Author: Joerg Hoh <jo...@apache.org>
AuthorDate: Sun Jul 2 15:06:19 2023 +0200

    SLING-11924 disallow the serialization of a ResourceResolver
---
 pom.xml                                            |  10 +-
 .../impl/IgnoringResourceResolverMixin.java        |  65 +++++
 .../impl/ResourceResolverModuleProvider.java       |  63 +++++
 .../impl/WarningResourceResolverMixin.java         |  73 +++++
 .../JacksonExporterLimitSerializationTest.java     | 311 +++++++++++++++++++++
 .../impl/example/PojoWithResourceResolver.java     |  36 +++
 .../jacksonexporter/impl/util/LogCapture.java      |  69 +++++
 7 files changed, 625 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 64872f9..1285634 100644
--- a/pom.xml
+++ b/pom.xml
@@ -152,10 +152,16 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.testing.logging-mock</artifactId>
-            <version>2.0.0</version>
+            <artifactId>org.apache.sling.testing.sling-mock.junit5</artifactId>
+            <version>3.2.0</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+           <groupId>ch.qos.logback</groupId>
+           <artifactId>logback-classic</artifactId>
+           <version>1.2.3</version>
+           <scope>test</scope>
+       </dependency>
 
     </dependencies>
 
diff --git a/src/main/java/org/apache/sling/models/jacksonexporter/impl/IgnoringResourceResolverMixin.java b/src/main/java/org/apache/sling/models/jacksonexporter/impl/IgnoringResourceResolverMixin.java
new file mode 100644
index 0000000..c7f56e4
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/jacksonexporter/impl/IgnoringResourceResolverMixin.java
@@ -0,0 +1,65 @@
+/*
+ * 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.models.jacksonexporter.impl;
+
+import java.io.IOException;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreType;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+/**
+ * This mixin disables the serialization of the ResourceResolver; if it's still requested, a ERROR log message is written.
+ *
+ */
+
+@JsonIgnoreType
+public abstract interface IgnoringResourceResolverMixin extends ResourceResolver {
+    
+    public static final String MESSAGE = "The serialization of a ResourceResolver was rejected, because the JacksonExporter servlet "
+            + "is configured to do so. Please review your Sling Model implementation class(es) and remove any public reference to a "
+            + "ResourceResolver."; 
+
+    public static final Logger LOG = LoggerFactory.getLogger(JacksonExporter.class);
+    
+    
+    @JsonSerialize(using=JustLoggingSerializer.class)
+    @Override
+    boolean isLive();
+    
+
+    
+    public class JustLoggingSerializer extends JsonSerializer<Boolean> {
+
+        @Override
+        public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider)
+                throws IOException, JsonProcessingException {
+            LOG.warn(MESSAGE);
+        }
+        
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceResolverModuleProvider.java b/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceResolverModuleProvider.java
new file mode 100644
index 0000000..63dd4d6
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceResolverModuleProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.models.jacksonexporter.impl;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.jacksonexporter.ModuleProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+@Component(service = ModuleProvider.class)
+@Designate(ocd = ResourceResolverModuleProvider.Config.class)
+public class ResourceResolverModuleProvider implements ModuleProvider {
+    
+    @ObjectClassDefinition(name = "Apache Sling Models Jackson Exporter - ResourceResolver support",
+            description = "Provider of a Jackson Module which enables/disables support for serializing a ResourceResolver")
+    static @interface Config {
+
+        @AttributeDefinition(name ="disable serialization of the ResourceResolver",
+                description = "if enabled, ResourceResolver instances are not longer deserialized as JSON")
+        boolean disable_serialization() default false;
+
+    }
+    
+    SimpleModule moduleInstance;
+    
+    
+    @Activate
+    private void activate(Config config) {
+        this.moduleInstance = new SimpleModule();
+        if (config.disable_serialization()) {
+            moduleInstance.setMixInAnnotation(ResourceResolver.class, IgnoringResourceResolverMixin.class);
+        } else {
+            moduleInstance.setMixInAnnotation(ResourceResolver.class, WarningResourceResolverMixin.class);
+        }
+    }
+    
+
+    @Override
+    public Module getModule() {
+        return moduleInstance;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/models/jacksonexporter/impl/WarningResourceResolverMixin.java b/src/main/java/org/apache/sling/models/jacksonexporter/impl/WarningResourceResolverMixin.java
new file mode 100644
index 0000000..3a6cc2c
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/jacksonexporter/impl/WarningResourceResolverMixin.java
@@ -0,0 +1,73 @@
+/*
+ * 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.models.jacksonexporter.impl;
+
+import java.io.IOException;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+
+/**
+ * This mixin exports data which Jackson would export by default (so not change in the default behaviour),
+ * but prints a warning whenever it does that.
+ */
+
+
+@JsonAutoDetect
+public interface WarningResourceResolverMixin extends ResourceResolver {
+    
+    public static final String MESSAGE = "A ResourceResolver is serialized with all its private fields containing "
+            + "implementation details you should not disclose. Please review your Sling Model implementation(s) and remove "
+            + "all public accessors to a ResourceResolver.";
+    
+    public static final Logger LOG = LoggerFactory.getLogger(JacksonExporter.class);
+    
+    
+
+    // This method is explicitly mentioned so we provide a custom serializer which prints the warning
+    
+    @Override
+    @JsonSerialize(using=WarningBooleanSerializer.class)
+    boolean isLive();
+    
+    
+    
+    public class WarningBooleanSerializer extends JsonSerializer<Boolean> {
+
+        @Override
+        public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider)
+                throws IOException, JsonProcessingException {
+            LOG.warn(MESSAGE);
+            jgen.writeObject(value);
+            
+        }
+        
+    }
+    
+    
+}
diff --git a/src/test/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporterLimitSerializationTest.java b/src/test/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporterLimitSerializationTest.java
new file mode 100644
index 0000000..fff0582
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporterLimitSerializationTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.models.jacksonexporter.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.factory.ExportException;
+import org.apache.sling.models.jacksonexporter.impl.example.PojoWithResourceResolver;
+import org.apache.sling.models.jacksonexporter.impl.util.LogCapture;
+import org.apache.sling.testing.mock.osgi.junit5.OsgiContext;
+import org.apache.sling.testing.mock.osgi.junit5.OsgiContextExtension;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import ch.qos.logback.classic.Level;
+
+@ExtendWith(OsgiContextExtension.class)
+public class JacksonExporterLimitSerializationTest {
+
+    private OsgiContext context = new OsgiContext();
+    
+    
+    
+    @Test
+    public void testWarnLogWhenSerializingResourceResolver() throws ExportException {
+        
+        LogCapture capture = new LogCapture(JacksonExporter.class.getName(),false);
+        
+        PojoWithResourceResolver pojo = new PojoWithResourceResolver("text", new EmptyResourceResolver());
+        
+        context.registerInjectActivateService(new ResourceResolverModuleProvider());
+        JacksonExporter underTest = context.registerInjectActivateService(JacksonExporter.class);
+        Map<String,String> options = Collections.emptyMap();
+
+        String expectedJson = "{\"msg\":\"text\",\"resolver\":{";
+        assertTrue(underTest.export(pojo, String.class, options).contains(expectedJson));
+        assertTrue(capture.anyMatch(event -> {
+           return event.getFormattedMessage().equals(WarningResourceResolverMixin.MESSAGE) && 
+                   event.getLevel().equals(Level.WARN);
+        }));
+    }
+    
+    
+    @Test
+    public void testNotSerializingResourceResolverWhenDisabled() throws ExportException {
+        
+        LogCapture capture = new LogCapture(IgnoringResourceResolverMixin.class.getName(),false);        
+        PojoWithResourceResolver pojo = new PojoWithResourceResolver("text",new EmptyResourceResolver());
+        
+        Map<String,Object> config = Collections.singletonMap("disable.serialization", "true");
+        context.registerInjectActivateService(new ResourceResolverModuleProvider(),config);
+        
+        
+        JacksonExporter underTest = context.registerInjectActivateService(JacksonExporter.class);
+        Map<String,String> options = Collections.emptyMap();
+
+        String expectedJson = "{\"msg\":\"text\"}";
+        assertEquals(expectedJson, underTest.export(pojo, String.class, options));
+//        assertTrue(capture.anyMatch(p -> p.getFormattedMessage().contains(IgnoringResourceResolverMixin.MESSAGE)));
+    }
+    
+    
+    /**
+     * A very simple ResourceResolver implementation which does not lead to any issues with any mocking framework
+     * when trying to export it with Jackson.
+     */
+    public class EmptyResourceResolver implements ResourceResolver {
+        
+//        public static final String SERIALIZED_STRING = "\"resolver\":{\"live\":false,\"userID\":null,\"searchPath\":null,\"propertyMap\":null,\"userID\":false}";
+        
+
+        @Override
+        public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource resolve(HttpServletRequest request, String absPath) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource resolve(String absPath) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource resolve(HttpServletRequest request) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public String map(String resourcePath) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public String map(HttpServletRequest request, String resourcePath) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource getResource(String path) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource getResource(Resource base, String path) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public String[] getSearchPath() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterator<Resource> listChildren(Resource parent) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource getParent(Resource child) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterable<Resource> getChildren(Resource parent) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterator<Resource> findResources(String query, String language) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterator<Map<String, Object>> queryResources(String query, String language) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean hasChildren(Resource resource) {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public ResourceResolver clone(Map<String, Object> authenticationInfo) throws LoginException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean isLive() {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public void close() {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public String getUserID() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterator<String> getAttributeNames() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Object getAttribute(String name) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public void delete(Resource resource) throws PersistenceException {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public Resource create(Resource parent, String name, Map<String, Object> properties)
+                throws PersistenceException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public void revert() {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public void commit() throws PersistenceException {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public boolean hasChanges() {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public String getParentResourceType(Resource resource) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public String getParentResourceType(String resourceType) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean isResourceType(Resource resource, String resourceType) {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public void refresh() {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public Resource copy(String srcAbsPath, String destAbsPath) throws PersistenceException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource move(String srcAbsPath, String destAbsPath) throws PersistenceException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean orderBefore(@NotNull Resource parent, @NotNull String name,
+                @Nullable String followingSiblingName)
+                throws UnsupportedOperationException, PersistenceException, IllegalArgumentException {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public @NotNull Map<String, Object> getPropertyMap() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+        
+    }
+    
+    
+}
diff --git a/src/test/java/org/apache/sling/models/jacksonexporter/impl/example/PojoWithResourceResolver.java b/src/test/java/org/apache/sling/models/jacksonexporter/impl/example/PojoWithResourceResolver.java
new file mode 100644
index 0000000..3e544b7
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/jacksonexporter/impl/example/PojoWithResourceResolver.java
@@ -0,0 +1,36 @@
+/*
+ * 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.models.jacksonexporter.impl.example;
+
+import org.apache.sling.api.resource.ResourceResolver;
+
+public class PojoWithResourceResolver {
+    
+    
+    public String msg;
+    public ResourceResolver resolver;
+    
+    public PojoWithResourceResolver (String msg, ResourceResolver resolver) {
+        this.msg = msg;
+        this.resolver = resolver;
+    }
+    
+    
+
+}
diff --git a/src/test/java/org/apache/sling/models/jacksonexporter/impl/util/LogCapture.java b/src/test/java/org/apache/sling/models/jacksonexporter/impl/util/LogCapture.java
new file mode 100644
index 0000000..be328b0
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/jacksonexporter/impl/util/LogCapture.java
@@ -0,0 +1,69 @@
+/*
+ * 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.models.jacksonexporter.impl.util;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.function.Predicate;
+
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+
+public class LogCapture  extends ListAppender<ILoggingEvent> implements Closeable {
+    private final boolean verboseFailure;
+
+    /** Setup the capture and start it */
+    public LogCapture(String loggerName, boolean verboseFailure) {
+        this.verboseFailure = verboseFailure;
+        Logger logger = (Logger) LoggerFactory.getLogger(loggerName);
+        logger.setLevel(Level.ALL);
+        setContext((LoggerContext) LoggerFactory.getILoggerFactory());
+        logger.addAppender(this);
+        start();
+    }
+
+    public boolean anyMatch(Predicate<ILoggingEvent> p) {
+        return this.list.stream().anyMatch(p);
+    }
+
+    public void assertMessage(String message) {
+        assertTrue(anyMatch(event -> {
+            return event.getFormattedMessage().equals(message);
+        }));
+    }
+    
+    
+    
+    
+
+    @Override
+    public void close() throws IOException {
+        stop();
+    }
+    
+    
+    
+}