diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..693e9c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# Created by https://www.gitignore.io/api/eclipse,java,maven + +### Eclipse ### +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + +### Java ### +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +### Vault ### +.vlt + +### IntelliJ ### +.idea/ +*.iml + +.DS_Store +/TODO.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1a09b0c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: java +jdk: +- openjdk9 +addons: + sonarcloud: + organization: valtech-avs + token: + secure: gPXlvZfPVk1JC+swRzh4hDNRMeYPCmPhxOVjmKrizTOnK0T3TOIAY2reUH0+U61fAAfobC4sO5gPsx0cy/diN+b2Ln/mOIY4T4S7be7jlXXuySTYwZSeI9IIOs+T3/OPko850GBU7IYrSFOyzae3OXs3j5R+nzg6stW1PXV3WceF+rZ2aSJbmcMIv17GHSDtxyYQkc/R9WW8OH2DOMng5LzJ3b4qv20Xzamt1povDX69YX+DXuGYErjBzcivwu33yNDxjX0a8AT51cLl6l31AZM7Z0TsAKMn3tPNObwKSGVwDjTfhgTUyH57MFF389om1BgxnZERiiTwYXYuVhm4rdSl8usw351LUO7VGtUCA/exh74R8R7uro+buoGmeOBKLp/NtOpvvVg4/c7YUi4KjuGdVua7b5XgoRsbAjFzHR2TeFw5eH5AzpTS6j1ryM8p+Os29fBJ72lVD35iFMZ8Vut5Fiz3chVitI25/94leU5+qWq1Rfh9s+4jJL/HldlGgXjII0lCFDa8up9ezbOn/j+9zo9vl4WiJgl9fSZ2KAQuFAhGkGWIda8ctjqwZmOwX2vF0F3eAZjWT+jNbyuZhmnR/K156fnEQHpTHVZkpgnJgK0Hc6P2XISbl5ZRxMTtJCbnZhCFnXp7xYcIfbWkokHzmLPGi6i3lXWtJfiVmF8= +script: +- mvn test javadoc:javadoc -B +- sonar-scanner +cache: + directories: + - "$HOME/.sonar/cache" + - "$HOME/.m2" diff --git a/HISTORY b/HISTORY new file mode 100644 index 0000000..704033c --- /dev/null +++ b/HISTORY @@ -0,0 +1,4 @@ + +2020 1.0.0 + - initial release + \ No newline at end of file diff --git a/README.md b/README.md index ceaf901..9fa6fb4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,192 @@ -# -aem-virus-check -AEM Virus Check adds malware scanning to your AEM instance. +# AEM Virus Scan + +AEM Virus Scan adds malware scanning to your AEM instance. It supports Clam AV out-of-the-box and can be extended to support more scan engines. + +Features: + +* Scanning of asset uploads +* API to run scan from custom code +* API to add custom scan engines +* Provided scan engines: + * Clam AV +* Health checks + + + +# Requirements + +AVS requires Java 8 and AEM 6.4 or above. + +| AEM Version | AVS | +| ------------- | --------- | +| 6.4 | 1.x | +| 6.5 | 1.x | + + + +# Installation + +You can download the package from [Maven Central](http://repo1.maven.org/maven2/de/valtech/avs/avs.ui.apps/) or our [releases section](https://github.com/valtech/aem-virus-scan/releases). The avs.ui.apps package will install the AVS software. + +```xml + + de.valtech.avs + avs.ui.apps + LATEST + zip + +``` + + +## Uninstallation + +The application can be removed by deleting the following paths: +* /apps/valtech/avs +* /var/avs + +Afterwards, you can delete the "avs.ui.apps" package in package manager. + +# Scan File Uploads + +You can scan files in any POST request. E.g. this way you can scan files that are uploaded to DAM. + +You should provide a [configuration](#conf_filter) of the URL patterns for the filter. + +# Tools + +AVS adds tools to the AEM menu. + + + + +## Perform a Manual Scan + +You can upload a file using AVS scan tool from the menu. This allows you to check if there is an issue with the file. + + + + +## History + +This shows the history of the last found infections. Scans that did not lead to an alert are not listed. + + + +# Configuration + +You can see an example for each configuration in [example package](/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config). + + + +## AVS Post Filter + +This filter can scan e.g. asset uploads for viruses. + +* includePatterns: List of regular expressions to match the URLs to check. If empty, all non-excluded URLs are scanned. +* excludePatterns: List of regular expressions to match the URLs to ignore. Has higher priority than include patterns. + +PID: de.valtech.avs.core.filter.AvsPostFilter + +File name: de.valtech.avs.core.filter.AvsPostFilter.xml + +``` + + +``` + + + +## AVS Notification Mailer + +This filter can scan e.g. asset uploads for viruses. + +* subject: mail subject +* body: body for all virus notification emails. Wildcards are ${FILE_NAME} for uploaded file name and ${SCAN_OUTPUT} for scan details. +* isHtml: specifies if mail format is HTML or TEXT +* from: FROM address for notification emails +* additionalRecipients: additional email recipients + +PID: de.valtech.avs.core.mail.AvsNotificationMailer + +File name: de.valtech.avs.core.mail.AvsNotificationMailer.xml + +``` + + +``` + + + +## Clam Scanning Engine + +You need to provide a configuration for Clam AV in case you want to use this scan engine. If no configuration is provided then it will not be activated. + +* command: command to scan a single file. The file name will be added at the end of the command. + +PID: de.valtech.avs.core.service.scanner.ClamScannerEngine + +File name: de.valtech.avs.core.service.scanner.ClamScannerEngine.xml + +``` + + +``` + + + +## Purge History + +This configures how long to keep the scan history. Older entries will be purged with maintenance task. + +PID: de.valtech.avs.core.maintenance.PurgeHistoryTask + +File name: de.valtech.avs.core.maintenance.PurgeHistoryTask.xml + +``` + + +``` + +# Health Checks + +Health checks show you the status of AVS. This includes its service user, scan engines and a test scan. +You can access them on the [status page](http://localhost:4502/libs/granite/operations/content/healthreports/healthreportlist.html/system/sling/monitoring/mbeans/org/apache/sling/healthcheck/HealthCheck/avsHealthCheckmBean). + + + + + +# API Documentation + +TODO + + + +# License + +The AVS tool is licensed under the [MIT LICENSE](LICENSE). + + + +# Changelog + +Please see our [history file](HISTORY). + + + +# Developers + +See our [developer zone](docs/developers.md). diff --git a/api/pom.xml b/api/pom.xml new file mode 100644 index 0000000..4c28d87 --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,114 @@ + + + 4.0.0 + + de.valtech.avs + avs + 0.9.0 + + + avs.api + bundle + AVS - API + Api bundle for AVS + + + + + org.apache.felix + osgicheck-maven-plugin + + + biz.aQute.bnd + bnd-baseline-maven-plugin + + false + + + + org.apache.sling + maven-sling-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + + + javax.inject;version=0.0.0, + javax.annotation;version=0.0.0, + * + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + + + org.osgi + osgi.core + + + org.osgi + osgi.cmpn + + + org.osgi + osgi.annotation + + + org.slf4j + slf4j-api + + + javax.jcr + jcr + + + javax.servlet + javax.servlet-api + + + com.adobe.aem + uber-jar + apis + + + org.apache.sling + org.apache.sling.models.api + + + org.apache.commons + commons-lang3 + + + junit + junit + + + org.mockito + mockito-core + + + junit-addons + junit-addons + + + com.google.code.gson + gson + + + com.google.code.findbugs + jsr305 + + + diff --git a/api/src/main/java/de/valtech/avs/api/history/HistoryEntry.java b/api/src/main/java/de/valtech/avs/api/history/HistoryEntry.java new file mode 100644 index 0000000..f671cd2 --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/history/HistoryEntry.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.api.history; + +import java.util.Date; + +import org.osgi.annotation.versioning.ProviderType; + +/** + * Represents an entry in the AVS history. + * + * @author Roland Gruber + */ +@ProviderType +public interface HistoryEntry { + + /** + * Returns the scan time. + * + * @return time + */ + Date getTime(); + + /** + * Returns the scan output. + * + * @return output + */ + String getOutput(); + + /** + * Returns if the file was clean. + * + * @return clean + */ + boolean isClean(); + + /** + * Returns the scanned node path if available. + * + * @return path + */ + String getPath(); + + /** + * Path in repository. + * + * @return path + */ + String getRepositoryPath(); + + /** + * Returns the user name. + * + * @return user name + */ + String getUserId(); + +} diff --git a/api/src/main/java/de/valtech/avs/api/service/AvsException.java b/api/src/main/java/de/valtech/avs/api/service/AvsException.java new file mode 100644 index 0000000..76aa82e --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/service/AvsException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.api.service; + +/** + * Thrown when the AVS service faces an error. + * + * @author Roland Gruber + */ +public class AvsException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Constructor + * + * @param message error message + * @param e original exception + */ + public AvsException(String message, Throwable e) { + super(message, e); + } + + /** + * Constructor + * + * @param message error message + */ + public AvsException(String message) { + super(message); + } + +} diff --git a/api/src/main/java/de/valtech/avs/api/service/AvsService.java b/api/src/main/java/de/valtech/avs/api/service/AvsService.java new file mode 100644 index 0000000..13f8677 --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/service/AvsService.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.api.service; + +import java.io.InputStream; + +import org.osgi.annotation.versioning.ProviderType; + +import de.valtech.avs.api.service.scanner.ScanResult; + +/** + * Scanner service interface. Use this to scan for viruses. + * + * @author Roland Gruber + */ +@ProviderType +public interface AvsService { + + /** + * Scans the given content for viruses. + * + * @param content content + * @param userId user name + * @return scan result + * @throws AvsException error during scan + */ + public ScanResult scan(InputStream content, String userId) throws AvsException; + + /** + * Scans the given content for viruses. + * + * @param content content + * @param userId user name + * @param path node path to add in history + * @return scan result + * @throws AvsException error during scan + */ + public ScanResult scan(InputStream content, String userId, String path) throws AvsException; + + /** + * Returns if there is at least one active scan engine. + * + * @return scan engines available + */ + public boolean hasActiveScanEngines(); + +} diff --git a/api/src/main/java/de/valtech/avs/api/service/package-info.java b/api/src/main/java/de/valtech/avs/api/service/package-info.java new file mode 100644 index 0000000..37c62be --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/service/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * This package contains the service API for AEM Virus Scan. You can use this to integrate AVS into + * your own software. + * + * @author Roland Gruber + */ +@Version("1.0.0") +package de.valtech.avs.api.service; + +import org.osgi.annotation.versioning.Version; diff --git a/api/src/main/java/de/valtech/avs/api/service/scanner/AvsScannerEnine.java b/api/src/main/java/de/valtech/avs/api/service/scanner/AvsScannerEnine.java new file mode 100644 index 0000000..596861d --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/service/scanner/AvsScannerEnine.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.api.service.scanner; + +import java.io.InputStream; + +import org.osgi.annotation.versioning.ConsumerType; + +import de.valtech.avs.api.service.AvsException; + +/** + * Interface for scanner engines. Do not use directly for scanning but to implement new scanner + * engines. + * + * @author Roland Gruber + */ +@ConsumerType +public interface AvsScannerEnine { + + /** + * Scans the given content for viruses. + * + * @param content content + * @return scan result + * @throws AvsException error during scan + */ + ScanResult scan(InputStream content) throws AvsException; + +} diff --git a/api/src/main/java/de/valtech/avs/api/service/scanner/ScanResult.java b/api/src/main/java/de/valtech/avs/api/service/scanner/ScanResult.java new file mode 100644 index 0000000..6d9a781 --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/service/scanner/ScanResult.java @@ -0,0 +1,106 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.api.service.scanner; + +/** + * Result of a single file scan. + * + * @author Roland Gruber + */ +public class ScanResult { + + private boolean clean; + + private String output; + + private String path; + + private String userId; + + /** + * Constructor + * + * @param output command output + * @param clean file is clean + */ + public ScanResult(String output, boolean clean) { + this.clean = clean; + this.output = output; + } + + /** + * Returns if the file is clean. + * + * @return clean + */ + public boolean isClean() { + return clean; + } + + /** + * Returns the command output. + * + * @return output + */ + public String getOutput() { + return output; + } + + @Override + public String toString() { + return "Clean: " + Boolean.toString(clean) + "\n" + "Output: " + output; + } + + /** + * Returns the path of the scanned node. + * + * @return path + */ + public String getPath() { + return path; + } + + /** + * Sets the path of the scanned node. + * + * @param path path + */ + public void setPath(String path) { + this.path = path; + } + + /** + * Sets the user id. + * + * @param userId user id + */ + public void setUserId(String userId) { + this.userId = userId; + } + + /** + * Returns the user id. + * + * @return user id + */ + public String getUserId() { + return userId; + } + +} diff --git a/api/src/main/java/de/valtech/avs/api/service/scanner/package-info.java b/api/src/main/java/de/valtech/avs/api/service/scanner/package-info.java new file mode 100644 index 0000000..cc99bba --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/service/scanner/package-info.java @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * This package contains the scanner interface. + * + * @author Roland Gruber + */ +@Version("1.0.0") +package de.valtech.avs.api.service.scanner; + +import org.osgi.annotation.versioning.Version; diff --git a/api/src/main/javadoc/overview.html b/api/src/main/javadoc/overview.html new file mode 100644 index 0000000..cf52d41 --- /dev/null +++ b/api/src/main/javadoc/overview.html @@ -0,0 +1,12 @@ + + + + + AVS API Overview + + + + This is the AEM Virus Scan (AVS) API documentation. + + + \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..f1a50a7 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,160 @@ + + + 4.0.0 + + de.valtech.avs + avs + 0.9.0 + + + avs.core + bundle + AVS - Core + Core bundle for AVS + + + + + org.apache.felix + osgicheck-maven-plugin + + + org.apache.sling + maven-sling-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + + de.valtech.avs.core + + true + + artifactId=velocity-engine-core + + + javax.annotation;version=0.0.0, + * + + + + + + org.jacoco + jacoco-maven-plugin + + + prepare-agent + + prepare-agent + + + + report + prepare-package + + report + + + + post-unit-test + test + + report + + + + + + org.owasp + dependency-check-maven + + + + + + + de.valtech.avs + avs.api + ${project.version} + + + org.apache.velocity + velocity-engine-core + + + javax.mail + javax.mail-api + + + org.osgi + osgi.core + + + org.osgi + osgi.cmpn + + + org.osgi + osgi.annotation + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + javax.jcr + jcr + + + javax.servlet + javax.servlet-api + + + com.adobe.aem + uber-jar + apis + + + org.apache.sling + org.apache.sling.models.api + + + + javax.annotation + javax.annotation-api + + + + org.apache.commons + commons-lang3 + + + junit + junit + + + org.mockito + mockito-core + + + junit-addons + junit-addons + + + com.google.code.gson + gson + + + com.google.code.findbugs + jsr305 + + + diff --git a/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java b/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java new file mode 100644 index 0000000..95db2b9 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java @@ -0,0 +1,251 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.filter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Part; + +import org.apache.commons.fileupload.FileUploadBase; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.SlingHttpServletRequest; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.adobe.granite.security.user.UserProperties; + +import de.valtech.avs.api.service.AvsException; +import de.valtech.avs.api.service.AvsService; +import de.valtech.avs.api.service.scanner.ScanResult; +import de.valtech.avs.core.history.HistoryService; +import de.valtech.avs.core.mail.AvsNotificationMailer; +import de.valtech.avs.core.serviceuser.ServiceResourceResolverService; + +/** + * Filter for POST requests. Checks included files. + * + * @author Roland Gruber + */ +@Component(service = Filter.class, property = {"sling.filter.scope=REQUEST", Constants.SERVICE_RANKING + ":Integer=50000"}) +@Designate(ocd = AvsPostFilterConfig.class) +public class AvsPostFilter implements Filter { + + private static final String REQUEST_PARTS = "request-parts-iterator"; + + private static final Logger LOG = LoggerFactory.getLogger(AvsPostFilter.class); + + @Reference + private AvsService avsService; + + @Reference + private HistoryService historyService; + + @Reference + private ServiceResourceResolverService serviceResolverService; + + @Reference + private AvsNotificationMailer mailer; + + private List includePatterns = new ArrayList<>(); + private List excludePatterns = new ArrayList<>(); + + /** + * Setup service + * + * @param config configuration + */ + @Activate + public void activate(AvsPostFilterConfig config) { + excludePatterns = new ArrayList<>(); + if (config.excludePatterns() != null) { + for (String patternString : config.excludePatterns()) { + excludePatterns.add(Pattern.compile(patternString)); + } + } + includePatterns = new ArrayList<>(); + if (config.includePatterns() != null) { + for (String patternString : config.includePatterns()) { + includePatterns.add(Pattern.compile(patternString)); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; + String contentType = slingRequest.getContentType(); + if (!"POST".equals(slingRequest.getMethod()) || !isMultipartRequest(contentType) || isUrlToIgnore(slingRequest)) { + chain.doFilter(request, response); + return; + } + Iterator parts = (Iterator) request.getAttribute(REQUEST_PARTS); + if (parts == null) { + chain.doFilter(request, response); + return; + } + List parameterFiles = new ArrayList<>(); + List newParts = new ArrayList<>(); + List fileNames = new ArrayList<>(); + while (parts.hasNext()) { + Part part = parts.next(); + String partContentType = part.getContentType(); + if (StringUtils.isEmpty(partContentType)) { + String partContent = IOUtils.toString(part.getInputStream(), StandardCharsets.UTF_8.name()); + newParts.add(new PartWrapper(part, partContent.getBytes())); + continue; + } + if (StringUtils.isNotEmpty(part.getSubmittedFileName())) { + fileNames.add(part.getSubmittedFileName()); + } + InputStream partStream = part.getInputStream(); + File file = File.createTempFile("valtech-avs", ".tmp"); + Files.copy(partStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING); + partStream.close(); + parameterFiles.add(file); + PartWrapper wrapper = new PartWrapper(part, file); + newParts.add(wrapper); + } + request.setAttribute(REQUEST_PARTS, newParts.iterator()); + List streams = new ArrayList<>(); + for (File file : parameterFiles) { + streams.add(new FileInputStream(file)); + } + if (streams.isEmpty()) { + chain.doFilter(request, response); + return; + } + SequenceInputStream combinedStream = new SequenceInputStream(Collections.enumeration(streams)); + try { + Session session = slingRequest.getResourceResolver().adaptTo(Session.class); + String userId = (session != null) ? session.getUserID() : StringUtils.EMPTY; + ScanResult result = avsService.scan(combinedStream, userId); + if (!result.isClean()) { + for (File file : parameterFiles) { + if (!file.delete()) { + LOG.warn("Unable to remove temp file {}", file.getPath()); + } + } + sendEmail(slingRequest, result, fileNames); + throw new ServletException("Uploaded file contains a virus"); + } + } catch (AvsException e) { + LOG.error("Virus scan failed", e); + } + combinedStream.close(); + chain.doFilter(request, response); + } + + /** + * Checks if the request is multipart form-data. + * + * @param contentType content type + * @return is multipart form-data + */ + private boolean isMultipartRequest(String contentType) { + if (StringUtils.isEmpty(contentType)) { + return false; + } + return contentType.contains(FileUploadBase.MULTIPART_FORM_DATA); + } + + /** + * Checks if the request URL should be ignored from checking. + * + * @param request request + * @return ignore + */ + protected boolean isUrlToIgnore(SlingHttpServletRequest request) { + String url = request.getRequestURI(); + for (Pattern pattern : excludePatterns) { + if (pattern.matcher(url).matches()) { + return true; + } + } + if (includePatterns.isEmpty()) { + return false; + } + for (Pattern pattern : includePatterns) { + if (pattern.matcher(url).matches()) { + return false; + } + } + return true; + } + + /** + * Sends out an email to notify the author about the virus upload. + * + * @param slingRequest request + * @param result scan result + * @param fileNames file names + */ + private void sendEmail(SlingHttpServletRequest slingRequest, ScanResult result, List fileNames) { + UserProperties properties = slingRequest.adaptTo(UserProperties.class); + List emails = new ArrayList<>(); + try { + if ((properties != null) && StringUtils.isNotEmpty(properties.getProperty(UserProperties.EMAIL))) { + emails.add(properties.getProperty(UserProperties.EMAIL)); + } + } catch (RepositoryException e) { + LOG.error("Cannot read email of user", e); + } + if (emails.isEmpty()) { + return; + } + mailer.sendEmail(emails, String.join(", ", fileNames), result); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // no action + } + + @Override + public void destroy() { + // no action + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilterConfig.java b/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilterConfig.java new file mode 100644 index 0000000..8907617 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilterConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.filter; + +import org.osgi.annotation.versioning.ProviderType; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.AttributeType; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +/** + * POST filter configuration. + * + * @author Roland Gruber + */ +@ObjectClassDefinition(name = "AVS POST filter configuration") +@ProviderType +public @interface AvsPostFilterConfig { + + /** + * Returns the URL include pattern list. + * + * @return list + */ + @AttributeDefinition(name = "URL include patterns", description = "List of regular expressions to match the URLs to check", + type = AttributeType.STRING) + String[] includePatterns(); + + /** + * Returns the URL exclude pattern list. + * + * @return list + */ + @AttributeDefinition(name = "URL exclude patterns", + description = "List of regular expressions to match the URLs to ignore. Has higher priority than include patterns.", + type = AttributeType.STRING) + String[] excludePatterns(); + +} diff --git a/core/src/main/java/de/valtech/avs/core/filter/PartWrapper.java b/core/src/main/java/de/valtech/avs/core/filter/PartWrapper.java new file mode 100644 index 0000000..0a0d8f2 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/filter/PartWrapper.java @@ -0,0 +1,118 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.filter; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Collection; + +import javax.servlet.http.Part; + +/** + * Wrapper to replace input stream from part. + * + * @author Roland Gruber + */ +public class PartWrapper implements Part { + + private Part part; + private File file; + private byte[] content; + + /** + * Constructor + * + * @param part original part + * @param file file + */ + public PartWrapper(Part part, File file) { + this.part = part; + this.file = file; + } + + /** + * Constructor + * + * @param part original part + * @param content content + */ + public PartWrapper(Part part, byte[] content) { + this.part = part; + this.content = content; + } + + @Override + public InputStream getInputStream() throws IOException { + if (file == null) { + return new ByteArrayInputStream(content); + } + return Files.newInputStream(Paths.get(file.getPath()), StandardOpenOption.DELETE_ON_CLOSE); + } + + @Override + public String getContentType() { + return part.getContentType(); + } + + @Override + public String getName() { + return part.getName(); + } + + @Override + public long getSize() { + return part.getSize(); + } + + @Override + public void write(String fileName) throws IOException { + part.write(fileName); + } + + @Override + public void delete() throws IOException { + part.delete(); + } + + @Override + public String getHeader(String name) { + return part.getHeader(name); + } + + @Override + public Collection getHeaders(String name) { + return part.getHeaders(name); + } + + @Override + public Collection getHeaderNames() { + return part.getHeaderNames(); + } + + @Override + public String getSubmittedFileName() { + return part.getSubmittedFileName(); + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java b/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java new file mode 100644 index 0000000..ac6bb08 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java @@ -0,0 +1,113 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.healthcheck; + +import java.io.ByteArrayInputStream; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.hc.api.HealthCheck; +import org.apache.sling.hc.api.Result; +import org.apache.sling.hc.api.Result.Status; +import org.apache.sling.hc.util.FormattingResultLog; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import de.valtech.avs.api.service.AvsException; +import de.valtech.avs.api.service.AvsService; +import de.valtech.avs.api.service.scanner.ScanResult; +import de.valtech.avs.core.serviceuser.ServiceResourceResolverService; + +/** + * Checks if the basic setup is ok. + * + * @author Roland Gruber + */ +@Component(immediate = true, service = HealthCheck.class, property = {HealthCheck.TAGS + "=avs", + HealthCheck.NAME + "=AVS Self Check", HealthCheck.MBEAN_NAME + "=avsSelfCheckHCmBean"}) +public class SelfCheckHealthCheck implements HealthCheck { + + @Reference + private ServiceResourceResolverService resolverService; + + @Reference + private AvsService avsService; + + @Override + public Result execute() { + final FormattingResultLog resultLog = new FormattingResultLog(); + checkServiceResolver(resultLog); + if (resultLog.getAggregateStatus().equals(Status.CRITICAL)) { + return new Result(resultLog); + } + checkActiveScannersAvailable(resultLog); + return new Result(resultLog); + } + + /** + * Checks if the service resource resolvers are accessible. + * + * @param resultLog result log + */ + private void checkServiceResolver(FormattingResultLog resultLog) { + try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) { + if (resolver == null) { + resultLog.critical("Unable to open service resource resolver: null"); + return; + } + resultLog.info("Service user ok"); + } catch (LoginException e) { + resultLog.critical("Unable to open service resource resolver {}", e.getMessage()); + } + } + + /** + * Checks if there is at least one scan engine configured. + * + * @param resultLog result log + */ + private void checkActiveScannersAvailable(FormattingResultLog resultLog) { + if (!avsService.hasActiveScanEngines()) { + resultLog.critical("No active scan engines available"); + } else { + resultLog.info("At least one active scan engine available"); + checkSampleScan(resultLog); + } + } + + /** + * Performs a test scan. + * + * @param resultLog result log + */ + private void checkSampleScan(FormattingResultLog resultLog) { + ByteArrayInputStream stream = new ByteArrayInputStream(SelfCheckHealthCheck.class.getName().getBytes()); + try { + ScanResult result = avsService.scan(stream, "HEALTH CHECK"); + if (result.isClean()) { + resultLog.info("Test scan ok"); + } else { + resultLog.critical("False positive result: {}", result.getOutput()); + } + } catch (AvsException e) { + resultLog.critical("Error running test scan: {}", e.getMessage()); + } + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/history/HistoryEntryImpl.java b/core/src/main/java/de/valtech/avs/core/history/HistoryEntryImpl.java new file mode 100644 index 0000000..bec5168 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/history/HistoryEntryImpl.java @@ -0,0 +1,119 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.history; + +import java.util.Date; + +import de.valtech.avs.api.history.HistoryEntry; + +/** + * Represents an entry in the AVS history. + * + * @author Roland Gruber + */ +public class HistoryEntryImpl implements HistoryEntry { + + private Date time; + + private String output; + + private boolean clean; + + private String path; + + private String repositoryPath; + + private String userId; + + /** + * Constructor + * + * @param time time + * @param output output text + * @param clean is clean + * @param path path that was scanned + * @param repositoryPath path in crx + * @param userId user id + */ + public HistoryEntryImpl(Date time, String output, boolean clean, String path, String repositoryPath, String userId) { + super(); + this.time = time; + this.output = output; + this.clean = clean; + this.path = path; + this.repositoryPath = repositoryPath; + this.userId = userId; + } + + /* + * (non-Javadoc) + * + * @see de.valtech.avs.api.history.HistoryEntry#getTime() + */ + @Override + public Date getTime() { + return time; + } + + /* + * (non-Javadoc) + * + * @see de.valtech.avs.api.history.HistoryEntry#getOutput() + */ + @Override + public String getOutput() { + return output; + } + + /* + * (non-Javadoc) + * + * @see de.valtech.avs.api.history.HistoryEntry#isClean() + */ + @Override + public boolean isClean() { + return clean; + } + + /* + * (non-Javadoc) + * + * @see de.valtech.avs.api.history.HistoryEntry#getPath() + */ + @Override + public String getPath() { + return path; + } + + /* + * (non-Javadoc) + * + * @see de.valtech.avs.api.history.HistoryEntry#getRepositoryPath() + */ + @Override + public String getRepositoryPath() { + return repositoryPath; + } + + @Override + public String getUserId() { + return userId; + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/history/HistoryService.java b/core/src/main/java/de/valtech/avs/core/history/HistoryService.java new file mode 100644 index 0000000..b0eba00 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/history/HistoryService.java @@ -0,0 +1,369 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.history; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants; +import org.apache.sling.api.resource.ModifiableValueMap; +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.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ResourceUtil.BatchResourceRemover; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.jcr.resource.api.JcrResourceConstants; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.valtech.avs.api.history.HistoryEntry; +import de.valtech.avs.api.service.AvsException; +import de.valtech.avs.api.service.scanner.ScanResult; + +/** + * Reads and writes history entries. + * + * @author Roland Gruber + */ +@Component(service = HistoryService.class) +public class HistoryService { + + private static final Logger LOG = LoggerFactory.getLogger(HistoryService.class); + + /** + * Base node for history entries + */ + public static final String HISTORY_BASE = "/var/avs/history"; + + protected static final String ATTR_TIME = "time"; + protected static final String ATTR_CLEAN = "clean"; + protected static final String ATTR_OUTPUT = "output"; + protected static final String ATTR_PATH = "path"; + protected static final String ATTR_USER_ID = "userId"; + private static final String NAME_INDEX = "oak:index"; + + private Random random = new Random(); + + /** + * Creates a new history entry. + * + * @param resolver resource resolver + * @param result scan result + * @throws AvsException error setting up entry + */ + public void createHistoryEntry(ResourceResolver resolver, ScanResult result) throws AvsException { + Calendar now = new GregorianCalendar(); + String basePath = HISTORY_BASE + "/" + now.get(Calendar.YEAR) + "/" + (now.get(Calendar.MONTH) + 1) + "/" + + now.get(Calendar.DAY_OF_MONTH); + String nodeName = generateHistoryNodeName(); + String nodePath = basePath + "/" + nodeName; + createPath(basePath, resolver, JcrResourceConstants.NT_SLING_ORDERED_FOLDER); + createPath(nodePath, resolver, JcrConstants.NT_UNSTRUCTURED); + Resource resource = resolver.getResource(nodePath); + ModifiableValueMap values = resource.adaptTo(ModifiableValueMap.class); + values.put(ATTR_TIME, now); + values.put(ATTR_OUTPUT, result.getOutput()); + values.put(ATTR_CLEAN, result.isClean()); + values.put(ATTR_PATH, result.getPath()); + values.put(ATTR_USER_ID, result.getUserId()); + try { + resolver.commit(); + } catch (PersistenceException e) { + throw new AvsException("Unable to story history entry", e); + } + } + + /** + * Returns the last history entries. The search starts at the newest entry. + * + * @param startIndex start reading at this index (first is 0) + * @param count number of entries to read + * @param resolver resource resolver + * @return history entries (newest first) + */ + public List getHistory(int startIndex, int count, ResourceResolver resolver) { + List entries = new ArrayList<>(); + if (count == 0) { + return entries; + } + Resource base = resolver.getResource(HISTORY_BASE); + Resource current = getLatestHistoryEntry(base); + if (current == null) { + return entries; + } + // skip up to start index + for (int i = 0; i < startIndex; i++) { + current = getPreviousHistoryEntry(current); + } + for (int i = 0; i < count; i++) { + if (current == null) { + break; + } + entries.add(readHistoryEntry(current)); + current = getPreviousHistoryEntry(current); + } + return entries; + } + + /** + * Returns the run before the given one. + * + * @param current current run + * @return previous run + */ + private Resource getPreviousHistoryEntry(Resource current) { + // check if the parent has a sibling before the current node + Resource previous = getPreviousSibling(current); + if (previous != null) { + return previous; + } + // go down till we find an earlier sibling + Resource base = descendToPreviousSiblingInHistory(current.getParent()); + // go back up the folders + return ascendToLastRun(base); + } + + /** + * Gos up the folders to last run. + * + * @param resource current node + * @return last run + */ + private Resource ascendToLastRun(Resource resource) { + if (resource == null) { + return null; + } + Resource last = getLastChild(resource); + if (last == null) { + // stop if there is no child at all + return null; + } + ValueMap values = last.adaptTo(ValueMap.class); + if (JcrResourceConstants.NT_SLING_ORDERED_FOLDER.equals(values.get(JcrConstants.JCR_PRIMARYTYPE, String.class))) { + return ascendToLastRun(last); + } + return last; + } + + /** + * Descends in history till a previous sibling is found. Descending stops at history base level + * + * @param current current resource + * @return previous sibling + */ + private Resource descendToPreviousSiblingInHistory(Resource current) { + if ((current == null) || HISTORY_BASE.equals(current.getPath())) { + return null; + } + Resource previous = getPreviousSibling(current); + if (previous != null) { + return previous; + } + previous = descendToPreviousSiblingInHistory(current.getParent()); + return previous; + } + + /** + * Returns the previous sibling of the given node. + * + * @param resource current node + * @return last sibling or null + */ + private Resource getPreviousSibling(Resource resource) { + Iterator siblings = resource.getParent().listChildren(); + Resource previous = null; + while (siblings.hasNext()) { + Resource sibling = siblings.next(); + if (sibling.getName().equals(resource.getName())) { + break; + } + if (!sibling.getName().equals(AccessControlConstants.REP_POLICY) && !sibling.getName().equals(NAME_INDEX)) { + previous = sibling; + } + } + return previous; + } + + /** + * Returns the latest history entry. + * + * @param base base resource + * @return latest run resource + */ + private Resource getLatestHistoryEntry(Resource base) { + if (base == null) { + return null; + } + return ascendToLastRun(base); + } + + /** + * Returns the last child of the given resource. + * + * @param resource resource + * @return last child + */ + private Resource getLastChild(Resource resource) { + if (resource == null) { + return null; + } + Resource last = null; + Iterator lastIterator = resource.listChildren(); + while (lastIterator.hasNext()) { + Resource candidate = lastIterator.next(); + if (!AccessControlConstants.REP_POLICY.equals(candidate.getName()) && !NAME_INDEX.equals(candidate.getName())) { + last = candidate; + } + } + return last; + } + + /** + * Reads a history entry from JCR. + * + * @param resource history resource + * @return history entry + */ + private HistoryEntry readHistoryEntry(Resource resource) { + ValueMap values = resource.adaptTo(ValueMap.class); + boolean clean = values.get(ATTR_CLEAN, false); + String output = values.get(ATTR_OUTPUT, ""); + String path = values.get(ATTR_PATH, ""); + String userId = values.get(ATTR_USER_ID, ""); + Calendar date = values.get(ATTR_TIME, Calendar.class); + return new HistoryEntryImpl(date.getTime(), output, clean, path, resource.getPath(), userId); + } + + /** + * Creates the folder at the given path if not yet existing. + * + * @param path path + * @param resolver resource resolver + * @param primaryType primary type + * @throws AvsException error creating folder + */ + protected void createPath(String path, ResourceResolver resolver, String primaryType) throws AvsException { + Resource folder = resolver.getResource(path); + if (folder == null) { + String parent = path.substring(0, path.lastIndexOf('/')); + String name = path.substring(path.lastIndexOf('/') + 1); + if (resolver.getResource(parent) == null) { + createPath(parent, resolver, primaryType); + } + Map properties = new HashMap<>(); + properties.put(JcrConstants.JCR_PRIMARYTYPE, primaryType); + try { + resolver.create(resolver.getResource(parent), name, properties); + } catch (PersistenceException e) { + throw new AvsException("Unable to create " + path, e); + } + } + } + + /** + * Generates the node name for a history entry. + * + * @return name + */ + private String generateHistoryNodeName() { + return System.currentTimeMillis() + "" + random.nextInt(100000); + } + + /** + * Purges the history by keeping only entries within the set number of days. + * + * @param resolver resource resolver + * @param daysToKeep number of days to keep + * @throws PersistenceException error deleting node + */ + public void purgeHistory(ResourceResolver resolver, int daysToKeep) throws PersistenceException { + Resource base = resolver.getResource(HISTORY_BASE); + Calendar calendar = new GregorianCalendar(); + calendar.add(Calendar.DAY_OF_MONTH, -daysToKeep); + LOG.debug("Starting purge with limit {}", calendar.getTime()); + deleteRecursive(base.listChildren(), calendar, new int[] {Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH}); + } + + /** + * Deletes the year resources that are too old. + * + * @param resources resources + * @param calendar time limit + * @param fields calendar fields + * @throws PersistenceException error deleting node + */ + private void deleteRecursive(Iterator resources, Calendar calendar, int[] fields) throws PersistenceException { + int currentField = fields[0]; + while (resources.hasNext()) { + Resource resource = resources.next(); + String name = resource.getName(); + // skip extra nodes such as ACLs + if (!StringUtils.isNumeric(name)) { + LOG.debug("Skipping purge of other node: {}", resource.getPath()); + continue; + } + int nodeValue = Integer.parseInt(name); + int limit = calendar.get(currentField); + if (currentField == Calendar.MONTH) { + // months start with 0 but are stored beginning with 1 in CRX + limit++; + } + if (nodeValue > limit) { + LOG.debug("Skipping purge of too young node: {}", resource.getPath()); + } else if (nodeValue == limit) { + LOG.debug("Skipping purge of too young node: {}", resource.getPath()); + // check next level + if (fields.length == 1) { + return; + } + int[] fieldsNew = new int[fields.length - 1]; + System.arraycopy(fields, 1, fieldsNew, 0, fieldsNew.length); + deleteRecursive(resource.listChildren(), calendar, fieldsNew); + } else { + LOG.debug("Purging node: {}", resource.getPath()); + BatchResourceRemover remover = ResourceUtil.getBatchResourceRemover(1000); + remover.delete(resource); + } + } + } + + /** + * Self test of history. Checks if the history node exists. + * + * @param resolver resource resolver + * @throws AvsException check failed + */ + public void selfCheck(ResourceResolver resolver) throws AvsException { + Resource base = resolver.getResource(HISTORY_BASE); + if (base == null) { + throw new AvsException(HISTORY_BASE + " does not exist or is not accessible."); + } + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBean.java b/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBean.java new file mode 100644 index 0000000..87c3d86 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBean.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.jmx; + +import org.osgi.annotation.versioning.ProviderType; + +import com.adobe.granite.jmx.annotation.Description; +import com.adobe.granite.jmx.annotation.Name; + +import de.valtech.avs.api.service.AvsException; + +/** + * JMX service to scan files via virus scan. + * + * @author Roland Gruber + */ +@Description("Anti virus scanner") +@ProviderType +public interface AntiVirusScannerMBean { + + /** + * Scans a text content. + * + * @param content content + * @return result + * @throws AvsException error during scan + */ + @Description("Scans a text content") + String scanContent(@Name("Content") @Description("Text content to be scanned") String content) throws AvsException; + +} diff --git a/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBeanImpl.java b/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBeanImpl.java new file mode 100644 index 0000000..18aef12 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBeanImpl.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.jmx; + +import java.io.ByteArrayInputStream; + +import javax.management.NotCompliantMBeanException; + +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.adobe.granite.jmx.annotation.AnnotatedStandardMBean; + +import de.valtech.avs.api.service.AvsException; +import de.valtech.avs.api.service.AvsService; + +/** + * JMX service to check virus scan. + * + * @author Roland Gruber + */ +@Component(service = AntiVirusScannerMBean.class, immediate = true, + property = {"jmx.objectname=de.valtech:type=AVS", "pattern=/.*"}) +public class AntiVirusScannerMBeanImpl extends AnnotatedStandardMBean implements AntiVirusScannerMBean { + + @Reference + private AvsService scanner; + + /** + * Constructor + * + * @throws NotCompliantMBeanException exception + */ + public AntiVirusScannerMBeanImpl() throws NotCompliantMBeanException { + super(AntiVirusScannerMBean.class); + } + + @Override + public String scanContent(String content) { + ByteArrayInputStream stream = new ByteArrayInputStream(content.getBytes()); + try { + return scanner.scan(stream, "JMX").toString(); + } catch (AvsException e) { + return "Error: " + e.getMessage(); + } + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/mail/AvsNotificationMailer.java b/core/src/main/java/de/valtech/avs/core/mail/AvsNotificationMailer.java new file mode 100644 index 0000000..9bc3b47 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/mail/AvsNotificationMailer.java @@ -0,0 +1,147 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.mail; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMultipart; + +import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.MultiPartEmail; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.day.cq.mailer.MailService; + +import de.valtech.avs.api.service.scanner.ScanResult; + +/** + * Sends out an email notification when a virus was found. + * + * @author Roland Gruber + */ +@Component(service = AvsNotificationMailer.class) +@Designate(ocd = AvsNotificationMailerConfig.class) +public class AvsNotificationMailer { + + private static final Logger LOG = LoggerFactory.getLogger(AvsNotificationMailer.class); + + @Reference + private MailService mailService; + + private AvsNotificationMailerConfig config; + + /** + * Activation + * + * @param config config + */ + @Activate + public void activate(AvsNotificationMailerConfig config) { + this.config = config; + } + + /** + * Sends out the email to the recipients. + * + * @param emails email recipients + * @param fileName file name that was scanned + * @param result scan result + */ + public void sendEmail(List emails, String fileName, ScanResult result) { + VelocityEngine ve = new VelocityEngine(); + ve.init(); + VelocityContext context = new VelocityContext(); + StringWriter writer = new StringWriter(); + if (fileName != null) { + context.put("FILE_NAME", fileName); + } + context.put("SCAN_OUTPUT", result.getOutput()); + ve.evaluate(context, writer, "AvsNotificationMailer", getBodyText()); + String body = writer.toString(); + String subject = getSubject(); + List emailTO = new ArrayList<>(emails); + if (config.additionalRecipients() != null) { + emailTO.addAll(Arrays.asList(config.additionalRecipients())); + } + try { + sendMail(emailTO, subject, body); + } catch (MessagingException | EmailException e) { + LOG.error("Unable to send virus notification", e); + } + } + + /** + * Returns the raw body text incl. wildcards. + * + * @return body + */ + private String getBodyText() { + return config.body(); + } + + /** + * Returns the email subject. + * + * @return subject + */ + private String getSubject() { + return config.subject(); + } + + /** + * Sends out the email. + * + * @param emails email addresses + * @param subject subject + * @param body body text + * @throws MessagingException error sending mail + * @throws EmailException error configuring email + */ + private void sendMail(List emails, String subject, String body) throws MessagingException, EmailException { + MultiPartEmail email = new MultiPartEmail(); + MimeBodyPart messagePart = new MimeBodyPart(); + if (config.isHtml()) { + messagePart.setContent(body, "text/html; charset=utf-8"); + } else { + messagePart.setContent(body, "text/plain; charset=utf-8"); + } + MimeMultipart multipart = new MimeMultipart(); + multipart.addBodyPart(messagePart); + email.setSubject(subject); + for (String toAddress : emails) { + email.addTo(toAddress); + } + email.setFrom(config.from()); + email.setContent(multipart); + mailService.send(email); + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/mail/AvsNotificationMailerConfig.java b/core/src/main/java/de/valtech/avs/core/mail/AvsNotificationMailerConfig.java new file mode 100644 index 0000000..ce0745b --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/mail/AvsNotificationMailerConfig.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.mail; + +import org.osgi.annotation.versioning.ProviderType; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.AttributeType; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +/** + * Configuration for mailer. + * + * @author Roland Gruber + */ +@ObjectClassDefinition(name = "AVS Notification Mailer Configuration") +@ProviderType +public @interface AvsNotificationMailerConfig { + + /** + * Returns the mail subject. + * + * @return subject + */ + @AttributeDefinition(name = "Email subject", description = "Subject for all virus notification emails", + type = AttributeType.STRING) + String subject() default "A virus was found in your file upload"; + + /** + * Returns the mail body. + * + * @return body + */ + @AttributeDefinition(name = "Email body", + description = "Body for all virus notification emails. Wildcards are ${FILE_NAME} for uploaded file name and ${SCAN_OUTPUT} for scan details.", + type = AttributeType.STRING) + String body() default "Dear Sir or Madam,

a virus was detected in your AEM file upload.

File name: ${FILE_NAME}
Scan report: ${SCAN_OUTPUT}"; + + /** + * Returns if email is HTML or TEXT. + * + * @return is HTML + */ + @AttributeDefinition(name = "HTML format", description = "Specifies if email is sent as HTML or plain text.", + type = AttributeType.BOOLEAN) + boolean isHtml() default true; + + /** + * Returns the FROM address. + * + * @return FROM + */ + @AttributeDefinition(name = "Email FROM address", description = "FROM address for notification emails.", + type = AttributeType.STRING) + String from() default "do-not-reply@example.com"; + + /** + * Returns list of additional email recipients when an alert is sent. + * + * @return list + */ + @AttributeDefinition(name = "Additional email recipients", description = "List of email addresses.", + type = AttributeType.STRING) + String[] additionalRecipients(); + +} diff --git a/core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryConfiguration.java b/core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryConfiguration.java new file mode 100644 index 0000000..614f4fb --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.maintenance; + +import org.osgi.annotation.versioning.ProviderType; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.AttributeType; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +/** + * Configuration for purge task. + * + * @author Roland Gruber + */ +@ObjectClassDefinition(name = "AVS Purge history configuration") +@ProviderType +public @interface PurgeHistoryConfiguration { + + @AttributeDefinition(type = AttributeType.INTEGER, name = "Days to keep", + description = "Entries younger than this will not be removed") + int daysToKeep() default 90; + +} diff --git a/core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryTask.java b/core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryTask.java new file mode 100644 index 0000000..ab5ffcb --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryTask.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.maintenance; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.event.jobs.Job; +import org.apache.sling.event.jobs.consumer.JobExecutionContext; +import org.apache.sling.event.jobs.consumer.JobExecutionResult; +import org.apache.sling.event.jobs.consumer.JobExecutor; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.Designate; + +import com.adobe.granite.maintenance.MaintenanceConstants; + +import de.valtech.avs.core.history.HistoryService; +import de.valtech.avs.core.serviceuser.ServiceResourceResolverService; + +/** + * Purges old entries from the history. + * + * @author Roland Gruber + */ +@Component(property = {MaintenanceConstants.PROPERTY_TASK_NAME + "=AVSPurgeHistory", + MaintenanceConstants.PROPERTY_TASK_TITLE + "=AVS Purge History", + JobExecutor.PROPERTY_TOPICS + "=" + MaintenanceConstants.TASK_TOPIC_PREFIX + "AVSPurgeHistory",}) +@Designate(ocd = PurgeHistoryConfiguration.class) +public class PurgeHistoryTask implements JobExecutor { + + private PurgeHistoryConfiguration config; + + @Reference + private ServiceResourceResolverService resolverService; + + @Reference + private HistoryService historyService; + + /** + * Activates the service. + * + * @param config configuration + */ + @Activate + public void activate(PurgeHistoryConfiguration config) { + this.config = config; + } + + @Override + public JobExecutionResult process(Job job, JobExecutionContext context) { + try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) { + historyService.purgeHistory(resolver, config.daysToKeep()); + resolver.commit(); + return context.result().message("Purged AVS history entries").succeeded(); + } catch (LoginException e) { + return context.result().message("Service resolver failed with " + e.getMessage()).failed(); + } catch (PersistenceException e) { + return context.result().message("Purge failed with " + e.getMessage()).failed(); + } + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/model/history/HistoryDataItem.java b/core/src/main/java/de/valtech/avs/core/model/history/HistoryDataItem.java new file mode 100644 index 0000000..45d79b5 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/model/history/HistoryDataItem.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.model.history; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import javax.annotation.PostConstruct; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.SlingObject; + +import de.valtech.avs.api.history.HistoryEntry; + +/** + * Model class for a single history item. + * + * @author Roland Gruber + */ +@Model(adaptables = Resource.class) +public class HistoryDataItem { + + private final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + @SlingObject + private Resource resource; + + protected HistoryEntry history = null; + + @PostConstruct + public void setup() { + history = resource.getValueMap().get(HistoryDataSource.ATTR_HISTORY, HistoryEntry.class); + } + + /** + * Returns the date of the scan. + * + * @return date + */ + public String getDate() { + return format.format(history.getTime()); + } + + /** + * Returns the output of the scan. + * + * @return output + */ + public String getOutput() { + return history.getOutput(); + } + + /** + * Returns the path of the scan. + * + * @return path + */ + public String getPath() { + return history.getPath(); + } + + /** + * Returns the user id that caused the scan. + * + * @return path + */ + public String getUserId() { + return history.getUserId(); + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/model/history/HistoryDataSource.java b/core/src/main/java/de/valtech/avs/core/model/history/HistoryDataSource.java new file mode 100644 index 0000000..d73c0f5 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/model/history/HistoryDataSource.java @@ -0,0 +1,112 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.model.history; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.OSGiService; +import org.apache.sling.models.annotations.injectorspecific.SlingObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.adobe.granite.ui.components.ds.AbstractDataSource; +import com.adobe.granite.ui.components.ds.DataSource; +import com.adobe.granite.ui.components.ds.ValueMapResource; + +import de.valtech.avs.api.history.HistoryEntry; +import de.valtech.avs.core.history.HistoryService; +import de.valtech.avs.core.serviceuser.ServiceResourceResolverService; + +/** + * Datasource model for history page. + * + * @author Roland Gruber + */ +@Model(adaptables = SlingHttpServletRequest.class) +public class HistoryDataSource { + + private static final String ITEM_TYPE = "valtech/avs/tools/history/dataitem"; + public static final String ATTR_HISTORY = "history"; + + private static final Logger LOG = LoggerFactory.getLogger(DataSource.class); + + @SlingObject + private SlingHttpServletRequest request; + + @OSGiService + private HistoryService historyService; + + @OSGiService + private ServiceResourceResolverService serviceResourceResolverService; + + @PostConstruct + public void setup() { + String[] selectors = request.getRequestPathInfo().getSelectors(); + int offset = 0; + int limit = 50; + if (selectors.length > 1) { + offset = Integer.parseInt(selectors[0]); + limit = Integer.parseInt(selectors[1]); + } + request.setAttribute(DataSource.class.getName(), getResourceIterator(offset, limit)); + } + + /** + * Returns the history entries. + * + * @param offset offset where to start reading + * @param limit maximum number of entries to return + * @return entries + */ + private DataSource getResourceIterator(int offset, int limit) { + return new AbstractDataSource() { + + @Override + public Iterator iterator() { + List entries = new ArrayList<>(); + try (ResourceResolver resolver = serviceResourceResolverService.getServiceResourceResolver()) { + List historyEntries = historyService.getHistory(offset, limit + 1, resolver); + for (HistoryEntry historyEntry : historyEntries) { + ValueMap vm = new ValueMapDecorator(new HashMap()); + vm.put(ATTR_HISTORY, historyEntry); + entries.add(new ValueMapResource(request.getResourceResolver(), historyEntry.getRepositoryPath(), + ITEM_TYPE, vm)); + } + } catch (LoginException e) { + LOG.error("Unable to read history entries", e); + } + return entries.iterator(); + } + + }; + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/model/scan/ScanModel.java b/core/src/main/java/de/valtech/avs/core/model/scan/ScanModel.java new file mode 100644 index 0000000..94e55cc --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/model/scan/ScanModel.java @@ -0,0 +1,118 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.model.scan; + +import java.io.IOException; +import java.io.InputStream; + +import javax.annotation.PostConstruct; +import javax.jcr.Session; +import javax.servlet.ServletException; +import javax.servlet.http.Part; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.OSGiService; +import org.apache.sling.models.annotations.injectorspecific.SlingObject; + +import de.valtech.avs.api.service.AvsException; +import de.valtech.avs.api.service.AvsService; +import de.valtech.avs.api.service.scanner.ScanResult; + +/** + * Sling model for scan tool. + * + * @author Roland Gruber + */ +@Model(adaptables = SlingHttpServletRequest.class) +public class ScanModel { + + private static final String FILE_PART = "scanfile"; + + @SlingObject + private SlingHttpServletRequest request; + + @OSGiService + private AvsService avsService; + + private boolean scanDone = false; + + private String resultOutput; + + private boolean scanFailed = false; + + private boolean clean = true; + + @PostConstruct + protected void init() { + try { + Part filePart = request.getPart(FILE_PART); + if (filePart != null) { + InputStream inputStream = filePart.getInputStream(); + if (inputStream != null) { + scanDone = true; + String userId = request.getResourceResolver().adaptTo(Session.class).getUserID(); + ScanResult result = avsService.scan(inputStream, userId); + clean = result.isClean(); + resultOutput = result.getOutput(); + } + } + } catch (IOException | ServletException | AvsException e) { + scanFailed = true; + resultOutput = e.getMessage(); + } + } + + /** + * Returns the scan result text. + * + * @return result + */ + public String getResult() { + return resultOutput; + } + + /** + * Returns if a scan was performed. + * + * @return scan done + */ + public boolean isScanDone() { + return scanDone; + } + + /** + * Returns if the scan failed. + * + * @return failed + */ + public boolean isScanFailed() { + return scanFailed; + } + + /** + * Returns if the file was clean. + * + * @return clean + */ + public boolean isClean() { + return clean; + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java b/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java new file mode 100644 index 0000000..2107638 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java @@ -0,0 +1,111 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.service; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.ResourceResolver; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; + +import de.valtech.avs.api.service.AvsException; +import de.valtech.avs.api.service.AvsService; +import de.valtech.avs.api.service.scanner.AvsScannerEnine; +import de.valtech.avs.api.service.scanner.ScanResult; +import de.valtech.avs.core.history.HistoryService; +import de.valtech.avs.core.serviceuser.ServiceResourceResolverService; + +/** + * AVS scan service. + * + * @author Roland Gruber + */ +@Component(service = AvsService.class) +public class AvsServiceImpl implements AvsService { + + @Reference + private HistoryService historyService; + + @Reference + private ServiceResourceResolverService serviceResourceResolverService; + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, bind = "bindEngine", + unbind = "unbindEngine") + private List engines = new ArrayList<>(); + + /** + * Adds a scanner engine. + * + * @param engine engine + */ + protected synchronized void bindEngine(AvsScannerEnine engine) { + engines.add(engine); + } + + /** + * Removes a scanner engine. + * + * @param engine engine + */ + protected synchronized void unbindEngine(AvsScannerEnine engine) { + engines.remove(engine); + } + + @Override + public ScanResult scan(InputStream content, String userId) throws AvsException { + return scan(content, userId, StringUtils.EMPTY); + } + + @Override + public ScanResult scan(InputStream content, String userId, String path) throws AvsException { + if (engines.isEmpty()) { + throw new AvsException("No scanning engines available"); + } + if (content == null) { + // skip empty content + return new ScanResult(null, true); + } + ScanResult result = null; + try (ResourceResolver resolver = serviceResourceResolverService.getServiceResourceResolver()) { + for (AvsScannerEnine engine : engines) { + result = engine.scan(content); + result.setPath(path); + result.setUserId(userId); + if (!result.isClean()) { + historyService.createHistoryEntry(resolver, result); + } + } + } catch (LoginException e) { + throw new AvsException("Unable to access service resolver", e); + } + return result; + } + + @Override + public boolean hasActiveScanEngines() { + return !engines.isEmpty(); + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerConfig.java b/core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerConfig.java new file mode 100644 index 0000000..e49926e --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerConfig.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.service.scanner; + +import org.osgi.annotation.versioning.ProviderType; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.AttributeType; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +/** + * Clam scanner configuration. + * + * @author Roland Gruber + */ +@ObjectClassDefinition(name = "AVS ClamAV configuration") +@ProviderType +public @interface ClamScannerConfig { + + /** + * Returns the scan command. + * + * @return command + */ + @AttributeDefinition(name = "Scan command", + description = "Command to scan a single file. The file name will be added at the end of the command.", + type = AttributeType.STRING) + String command(); + +} diff --git a/core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerEngine.java b/core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerEngine.java new file mode 100644 index 0000000..d75999d --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerEngine.java @@ -0,0 +1,107 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.service.scanner; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +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.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.valtech.avs.api.service.AvsException; +import de.valtech.avs.api.service.scanner.AvsScannerEnine; +import de.valtech.avs.api.service.scanner.ScanResult; + +/** + * AVS scan engine using ClamAV. + * + * @author Roland Gruber + */ +@Component(service = AvsScannerEnine.class, configurationPolicy = ConfigurationPolicy.REQUIRE, immediate = true) +@Designate(ocd = ClamScannerConfig.class) +public class ClamScannerEngine implements AvsScannerEnine { + + private static final Logger LOG = LoggerFactory.getLogger(ClamScannerEngine.class); + + private ClamScannerConfig config; + + /** + * Setup service + * + * @param config configuration + */ + @Activate + public void activate(ClamScannerConfig config) { + this.config = config; + } + + @Override + public ScanResult scan(InputStream content) throws AvsException { + try { + File tempFile = createTemporaryFile(content); + Runtime runtime = Runtime.getRuntime(); + String command = config.command() + " " + tempFile.getPath(); + Process process = runtime.exec(command); + InputStream in = process.getInputStream(); + InputStream err = process.getErrorStream(); + int returnCode = process.waitFor(); + String output = IOUtils.toString(in, Charset.forName(StandardCharsets.UTF_8.name())); + String error = IOUtils.toString(err, Charset.forName(StandardCharsets.UTF_8.name())); + in.close(); + err.close(); + output = output.replace(tempFile.getPath(), "SCANNED_FILE"); + Files.delete(Paths.get(tempFile.getPath())); + if ((returnCode == 0) && StringUtils.isBlank(error)) { + return new ScanResult(output, true); + } + return new ScanResult(output + "\n" + error, false); + } catch (IOException | InterruptedException e) { + LOG.error("Error during scanning", e); + Thread.currentThread().interrupt(); + throw new AvsException("Error during scanning", e); + } + } + + /** + * Creates a temporary file with the given content. + * + * @param content content + * @return file handle + * @throws IOException error creating file + */ + private File createTemporaryFile(InputStream content) throws IOException { + File file = File.createTempFile("valtech-avs", ".tmp"); + Files.copy(content, file.toPath(), StandardCopyOption.REPLACE_EXISTING); + content.close(); + return file; + } + +} diff --git a/core/src/main/java/de/valtech/avs/core/serviceuser/ServiceResourceResolverService.java b/core/src/main/java/de/valtech/avs/core/serviceuser/ServiceResourceResolverService.java new file mode 100644 index 0000000..99975fb --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/serviceuser/ServiceResourceResolverService.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.serviceuser; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * Provides the service resource resolver. + * + * @author Roland Gruber + */ +@Component(service = ServiceResourceResolverService.class) +public class ServiceResourceResolverService { + + private static final String SUBSERVICE_AVS = "avs"; + + @Reference + ResourceResolverFactory resolverFactory; + + /** + * Returns a resource resolver of the AVS service user. + * + * @return service resource resolver + * @throws LoginException error opening resource resolver + */ + public ResourceResolver getServiceResourceResolver() throws LoginException { + final Map authenticationInfo = new HashMap<>(); + authenticationInfo.put(ResourceResolverFactory.SUBSERVICE, SUBSERVICE_AVS); + return resolverFactory.getServiceResourceResolver(authenticationInfo); + } + +} diff --git a/core/src/test/java/de/valtech/avs/core/filter/AvsPostFilterTest.java b/core/src/test/java/de/valtech/avs/core/filter/AvsPostFilterTest.java new file mode 100644 index 0000000..550b042 --- /dev/null +++ b/core/src/test/java/de/valtech/avs/core/filter/AvsPostFilterTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.avs.core.filter; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Tests AvsPostFilter + * + * @author Roland Gruber + * + */ +@RunWith(MockitoJUnitRunner.class) +public class AvsPostFilterTest { + + @Mock + private SlingHttpServletRequest request; + + @Mock + private AvsPostFilterConfig config; + + @InjectMocks + private AvsPostFilter filter; + + @Before + public void setup() { + when(request.getRequestURI()).thenReturn("/some/sample/request.html"); + } + + @Test + public void isUrlToIgnore() { + filter.activate(config); + + assertFalse(filter.isUrlToIgnore(request)); + } + + @Test + public void isUrlToIgnore_excludeFilter_match() { + when(config.excludePatterns()).thenReturn(new String[] {"/some/.*"}); + filter.activate(config); + + assertTrue(filter.isUrlToIgnore(request)); + } + + @Test + public void isUrlToIgnore_excludeFilter_nomatch() { + when(config.excludePatterns()).thenReturn(new String[] {"/bla"}); + filter.activate(config); + + assertFalse(filter.isUrlToIgnore(request)); + } + + @Test + public void isUrlToIgnore_includeFilter_match() { + when(config.includePatterns()).thenReturn(new String[] {"/some/.*"}); + filter.activate(config); + + assertFalse(filter.isUrlToIgnore(request)); + } + + @Test + public void isUrlToIgnore_includeFilter_nomatch() { + when(config.includePatterns()).thenReturn(new String[] {"/bla"}); + filter.activate(config); + + assertTrue(filter.isUrlToIgnore(request)); + } + +} diff --git a/docs/developers.md b/docs/developers.md new file mode 100644 index 0000000..07f141a --- /dev/null +++ b/docs/developers.md @@ -0,0 +1,35 @@ +# AEM Server Setup + +By default AEM is expected to listen on localhost on port 5702. This setting can be overridden by adding parameters: +* -Daem.port=4502 +* -Daem.host=localhost +* -Daem.publish.port=4503 +* -Daem.publish.host=localhost + +You need AEM 6.4 with service pack 2 or AEM 6.5. + +# Build and Deploy + +To build and deploy run this in the base (aem-virus-scan) or ui.apps/examples folder: + +```bash +mvn clean install -PautoInstallPackage +``` + +In case you want to deploy core only you can use this command in core folder: + +```bash +mvn clean install -PautoInstallBundle +``` + +To build and deploy on publish instance run this in the base (aem-virus-scan) or ui.apps/examples folder: + +```bash +mvn clean install -PautoInstallPackagePublish +``` + + +# Code Formatting + +Please use our standard code formatters for [Eclipse](formatter/eclipse-avs.xml) +and [IntelliJ](formatter/intellij-avs.xml). diff --git a/docs/formatter/eclipse-avs.xml b/docs/formatter/eclipse-avs.xml new file mode 100644 index 0000000..ff0ca7b --- /dev/null +++ b/docs/formatter/eclipse-avs.xml @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/formatter/intellij-avs.xml b/docs/formatter/intellij-avs.xml new file mode 100644 index 0000000..49df1d8 --- /dev/null +++ b/docs/formatter/intellij-avs.xml @@ -0,0 +1,476 @@ + + + + + + diff --git a/docs/images/healthcheck.png b/docs/images/healthcheck.png new file mode 100644 index 0000000..f3cae88 Binary files /dev/null and b/docs/images/healthcheck.png differ diff --git a/docs/images/history.png b/docs/images/history.png new file mode 100644 index 0000000..9c4bd48 Binary files /dev/null and b/docs/images/history.png differ diff --git a/docs/images/manualScan.png b/docs/images/manualScan.png new file mode 100644 index 0000000..6ccc598 Binary files /dev/null and b/docs/images/manualScan.png differ diff --git a/docs/images/tools.png b/docs/images/tools.png new file mode 100644 index 0000000..ba7b7b6 Binary files /dev/null and b/docs/images/tools.png differ diff --git a/examples/pom.xml b/examples/pom.xml new file mode 100644 index 0000000..d0e9945 --- /dev/null +++ b/examples/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + de.valtech.avs + avs + 0.9.0 + + + avs.examples + content-package + AVS - Examples + Examples package for AVS + + + src/main/content/jcr_root + + + + ${basedir}/src/main/content/jcr_root + + + **/.vlt + **/.vltignore + **/.gitignore + **/*.iml + **/.classpath + **/.project + **/.settings + **/.DS_Store + **/target/** + **/pom.xml + + + + src/main/content/META-INF/vault/definition + ../vault-work/META-INF/vault/definition + + + + + maven-resources-plugin + + true + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + + src/main/content/META-INF/vault/filter.xml + true + true + Valtech + + + de.valtech.avs + ui.apps.structure + + + mixed + + + + com.day.jcr.vault + content-package-maven-plugin + + + + org.apache.sling + htl-maven-plugin + + + + + + + de.valtech.avs + ui.apps.structure + ${project.version} + zip + + + + diff --git a/examples/src/main/content/META-INF/vault/definition/.content.xml b/examples/src/main/content/META-INF/vault/definition/.content.xml new file mode 100644 index 0000000..2edc22d --- /dev/null +++ b/examples/src/main/content/META-INF/vault/definition/.content.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/examples/src/main/content/META-INF/vault/definition/thumbnail.png b/examples/src/main/content/META-INF/vault/definition/thumbnail.png new file mode 100644 index 0000000..c4548ee Binary files /dev/null and b/examples/src/main/content/META-INF/vault/definition/thumbnail.png differ diff --git a/examples/src/main/content/META-INF/vault/filter.xml b/examples/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 0000000..c968477 --- /dev/null +++ b/examples/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,4 @@ + + + + diff --git a/examples/src/main/content/jcr_root/.gitignore b/examples/src/main/content/jcr_root/.gitignore new file mode 100644 index 0000000..3385916 --- /dev/null +++ b/examples/src/main/content/jcr_root/.gitignore @@ -0,0 +1 @@ +/META-INF/ diff --git a/examples/src/main/content/jcr_root/apps/valtech/.content.xml b/examples/src/main/content/jcr_root/apps/valtech/.content.xml new file mode 100644 index 0000000..84ee745 --- /dev/null +++ b/examples/src/main/content/jcr_root/apps/valtech/.content.xml @@ -0,0 +1,4 @@ + + diff --git a/examples/src/main/content/jcr_root/apps/valtech/avs-examples/.content.xml b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/.content.xml new file mode 100644 index 0000000..84ee745 --- /dev/null +++ b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/.content.xml @@ -0,0 +1,4 @@ + + diff --git a/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.filter.AvsPostFilter.xml b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.filter.AvsPostFilter.xml new file mode 100644 index 0000000..20248e8 --- /dev/null +++ b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.filter.AvsPostFilter.xml @@ -0,0 +1,5 @@ + + diff --git a/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.mail.AvsNotificationMailer.xml b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.mail.AvsNotificationMailer.xml new file mode 100644 index 0000000..503f4a1 --- /dev/null +++ b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.mail.AvsNotificationMailer.xml @@ -0,0 +1,8 @@ + + diff --git a/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.maintenance.PurgeHistoryTask.xml b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.maintenance.PurgeHistoryTask.xml new file mode 100644 index 0000000..005c729 --- /dev/null +++ b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.maintenance.PurgeHistoryTask.xml @@ -0,0 +1,4 @@ + + diff --git a/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.service.scanner.ClamScannerEngine.xml b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.service.scanner.ClamScannerEngine.xml new file mode 100644 index 0000000..fb06017 --- /dev/null +++ b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.service.scanner.ClamScannerEngine.xml @@ -0,0 +1,4 @@ + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d3c9bff --- /dev/null +++ b/pom.xml @@ -0,0 +1,657 @@ + + + 4.0.0 + de.valtech.avs + avs + pom + 0.9.0 + AVS + AEM Virus Scan + https://github.com/valtech/aem-virus-scan + + + api + core + ui.apps.structure + ui.apps + examples + + + + localhost + 4502 + localhost + 4503 + admin + admin + admin + admin + + UTF-8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + true + + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-maven + + enforce + + + + + [3.2.5,) + + + Project must be compiled with Java 8 or higher + 1.8.0 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + -parameters + + + + + org.apache.maven.plugins + maven-idea-plugin + 2.2.1 + + 1.8 + true + true + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.10 + + true + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.3 + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.0.0 + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.2 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.7 + + + org.apache.maven.plugins + maven-site-plugin + 3.3 + + + org.apache.maven.plugins + maven-install-plugin + 2.5.2 + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.20 + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + external.atlassian.jgitflow + jgitflow-maven-plugin + 1.0-m5.1 + + + com.jcraft + jsch + 0.1.54 + + + + true + true + true + true + rc + + + + org.apache.sling + maven-sling-plugin + 2.3.8 + + http://${aem.host}:${aem.port}/system/console + WebConsole + + + + org.apache.sling + htl-maven-plugin + 1.2.4-1.4.0 + + true + + + + + validate + + + + + + org.apache.felix + osgicheck-maven-plugin + 0.1.0 + + + check-bundle + + check + + + + + + biz.aQute.bnd + bnd-baseline-maven-plugin + 4.1.0 + + + baseline + + baseline + + + + + + com.day.jcr.vault + content-package-maven-plugin + 1.0.2 + + http://${aem.host}:${aem.port}/crx/packmgr/service.jsp + true + true + ${vault.user} + ${vault.password} + + + + org.apache.jackrabbit + filevault-package-maven-plugin + 1.1.0 + true + + + org.apache.felix + maven-bundle-plugin + 3.5.0 + true + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4.1 + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.0 + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + [1.0.0,) + + enforce + + + + + + + + + + org.apache.maven.plugins + + + maven-dependency-plugin + + + [2.2,) + + + copy-dependencies + unpack + + + + + + + + + + org.codehaus.mojo + + + build-helper-maven-plugin + + + [1.5,) + + + + reserve-network-port + + + + + + + + + + + + + org.owasp + dependency-check-maven + 5.2.1 + + 0 + true + true + + + + + check + + + + + + + + + + + autoInstallBundle + + false + + + + + + org.apache.sling + maven-sling-plugin + + + install-bundle + + install + + + + + + + + + + + autoInstallPackage + + false + + + + + + com.day.jcr.vault + content-package-maven-plugin + + + install-package + + install + + + http://${aem.host}:${aem.port}/crx/packmgr/service.jsp + + + + + + + + + + + autoInstallPackagePublish + + false + + + + + + com.day.jcr.vault + content-package-maven-plugin + + + install-package-publish + + install + + + http://${aem.publish.host}:${aem.publish.port}/crx/packmgr/service.jsp + + + + + + + + + + + + release + + + performRelease + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + + + + + + + org.apache.velocity + velocity-engine-core + 2.2 + + + javax.mail + javax.mail-api + 1.5.6 + provided + + + org.osgi + osgi.core + 6.0.0 + provided + + + org.osgi + osgi.cmpn + 6.0.0 + provided + + + org.osgi + osgi.annotation + 6.0.1 + provided + + + org.slf4j + slf4j-api + 1.7.25 + provided + + + com.adobe.aem + uber-jar + 6.4.3 + apis + provided + + + org.apache.sling + org.apache.sling.models.api + 1.3.6 + provided + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + javax.servlet.jsp + jsp-api + 2.1 + provided + + + javax.jcr + jcr + 2.0 + provided + + + javax.annotation + javax.annotation-api + 1.3.2 + provided + + + + org.apache.commons + commons-lang3 + 3.6 + + + junit + junit + 4.12 + test + + + org.slf4j + slf4j-simple + 1.7.21 + test + + + org.mockito + mockito-core + 2.28.2 + test + + + junit-addons + junit-addons + 1.4 + test + + + + com.google.code.gson + gson + 2.8.2 + + + com.google.code.findbugs + jsr305 + 2.0.1 + compile + + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Roland Gruber + roland.gruber@valtech.com + Valtech GmbH + https://www.valtech.de/ + + + + + scm:git:https://github.com/valtech/aem-virus-scan.git + scm:git:git@github.com:valtech/aem-virus-scan.git + https://github.com/valtech/aem-virus-scan + + + + Github + https://github.com/valtech/aem-virus-scan/issues + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + adobe + Adobe Public Repository + http://repo.adobe.com/nexus/content/groups/public/ + default + + + central + http://repo1.maven.org/maven2 + default + + false + + + + + + adobe + Adobe Public Repository + http://repo.adobe.com/nexus/content/groups/public/ + default + + + + diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..8c38a1f --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,16 @@ +sonar.projectKey=avs +sonar.projectName=AEM Virus Scan +sonar.projectVersion=1.0.0-SNAPSHOT + +# Encoding of the source code. Default is default system encoding +sonar.sourceEncoding=UTF-8 + +sonar.links.homepage=https://github.com/valtech/aem-virus-scan +sonar.links.ci=https://travis-ci.org/valtech/aem-virus-scan +sonar.links.scm=https://github.com/valtech/aem-virus-scan +sonar.links.issue=https://github.com/valtech/aem-virus-scan/issues + +sonar.sources=core/src/main +sonar.tests=core/src/test +sonar.java.binaries=core/target/classes +sonar.coverage.jacoco.xmlReportPaths=core/target/site/jacoco/jacoco.xml diff --git a/ui.apps.structure/pom.xml b/ui.apps.structure/pom.xml new file mode 100644 index 0000000..8f29e43 --- /dev/null +++ b/ui.apps.structure/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + + de.valtech.avs + avs + 0.9.0 + + + ui.apps.structure + content-package + AVS - Repository Structure Package + + Empty package that defines the structure of the Adobe Experience Manager repository the Code packages in this project deploy into. + Any roots in the Code packages of this project should have their parent enumerated in the Filters list below. + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + + none + + + + /apps + + + /apps/sling + + + /apps/cq + + + /apps/dam + + + /apps/wcm + + + /apps/msm + + + /apps/settings + + + /home/users + + + /home/users/system + + + /var/workflow/models + + + /conf + + + /conf/global + + + /conf/global/settings + + + /conf/global/settings/workflow + + + /conf/global/settings/workflow/models + + + + + + + diff --git a/ui.apps.structure/src/main/.gitignore b/ui.apps.structure/src/main/.gitignore new file mode 100644 index 0000000..cb6eb2c --- /dev/null +++ b/ui.apps.structure/src/main/.gitignore @@ -0,0 +1 @@ +/resources/ diff --git a/ui.apps/pom.xml b/ui.apps/pom.xml new file mode 100644 index 0000000..a612983 --- /dev/null +++ b/ui.apps/pom.xml @@ -0,0 +1,135 @@ + + + 4.0.0 + + + de.valtech.avs + avs + 0.9.0 + + + avs.ui.apps + content-package + AVS - UI apps + UI apps package for AVS + + + src/main/content/jcr_root + + + ${basedir}/src/main/content/jcr_root + + **/.vlt + **/.vltignore + **/.gitignore + **/*.iml + **/.classpath + **/.project + **/.settings + **/.DS_Store + **/target/** + **/pom.xml + + + + src/main/content/META-INF/vault/definition + ../vault-work/META-INF/vault/definition + + + + + maven-resources-plugin + + true + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + add-sling-install-hook + generate-resources + + copy + + + + + org.apache.sling + org.apache.sling.installer.provider.installhook + 1.0.4 + + + ${project.build.directory}/vault-work/META-INF/vault/hooks + + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + + true + src/main/content/META-INF/vault/filter.xml + true + true + Valtech + + merge_preserve + /apps/valtech/avs/(config|install)/.* + 7 + + + + de.valtech.avs + ui.apps.structure + + + + + de.valtech.avs + avs.api + /apps/valtech/avs/install + + + de.valtech.avs + avs.core + /apps/valtech/avs/install + + + + + + com.day.jcr.vault + content-package-maven-plugin + + + + org.apache.sling + htl-maven-plugin + + + + + + + de.valtech.avs + avs.core + ${project.version} + + + de.valtech.avs + avs.api + ${project.version} + + + de.valtech.avs + ui.apps.structure + ${project.version} + zip + + + diff --git a/ui.apps/src/main/content/META-INF/vault/definition/.content.xml b/ui.apps/src/main/content/META-INF/vault/definition/.content.xml new file mode 100644 index 0000000..2edc22d --- /dev/null +++ b/ui.apps/src/main/content/META-INF/vault/definition/.content.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/ui.apps/src/main/content/META-INF/vault/definition/thumbnail.png b/ui.apps/src/main/content/META-INF/vault/definition/thumbnail.png new file mode 100644 index 0000000..c4548ee Binary files /dev/null and b/ui.apps/src/main/content/META-INF/vault/definition/thumbnail.png differ diff --git a/ui.apps/src/main/content/META-INF/vault/filter.xml b/ui.apps/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 0000000..7591591 --- /dev/null +++ b/ui.apps/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui.apps/src/main/content/jcr_root/.gitignore b/ui.apps/src/main/content/jcr_root/.gitignore new file mode 100644 index 0000000..3385916 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/.gitignore @@ -0,0 +1 @@ +/META-INF/ diff --git a/ui.apps/src/main/content/jcr_root/apps/.content.xml b/ui.apps/src/main/content/jcr_root/apps/.content.xml new file mode 100644 index 0000000..84ee745 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/.content.xml @@ -0,0 +1,4 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/.content.xml b/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/.content.xml new file mode 100644 index 0000000..ab2ea4f --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/.content.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/history/.content.xml b/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/history/.content.xml new file mode 100644 index 0000000..c6f8e46 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/history/.content.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/scan/.content.xml b/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/scan/.content.xml new file mode 100644 index 0000000..08c9cb1 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/scan/.content.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/settings/.content.xml b/ui.apps/src/main/content/jcr_root/apps/settings/.content.xml new file mode 100644 index 0000000..dfac52e --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/settings/.content.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/settings/granite/.content.xml b/ui.apps/src/main/content/jcr_root/apps/settings/granite/.content.xml new file mode 100644 index 0000000..3d0c31a --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/settings/granite/.content.xml @@ -0,0 +1,3 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/.content.xml b/ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/.content.xml new file mode 100644 index 0000000..9ef5e22 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/.content.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/hc/.content.xml b/ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/hc/.content.xml new file mode 100644 index 0000000..4210640 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/hc/.content.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/hc/avs/.content.xml b/ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/hc/avs/.content.xml new file mode 100644 index 0000000..a334556 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/hc/avs/.content.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/.content.xml new file mode 100644 index 0000000..84ee745 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/.content.xml @@ -0,0 +1,4 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/.content.xml new file mode 100644 index 0000000..84a6493 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/css.txt b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/css.txt new file mode 100644 index 0000000..f4df679 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/css.txt @@ -0,0 +1,3 @@ +#base=css + +avs.css diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/css/avs.css b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/css/avs.css new file mode 100644 index 0000000..f5cb9b0 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/css/avs.css @@ -0,0 +1,20 @@ + +.avs-scantool { + margin: 25px; + font-size: 1rem; +} + +.avs-upload-table td { + padding: 5px; +} + +.avs-icon-color-ok { + color: #00AD31; +} +.avs-icon-color-fail { + color: #FA0A0A; +} + +.avs-scanresult-title { + font-size: 1.5rem; +} \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/history/title/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/history/title/.content.xml new file mode 100644 index 0000000..4a8780f --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/history/title/.content.xml @@ -0,0 +1,5 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/history/title/title.html b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/history/title/title.html new file mode 100644 index 0000000..b558126 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/history/title/title.html @@ -0,0 +1 @@ +AEM Virus Scan - History diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/content/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/content/.content.xml new file mode 100644 index 0000000..4a8780f --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/content/.content.xml @@ -0,0 +1,5 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/content/content.html b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/content/content.html new file mode 100644 index 0000000..3c3c7e5 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/content/content.html @@ -0,0 +1,40 @@ + +
+
+ + + + + + + + + +
File
+
+
+ + + +
+

+
+ + The scanner reported no virus +
+
+ + The scanner reported a virus +
+
+ + The scanner reported an error +
+ +
+
Result: +
${model.result}
+
+
+ +
diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/title/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/title/.content.xml new file mode 100644 index 0000000..4a8780f --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/title/.content.xml @@ -0,0 +1,5 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/title/title.html b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/title/title.html new file mode 100644 index 0000000..061f55c --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/title/title.html @@ -0,0 +1 @@ +AEM Virus Scan - Scan a File diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/de.valtech.avs.core.maintenance.PurgeHistoryTask.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/de.valtech.avs.core.maintenance.PurgeHistoryTask.xml new file mode 100644 index 0000000..db8d13a --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/de.valtech.avs.core.maintenance.PurgeHistoryTask.xml @@ -0,0 +1,5 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/org.apache.sling.hc.core.impl.CompositeHealthCheck-avs.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/org.apache.sling.hc.core.impl.CompositeHealthCheck-avs.xml new file mode 100644 index 0000000..9faa728 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/org.apache.sling.hc.core.impl.CompositeHealthCheck-avs.xml @@ -0,0 +1,8 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-valtechAvsUser.config b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-valtechAvsUser.config new file mode 100644 index 0000000..5d9a74c --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-valtechAvsUser.config @@ -0,0 +1 @@ +user.mapping=["de.valtech.avs.core:avs\=avs-service"] \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/.content.xml new file mode 100644 index 0000000..56d6dc7 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/.content.xml @@ -0,0 +1,5 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/dataitem/dataitem.html b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/dataitem/dataitem.html new file mode 100644 index 0000000..e23a355 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/dataitem/dataitem.html @@ -0,0 +1,15 @@ + + + + ${dataItem.date} + + + ${dataItem.userId} + + + ${dataItem.path} + + + ${dataItem.output} + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/datasource/datasource.html b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/datasource/datasource.html new file mode 100644 index 0000000..a222434 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/datasource/datasource.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/page/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/page/.content.xml new file mode 100644 index 0000000..c96053b --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/page/.content.xml @@ -0,0 +1,73 @@ + + + + + + + + <views jcr:primaryType="nt:unstructured"> + + <list + jcr:primaryType="nt:unstructured" + layoutId="list" + sling:resourceType="granite/ui/components/coral/foundation/table" + limit="50" + src="/apps/valtech/avs/tools/history/page/jcr:content/views/list{.offset,limit}.html?wcmmode=disabled" + path="${requestPathInfo.suffix}" + sortMode="remote" + stateId="shell.collectionpage" + modeGroup="avs-history-entries" + granite:rel="avs-history-entries" + > + <columns jcr:primaryType="nt:unstructured"> + <date + jcr:primaryType="nt:unstructured" + jcr:title="Date" + /> + <user + jcr:primaryType="nt:unstructured" + jcr:title="User" + /> + <path + jcr:primaryType="nt:unstructured" + jcr:title="Path" + /> + <output + jcr:primaryType="nt:unstructured" + jcr:title="Output" + /> + </columns> + <datasource + jcr:primaryType="nt:unstructured" + path="${requestPathInfo.suffix}" + sling:resourceType="valtech/avs/tools/history/datasource" + itemResourceType="valtech/avs/tools/history/dataitem" + limit="${empty requestPathInfo.selectors[1] ? "50" : requestPathInfo.selectors[1]}" + offset="${requestPathInfo.selectors[0]}" + /> + </list> + + </views> + </jcr:content> +</jcr:root> diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/scan/page/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/scan/page/.content.xml new file mode 100644 index 0000000..8d00fe4 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/scan/page/.content.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" + xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" + jcr:primaryType="cq:Page"> + <jcr:content + jcr:mixinTypes="[sling:VanityPath]" + jcr:primaryType="nt:unstructured" + jcr:title="AEM Virus Scan - Scan a File" + sling:redirect="{Boolean}false" + sling:resourceType="granite/ui/components/shell/page" + modeGroup="avs-scan" + targetCollection=".avs-scan" + consoleId="avs-scan" + contentPath="${requestPathInfo.suffix}" + > + <head jcr:primaryType="nt:unstructured"> + <clientlibs + jcr:primaryType="nt:unstructured" + sling:resourceType="granite/ui/components/foundation/includeclientlibs" + categories="[coralui3,granite.ui.coral.foundation,avs.editor]"/> + </head> + <title + jcr:primaryType="nt:unstructured" + sling:resourceType="valtech/avs/components/content/scan/title" + /> + <content + jcr:primaryType="nt:unstructured" + sling:resourceType="valtech/avs/components/content/scan/content" + > + </content> + </jcr:content> +</jcr:root> diff --git a/ui.apps/src/main/content/jcr_root/conf/global/settings/.content.xml b/ui.apps/src/main/content/jcr_root/conf/global/settings/.content.xml new file mode 100755 index 0000000..457768d --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/conf/global/settings/.content.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="sling:Folder" + jcr:mixinTypes="rep:AccessControllable" + > +</jcr:root> diff --git a/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/.content.xml b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/.content.xml new file mode 100755 index 0000000..d575b89 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/.content.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="nt:folder"> +</jcr:root> diff --git a/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/.content.xml b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/.content.xml new file mode 100755 index 0000000..d575b89 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/.content.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="nt:folder"> +</jcr:root> diff --git a/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/.content.xml b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/.content.xml new file mode 100755 index 0000000..9722c01 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/.content.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="sling:OrderedFolder" + sling:configCollectionInherit="true" + sling:configPropertyInherit="true"> +</jcr:root> diff --git a/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/granite_weekly/.content.xml b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/granite_weekly/.content.xml new file mode 100755 index 0000000..9722c01 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/granite_weekly/.content.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="sling:OrderedFolder" + sling:configCollectionInherit="true" + sling:configPropertyInherit="true"> +</jcr:root> diff --git a/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/granite_weekly/avs_history_purge/.content.xml b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/granite_weekly/avs_history_purge/.content.xml new file mode 100644 index 0000000..5dd8518 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/granite_weekly/avs_history_purge/.content.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" + jcr:primaryType="nt:unstructured" + name="/libs/granite/operations/content/maintenanceWindow.html/apps/settings/granite/operations/maintenance/granite_weekly" + sling:resourceType="granite/operations/components/maintenance/task" + granite.maintenance.name="AVSPurgeHistory" +/> \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/home/users/system/avs/.content.xml b/ui.apps/src/main/content/jcr_root/home/users/system/avs/.content.xml new file mode 100644 index 0000000..5ea6a38 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/home/users/system/avs/.content.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" + jcr:primaryType="rep:AuthorizableFolder"/> \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/home/users/system/avs/avs-service/.content.xml b/ui.apps/src/main/content/jcr_root/home/users/system/avs/avs-service/.content.xml new file mode 100644 index 0000000..a32d091 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/home/users/system/avs/avs-service/.content.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" + jcr:primaryType="rep:SystemUser" + jcr:uuid="49a21fc5-4453-34cb-b150-747948b25435" + rep:authorizableId="avs-service" + rep:principalName="avs-service" +/> diff --git a/ui.apps/src/main/content/jcr_root/var/avs/.content.xml b/ui.apps/src/main/content/jcr_root/var/avs/.content.xml new file mode 100644 index 0000000..4210640 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/var/avs/.content.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" + jcr:primaryType="sling:OrderedFolder" +/> \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/var/avs/_rep_policy.xml b/ui.apps/src/main/content/jcr_root/var/avs/_rep_policy.xml new file mode 100644 index 0000000..d39913a --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/var/avs/_rep_policy.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" + jcr:primaryType="rep:ACL"> + <allow + jcr:primaryType="rep:GrantACE" + rep:principalName="avs-service" + rep:privileges="{Name}[jcr:all]"/> +</jcr:root> \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/var/avs/history/.content.xml b/ui.apps/src/main/content/jcr_root/var/avs/history/.content.xml new file mode 100644 index 0000000..4210640 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/var/avs/history/.content.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" + jcr:primaryType="sling:OrderedFolder" +/> \ No newline at end of file