From 797b437ba080263802d304b19578551cf880976e Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Tue, 7 Jul 2020 14:13:20 +0200 Subject: [PATCH 01/25] initial structure --- .gitignore | 78 +++ .travis.yml | 19 + HISTORY | 4 + api/pom.xml | 116 ++++ .../valtech/avs/api/service/AvsException.java | 49 ++ .../valtech/avs/api/service/package-info.java | 29 + api/src/main/javadoc/overview.html | 12 + core/pom.xml | 144 ++++ examples/pom.xml | 89 +++ .../META-INF/vault/definition/thumbnail.png | Bin 0 -> 1874 bytes .../main/content/META-INF/vault/filter.xml | 4 + examples/src/main/content/jcr_root/.gitignore | 1 + .../jcr_root/apps/valtech/.content.xml | 4 + .../apps/valtech/avs-examples/.content.xml | 4 + .../avs-examples/components/.content.xml | 4 + pom.xml | 646 ++++++++++++++++++ sonar-project.properties | 16 + ui.apps.structure/pom.xml | 82 +++ ui.apps.structure/src/main/.gitignore | 1 + ui.apps/pom.xml | 137 ++++ ui.apps/src/main/content/.gitignore | 1 + .../META-INF/vault/definition/thumbnail.png | Bin 0 -> 1874 bytes .../main/content/META-INF/vault/filter.xml | 4 + ui.apps/src/main/content/jcr_root/.gitignore | 1 + .../main/content/jcr_root/apps/.content.xml | 4 + .../jcr_root/apps/valtech/.content.xml | 4 + 26 files changed, 1453 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 HISTORY create mode 100644 api/pom.xml create mode 100644 api/src/main/java/de/valtech/avs/api/service/AvsException.java create mode 100644 api/src/main/java/de/valtech/avs/api/service/package-info.java create mode 100644 api/src/main/javadoc/overview.html create mode 100644 core/pom.xml create mode 100644 examples/pom.xml create mode 100644 examples/src/main/content/META-INF/vault/definition/thumbnail.png create mode 100644 examples/src/main/content/META-INF/vault/filter.xml create mode 100644 examples/src/main/content/jcr_root/.gitignore create mode 100644 examples/src/main/content/jcr_root/apps/valtech/.content.xml create mode 100644 examples/src/main/content/jcr_root/apps/valtech/avs-examples/.content.xml create mode 100644 examples/src/main/content/jcr_root/apps/valtech/avs-examples/components/.content.xml create mode 100644 pom.xml create mode 100644 sonar-project.properties create mode 100644 ui.apps.structure/pom.xml create mode 100644 ui.apps.structure/src/main/.gitignore create mode 100644 ui.apps/pom.xml create mode 100644 ui.apps/src/main/content/.gitignore create mode 100644 ui.apps/src/main/content/META-INF/vault/definition/thumbnail.png create mode 100644 ui.apps/src/main/content/META-INF/vault/filter.xml create mode 100644 ui.apps/src/main/content/jcr_root/.gitignore create mode 100644 ui.apps/src/main/content/jcr_root/apps/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/.content.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c45fb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# 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 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1b5f432 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: java + +jdk: + - openjdk9 + +addons: + sonarcloud: + organization: "valtech_avs" + token: + secure: TODO + +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/api/pom.xml b/api/pom.xml new file mode 100644 index 0000000..ff11227 --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + + de.valtech.avs + avs + 1.0.0-SNAPSHOT + + + 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/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/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/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..4bcad81 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,144 @@ + + + 4.0.0 + + de.valtech.avs + avs + 1.0.0-SNAPSHOT + + + 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 + + + + + + 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.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/examples/pom.xml b/examples/pom.xml new file mode 100644 index 0000000..afa89de --- /dev/null +++ b/examples/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + + de.valtech.avs + avs + 1.0.0-SNAPSHOT + + + 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/thumbnail.png b/examples/src/main/content/META-INF/vault/definition/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..c4548ee8bb2146e82d2f00bbe1c2dfb58b55694d GIT binary patch literal 1874 zcmV-Y2d(&tP)d`tAr5a+P?0i5kN{eOSnacG~T3x7}^KyMFLLW->na?%sA` z8z*`4+-F|T%sKNv|8s7QaVplw$v|D7HJ~pzGt3cOtdH$L2ax34ADlTbmTT0+D<$)9flJ4Ff%b9S-1~udX5W(mf-_hj&j*$PeQH}CocZPv zppdso?o=NAe^SU$@twof(S@@9a)hAP@Ce^;4rDcgkB zZiw}f;7o#=nZTKV(V=bw)&*z!^1h=e{l)izMu(W@vp&8XI7Lo(JMdg^W>2$qv$5~d zjqH%jF9v6NMj>-8lj~eyHn10XAvm+O#XbLLLhIu^;GwK;Juny8kxyiOJP+_dr>eRE z6Py{cKF$H|2Hw?Roj(9K2WK`U0j-Z4fm5?SCjsXavQ*yc#Kx?T6M#+^Y{1TX2jDH-MADr3UDrEg$R*}jXz|2A! z$nx_|{}>puK3)kt(?l5n9|ZnreLSZXYZ>8Yb^t#Ie%e48hy`_7A1^JGF-fFhSp#JN zd=%JXeY~m(tX?OjPpxSG2=KD?@vW^Yq!;*IR@VkxRP<}p0;%i;R_bt<6uu|`cL5Io zI|~+d`v~%E0)7EJUDS60_XEGm>XrbXDEh4t9o?SQO$6>O4(1LWX6~>4sde`ikaU;z z@$?38^#T3fCKT9S%y|Ov)s_?zoH?jCAjj?bg=1@%#ia5w5Uh`v05h`smBE>_1W(rk zbAX<#{rqOa1ZQ3f&fEZeuV{KEICG;QVA%R-E0l3TaOOhbV}id&vbLF$Dfx>li-Y}2 zaOT9|%!g#{>x!|LHxYYLaAp>8QE+C4kRy+o&a^&uw#u@eC}8VC>*G<$Sp%2I(o%Uc zII~aJeMJ`FeK&WOvY5d!pqCW$&I2Z7P2UdAtWA9++t-R4`dPit(;@#imI%(=W__#) z(hdN(0$YLCfY*Ujt&dfV^kLvfiD>Bkje^o2Rmj?$Wl4zL*2k5=%~{*^z*7DGLNV?q z+WbSw2`d$Hw)OEdVjFJ*F-Zl$U$7m*E0-FBVj% zw9Zfk4Sp^kr}nyx`}j@3Z$ zX5!wri4|=L&g`{5UJgtdD}jSE1J=h<$@Ar}YE67mT}*0k(dX!#ipxMdfOljFvL`t6 zkMW4qUl8>?Nsj1Ky5@Q8kt?udA6KWSJcF0EH$d)eo=PdX+yohsw}wbsKDs{>@e!U7Z1^?mzF*;9dK}F z5LhmLpV-Tt*2mSr=HSc$>*GY=YQ;3E^aN+7S|49jtT9>jU$;I!2)tr_90HCNQslS# z*#=I%)x6nAzICv@fP+En<0@dG=vsThJpPufC^*w^ecVwv$=_&mor=!qAL0!u|Ma=o z`nXJ)ft!LeYpsvV6>|(r);|F&B-1SZ_rHo8^?piwxgb;iI??w&;M^jm^NQLIx3mGD z)9o9qFu(!e%T>w6r^UZ#7pd|U;`e#N?3N;WzCsawUCP`nD?UE6lxe}4?h%ClbMbu~ z@Jao?N$+cw_L-BYZwhED$`7P0f;0P-zuN#DqxMO_#~b`ba7}RL^)bqCv;Zcy<9hXoP3-53}+`f-{@SRC4A|PF7mP=m0kcXP#G| zDT1{mR4q8uWqmv$^8q#BRi$zA`HvB)n4+=!gEMO? + + + 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/components/.content.xml b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/components/.content.xml new file mode 100644 index 0000000..84ee745 --- /dev/null +++ b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/components/.content.xml @@ -0,0 +1,4 @@ + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b6fa51f --- /dev/null +++ b/pom.xml @@ -0,0 +1,646 @@ + + + 4.0.0 + de.valtech.avs + avs + pom + 1.0.0-SNAPSHOT + AVS + AEM Virus Scan + https://github.com/valtech/aem-virus-scan + + + api + core + ui.apps.structure + ui.apps + examples + + + + localhost + 5602 + localhost + 5705 + 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.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.0.1 + 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..e701d8c --- /dev/null +++ b/ui.apps.structure/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + + de.valtech.avs + avs + 1.0.0-SNAPSHOT + + + 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..9d5dd76 --- /dev/null +++ b/ui.apps/pom.xml @@ -0,0 +1,137 @@ + + + 4.0.0 + + + de.valtech.avs + avs + 1.0.0-SNAPSHOT + + + 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/.gitignore b/ui.apps/src/main/content/.gitignore new file mode 100644 index 0000000..3385916 --- /dev/null +++ b/ui.apps/src/main/content/.gitignore @@ -0,0 +1 @@ +/META-INF/ 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 0000000000000000000000000000000000000000..c4548ee8bb2146e82d2f00bbe1c2dfb58b55694d GIT binary patch literal 1874 zcmV-Y2d(&tP)d`tAr5a+P?0i5kN{eOSnacG~T3x7}^KyMFLLW->na?%sA` z8z*`4+-F|T%sKNv|8s7QaVplw$v|D7HJ~pzGt3cOtdH$L2ax34ADlTbmTT0+D<$)9flJ4Ff%b9S-1~udX5W(mf-_hj&j*$PeQH}CocZPv zppdso?o=NAe^SU$@twof(S@@9a)hAP@Ce^;4rDcgkB zZiw}f;7o#=nZTKV(V=bw)&*z!^1h=e{l)izMu(W@vp&8XI7Lo(JMdg^W>2$qv$5~d zjqH%jF9v6NMj>-8lj~eyHn10XAvm+O#XbLLLhIu^;GwK;Juny8kxyiOJP+_dr>eRE z6Py{cKF$H|2Hw?Roj(9K2WK`U0j-Z4fm5?SCjsXavQ*yc#Kx?T6M#+^Y{1TX2jDH-MADr3UDrEg$R*}jXz|2A! z$nx_|{}>puK3)kt(?l5n9|ZnreLSZXYZ>8Yb^t#Ie%e48hy`_7A1^JGF-fFhSp#JN zd=%JXeY~m(tX?OjPpxSG2=KD?@vW^Yq!;*IR@VkxRP<}p0;%i;R_bt<6uu|`cL5Io zI|~+d`v~%E0)7EJUDS60_XEGm>XrbXDEh4t9o?SQO$6>O4(1LWX6~>4sde`ikaU;z z@$?38^#T3fCKT9S%y|Ov)s_?zoH?jCAjj?bg=1@%#ia5w5Uh`v05h`smBE>_1W(rk zbAX<#{rqOa1ZQ3f&fEZeuV{KEICG;QVA%R-E0l3TaOOhbV}id&vbLF$Dfx>li-Y}2 zaOT9|%!g#{>x!|LHxYYLaAp>8QE+C4kRy+o&a^&uw#u@eC}8VC>*G<$Sp%2I(o%Uc zII~aJeMJ`FeK&WOvY5d!pqCW$&I2Z7P2UdAtWA9++t-R4`dPit(;@#imI%(=W__#) z(hdN(0$YLCfY*Ujt&dfV^kLvfiD>Bkje^o2Rmj?$Wl4zL*2k5=%~{*^z*7DGLNV?q z+WbSw2`d$Hw)OEdVjFJ*F-Zl$U$7m*E0-FBVj% zw9Zfk4Sp^kr}nyx`}j@3Z$ zX5!wri4|=L&g`{5UJgtdD}jSE1J=h<$@Ar}YE67mT}*0k(dX!#ipxMdfOljFvL`t6 zkMW4qUl8>?Nsj1Ky5@Q8kt?udA6KWSJcF0EH$d)eo=PdX+yohsw}wbsKDs{>@e!U7Z1^?mzF*;9dK}F z5LhmLpV-Tt*2mSr=HSc$>*GY=YQ;3E^aN+7S|49jtT9>jU$;I!2)tr_90HCNQslS# z*#=I%)x6nAzICv@fP+En<0@dG=vsThJpPufC^*w^ecVwv$=_&mor=!qAL0!u|Ma=o z`nXJ)ft!LeYpsvV6>|(r);|F&B-1SZ_rHo8^?piwxgb;iI??w&;M^jm^NQLIx3mGD z)9o9qFu(!e%T>w6r^UZ#7pd|U;`e#N?3N;WzCsawUCP`nD?UE6lxe}4?h%ClbMbu~ z@Jao?N$+cw_L-BYZwhED$`7P0f;0P-zuN#DqxMO_#~b`ba7}RL^)bqCv;Zcy<9hXoP3-53}+`f-{@SRC4A|PF7mP=m0kcXP#G| zDT1{mR4q8uWqmv$^8q#BRi$zA`HvB)n4+=!gEMO? + + + 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/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 @@ + + From 42976ec9c77c1e578ae20c57238444b941cfe8a9 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 8 Jul 2020 11:18:17 +0200 Subject: [PATCH 02/25] added interfaces --- .../valtech/avs/api/service/AvsService.java | 42 +++++++++++ .../api/service/scanner/AvsScannerEnine.java | 43 ++++++++++++ .../avs/api/service/scanner/ScanResult.java | 66 ++++++++++++++++++ .../avs/api/service/scanner/package-info.java | 28 ++++++++ core/pom.xml | 5 ++ .../avs/core/jmx/AntiVirusScannerMBean.java | 46 +++++++++++++ .../core/jmx/AntiVirusScannerMBeanImpl.java | 61 ++++++++++++++++ .../avs/core/service/AvsServiceImpl.java | 69 +++++++++++++++++++ .../service/scanner/ClamScannerConfig.java | 45 ++++++++++++ .../service/scanner/ClamScannerEngine.java | 39 +++++++++++ 10 files changed, 444 insertions(+) create mode 100644 api/src/main/java/de/valtech/avs/api/service/AvsService.java create mode 100644 api/src/main/java/de/valtech/avs/api/service/scanner/AvsScannerEnine.java create mode 100644 api/src/main/java/de/valtech/avs/api/service/scanner/ScanResult.java create mode 100644 api/src/main/java/de/valtech/avs/api/service/scanner/package-info.java create mode 100644 core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBean.java create mode 100644 core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBeanImpl.java create mode 100644 core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java create mode 100644 core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerConfig.java create mode 100644 core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerEngine.java 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..3b15f76 --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/service/AvsService.java @@ -0,0 +1,42 @@ +/* + * 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 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 + * @return scan result + * @throws AvsException error during scan + */ + public ScanResult scanContent(String content) throws AvsException; + +} 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..4ec9205 --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/service/scanner/AvsScannerEnine.java @@ -0,0 +1,43 @@ +/* + * 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 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 scanContent(String 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..4c4dbf1 --- /dev/null +++ b/api/src/main/java/de/valtech/avs/api/service/scanner/ScanResult.java @@ -0,0 +1,66 @@ +/* + * 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; + + /** + * 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; + } + +} 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/core/pom.xml b/core/pom.xml index 4bcad81..02d0d5d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -140,5 +140,10 @@ com.google.code.findbugs jsr305 + + de.valtech.avs + avs.api + ${project.version} + 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..2168ce3 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBean.java @@ -0,0 +1,46 @@ +/* + * 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 + */ + @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..5681ebf --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBeanImpl.java @@ -0,0 +1,61 @@ +/* + * 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 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) { + try { + return scanner.scanContent(content).toString(); + } catch (AvsException e) { + return "Error: " + e.getMessage(); + } + } + +} 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..06a6bb5 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java @@ -0,0 +1,69 @@ +/* + * 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.util.ArrayList; +import java.util.List; + +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; + +/** + * AVS scan service. + * + * @author Roland Gruber + */ +@Component(service = AvsService.class) +public class AvsServiceImpl implements AvsService { + + @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 scanContent(String content) throws AvsException { + return new ScanResult("out", true); + } + +} 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..5bd63e7 --- /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 = "ClamAV configuration") +@ProviderType +public @interface ClamScannerConfig { + + /** + * Returns the scan command. + * + * @return command + */ + @AttributeDefinition(name = "Scan command", + description = "Command to run 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..b3e474c --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerEngine.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.service.scanner; + +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.metatype.annotations.Designate; + +import de.valtech.avs.api.service.AvsException; +import de.valtech.avs.api.service.scanner.AvsScannerEnine; +import de.valtech.avs.api.service.scanner.ScanResult; + +@Component(service = ClamScannerEngine.class, configurationPolicy = ConfigurationPolicy.REQUIRE, immediate = true) +@Designate(ocd = ClamScannerConfig.class) +public class ClamScannerEngine implements AvsScannerEnine { + + @Override + public ScanResult scanContent(String content) throws AvsException { + // TODO Auto-generated method stub + return null; + } + +} From b86a6f2ff7940ae8e694b43273e9cb390882c590 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 15 Jul 2020 08:12:56 +0200 Subject: [PATCH 03/25] scan file --- .../avs/core/service/AvsServiceImpl.java | 17 ++++- .../service/scanner/ClamScannerConfig.java | 2 +- .../service/scanner/ClamScannerEngine.java | 74 ++++++++++++++++++- 3 files changed, 88 insertions(+), 5 deletions(-) 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 index 06a6bb5..20b8fe2 100644 --- a/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java +++ b/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; @@ -63,7 +64,21 @@ protected synchronized void unbindEngine(AvsScannerEnine engine) { @Override public ScanResult scanContent(String content) throws AvsException { - return new ScanResult("out", true); + if (engines.isEmpty()) { + throw new AvsException("No scanning engines available"); + } + if (StringUtils.isEmpty(content)) { + // skip empty content + return new ScanResult(null, true); + } + ScanResult result = null; + for (AvsScannerEnine engine : engines) { + result = engine.scanContent(content); + if (!result.isClean()) { + return result; + } + } + return result; } } 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 index 5bd63e7..5d9ee4f 100644 --- 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 @@ -28,7 +28,7 @@ * * @author Roland Gruber */ -@ObjectClassDefinition(name = "ClamAV configuration") +@ObjectClassDefinition(name = "AVS ClamAV configuration") @ProviderType public @interface ClamScannerConfig { 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 index b3e474c..e089b4a 100644 --- 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 @@ -18,22 +18,90 @@ */ package de.valtech.avs.core.service.scanner; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; + +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; -@Component(service = ClamScannerEngine.class, configurationPolicy = ConfigurationPolicy.REQUIRE, immediate = true) +/** + * 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 avconfig configuration + */ + @Activate + public void activate(ClamScannerConfig config) { + this.config = config; + } + @Override public ScanResult scanContent(String content) throws AvsException { - // TODO Auto-generated method stub - return null; + 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("UTF-8")); + String error = IOUtils.toString(err, Charset.forName("UTF-8")); + in.close(); + err.close(); + + 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(String content) throws IOException { + File file = File.createTempFile("valtech-avs", ".tmp"); + try (FileWriter writer = new FileWriter(file)) { + writer.write(content); + } + return file; } } From bc6afc33d055662ebc69d2b5473aa7f09edd729a Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 15 Jul 2020 08:41:40 +0200 Subject: [PATCH 04/25] added tool --- .../main/content/META-INF/vault/filter.xml | 1 + .../core/content/nav/tools/avs/.content.xml | 6 ++++ .../content/nav/tools/avs/scan/.content.xml | 9 ++++++ .../content/scan/title/.content.xml | 5 +++ .../components/content/scan/title/title.html | 1 + .../apps/valtech/avs/tools/.content.xml | 5 +++ .../valtech/avs/tools/scan/page/.content.xml | 32 +++++++++++++++++++ 7 files changed, 59 insertions(+) create mode 100644 ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/scan/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/title/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/title/title.html create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/scan/page/.content.xml diff --git a/ui.apps/src/main/content/META-INF/vault/filter.xml b/ui.apps/src/main/content/META-INF/vault/filter.xml index de13d17..76a1f36 100644 --- a/ui.apps/src/main/content/META-INF/vault/filter.xml +++ b/ui.apps/src/main/content/META-INF/vault/filter.xml @@ -1,4 +1,5 @@ + 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/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..680c58d --- /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/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/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/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..3380a6b --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/scan/page/.content.xml @@ -0,0 +1,32 @@ + + + + + + + + <content + jcr:primaryType="nt:unstructured" + sling:resourceType="granite/ui/components/coral/foundation/container" + > + </content> + </jcr:content> +</jcr:root> From 2119493a681832cfebdc9b042292e6bb6b283362 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 17 Jul 2020 08:58:51 +0200 Subject: [PATCH 05/25] added scan tool --- core/pom.xml | 4 + .../avs/core/model/scan/ScanModel.java | 120 ++++++++++++++++++ .../avs-examples/components/.content.xml | 4 - ...core.service.scanner.ClamScannerEngine.xml | 4 + .../avs/clientlibs/avs.editor/.content.xml | 6 + .../valtech/avs/clientlibs/avs.editor/css.txt | 3 + .../avs/clientlibs/avs.editor/css/avs.css | 20 +++ .../content/scan/content/.content.xml | 5 + .../content/scan/content/content.html | 40 ++++++ .../valtech/avs/tools/scan/page/.content.xml | 2 +- 10 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/de/valtech/avs/core/model/scan/ScanModel.java delete mode 100644 examples/src/main/content/jcr_root/apps/valtech/avs-examples/components/.content.xml create mode 100644 examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.service.scanner.ClamScannerEngine.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/css.txt create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/clientlibs/avs.editor/css/avs.css create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/content/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/scan/content/content.html diff --git a/core/pom.xml b/core/pom.xml index 02d0d5d..7ea46cb 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -31,6 +31,10 @@ <Sling-Model-Packages> de.valtech.avs.core </Sling-Model-Packages> + <Import-Package> + javax.annotation;version=0.0.0, + * + </Import-Package> </instructions> </configuration> </plugin> 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..e307cd9 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/model/scan/ScanModel.java @@ -0,0 +1,120 @@ +/* + * 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 java.nio.charset.StandardCharsets; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletException; +import javax.servlet.http.Part; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +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(); + String fileContent = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name()); + if (!StringUtils.isEmpty(fileContent)) { + scanDone = true; + ScanResult result = avsService.scanContent(fileContent); + 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/examples/src/main/content/jcr_root/apps/valtech/avs-examples/components/.content.xml b/examples/src/main/content/jcr_root/apps/valtech/avs-examples/components/.content.xml deleted file mode 100644 index 84ee745..0000000 --- a/examples/src/main/content/jcr_root/apps/valtech/avs-examples/components/.content.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:rep="internal" - jcr:mixinTypes="[rep:AccessControllable]" - jcr:primaryType="nt:folder"/> 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..74a40f0 --- /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 @@ +<?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:OsgiConfig" + command="/usr/local/bin/clamscan --infected --no-summary" +/> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="cq:ClientLibraryFolder" + allowProxy="{Boolean}true" + categories="[avs.editor]" + embed="[]"/> 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/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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="cq:Component" + componentGroup=".hidden" + /> 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 @@ +<sly data-sly-use.model="de.valtech.avs.core.model.scan.ScanModel"/> +<div class="avs-scantool"> + <form method="post" enctype="multipart/form-data" action="page.external.html"> + <table class="avs-upload-table"> + <tr> + <td>File</td> + <td><input type="file" name="scanfile"></td> + </tr> + <tr> + <td></td> + <td><input type="submit" name="doscan" value="Upload"> + </td> + </tr> + </table> + </form> + + + + <div data-sly-test="${model.scanDone}" class="avs-scan-result"> + <br><br> + <div data-sly-test="${!model.scanFailed && model.clean}" class="avs-scanresult-title"> + <coral-icon icon="checkCircle" size="M" class="avs-icon-color-ok"></coral-icon> + The scanner reported no virus + </div> + <div data-sly-test="${!model.scanFailed && !model.clean}" class="avs-scanresult-title"> + <coral-icon icon="closeCircle" size="M" class="avs-icon-color-fail"></coral-icon> + The scanner reported a virus + </div> + <div data-sly-test="${model.scanFailed}" class="avs-scanresult-title"> + <coral-icon icon="closeCircle" size="M" class="avs-icon-color-fail"></coral-icon> + The scanner reported an error + </div> + <sly data-sly-test="${model.result}"> + <br> + <br> Result: + <pre class="avs-scan-result-text">${model.result}</pre> + </sly> + </div> + +</div> 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 index 3380a6b..8d00fe4 100644 --- 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 @@ -25,7 +25,7 @@ /> <content jcr:primaryType="nt:unstructured" - sling:resourceType="granite/ui/components/coral/foundation/container" + sling:resourceType="valtech/avs/components/content/scan/content" > </content> </jcr:content> From 33696a1961615810cf4c2ba3602bf9c7254933ec Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 17 Jul 2020 11:09:41 +0200 Subject: [PATCH 06/25] hide internal path --- .../de/valtech/avs/core/service/scanner/ClamScannerEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index e089b4a..83097e0 100644 --- 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 @@ -76,7 +76,7 @@ public ScanResult scanContent(String content) throws AvsException { String error = IOUtils.toString(err, Charset.forName("UTF-8")); 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); From c85793a67f0dce28ddfa4f10182ab8b8a1c09050 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Mon, 27 Jul 2020 15:16:34 +0200 Subject: [PATCH 07/25] added history, health check and maintenance task --- .../valtech/avs/api/history/HistoryEntry.java | 75 ++++ .../valtech/avs/api/service/AvsService.java | 16 +- .../api/service/scanner/AvsScannerEnine.java | 4 +- .../avs/api/service/scanner/ScanResult.java | 40 ++ .../healthcheck/SelfCheckHealthCheck.java | 71 ++++ .../avs/core/history/HistoryEntryImpl.java | 118 ++++++ .../avs/core/history/HistoryService.java | 370 ++++++++++++++++++ .../core/jmx/AntiVirusScannerMBeanImpl.java | 5 +- .../PurgeHistoryConfiguration.java | 39 ++ .../core/maintenance/PurgeHistoryTask.java | 80 ++++ .../core/model/history/HistoryDataItem.java | 88 +++++ .../core/model/history/HistoryDataSource.java | 112 ++++++ .../avs/core/model/scan/ScanModel.java | 10 +- .../avs/core/service/AvsServiceImpl.java | 34 +- .../service/scanner/ClamScannerEngine.java | 11 +- .../ServiceResourceResolverService.java | 55 +++ ...core.service.scanner.ClamScannerEngine.xml | 2 +- .../main/content/META-INF/vault/filter.xml | 4 + .../nav/tools/avs/history/.content.xml | 9 + .../content/nav/tools/avs/scan/.content.xml | 2 +- .../jcr_root/apps/settings/.content.xml | 4 + .../apps/settings/granite/.content.xml | 3 + .../settings/granite/operations/.content.xml | 4 + .../granite/operations/hc/.content.xml | 4 + .../granite/operations/hc/avs/.content.xml | 6 + .../content/history/title/.content.xml | 5 + .../content/history/title/title.html | 1 + ....avs.core.maintenance.PurgeHistoryTask.xml | 5 + ....hc.core.impl.CompositeHealthCheck-avs.xml | 8 + ...erMapperImpl.amended-valtechAvsUser.config | 1 + .../avs/tools/history/dataitem/dataitem.html | 15 + .../tools/history/datasource/datasource.html | 1 + .../avs/tools/history/page/.content.xml | 73 ++++ .../conf/global/settings/.content.xml | 6 + .../conf/global/settings/granite/.content.xml | 4 + .../settings/granite/operations/.content.xml | 4 + .../operations/maintenance/.content.xml | 6 + .../maintenance/granite_weekly/.content.xml | 6 + .../avs_history_purge/.content.xml | 7 + .../home/users/system/avs/.content.xml | 3 + .../users/system/avs/avs-service/.content.xml | 7 + .../content/jcr_root/var/avs/.content.xml | 4 + .../content/jcr_root/var/avs/_rep_policy.xml | 8 + .../jcr_root/var/avs/history/.content.xml | 4 + .../groovyconsole/scripts/aecu/.content.xml | 4 + 45 files changed, 1315 insertions(+), 23 deletions(-) create mode 100644 api/src/main/java/de/valtech/avs/api/history/HistoryEntry.java create mode 100644 core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java create mode 100644 core/src/main/java/de/valtech/avs/core/history/HistoryEntryImpl.java create mode 100644 core/src/main/java/de/valtech/avs/core/history/HistoryService.java create mode 100644 core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryConfiguration.java create mode 100644 core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryTask.java create mode 100644 core/src/main/java/de/valtech/avs/core/model/history/HistoryDataItem.java create mode 100644 core/src/main/java/de/valtech/avs/core/model/history/HistoryDataSource.java create mode 100644 core/src/main/java/de/valtech/avs/core/serviceuser/ServiceResourceResolverService.java create mode 100644 ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/avs/history/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/settings/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/settings/granite/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/hc/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/settings/granite/operations/hc/avs/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/history/title/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/components/content/history/title/title.html create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/de.valtech.avs.core.maintenance.PurgeHistoryTask.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/org.apache.sling.hc.core.impl.CompositeHealthCheck-avs.xml create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-valtechAvsUser.config create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/dataitem/dataitem.html create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/datasource/datasource.html create mode 100644 ui.apps/src/main/content/jcr_root/apps/valtech/avs/tools/history/page/.content.xml create mode 100755 ui.apps/src/main/content/jcr_root/conf/global/settings/.content.xml create mode 100755 ui.apps/src/main/content/jcr_root/conf/global/settings/granite/.content.xml create mode 100755 ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/.content.xml create mode 100755 ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/.content.xml create mode 100755 ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/granite_weekly/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/conf/global/settings/granite/operations/maintenance/granite_weekly/avs_history_purge/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/home/users/system/avs/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/home/users/system/avs/avs-service/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/var/avs/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/var/avs/_rep_policy.xml create mode 100644 ui.apps/src/main/content/jcr_root/var/avs/history/.content.xml create mode 100644 ui.apps/src/main/content/jcr_root/var/groovyconsole/scripts/aecu/.content.xml 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/AvsService.java b/api/src/main/java/de/valtech/avs/api/service/AvsService.java index 3b15f76..66cbc3b 100644 --- a/api/src/main/java/de/valtech/avs/api/service/AvsService.java +++ b/api/src/main/java/de/valtech/avs/api/service/AvsService.java @@ -18,6 +18,8 @@ */ package de.valtech.avs.api.service; +import java.io.InputStream; + import org.osgi.annotation.versioning.ProviderType; import de.valtech.avs.api.service.scanner.ScanResult; @@ -34,9 +36,21 @@ 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 scanContent(String content) throws AvsException; + public ScanResult scan(InputStream content, String userId, String path) throws AvsException; } 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 index 4ec9205..596861d 100644 --- 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 @@ -18,6 +18,8 @@ */ package de.valtech.avs.api.service.scanner; +import java.io.InputStream; + import org.osgi.annotation.versioning.ConsumerType; import de.valtech.avs.api.service.AvsException; @@ -38,6 +40,6 @@ public interface AvsScannerEnine { * @return scan result * @throws AvsException error during scan */ - ScanResult scanContent(String content) throws AvsException; + 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 index 4c4dbf1..6d9a781 100644 --- 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 @@ -29,6 +29,10 @@ public class ScanResult { private String output; + private String path; + + private String userId; + /** * Constructor * @@ -63,4 +67,40 @@ 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/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..77379cc --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java @@ -0,0 +1,71 @@ +/* + * 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 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.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; + + @Override + public Result execute() { + final FormattingResultLog resultLog = new FormattingResultLog(); + checkServiceResolver(resultLog); + if (resultLog.getAggregateStatus().equals(Status.CRITICAL)) { + return new Result(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()); + } + } + +} 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..fe748c1 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/history/HistoryEntryImpl.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.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 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..72e9069 --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/history/HistoryService.java @@ -0,0 +1,370 @@ +/* + * 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 + * @return history entry + * @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<HistoryEntry> getHistory(int startIndex, int count, ResourceResolver resolver) { + List<HistoryEntry> 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<Resource> 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<Resource> 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<String, Object> 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<Resource> 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 AecuException 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/AntiVirusScannerMBeanImpl.java b/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBeanImpl.java index 5681ebf..18aef12 100644 --- a/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBeanImpl.java +++ b/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBeanImpl.java @@ -18,6 +18,8 @@ */ package de.valtech.avs.core.jmx; +import java.io.ByteArrayInputStream; + import javax.management.NotCompliantMBeanException; import org.osgi.service.component.annotations.Component; @@ -51,8 +53,9 @@ public AntiVirusScannerMBeanImpl() throws NotCompliantMBeanException { @Override public String scanContent(String content) { + ByteArrayInputStream stream = new ByteArrayInputStream(content.getBytes()); try { - return scanner.scanContent(content).toString(); + return scanner.scan(stream, "JMX").toString(); } catch (AvsException e) { return "Error: " + e.getMessage(); } 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..f469a65 --- /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(); + +} 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<Resource> iterator() { + List<Resource> entries = new ArrayList<>(); + try (ResourceResolver resolver = serviceResourceResolverService.getServiceResourceResolver()) { + List<HistoryEntry> historyEntries = historyService.getHistory(offset, limit + 1, resolver); + for (HistoryEntry historyEntry : historyEntries) { + ValueMap vm = new ValueMapDecorator(new HashMap<String, Object>()); + 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 index e307cd9..94e55cc 100644 --- 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 @@ -20,14 +20,12 @@ import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; import javax.annotation.PostConstruct; +import javax.jcr.Session; import javax.servlet.ServletException; import javax.servlet.http.Part; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.injectorspecific.OSGiService; @@ -67,10 +65,10 @@ protected void init() { Part filePart = request.getPart(FILE_PART); if (filePart != null) { InputStream inputStream = filePart.getInputStream(); - String fileContent = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name()); - if (!StringUtils.isEmpty(fileContent)) { + if (inputStream != null) { scanDone = true; - ScanResult result = avsService.scanContent(fileContent); + String userId = request.getResourceResolver().adaptTo(Session.class).getUserID(); + ScanResult result = avsService.scan(inputStream, userId); clean = result.isClean(); resultOutput = result.getOutput(); } 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 index 20b8fe2..0231319 100644 --- a/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java +++ b/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java @@ -18,10 +18,13 @@ */ 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; @@ -31,6 +34,8 @@ 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. @@ -40,6 +45,12 @@ @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<AvsScannerEnine> engines = new ArrayList<>(); @@ -63,20 +74,31 @@ protected synchronized void unbindEngine(AvsScannerEnine engine) { } @Override - public ScanResult scanContent(String content) throws AvsException { + 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 (StringUtils.isEmpty(content)) { + if (content == null) { // skip empty content return new ScanResult(null, true); } ScanResult result = null; - for (AvsScannerEnine engine : engines) { - result = engine.scanContent(content); - if (!result.isClean()) { - return result; + 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; } 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 index 83097e0..8267691 100644 --- 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 @@ -19,12 +19,12 @@ package de.valtech.avs.core.service.scanner; import java.io.File; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; 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; @@ -63,7 +63,7 @@ public void activate(ClamScannerConfig config) { } @Override - public ScanResult scanContent(String content) throws AvsException { + public ScanResult scan(InputStream content) throws AvsException { try { File tempFile = createTemporaryFile(content); Runtime runtime = Runtime.getRuntime(); @@ -96,11 +96,10 @@ public ScanResult scanContent(String content) throws AvsException { * @return file handle * @throws IOException error creating file */ - private File createTemporaryFile(String content) throws IOException { + private File createTemporaryFile(InputStream content) throws IOException { File file = File.createTempFile("valtech-avs", ".tmp"); - try (FileWriter writer = new FileWriter(file)) { - writer.write(content); - } + 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<String, Object> authenticationInfo = new HashMap<>(); + authenticationInfo.put(ResourceResolverFactory.SUBSERVICE, SUBSERVICE_AVS); + return resolverFactory.getServiceResourceResolver(authenticationInfo); + } + +} 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 index 74a40f0..fb06017 100644 --- 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 @@ -1,4 +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="sling:OsgiConfig" - command="/usr/local/bin/clamscan --infected --no-summary" + command="/usr/local/bin/clamdscan --infected --no-summary" /> diff --git a/ui.apps/src/main/content/META-INF/vault/filter.xml b/ui.apps/src/main/content/META-INF/vault/filter.xml index 76a1f36..7591591 100644 --- a/ui.apps/src/main/content/META-INF/vault/filter.xml +++ b/ui.apps/src/main/content/META-INF/vault/filter.xml @@ -2,4 +2,8 @@ <workspaceFilter version="1.0"> <filter root="/apps/valtech/avs"/> <filter root="/apps/cq/core/content/nav/tools/avs"/> + <filter root="/home/users/system/avs"/> + <filter root="/var/avs" mode="merge"/> + <filter root="/apps/settings/granite/operations/hc/avs"/> + <filter root="/conf/global/settings/granite/operations/maintenance/granite_weekly/avs_history_purge"/> </workspaceFilter> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" + jcr:primaryType="nt:unstructured" + jcr:title="History" + jcr:description="Show history of last scans" + href="/apps/valtech/avs/tools/history/page.html" + icon="history" + id="avs-history" +/> \ 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 index 680c58d..08c9cb1 100644 --- 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 @@ -2,7 +2,7 @@ <jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" jcr:primaryType="nt:unstructured" jcr:title="Scan a File" - jcr:description="Scan a file to check if it contains a virus" + jcr:description="Upload a file to check if it contains a virus" href="/apps/valtech/avs/tools/scan/page.html" icon="search" id="avs-scan" 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 @@ +<?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:folder" +/> \ 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 @@ +<?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"/> 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 @@ +<?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:Folder" +/> \ 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 @@ +<?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/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 @@ +<?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" + sling:resourceType="granite/operations/components/mbean" + resource="/system/sling/monitoring/mbeans/org/apache/sling/healthcheck/HealthCheck/avsHealthCheckmBean" +/> \ 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="cq:Component" + componentGroup=".hidden" + /> 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/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 @@ +<?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:OsgiConfig" + daysToKeep="90" +/> 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 @@ +<?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:OsgiConfig" + hc.name="AVS Health Checks" + hc.mbean.name="avsHealthCheckmBean" + filter.tags="[avs]" + hc.tags="[avs-all]" +/> 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/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 @@ +<sly data-sly-use.dataItem="de.valtech.avs.core.model.history.HistoryDataItem"/> +<tr is="coral-table-row" class="foundation-collection-item"> + <td is="coral-table-cell"> + ${dataItem.date} + </td> + <td is="coral-table-cell"> + ${dataItem.userId} + </td> + <td is="coral-table-cell"> + ${dataItem.path} + </td> + <td is="coral-table-cell"> + ${dataItem.output} + </td> +</tr> \ 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 @@ +<sly data-sly-use.datasource="de.valtech.avs.core.model.history.HistoryDataSource"/> \ 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jcr:root + xmlns:jcr="http://www.jcp.org/jcr/1.0" + xmlns:rep="internal" + xmlns:sling="http://sling.apache.org/jcr/sling/1.0" + xmlns:granite="http://www.adobe.com/jcr/granite/1.0" + jcr:primaryType="cq:Page"> + <jcr:content + jcr:primaryType="nt:unstructured" + jcr:title="AEM Virus Scan - History" + consoleId="avs-history" + sling:resourceType="granite/ui/components/shell/collectionpage" + targetCollection=".avs-history-entries" + modeGroup="avs-history-entries" + granite:class="avs-history-table" + 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/history/title" + /> + <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/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 diff --git a/ui.apps/src/main/content/jcr_root/var/groovyconsole/scripts/aecu/.content.xml b/ui.apps/src/main/content/jcr_root/var/groovyconsole/scripts/aecu/.content.xml new file mode 100644 index 0000000..dfac52e --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/var/groovyconsole/scripts/aecu/.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="nt:folder" +/> \ No newline at end of file From 8f596af82fd5fb6536550faa60e89d49a594bd6f Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Mon, 27 Jul 2020 15:22:24 +0200 Subject: [PATCH 08/25] check scan engines --- .../de/valtech/avs/api/service/AvsService.java | 7 +++++++ .../core/healthcheck/SelfCheckHealthCheck.java | 18 ++++++++++++++++++ .../avs/core/service/AvsServiceImpl.java | 5 +++++ 3 files changed, 30 insertions(+) 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 index 66cbc3b..13f8677 100644 --- a/api/src/main/java/de/valtech/avs/api/service/AvsService.java +++ b/api/src/main/java/de/valtech/avs/api/service/AvsService.java @@ -53,4 +53,11 @@ public interface AvsService { */ 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/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java b/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java index 77379cc..4aa4d10 100644 --- a/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java +++ b/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java @@ -27,6 +27,7 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import de.valtech.avs.api.service.AvsService; import de.valtech.avs.core.serviceuser.ServiceResourceResolverService; /** @@ -41,6 +42,9 @@ public class SelfCheckHealthCheck implements HealthCheck { @Reference private ServiceResourceResolverService resolverService; + @Reference + private AvsService avsService; + @Override public Result execute() { final FormattingResultLog resultLog = new FormattingResultLog(); @@ -48,6 +52,7 @@ public Result execute() { if (resultLog.getAggregateStatus().equals(Status.CRITICAL)) { return new Result(resultLog); } + checkActiveScannersAvailable(resultLog); return new Result(resultLog); } @@ -68,4 +73,17 @@ private void checkServiceResolver(FormattingResultLog resultLog) { } } + /** + * 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"); + } + } + } 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 index 0231319..2107638 100644 --- a/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java +++ b/core/src/main/java/de/valtech/avs/core/service/AvsServiceImpl.java @@ -103,4 +103,9 @@ public ScanResult scan(InputStream content, String userId, String path) throws A return result; } + @Override + public boolean hasActiveScanEngines() { + return !engines.isEmpty(); + } + } From 24f2243c9c53199b481ec153552d2014f6feed5b Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Tue, 28 Jul 2020 07:57:20 +0200 Subject: [PATCH 09/25] extended check --- .../healthcheck/SelfCheckHealthCheck.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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 index 4aa4d10..ac6bb08 100644 --- a/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java +++ b/core/src/main/java/de/valtech/avs/core/healthcheck/SelfCheckHealthCheck.java @@ -18,6 +18,8 @@ */ 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; @@ -27,7 +29,9 @@ 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; /** @@ -83,6 +87,26 @@ private void checkActiveScannersAvailable(FormattingResultLog resultLog) { 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()); } } From 30104d7e927b11b3e8556f8f7b915bea790eb0f3 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Wed, 29 Jul 2020 15:36:02 +0200 Subject: [PATCH 10/25] added POST filter --- .../avs/core/filter/AvsPostFilter.java | 155 ++++++++++++++++++ .../valtech/avs/core/filter/PartWrapper.java | 118 +++++++++++++ pom.xml | 2 +- 3 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java create mode 100644 core/src/main/java/de/valtech/avs/core/filter/PartWrapper.java 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..177cf8f --- /dev/null +++ b/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java @@ -0,0 +1,155 @@ +/* + * 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 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.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.serviceuser.ServiceResourceResolverService; + +/** + * Filter for POST requests. Checks included files. + * + * @author Roland Gruber + */ +@Component(service = Filter.class, name = "AVS POST Filter", + property = {"sling.filter.scope=REQUEST", Constants.SERVICE_RANKING + ":Integer=50000"}) +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; + + @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)) { + chain.doFilter(request, response); + return; + } + Iterator<Part> parts = (Iterator<Part>) request.getAttribute(REQUEST_PARTS); + if (parts == null) { + chain.doFilter(request, response); + return; + } + List<File> parameterFiles = new ArrayList<>(); + List<Part> newParts = new ArrayList<>(); + while (parts.hasNext()) { + Part part = parts.next(); + String partContentType = part.getContentType(); + if (StringUtils.isEmpty(partContentType) || !partContentType.contains("stream")) { + String partContent = IOUtils.toString(part.getInputStream(), StandardCharsets.UTF_8.name()); + newParts.add(new PartWrapper(part, partContent.getBytes())); + continue; + } + 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<InputStream> streams = new ArrayList<>(); + for (File file : parameterFiles) { + streams.add(new FileInputStream(file)); + } + SequenceInputStream combinedStream = new SequenceInputStream(Collections.enumeration(streams)); + try { + String userId = slingRequest.getResourceResolver().adaptTo(Session.class).getUserID(); + ScanResult result = avsService.scan(combinedStream, userId); + if (!result.isClean()) { + 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); + } + + @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/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<String> getHeaders(String name) { + return part.getHeaders(name); + } + + @Override + public Collection<String> getHeaderNames() { + return part.getHeaderNames(); + } + + @Override + public String getSubmittedFileName() { + return part.getSubmittedFileName(); + } + +} diff --git a/pom.xml b/pom.xml index b6fa51f..d059c1d 100644 --- a/pom.xml +++ b/pom.xml @@ -514,7 +514,7 @@ <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> - <version>3.0.1</version> + <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> From bc217fb3a26cec3858ec30a48e2ca3882c3090f4 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 31 Jul 2020 11:35:32 +0200 Subject: [PATCH 11/25] added POST filter servlet --- .../avs/core/filter/AvsPostFilter.java | 62 +++++++++++- .../avs/core/filter/AvsPostFilterConfig.java | 54 +++++++++++ .../avs/core/filter/AvsPostFilterTest.java | 95 +++++++++++++++++++ ....valtech.avs.core.filter.AvsPostFilter.xml | 5 + 4 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/de/valtech/avs/core/filter/AvsPostFilterConfig.java create mode 100644 core/src/test/java/de/valtech/avs/core/filter/AvsPostFilterTest.java create mode 100644 examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.filter.AvsPostFilter.xml 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 index 177cf8f..963fb12 100644 --- a/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java +++ b/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.regex.Pattern; import javax.jcr.Session; import javax.servlet.Filter; @@ -45,8 +46,10 @@ 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; @@ -61,8 +64,8 @@ * * @author Roland Gruber */ -@Component(service = Filter.class, name = "AVS POST Filter", - property = {"sling.filter.scope=REQUEST", Constants.SERVICE_RANKING + ":Integer=50000"}) +@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"; @@ -78,12 +81,36 @@ public class AvsPostFilter implements Filter { @Reference private ServiceResourceResolverService serviceResolverService; + private List<Pattern> includePatterns = new ArrayList<>(); + private List<Pattern> excludePatterns = new ArrayList<>(); + + /** + * Setup service + * + * @param avconfig 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)) { + if (!"POST".equals(slingRequest.getMethod()) || !isMultipartRequest(contentType) || isUrlToIgnore(slingRequest)) { chain.doFilter(request, response); return; } @@ -97,7 +124,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha while (parts.hasNext()) { Part part = parts.next(); String partContentType = part.getContentType(); - if (StringUtils.isEmpty(partContentType) || !partContentType.contains("stream")) { + if (StringUtils.isEmpty(partContentType)) { String partContent = IOUtils.toString(part.getInputStream(), StandardCharsets.UTF_8.name()); newParts.add(new PartWrapper(part, partContent.getBytes())); continue; @@ -120,6 +147,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String userId = slingRequest.getResourceResolver().adaptTo(Session.class).getUserID(); ScanResult result = avsService.scan(combinedStream, userId); if (!result.isClean()) { + for (File file : parameterFiles) { + file.delete(); + } throw new ServletException("Uploaded file contains a virus"); } } catch (AvsException e) { @@ -142,6 +172,30 @@ private boolean isMultipartRequest(String contentType) { 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; + } + @Override public void init(FilterConfig filterConfig) throws ServletException { // 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/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/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 @@ +<?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:OsgiConfig" + includePatterns="[/content/dam/.*]" + excludePatterns="[]" +/> From df48ec2313bb8168f49ae46715679bb841e7d833 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Mon, 3 Aug 2020 12:48:34 +0200 Subject: [PATCH 12/25] send mails --- core/pom.xml | 312 ++-- .../avs/core/filter/AvsPostFilter.java | 45 +- .../avs/core/mail/AvsNotificationMailer.java | 147 ++ .../mail/AvsNotificationMailerConfig.java | 81 ++ ...ch.avs.core.mail.AvsNotificationMailer.xml | 8 + pom.xml | 1275 +++++++++-------- 6 files changed, 1087 insertions(+), 781 deletions(-) create mode 100644 core/src/main/java/de/valtech/avs/core/mail/AvsNotificationMailer.java create mode 100644 core/src/main/java/de/valtech/avs/core/mail/AvsNotificationMailerConfig.java create mode 100644 examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.mail.AvsNotificationMailer.xml diff --git a/core/pom.xml b/core/pom.xml index 7ea46cb..9704ec7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,153 +1,167 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>de.valtech.avs</groupId> - <artifactId>avs</artifactId> - <version>1.0.0-SNAPSHOT</version> - </parent> - - <artifactId>avs.core</artifactId> - <packaging>bundle</packaging> - <name>AVS - Core</name> - <description>Core bundle for AVS</description> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>de.valtech.avs</groupId> + <artifactId>avs</artifactId> + <version>1.0.0-SNAPSHOT</version> + </parent> - <build> - <plugins> - <plugin> - <groupId>org.apache.felix</groupId> - <artifactId>osgicheck-maven-plugin</artifactId> - </plugin> - <plugin> - <groupId>org.apache.sling</groupId> - <artifactId>maven-sling-plugin</artifactId> - </plugin> - <plugin> - <groupId>org.apache.felix</groupId> - <artifactId>maven-bundle-plugin</artifactId> - <extensions>true</extensions> - <configuration> - <instructions> - <Sling-Model-Packages> - de.valtech.avs.core - </Sling-Model-Packages> - <Import-Package> - javax.annotation;version=0.0.0, - * - </Import-Package> - </instructions> - </configuration> - </plugin> - <plugin> - <groupId>org.jacoco</groupId> - <artifactId>jacoco-maven-plugin</artifactId> - <executions> - <execution> - <id>prepare-agent</id> - <goals> - <goal>prepare-agent</goal> - </goals> - </execution> - <execution> - <id>report</id> - <phase>prepare-package</phase> - <goals> - <goal>report</goal> - </goals> - </execution> - <execution> - <id>post-unit-test</id> - <phase>test</phase> - <goals> - <goal>report</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.owasp</groupId> - <artifactId>dependency-check-maven</artifactId> - </plugin> - </plugins> - </build> + <artifactId>avs.core</artifactId> + <packaging>bundle</packaging> + <name>AVS - Core</name> + <description>Core bundle for AVS</description> - <dependencies> - <dependency> - <groupId>de.valtech.avs</groupId> - <artifactId>avs.api</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>osgi.core</artifactId> - </dependency> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>osgi.cmpn</artifactId> - </dependency> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>osgi.annotation</artifactId> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-simple</artifactId> - </dependency> - <dependency> - <groupId>javax.jcr</groupId> - <artifactId>jcr</artifactId> - </dependency> - <dependency> - <groupId>javax.servlet</groupId> - <artifactId>javax.servlet-api</artifactId> - </dependency> - <dependency> - <groupId>com.adobe.aem</groupId> - <artifactId>uber-jar</artifactId> - <classifier>apis</classifier> - </dependency> - <dependency> - <groupId>org.apache.sling</groupId> - <artifactId>org.apache.sling.models.api</artifactId> - </dependency> - - <dependency> - <groupId>javax.annotation</groupId> - <artifactId>javax.annotation-api</artifactId> - </dependency> - - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - </dependency> - <dependency> - <groupId>junit-addons</groupId> - <artifactId>junit-addons</artifactId> - </dependency> - <dependency> - <groupId>com.google.code.gson</groupId> - <artifactId>gson</artifactId> - </dependency> - <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>jsr305</artifactId> - </dependency> - <dependency> - <groupId>de.valtech.avs</groupId> - <artifactId>avs.api</artifactId> - <version>${project.version}</version> - </dependency> - </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>osgicheck-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.sling</groupId> + <artifactId>maven-sling-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Sling-Model-Packages> + de.valtech.avs.core + </Sling-Model-Packages> + <Embed-Transitive>true</Embed-Transitive> + <Embed-Dependency> + artifactId=velocity-engine-core + </Embed-Dependency> + <Import-Package> + javax.annotation;version=0.0.0, + * + </Import-Package> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>prepare-package</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + <execution> + <id>post-unit-test</id> + <phase>test</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.owasp</groupId> + <artifactId>dependency-check-maven</artifactId> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>de.valtech.avs</groupId> + <artifactId>avs.api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.velocity</groupId> + <artifactId>velocity-engine-core</artifactId> + </dependency> + <dependency> + <groupId>javax.mail</groupId> + <artifactId>javax.mail-api</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.core</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.cmpn</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.annotation</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + </dependency> + <dependency> + <groupId>javax.jcr</groupId> + <artifactId>jcr</artifactId> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + </dependency> + <dependency> + <groupId>com.adobe.aem</groupId> + <artifactId>uber-jar</artifactId> + <classifier>apis</classifier> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.models.api</artifactId> + </dependency> + + <dependency> + <groupId>javax.annotation</groupId> + <artifactId>javax.annotation-api</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + </dependency> + <dependency> + <groupId>junit-addons</groupId> + <artifactId>junit-addons</artifactId> + </dependency> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + </dependency> + <dependency> + <groupId>de.valtech.avs</groupId> + <artifactId>avs.api</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> </project> 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 index 963fb12..b2e0a03 100644 --- a/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java +++ b/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java @@ -32,6 +32,7 @@ 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; @@ -53,10 +54,13 @@ 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; /** @@ -81,9 +85,14 @@ public class AvsPostFilter implements Filter { @Reference private ServiceResourceResolverService serviceResolverService; + @Reference + private AvsNotificationMailer mailer; + private List<Pattern> includePatterns = new ArrayList<>(); private List<Pattern> excludePatterns = new ArrayList<>(); + private AvsPostFilterConfig config; + /** * Setup service * @@ -103,6 +112,7 @@ public void activate(AvsPostFilterConfig config) { includePatterns.add(Pattern.compile(patternString)); } } + this.config = config; } @Override @@ -121,6 +131,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } List<File> parameterFiles = new ArrayList<>(); List<Part> newParts = new ArrayList<>(); + List<String> fileNames = new ArrayList<>(); while (parts.hasNext()) { Part part = parts.next(); String partContentType = part.getContentType(); @@ -129,6 +140,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha 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); @@ -142,14 +156,20 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha 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 { - String userId = slingRequest.getResourceResolver().adaptTo(Session.class).getUserID(); + 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) { file.delete(); } + sendEmail(slingRequest, result, fileNames); throw new ServletException("Uploaded file contains a virus"); } } catch (AvsException e) { @@ -196,6 +216,29 @@ protected boolean isUrlToIgnore(SlingHttpServletRequest request) { 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<String> fileNames) { + UserProperties properties = slingRequest.adaptTo(UserProperties.class); + List<String> 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 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<String> 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<String> 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<String> 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,<br><br>a virus was detected in your AEM file upload.<br><br>File name: ${FILE_NAME}<br>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/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..1e0c0ff --- /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 @@ +<?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:OsgiConfig" + additionalRecipients="root@localhost" + subject="A virus was found" + body="Dear Sir or Madam,<br><br>a virus was detected in your file upload.<br><br>File name: ${FILE_NAME}<br>Scan report: ${SCAN_OUTPUT}" + isHtml="{Boolean}true" + from="no-reply@example.com" +/> diff --git a/pom.xml b/pom.xml index d059c1d..1b67410 100644 --- a/pom.xml +++ b/pom.xml @@ -1,646 +1,659 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>de.valtech.avs</groupId> - <artifactId>avs</artifactId> - <packaging>pom</packaging> - <version>1.0.0-SNAPSHOT</version> - <name>AVS</name> - <description>AEM Virus Scan</description> - <url>https://github.com/valtech/aem-virus-scan</url> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>de.valtech.avs</groupId> + <artifactId>avs</artifactId> + <packaging>pom</packaging> + <version>1.0.0-SNAPSHOT</version> + <name>AVS</name> + <description>AEM Virus Scan</description> + <url>https://github.com/valtech/aem-virus-scan</url> - <modules> - <module>api</module> - <module>core</module> - <module>ui.apps.structure</module> - <module>ui.apps</module> - <module>examples</module> - </modules> + <modules> + <module>api</module> + <module>core</module> + <module>ui.apps.structure</module> + <module>ui.apps</module> + <module>examples</module> + </modules> - <properties> - <aem.host>localhost</aem.host> - <aem.port>5602</aem.port> - <aem.publish.host>localhost</aem.publish.host> - <aem.publish.port>5705</aem.publish.port> - <sling.user>admin</sling.user> - <sling.password>admin</sling.password> - <vault.user>admin</vault.user> - <vault.password>admin</vault.password> + <properties> + <aem.host>localhost</aem.host> + <aem.port>5602</aem.port> + <aem.publish.host>localhost</aem.publish.host> + <aem.publish.port>5705</aem.publish.port> + <sling.user>admin</sling.user> + <sling.password>admin</sling.password> + <vault.user>admin</vault.user> + <vault.password>admin</vault.password> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> - </properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + </properties> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-source-plugin</artifactId> - <version>3.0.1</version> - <inherited>true</inherited> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-enforcer-plugin</artifactId> - <executions> - <execution> - <id>enforce-maven</id> - <goals> - <goal>enforce</goal> - </goals> - <configuration> - <rules> - <requireMavenVersion> - <version>[3.2.5,)</version> - </requireMavenVersion> - <requireJavaVersion> - <message>Project must be compiled with Java 8 or higher</message> - <version>1.8.0</version> - </requireJavaVersion> - </rules> - </configuration> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - <configuration> - <source>1.8</source> - <target>1.8</target> - <compilerArgs> - <arg>-parameters</arg> - </compilerArgs> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-idea-plugin</artifactId> - <version>2.2.1</version> - <configuration> - <jdkLevel>1.8</jdkLevel> - <linkModules>true</linkModules> - <downloadSources>true</downloadSources> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-eclipse-plugin</artifactId> - <version>2.10</version> - <configuration> - <downloadSources>true</downloadSources> - </configuration> - </plugin> - <plugin> - <groupId>org.sonatype.plugins</groupId> - <artifactId>nexus-staging-maven-plugin</artifactId> - <version>1.6.3</version> - <extensions>true</extensions> - <configuration> - <serverId>ossrh</serverId> - <nexusUrl>https://oss.sonatype.org/</nexusUrl> - <autoReleaseAfterClose>true</autoReleaseAfterClose> - </configuration> - </plugin> - </plugins> - <pluginManagement> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-clean-plugin</artifactId> - <version>3.0.0</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-resources-plugin</artifactId> - <version>3.0.2</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - <version>3.8.1</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-javadoc-plugin</artifactId> - <version>2.9.1</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-project-info-reports-plugin</artifactId> - <version>2.7</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-site-plugin</artifactId> - <version>3.3</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-install-plugin</artifactId> - <version>2.5.2</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <version>2.22.2</version> - </plugin> - <plugin> - <groupId>org.jacoco</groupId> - <artifactId>jacoco-maven-plugin</artifactId> - <version>0.8.5</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-failsafe-plugin</artifactId> - <version>2.20</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-deploy-plugin</artifactId> - <version>2.8.2</version> - </plugin> - <plugin> - <groupId>external.atlassian.jgitflow</groupId> - <artifactId>jgitflow-maven-plugin</artifactId> - <version>1.0-m5.1</version> - <dependencies> - <dependency> - <groupId>com.jcraft</groupId> - <artifactId>jsch</artifactId> - <version>0.1.54</version> - </dependency> - </dependencies> - <configuration> - <enableSshAgent>true</enableSshAgent> - <enableFeatureVersions>true</enableFeatureVersions> - <pushReleases>true</pushReleases> - <autoVersionSubmodules>true</autoVersionSubmodules> - <releaseBranchVersionSuffix>rc</releaseBranchVersionSuffix> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.sling</groupId> - <artifactId>maven-sling-plugin</artifactId> - <version>2.3.8</version> - <configuration> - <slingUrl>http://${aem.host}:${aem.port}/system/console</slingUrl> - <deploymentMethod>WebConsole</deploymentMethod> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.sling</groupId> - <artifactId>htl-maven-plugin</artifactId> - <version>1.2.4-1.4.0</version> - <configuration> - <failOnWarnings>true</failOnWarnings> - </configuration> - <executions> - <execution> - <goals> - <goal>validate</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.felix</groupId> - <artifactId>osgicheck-maven-plugin</artifactId> - <version>0.1.0</version> - <executions> - <execution> - <id>check-bundle</id> - <goals> - <goal>check</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>biz.aQute.bnd</groupId> - <artifactId>bnd-baseline-maven-plugin</artifactId> - <version>4.1.0</version> - <executions> - <execution> - <id>baseline</id> - <goals> - <goal>baseline</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>com.day.jcr.vault</groupId> - <artifactId>content-package-maven-plugin</artifactId> - <version>1.0.2</version> - <configuration> - <targetURL>http://${aem.host}:${aem.port}/crx/packmgr/service.jsp</targetURL> - <failOnError>true</failOnError> - <failOnMissingEmbed>true</failOnMissingEmbed> - <userId>${vault.user}</userId> - <password>${vault.password}</password> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.jackrabbit</groupId> - <artifactId>filevault-package-maven-plugin</artifactId> - <version>1.1.0</version> - <extensions>true</extensions> - </plugin> - <plugin> - <groupId>org.apache.felix</groupId> - <artifactId>maven-bundle-plugin</artifactId> - <version>3.5.0</version> - <inherited>true</inherited> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-enforcer-plugin</artifactId> - <version>1.4.1</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-dependency-plugin</artifactId> - <version>3.0.0</version> - </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>build-helper-maven-plugin</artifactId> - <version>3.0.0</version> - </plugin> - <plugin> - <groupId>org.eclipse.m2e</groupId> - <artifactId>lifecycle-mapping</artifactId> - <version>1.0.0</version> - <configuration> - <lifecycleMappingMetadata> - <pluginExecutions> - <pluginExecution> - <pluginExecutionFilter> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-enforcer-plugin</artifactId> - <versionRange>[1.0.0,)</versionRange> - <goals> - <goal>enforce</goal> - </goals> - </pluginExecutionFilter> - <action> - <ignore /> - </action> - </pluginExecution> - <pluginExecution> - <pluginExecutionFilter> - <groupId> - org.apache.maven.plugins - </groupId> - <artifactId> - maven-dependency-plugin - </artifactId> - <versionRange> - [2.2,) - </versionRange> - <goals> - <goal>copy-dependencies</goal> - <goal>unpack</goal> - </goals> - </pluginExecutionFilter> - <action> - <ignore /> - </action> - </pluginExecution> - <pluginExecution> - <pluginExecutionFilter> - <groupId> - org.codehaus.mojo - </groupId> - <artifactId> - build-helper-maven-plugin - </artifactId> - <versionRange> - [1.5,) - </versionRange> - <goals> - <goal> - reserve-network-port - </goal> - </goals> - </pluginExecutionFilter> - <action> - <ignore /> - </action> - </pluginExecution> - </pluginExecutions> - </lifecycleMappingMetadata> - </configuration> - </plugin> - <plugin> - <groupId>org.owasp</groupId> - <artifactId>dependency-check-maven</artifactId> - <version>5.2.1</version> - <configuration> - <failBuildOnCVSS>0</failBuildOnCVSS> - <failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability> - <skipProvidedScope>true</skipProvidedScope> - </configuration> - <executions> - <execution> - <goals> - <goal>check</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </pluginManagement> - </build> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.0.1</version> + <inherited>true</inherited> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.0.2</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + <executions> + <execution> + <id>enforce-maven</id> + <goals> + <goal>enforce</goal> + </goals> + <configuration> + <rules> + <requireMavenVersion> + <version>[3.2.5,)</version> + </requireMavenVersion> + <requireJavaVersion> + <message>Project must be compiled with Java 8 or higher</message> + <version>1.8.0</version> + </requireJavaVersion> + </rules> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.8</source> + <target>1.8</target> + <compilerArgs> + <arg>-parameters</arg> + </compilerArgs> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-idea-plugin</artifactId> + <version>2.2.1</version> + <configuration> + <jdkLevel>1.8</jdkLevel> + <linkModules>true</linkModules> + <downloadSources>true</downloadSources> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-eclipse-plugin</artifactId> + <version>2.10</version> + <configuration> + <downloadSources>true</downloadSources> + </configuration> + </plugin> + <plugin> + <groupId>org.sonatype.plugins</groupId> + <artifactId>nexus-staging-maven-plugin</artifactId> + <version>1.6.3</version> + <extensions>true</extensions> + <configuration> + <serverId>ossrh</serverId> + <nexusUrl>https://oss.sonatype.org/</nexusUrl> + <autoReleaseAfterClose>true</autoReleaseAfterClose> + </configuration> + </plugin> + </plugins> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-clean-plugin</artifactId> + <version>3.0.0</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>3.0.2</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.9.1</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-project-info-reports-plugin</artifactId> + <version>2.7</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-site-plugin</artifactId> + <version>3.3</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-install-plugin</artifactId> + <version>2.5.2</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>2.22.2</version> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.5</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <version>2.20</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>2.8.2</version> + </plugin> + <plugin> + <groupId>external.atlassian.jgitflow</groupId> + <artifactId>jgitflow-maven-plugin</artifactId> + <version>1.0-m5.1</version> + <dependencies> + <dependency> + <groupId>com.jcraft</groupId> + <artifactId>jsch</artifactId> + <version>0.1.54</version> + </dependency> + </dependencies> + <configuration> + <enableSshAgent>true</enableSshAgent> + <enableFeatureVersions>true</enableFeatureVersions> + <pushReleases>true</pushReleases> + <autoVersionSubmodules>true</autoVersionSubmodules> + <releaseBranchVersionSuffix>rc</releaseBranchVersionSuffix> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.sling</groupId> + <artifactId>maven-sling-plugin</artifactId> + <version>2.3.8</version> + <configuration> + <slingUrl>http://${aem.host}:${aem.port}/system/console</slingUrl> + <deploymentMethod>WebConsole</deploymentMethod> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.sling</groupId> + <artifactId>htl-maven-plugin</artifactId> + <version>1.2.4-1.4.0</version> + <configuration> + <failOnWarnings>true</failOnWarnings> + </configuration> + <executions> + <execution> + <goals> + <goal>validate</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>osgicheck-maven-plugin</artifactId> + <version>0.1.0</version> + <executions> + <execution> + <id>check-bundle</id> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-baseline-maven-plugin</artifactId> + <version>4.1.0</version> + <executions> + <execution> + <id>baseline</id> + <goals> + <goal>baseline</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>com.day.jcr.vault</groupId> + <artifactId>content-package-maven-plugin</artifactId> + <version>1.0.2</version> + <configuration> + <targetURL>http://${aem.host}:${aem.port}/crx/packmgr/service.jsp</targetURL> + <failOnError>true</failOnError> + <failOnMissingEmbed>true</failOnMissingEmbed> + <userId>${vault.user}</userId> + <password>${vault.password}</password> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.jackrabbit</groupId> + <artifactId>filevault-package-maven-plugin</artifactId> + <version>1.1.0</version> + <extensions>true</extensions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <version>3.5.0</version> + <inherited>true</inherited> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + <version>1.4.1</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <version>3.0.0</version> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <version>3.0.0</version> + </plugin> + <plugin> + <groupId>org.eclipse.m2e</groupId> + <artifactId>lifecycle-mapping</artifactId> + <version>1.0.0</version> + <configuration> + <lifecycleMappingMetadata> + <pluginExecutions> + <pluginExecution> + <pluginExecutionFilter> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + <versionRange>[1.0.0,)</versionRange> + <goals> + <goal>enforce</goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore /> + </action> + </pluginExecution> + <pluginExecution> + <pluginExecutionFilter> + <groupId> + org.apache.maven.plugins + </groupId> + <artifactId> + maven-dependency-plugin + </artifactId> + <versionRange> + [2.2,) + </versionRange> + <goals> + <goal>copy-dependencies</goal> + <goal>unpack</goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore /> + </action> + </pluginExecution> + <pluginExecution> + <pluginExecutionFilter> + <groupId> + org.codehaus.mojo + </groupId> + <artifactId> + build-helper-maven-plugin + </artifactId> + <versionRange> + [1.5,) + </versionRange> + <goals> + <goal> + reserve-network-port + </goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore /> + </action> + </pluginExecution> + </pluginExecutions> + </lifecycleMappingMetadata> + </configuration> + </plugin> + <plugin> + <groupId>org.owasp</groupId> + <artifactId>dependency-check-maven</artifactId> + <version>5.2.1</version> + <configuration> + <failBuildOnCVSS>0</failBuildOnCVSS> + <failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability> + <skipProvidedScope>true</skipProvidedScope> + </configuration> + <executions> + <execution> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </pluginManagement> + </build> - <profiles> - <profile> - <id>autoInstallBundle</id> - <activation> - <activeByDefault>false</activeByDefault> - </activation> - <build> - <pluginManagement> - <plugins> - <plugin> - <groupId>org.apache.sling</groupId> - <artifactId>maven-sling-plugin</artifactId> - <executions> - <execution> - <id>install-bundle</id> - <goals> - <goal>install</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </pluginManagement> - </build> - </profile> + <profiles> + <profile> + <id>autoInstallBundle</id> + <activation> + <activeByDefault>false</activeByDefault> + </activation> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.sling</groupId> + <artifactId>maven-sling-plugin</artifactId> + <executions> + <execution> + <id>install-bundle</id> + <goals> + <goal>install</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </pluginManagement> + </build> + </profile> - <profile> - <id>autoInstallPackage</id> - <activation> - <activeByDefault>false</activeByDefault> - </activation> - <build> - <pluginManagement> - <plugins> - <plugin> - <groupId>com.day.jcr.vault</groupId> - <artifactId>content-package-maven-plugin</artifactId> - <executions> - <execution> - <id>install-package</id> - <goals> - <goal>install</goal> - </goals> - <configuration> - <targetURL>http://${aem.host}:${aem.port}/crx/packmgr/service.jsp</targetURL> - </configuration> - </execution> - </executions> - </plugin> - </plugins> - </pluginManagement> - </build> - </profile> + <profile> + <id>autoInstallPackage</id> + <activation> + <activeByDefault>false</activeByDefault> + </activation> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>com.day.jcr.vault</groupId> + <artifactId>content-package-maven-plugin</artifactId> + <executions> + <execution> + <id>install-package</id> + <goals> + <goal>install</goal> + </goals> + <configuration> + <targetURL>http://${aem.host}:${aem.port}/crx/packmgr/service.jsp</targetURL> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </pluginManagement> + </build> + </profile> - <profile> - <id>autoInstallPackagePublish</id> - <activation> - <activeByDefault>false</activeByDefault> - </activation> - <build> - <pluginManagement> - <plugins> - <plugin> - <groupId>com.day.jcr.vault</groupId> - <artifactId>content-package-maven-plugin</artifactId> - <executions> - <execution> - <id>install-package-publish</id> - <goals> - <goal>install</goal> - </goals> - <configuration> - <targetURL>http://${aem.publish.host}:${aem.publish.port}/crx/packmgr/service.jsp - </targetURL> - </configuration> - </execution> - </executions> - </plugin> - </plugins> - </pluginManagement> - </build> - </profile> - - <profile> - <id>release</id> - <activation> - <property> - <name>performRelease</name> - </property> - </activation> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-gpg-plugin</artifactId> - <version>1.6</version> - <executions> - <execution> - <id>sign-artifacts</id> - <phase>verify</phase> - <goals> - <goal>sign</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> - </profile> + <profile> + <id>autoInstallPackagePublish</id> + <activation> + <activeByDefault>false</activeByDefault> + </activation> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>com.day.jcr.vault</groupId> + <artifactId>content-package-maven-plugin</artifactId> + <executions> + <execution> + <id>install-package-publish</id> + <goals> + <goal>install</goal> + </goals> + <configuration> + <targetURL>http://${aem.publish.host}:${aem.publish.port}/crx/packmgr/service.jsp + </targetURL> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </pluginManagement> + </build> + </profile> - </profiles> + <profile> + <id>release</id> + <activation> + <property> + <name>performRelease</name> + </property> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <version>1.6</version> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> - <dependencyManagement> - <dependencies> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>osgi.core</artifactId> - <version>6.0.0</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>osgi.cmpn</artifactId> - <version>6.0.0</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>osgi.annotation</artifactId> - <version>6.0.1</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - <version>1.7.25</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>com.adobe.aem</groupId> - <artifactId>uber-jar</artifactId> - <version>6.4.3</version> - <classifier>apis</classifier> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>org.apache.sling</groupId> - <artifactId>org.apache.sling.models.api</artifactId> - <version>1.3.6</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>javax.servlet</groupId> - <artifactId>javax.servlet-api</artifactId> - <version>3.1.0</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>javax.servlet.jsp</groupId> - <artifactId>jsp-api</artifactId> - <version>2.1</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>javax.jcr</groupId> - <artifactId>jcr</artifactId> - <version>2.0</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>javax.annotation</groupId> - <artifactId>javax.annotation-api</artifactId> - <version>1.3.2</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - <version>3.6</version> - </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.12</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-simple</artifactId> - <version>1.7.21</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <version>2.28.2</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>junit-addons</groupId> - <artifactId>junit-addons</artifactId> - <version>1.4</version> - <scope>test</scope> - </dependency> - <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> - <dependency> - <groupId>com.google.code.gson</groupId> - <artifactId>gson</artifactId> - <version>2.8.2</version> - </dependency> - <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>jsr305</artifactId> - <version>2.0.1</version> - <scope>compile</scope> - </dependency> - </dependencies> - </dependencyManagement> - - <licenses> - <license> - <name>MIT License</name> - <url>http://www.opensource.org/licenses/mit-license.php</url> - </license> - </licenses> - - <developers> - <developer> - <name>Roland Gruber</name> - <email>roland.gruber@valtech.com</email> - <organization>Valtech GmbH</organization> - <organizationUrl>https://www.valtech.de/</organizationUrl> - </developer> - </developers> - - <scm> - <connection>scm:git:https://github.com/valtech/aem-virus-scan.git</connection> - <developerConnection>scm:git:git@github.com:valtech/aem-virus-scan.git</developerConnection> - <url>https://github.com/valtech/aem-virus-scan</url> - </scm> - - <issueManagement> - <system>Github</system> - <url>https://github.com/valtech/aem-virus-scan/issues</url> - </issueManagement> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - <repository> - <id>ossrh</id> - <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> - </repository> - </distributionManagement> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.apache.velocity</groupId> + <artifactId>velocity-engine-core</artifactId> + <version>2.2</version> + </dependency> + <dependency> + <groupId>javax.mail</groupId> + <artifactId>javax.mail-api</artifactId> + <version>1.5.6</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.core</artifactId> + <version>6.0.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.cmpn</artifactId> + <version>6.0.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.annotation</artifactId> + <version>6.0.1</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.25</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.adobe.aem</groupId> + <artifactId>uber-jar</artifactId> + <version>6.4.3</version> + <classifier>apis</classifier> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.models.api</artifactId> + <version>1.3.6</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>3.1.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>javax.servlet.jsp</groupId> + <artifactId>jsp-api</artifactId> + <version>2.1</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>javax.jcr</groupId> + <artifactId>jcr</artifactId> + <version>2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>javax.annotation</groupId> + <artifactId>javax.annotation-api</artifactId> + <version>1.3.2</version> + <scope>provided</scope> + </dependency> - <repositories> - <repository> - <id>adobe</id> - <name>Adobe Public Repository</name> - <url>http://repo.adobe.com/nexus/content/groups/public/</url> - <layout>default</layout> - </repository> - <repository> - <id>central</id> - <url>http://repo1.maven.org/maven2</url> - <layout>default</layout> - <snapshots> - <enabled>false</enabled> - </snapshots> - </repository> - </repositories> - <pluginRepositories> - <pluginRepository> - <id>adobe</id> - <name>Adobe Public Repository</name> - <url>http://repo.adobe.com/nexus/content/groups/public/</url> - <layout>default</layout> - </pluginRepository> - </pluginRepositories> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.6</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>1.7.21</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>2.28.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit-addons</groupId> + <artifactId>junit-addons</artifactId> + <version>1.4</version> + <scope>test</scope> + </dependency> + <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.8.2</version> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>2.0.1</version> + <scope>compile</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <licenses> + <license> + <name>MIT License</name> + <url>http://www.opensource.org/licenses/mit-license.php</url> + </license> + </licenses> + + <developers> + <developer> + <name>Roland Gruber</name> + <email>roland.gruber@valtech.com</email> + <organization>Valtech GmbH</organization> + <organizationUrl>https://www.valtech.de/</organizationUrl> + </developer> + </developers> + + <scm> + <connection>scm:git:https://github.com/valtech/aem-virus-scan.git</connection> + <developerConnection>scm:git:git@github.com:valtech/aem-virus-scan.git</developerConnection> + <url>https://github.com/valtech/aem-virus-scan</url> + </scm> + + <issueManagement> + <system>Github</system> + <url>https://github.com/valtech/aem-virus-scan/issues</url> + </issueManagement> + + <distributionManagement> + <snapshotRepository> + <id>ossrh</id> + <url>https://oss.sonatype.org/content/repositories/snapshots</url> + </snapshotRepository> + <repository> + <id>ossrh</id> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> + </distributionManagement> + + <repositories> + <repository> + <id>adobe</id> + <name>Adobe Public Repository</name> + <url>http://repo.adobe.com/nexus/content/groups/public/</url> + <layout>default</layout> + </repository> + <repository> + <id>central</id> + <url>http://repo1.maven.org/maven2</url> + <layout>default</layout> + <snapshots> + <enabled>false</enabled> + </snapshots> + </repository> + </repositories> + <pluginRepositories> + <pluginRepository> + <id>adobe</id> + <name>Adobe Public Repository</name> + <url>http://repo.adobe.com/nexus/content/groups/public/</url> + <layout>default</layout> + </pluginRepository> + </pluginRepositories> </project> From f777b0c6fe08940d108c0a34dfbdd5c921da2890 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Thu, 6 Aug 2020 14:38:54 +0200 Subject: [PATCH 13/25] thumbnail --- .../META-INF/vault/definition/.content.xml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/src/main/content/META-INF/vault/definition/.content.xml 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright 2012 Adobe Systems Incorporated + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<jcr:root xmlns:vlt="http://www.day.com/jcr/vault/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="vlt:PackageDefinition"> + <thumbnail.png/> +</jcr:root> \ No newline at end of file From de43864d0dad104fd9d559a8b459da637dcae58c Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Thu, 6 Aug 2020 14:40:00 +0200 Subject: [PATCH 14/25] thumbnail --- ui.apps/src/main/content/.gitignore | 1 - .../META-INF/vault/definition/.content.xml | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) delete mode 100644 ui.apps/src/main/content/.gitignore create mode 100644 ui.apps/src/main/content/META-INF/vault/definition/.content.xml diff --git a/ui.apps/src/main/content/.gitignore b/ui.apps/src/main/content/.gitignore deleted file mode 100644 index 3385916..0000000 --- a/ui.apps/src/main/content/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/META-INF/ 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright 2012 Adobe Systems Incorporated + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<jcr:root xmlns:vlt="http://www.day.com/jcr/vault/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" + jcr:primaryType="vlt:PackageDefinition"> + <thumbnail.png/> +</jcr:root> \ No newline at end of file From d690dde1827d486d8300da8ab204de1db86c94d3 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Thu, 6 Aug 2020 16:03:13 +0200 Subject: [PATCH 15/25] documentation --- README.md | 194 ++++++- .../PurgeHistoryConfiguration.java | 2 +- .../service/scanner/ClamScannerConfig.java | 2 +- docs/developers.md | 35 ++ docs/formatter/eclipse-avs.xml | 318 ++++++++++++ docs/formatter/intellij-avs.xml | 476 ++++++++++++++++++ docs/images/healthcheck.png | Bin 0 -> 19910 bytes docs/images/history.png | Bin 0 -> 28884 bytes docs/images/manualScan.png | Bin 0 -> 27068 bytes docs/images/tools.png | Bin 0 -> 49561 bytes ...ch.avs.core.mail.AvsNotificationMailer.xml | 2 +- ....avs.core.maintenance.PurgeHistoryTask.xml | 4 + pom.xml | 4 +- 13 files changed, 1030 insertions(+), 7 deletions(-) create mode 100644 docs/developers.md create mode 100644 docs/formatter/eclipse-avs.xml create mode 100644 docs/formatter/intellij-avs.xml create mode 100644 docs/images/healthcheck.png create mode 100644 docs/images/history.png create mode 100644 docs/images/manualScan.png create mode 100644 docs/images/tools.png create mode 100644 examples/src/main/content/jcr_root/apps/valtech/avs-examples/config/de.valtech.avs.core.maintenance.PurgeHistoryTask.xml 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 + +<a name="requirements"></a> + +# Requirements + +AVS requires Java 8 and AEM 6.4 or above. + +| AEM Version | AVS | +| ------------- | --------- | +| 6.4 | 1.x | +| 6.5 | 1.x | + +<a name="installation"></a> + +# 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 + <dependency> + <groupId>de.valtech.avs</groupId> + <artifactId>avs.ui.apps</artifactId> + <version>LATEST</version> + <type>zip</type> + </dependency> +``` + + +## 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. + +<img src="docs/images/tools.png"> + + +## 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. + +<img src="docs/images/manualScan.png"> + + +## History + +This shows the history of the last found infections. Scans that did not lead to an alert are not listed. + +<img src="docs/images/history.png"> + +# Configuration + +You can see an example for each configuration in [example package](/examples/src/main/content/jcr_root/apps/valtech/avs-examples/config). + +<a name="conf_filter"></a> + +## 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 + +``` +<?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:OsgiConfig" + includePatterns="[/content/dam/.*]" + excludePatterns="[]" +/> +``` + +<a name="conf_mail"></a> + +## 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 + +``` +<?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:OsgiConfig" + subject="A virus was found" + body="Dear Sir or Madam,<br><br>a virus was detected in your file upload.<br><br>File name: ${FILE_NAME}<br>Scan report: ${SCAN_OUTPUT}" + isHtml="{Boolean}true" + from="no-reply@example.com" + additionalRecipients="[root@localhost]" +/> +``` + +<a name="conf_clam"></a> + +## 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 + +``` +<?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:OsgiConfig" + command="/usr/local/bin/clamdscan --infected --no-summary" +/> +``` + +<a name="conf_history"></a> + +## 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 + +``` +<?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:OsgiConfig" + daysToKeep="30" +/> +``` + +# 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). + +<img src="docs/images/healthcheck.png"> + +<a name="api"></a> + +# API Documentation + +TODO + +<a name="license"></a> + +# License + +The AVS tool is licensed under the [MIT LICENSE](LICENSE). + +<a name="changelog"></a> + +# Changelog + +Please see our [history file](HISTORY). + +<a name="developers"></a> + +# Developers + +See our [developer zone](docs/developers.md). 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 index f469a65..614f4fb 100644 --- a/core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryConfiguration.java +++ b/core/src/main/java/de/valtech/avs/core/maintenance/PurgeHistoryConfiguration.java @@ -34,6 +34,6 @@ @AttributeDefinition(type = AttributeType.INTEGER, name = "Days to keep", description = "Entries younger than this will not be removed") - int daysToKeep(); + int daysToKeep() default 90; } 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 index 5d9ee4f..e49926e 100644 --- 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 @@ -38,7 +38,7 @@ * @return command */ @AttributeDefinition(name = "Scan command", - description = "Command to run to scan a single file. The file name will be added at the end of the 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/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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<profiles version="14"> +<profile kind="CodeFormatterProfile" name="AVS" version="14"> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/> +<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.compiler.release" value="enabled"/> +<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="2"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/> +<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="100"/> +<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/> +<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="3"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/> +<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/> +<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/> +<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/> +<setting id="org.eclipse.jdt.core.compiler.source" value="10"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="10"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/> +<setting id="org.eclipse.jdt.core.compiler.compliance" value="10"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/> +<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/> +<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/> +<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="130"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/> +<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/> +</profile> +</profiles> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<code_scheme name="Code Style"> + <option name="JAVA_INDENT_OPTIONS"> + <value> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="8" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </value> + </option> + <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" /> + <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" /> + <option name="IMPORT_LAYOUT_TABLE"> + <value> + <package name="com.google" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="android" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="antenna" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="antlr" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="ar" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="asposewobfuscated" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="asquare" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="atg" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="au" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="beaver" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="bibtex" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="bmsi" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="bsh" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="ccl" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="cern" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="ChartDirector" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="checkers" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="com" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="COM" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="common" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="contribs" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="corejava" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="cryptix" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="cybervillains" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="dalvik" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="danbikel" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="de" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="EDU" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="eg" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="eu" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="examples" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="fat" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="fit" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="fitlibrary" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="fmpp" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="freemarker" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="gnu" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="groovy" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="groovyjarjarantlr" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="groovyjarjarasm" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="hak" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="hep" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="ie" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="imageinfo" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="info" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="it" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jal" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="Jama" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="japa" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="japacheckers" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jas" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jasmin" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="javancss" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="javanet" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="javassist" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="javazoom" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="java_cup" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jcifs" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jetty" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="JFlex" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jj2000" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jline" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jp" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="JSci" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jsr166y" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="junit" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jxl" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="jxxload_help" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="kawa" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="kea" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="libcore" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="libsvm" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="lti" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="memetic" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="mt" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="mx4j" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="net" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="netscape" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="nl" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="nu" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="oauth" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="ognl" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="opennlp" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="oracle" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="org" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="penn2dg" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="pennconverter" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="pl" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="prefuse" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="proguard" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="repackage" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="scm" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="se" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="serp" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="simple" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="soot" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="sqlj" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="src" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="ssa" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="sun" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="sunlabs" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="tcl" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="testdata" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="testshell" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="testsuite" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="twitter4j" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="uk" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="ViolinStrings" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="weka" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="wet" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="winstone" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="woolfel" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="wowza" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="java" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="javax" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="" withSubpackages="true" static="true" /> + </value> + </option> + <option name="RIGHT_MARGIN" value="130" /> + <option name="JD_P_AT_EMPTY_LINES" value="false" /> + <option name="JD_KEEP_EMPTY_PARAMETER" value="false" /> + <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" /> + <option name="JD_KEEP_EMPTY_RETURN" value="false" /> + <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" /> + <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> + <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" /> + <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> + <option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" /> + <option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" /> + <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" /> + <option name="ALIGN_MULTILINE_THROWS_LIST" value="true" /> + <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" /> + <option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="true" /> + <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" /> + <option name="CALL_PARAMETERS_WRAP" value="1" /> + <option name="METHOD_PARAMETERS_WRAP" value="1" /> + <option name="EXTENDS_LIST_WRAP" value="1" /> + <option name="THROWS_LIST_WRAP" value="1" /> + <option name="EXTENDS_KEYWORD_WRAP" value="1" /> + <option name="THROWS_KEYWORD_WRAP" value="1" /> + <option name="METHOD_CALL_CHAIN_WRAP" value="1" /> + <option name="BINARY_OPERATION_WRAP" value="1" /> + <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" /> + <option name="TERNARY_OPERATION_WRAP" value="1" /> + <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" /> + <option name="FOR_STATEMENT_WRAP" value="1" /> + <option name="ARRAY_INITIALIZER_WRAP" value="1" /> + <option name="ASSIGNMENT_WRAP" value="5" /> + <option name="WRAP_COMMENTS" value="true" /> + <option name="IF_BRACE_FORCE" value="3" /> + <option name="DOWHILE_BRACE_FORCE" value="3" /> + <option name="WHILE_BRACE_FORCE" value="3" /> + <option name="FOR_BRACE_FORCE" value="3" /> + <ADDITIONAL_INDENT_OPTIONS fileType="css"> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="haml"> + <option name="INDENT_SIZE" value="2" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="java"> + <option name="INDENT_SIZE" value="2" /> + <option name="CONTINUATION_INDENT_SIZE" value="4" /> + <option name="TAB_SIZE" value="8" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="js"> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="4" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="jsp"> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="php"> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="sass"> + <option name="INDENT_SIZE" value="2" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="xml"> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="yml"> + <option name="INDENT_SIZE" value="2" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + <option name="USE_RELATIVE_INDENTS" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <codeStyleSettings language="ECMA Script Level 4"> + <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" /> + <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> + <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" /> + <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> + <option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" /> + <option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" /> + <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" /> + <option name="ALIGN_MULTILINE_THROWS_LIST" value="true" /> + <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" /> + <option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="true" /> + <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" /> + <option name="CALL_PARAMETERS_WRAP" value="1" /> + <option name="METHOD_PARAMETERS_WRAP" value="1" /> + <option name="EXTENDS_LIST_WRAP" value="1" /> + <option name="THROWS_LIST_WRAP" value="1" /> + <option name="EXTENDS_KEYWORD_WRAP" value="1" /> + <option name="THROWS_KEYWORD_WRAP" value="1" /> + <option name="METHOD_CALL_CHAIN_WRAP" value="1" /> + <option name="BINARY_OPERATION_WRAP" value="1" /> + <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" /> + <option name="TERNARY_OPERATION_WRAP" value="1" /> + <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" /> + <option name="FOR_STATEMENT_WRAP" value="1" /> + <option name="ARRAY_INITIALIZER_WRAP" value="1" /> + <option name="ASSIGNMENT_WRAP" value="5" /> + <option name="WRAP_COMMENTS" value="true" /> + <option name="IF_BRACE_FORCE" value="3" /> + <option name="DOWHILE_BRACE_FORCE" value="3" /> + <option name="WHILE_BRACE_FORCE" value="3" /> + <option name="FOR_BRACE_FORCE" value="3" /> + <option name="PARENT_SETTINGS_INSTALLED" value="true" /> + </codeStyleSettings> + <codeStyleSettings language="JavaScript"> + <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" /> + <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> + <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" /> + <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> + <option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" /> + <option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" /> + <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" /> + <option name="ALIGN_MULTILINE_THROWS_LIST" value="true" /> + <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" /> + <option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="true" /> + <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" /> + <option name="CALL_PARAMETERS_WRAP" value="1" /> + <option name="METHOD_PARAMETERS_WRAP" value="1" /> + <option name="EXTENDS_LIST_WRAP" value="1" /> + <option name="THROWS_LIST_WRAP" value="1" /> + <option name="EXTENDS_KEYWORD_WRAP" value="1" /> + <option name="THROWS_KEYWORD_WRAP" value="1" /> + <option name="METHOD_CALL_CHAIN_WRAP" value="1" /> + <option name="BINARY_OPERATION_WRAP" value="1" /> + <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" /> + <option name="TERNARY_OPERATION_WRAP" value="1" /> + <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" /> + <option name="FOR_STATEMENT_WRAP" value="1" /> + <option name="ARRAY_INITIALIZER_WRAP" value="1" /> + <option name="ASSIGNMENT_WRAP" value="5" /> + <option name="WRAP_COMMENTS" value="true" /> + <option name="IF_BRACE_FORCE" value="3" /> + <option name="DOWHILE_BRACE_FORCE" value="3" /> + <option name="WHILE_BRACE_FORCE" value="3" /> + <option name="FOR_BRACE_FORCE" value="3" /> + <option name="PARENT_SETTINGS_INSTALLED" value="true" /> + </codeStyleSettings> + <codeStyleSettings language="PHP"> + <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" /> + <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> + <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" /> + <option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" /> + <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" /> + <option name="ALIGN_MULTILINE_THROWS_LIST" value="true" /> + <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" /> + <option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="true" /> + <option name="CALL_PARAMETERS_WRAP" value="1" /> + <option name="METHOD_PARAMETERS_WRAP" value="1" /> + <option name="EXTENDS_LIST_WRAP" value="1" /> + <option name="THROWS_LIST_WRAP" value="1" /> + <option name="EXTENDS_KEYWORD_WRAP" value="1" /> + <option name="THROWS_KEYWORD_WRAP" value="1" /> + <option name="METHOD_CALL_CHAIN_WRAP" value="1" /> + <option name="BINARY_OPERATION_WRAP" value="1" /> + <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" /> + <option name="TERNARY_OPERATION_WRAP" value="1" /> + <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" /> + <option name="FOR_STATEMENT_WRAP" value="1" /> + <option name="ARRAY_INITIALIZER_WRAP" value="1" /> + <option name="ASSIGNMENT_WRAP" value="5" /> + <option name="WRAP_COMMENTS" value="true" /> + <option name="IF_BRACE_FORCE" value="3" /> + <option name="DOWHILE_BRACE_FORCE" value="3" /> + <option name="WHILE_BRACE_FORCE" value="3" /> + <option name="FOR_BRACE_FORCE" value="3" /> + <option name="PARENT_SETTINGS_INSTALLED" value="true" /> + </codeStyleSettings> +</code_scheme> + diff --git a/docs/images/healthcheck.png b/docs/images/healthcheck.png new file mode 100644 index 0000000000000000000000000000000000000000..f3cae88e22c41d16ca5b33e593aed715d6e6661a GIT binary patch literal 19910 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 literal 0 HcmV?d00001 diff --git a/docs/images/history.png b/docs/images/history.png new file mode 100644 index 0000000000000000000000000000000000000000..9c4bd4838130c8f4c7dcc09a21dd7961f97f73bf GIT binary patch literal 28884 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�|I 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�?{a-Nk3c`=~rk~<l@CJwo}^W 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 literal 0 HcmV?d00001 diff --git a/docs/images/manualScan.png b/docs/images/manualScan.png new file mode 100644 index 0000000000000000000000000000000000000000..6ccc5987b3e1043a835170ad5f33495b50385ab0 GIT binary patch literal 27068 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 literal 0 HcmV?d00001 diff --git a/docs/images/tools.png b/docs/images/tools.png new file mode 100644 index 0000000000000000000000000000000000000000..ba7b7b6efed5c705dcd3925545d26efc739169bb GIT binary patch literal 49561 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 literal 0 HcmV?d00001 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 index 1e0c0ff..503f4a1 100644 --- 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 @@ -1,8 +1,8 @@ <?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:OsgiConfig" - additionalRecipients="root@localhost" subject="A virus was found" body="Dear Sir or Madam,<br><br>a virus was detected in your file upload.<br><br>File name: ${FILE_NAME}<br>Scan report: ${SCAN_OUTPUT}" isHtml="{Boolean}true" from="no-reply@example.com" + additionalRecipients="[root@localhost]" /> 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 @@ +<?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:OsgiConfig" + daysToKeep="30" +/> diff --git a/pom.xml b/pom.xml index 1b67410..4a84b86 100644 --- a/pom.xml +++ b/pom.xml @@ -21,9 +21,9 @@ <properties> <aem.host>localhost</aem.host> - <aem.port>5602</aem.port> + <aem.port>4502</aem.port> <aem.publish.host>localhost</aem.publish.host> - <aem.publish.port>5705</aem.publish.port> + <aem.publish.port>4503</aem.publish.port> <sling.user>admin</sling.user> <sling.password>admin</sling.password> <vault.user>admin</vault.user> From 1263ba6804c7b35ce11594d22f0175c4d6aacbb1 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Thu, 6 Aug 2020 16:28:28 +0200 Subject: [PATCH 16/25] travis --- .travis.yml | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b5f432..ed1eb97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,16 @@ language: java - jdk: - - openjdk9 - +- openjdk9 addons: sonarcloud: - organization: "valtech_avs" - token: - secure: TODO - + organization: valtech_avs script: - - mvn test javadoc:javadoc -B - - sonar-scanner - +- mvn test javadoc:javadoc -B +- sonar-scanner cache: directories: - - '$HOME/.sonar/cache' - - '$HOME/.m2' + - "$HOME/.sonar/cache" + - "$HOME/.m2" +env: + global: + secure: jAVDdEYj3Nfatl6AceGgMpAangeIHaUQdYL0u40I0FJHkkC6GxoaHf1TMy3k74nRobTwN4HDctQT3/nczhStnKZ20aE19oppYzvjhrAmhddlMTbwvn2Wlh3hQX0Ejqc6PoOrqyUlpZdHTXS0vW7dVzpTyaxtAw+z8RCCIe/+kZeSF3Noy1dsB+dBI657tC2TIV+sfXref7v6Ly1BwTJp1827u/OE9d+S3PR/GwtAKNfQQFODj5W0OLmFaMf7pEEPML4czX1AfaxYBrW9d1wmeTeoTvkU6AkeBlzHWQUM9XinObIIr/PahUZExX9Yf/K1GWHyoAEgsUVOOJe/mqEo1JQG2LNhyVWBrIcAXdFo0gjYPudIBc7n+IVnq7OZjjB+cg+I2yBdHSWwkaHHgeD4tpp8d/dJMmoMP8gBv7a10W5VnuHLxv2/j9y5Z9HGUoQW7mZ4X7l8eeCra/Qtt+yIdx7wZ99FCkt7FXfyjWSCu54xZQFRdCu2JTvE6ZFLAYcGUlszMltqcJmLdUIh24zAvqHVcKSnzBTmHoax1897i4m/yM5qoW6Tvak1UcwG/mWkzNTZaMr998PR+49CtbRfKjoc/HvqvxA3T8IhXfmbh4Ee9NZcpWmUZeFBKvh0HBXP8NRmpSpdNFrKX0x1lbpH8eZwLuFS1jo7Pz1XHYgscX0= From c17d05841c2be1cb4f49d50c49713cdb697ad37d Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 7 Aug 2020 15:08:43 +0200 Subject: [PATCH 17/25] JavaDoc --- .../de/valtech/avs/core/filter/AvsPostFilter.java | 2 +- .../de/valtech/avs/core/history/HistoryEntryImpl.java | 11 ++++++----- .../de/valtech/avs/core/history/HistoryService.java | 3 +-- .../valtech/avs/core/jmx/AntiVirusScannerMBean.java | 1 + .../avs/core/service/scanner/ClamScannerEngine.java | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) 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 index b2e0a03..062996a 100644 --- a/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java +++ b/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java @@ -96,7 +96,7 @@ public class AvsPostFilter implements Filter { /** * Setup service * - * @param avconfig configuration + * @param config configuration */ @Activate public void activate(AvsPostFilterConfig config) { 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 index fe748c1..bec5168 100644 --- a/core/src/main/java/de/valtech/avs/core/history/HistoryEntryImpl.java +++ b/core/src/main/java/de/valtech/avs/core/history/HistoryEntryImpl.java @@ -44,11 +44,12 @@ public class HistoryEntryImpl implements HistoryEntry { /** * Constructor * - * @param time time - * @param output output text - * @param clean is clean - * @param path path that was scanned - * @param userId user id + * @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(); 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 index 72e9069..b0eba00 100644 --- a/core/src/main/java/de/valtech/avs/core/history/HistoryService.java +++ b/core/src/main/java/de/valtech/avs/core/history/HistoryService.java @@ -75,7 +75,6 @@ public class HistoryService { * * @param resolver resource resolver * @param result scan result - * @return history entry * @throws AvsException error setting up entry */ public void createHistoryEntry(ResourceResolver resolver, ScanResult result) throws AvsException { @@ -358,7 +357,7 @@ private void deleteRecursive(Iterator<Resource> resources, Calendar calendar, in * Self test of history. Checks if the history node exists. * * @param resolver resource resolver - * @throws AecuException check failed + * @throws AvsException check failed */ public void selfCheck(ResourceResolver resolver) throws AvsException { Resource base = resolver.getResource(HISTORY_BASE); 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 index 2168ce3..87c3d86 100644 --- a/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBean.java +++ b/core/src/main/java/de/valtech/avs/core/jmx/AntiVirusScannerMBean.java @@ -39,6 +39,7 @@ public interface AntiVirusScannerMBean { * * @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/service/scanner/ClamScannerEngine.java b/core/src/main/java/de/valtech/avs/core/service/scanner/ClamScannerEngine.java index 8267691..4e4a6f2 100644 --- 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 @@ -55,7 +55,7 @@ public class ClamScannerEngine implements AvsScannerEnine { /** * Setup service * - * @param avconfig configuration + * @param config configuration */ @Activate public void activate(ClamScannerConfig config) { From e7e4e7247b2cc64b8dcadf69d7f3bdc458e1e951 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 7 Aug 2020 15:20:53 +0200 Subject: [PATCH 18/25] fixed double dependency --- core/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 9704ec7..72a387c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -158,10 +158,5 @@ <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> </dependency> - <dependency> - <groupId>de.valtech.avs</groupId> - <artifactId>avs.api</artifactId> - <version>${project.version}</version> - </dependency> </dependencies> </project> From 377eaaa59376157279f28dbae9dc8742afcc83fa Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 7 Aug 2020 15:23:20 +0200 Subject: [PATCH 19/25] typo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ed1eb97..08c481d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ jdk: - openjdk9 addons: sonarcloud: - organization: valtech_avs + organization: valtech-avs script: - mvn test javadoc:javadoc -B - sonar-scanner From 53a256ee5d7604c12def856b753a02eac68a2078 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 7 Aug 2020 15:26:32 +0200 Subject: [PATCH 20/25] refactoring --- .../jcr_root/var/groovyconsole/scripts/aecu/.content.xml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 ui.apps/src/main/content/jcr_root/var/groovyconsole/scripts/aecu/.content.xml diff --git a/ui.apps/src/main/content/jcr_root/var/groovyconsole/scripts/aecu/.content.xml b/ui.apps/src/main/content/jcr_root/var/groovyconsole/scripts/aecu/.content.xml deleted file mode 100644 index dfac52e..0000000 --- a/ui.apps/src/main/content/jcr_root/var/groovyconsole/scripts/aecu/.content.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?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:folder" -/> \ No newline at end of file From 331c6e0c8c7b46c36bf309b3013b9a98eb5b49ec Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 7 Aug 2020 15:31:07 +0200 Subject: [PATCH 21/25] travis --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 08c481d..1a09b0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ jdk: 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 @@ -11,6 +13,3 @@ cache: directories: - "$HOME/.sonar/cache" - "$HOME/.m2" -env: - global: - secure: jAVDdEYj3Nfatl6AceGgMpAangeIHaUQdYL0u40I0FJHkkC6GxoaHf1TMy3k74nRobTwN4HDctQT3/nczhStnKZ20aE19oppYzvjhrAmhddlMTbwvn2Wlh3hQX0Ejqc6PoOrqyUlpZdHTXS0vW7dVzpTyaxtAw+z8RCCIe/+kZeSF3Noy1dsB+dBI657tC2TIV+sfXref7v6Ly1BwTJp1827u/OE9d+S3PR/GwtAKNfQQFODj5W0OLmFaMf7pEEPML4czX1AfaxYBrW9d1wmeTeoTvkU6AkeBlzHWQUM9XinObIIr/PahUZExX9Yf/K1GWHyoAEgsUVOOJe/mqEo1JQG2LNhyVWBrIcAXdFo0gjYPudIBc7n+IVnq7OZjjB+cg+I2yBdHSWwkaHHgeD4tpp8d/dJMmoMP8gBv7a10W5VnuHLxv2/j9y5Z9HGUoQW7mZ4X7l8eeCra/Qtt+yIdx7wZ99FCkt7FXfyjWSCu54xZQFRdCu2JTvE6ZFLAYcGUlszMltqcJmLdUIh24zAvqHVcKSnzBTmHoax1897i4m/yM5qoW6Tvak1UcwG/mWkzNTZaMr998PR+49CtbRfKjoc/HvqvxA3T8IhXfmbh4Ee9NZcpWmUZeFBKvh0HBXP8NRmpSpdNFrKX0x1lbpH8eZwLuFS1jo7Pz1XHYgscX0= From d53b08b8e95a5a1c51b41666068c0fe54aa9bf89 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 7 Aug 2020 15:40:40 +0200 Subject: [PATCH 22/25] refactoring --- .../java/de/valtech/avs/core/filter/AvsPostFilter.java | 7 +++---- .../avs/core/service/scanner/ClamScannerEngine.java | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) 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 index 062996a..95db2b9 100644 --- a/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java +++ b/core/src/main/java/de/valtech/avs/core/filter/AvsPostFilter.java @@ -91,8 +91,6 @@ public class AvsPostFilter implements Filter { private List<Pattern> includePatterns = new ArrayList<>(); private List<Pattern> excludePatterns = new ArrayList<>(); - private AvsPostFilterConfig config; - /** * Setup service * @@ -112,7 +110,6 @@ public void activate(AvsPostFilterConfig config) { includePatterns.add(Pattern.compile(patternString)); } } - this.config = config; } @Override @@ -167,7 +164,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha ScanResult result = avsService.scan(combinedStream, userId); if (!result.isClean()) { for (File file : parameterFiles) { - file.delete(); + if (!file.delete()) { + LOG.warn("Unable to remove temp file {}", file.getPath()); + } } sendEmail(slingRequest, result, fileNames); throw new ServletException("Uploaded file contains a virus"); 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 index 4e4a6f2..d75999d 100644 --- 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 @@ -22,6 +22,7 @@ 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; @@ -72,8 +73,8 @@ public ScanResult scan(InputStream content) throws AvsException { InputStream in = process.getInputStream(); InputStream err = process.getErrorStream(); int returnCode = process.waitFor(); - String output = IOUtils.toString(in, Charset.forName("UTF-8")); - String error = IOUtils.toString(err, Charset.forName("UTF-8")); + 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"); From 5a415190b0f95ef9bb4d78821c31e3cc90beff15 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 7 Aug 2020 15:48:50 +0200 Subject: [PATCH 23/25] ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1c45fb5..693e9c1 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ buildNumber.properties *.iml .DS_Store +/TODO.txt From 210684db514f59497757e9bc00025486303bf6dc Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 7 Aug 2020 15:49:12 +0200 Subject: [PATCH 24/25] updating poms for 0.9.0 branch with snapshot versions --- api/pom.xml | 6 ++---- core/pom.xml | 6 ++---- examples/pom.xml | 6 ++---- pom.xml | 6 ++---- ui.apps.structure/pom.xml | 6 ++---- ui.apps/pom.xml | 6 ++---- 6 files changed, 12 insertions(+), 24 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index ff11227..362790d 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -1,12 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>1.0.0-SNAPSHOT</version> + <version>0.9.0-rc-SNAPSHOT</version> </parent> <artifactId>avs.api</artifactId> diff --git a/core/pom.xml b/core/pom.xml index 72a387c..7e97a56 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,12 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>1.0.0-SNAPSHOT</version> + <version>0.9.0-rc-SNAPSHOT</version> </parent> <artifactId>avs.core</artifactId> diff --git a/examples/pom.xml b/examples/pom.xml index afa89de..9481759 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -1,13 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>1.0.0-SNAPSHOT</version> + <version>0.9.0-rc-SNAPSHOT</version> </parent> <artifactId>avs.examples</artifactId> diff --git a/pom.xml b/pom.xml index 4a84b86..b3cdca3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> <packaging>pom</packaging> - <version>1.0.0-SNAPSHOT</version> + <version>0.9.0-rc-SNAPSHOT</version> <name>AVS</name> <description>AEM Virus Scan</description> <url>https://github.com/valtech/aem-virus-scan</url> diff --git a/ui.apps.structure/pom.xml b/ui.apps.structure/pom.xml index e701d8c..c807f69 100644 --- a/ui.apps.structure/pom.xml +++ b/ui.apps.structure/pom.xml @@ -1,13 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>1.0.0-SNAPSHOT</version> + <version>0.9.0-rc-SNAPSHOT</version> </parent> <artifactId>ui.apps.structure</artifactId> diff --git a/ui.apps/pom.xml b/ui.apps/pom.xml index 9d5dd76..9ca4c8c 100644 --- a/ui.apps/pom.xml +++ b/ui.apps/pom.xml @@ -1,13 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>1.0.0-SNAPSHOT</version> + <version>0.9.0-rc-SNAPSHOT</version> </parent> <artifactId>avs.ui.apps</artifactId> From b0cff586f9175c5726635c1a05b7a7b3d31a3da0 Mon Sep 17 00:00:00 2001 From: Roland Gruber <roland.gruber@valtech.de> Date: Fri, 7 Aug 2020 15:49:48 +0200 Subject: [PATCH 25/25] updating poms for branch'release/0.9.0' with non-snapshot versions --- api/pom.xml | 2 +- core/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 2 +- ui.apps.structure/pom.xml | 2 +- ui.apps/pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 362790d..4c28d87 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>0.9.0-rc-SNAPSHOT</version> + <version>0.9.0</version> </parent> <artifactId>avs.api</artifactId> diff --git a/core/pom.xml b/core/pom.xml index 7e97a56..f1a50a7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>0.9.0-rc-SNAPSHOT</version> + <version>0.9.0</version> </parent> <artifactId>avs.core</artifactId> diff --git a/examples/pom.xml b/examples/pom.xml index 9481759..d0e9945 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>0.9.0-rc-SNAPSHOT</version> + <version>0.9.0</version> </parent> <artifactId>avs.examples</artifactId> diff --git a/pom.xml b/pom.xml index b3cdca3..d3c9bff 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> <packaging>pom</packaging> - <version>0.9.0-rc-SNAPSHOT</version> + <version>0.9.0</version> <name>AVS</name> <description>AEM Virus Scan</description> <url>https://github.com/valtech/aem-virus-scan</url> diff --git a/ui.apps.structure/pom.xml b/ui.apps.structure/pom.xml index c807f69..8f29e43 100644 --- a/ui.apps.structure/pom.xml +++ b/ui.apps.structure/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>0.9.0-rc-SNAPSHOT</version> + <version>0.9.0</version> </parent> <artifactId>ui.apps.structure</artifactId> diff --git a/ui.apps/pom.xml b/ui.apps/pom.xml index 9ca4c8c..a612983 100644 --- a/ui.apps/pom.xml +++ b/ui.apps/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>de.valtech.avs</groupId> <artifactId>avs</artifactId> - <version>0.9.0-rc-SNAPSHOT</version> + <version>0.9.0</version> </parent> <artifactId>avs.ui.apps</artifactId>