├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── docs ├── NoVulnerabilities.PNG ├── Tree.PNG ├── Vulnerabilities.PNG ├── example_report.png └── pipeline_stages.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ossi-mapdb └── build.gradle ├── settings.gradle └── src ├── main ├── java │ └── net │ │ └── ossindex │ │ ├── common │ │ ├── IPackageRequest.java │ │ ├── OssIndexApi.java │ │ ├── OssiPackage.java │ │ ├── OssiVulnerability.java │ │ ├── PackageCoordinate.java │ │ ├── filter │ │ │ ├── IVulnerabilityFilter.java │ │ │ ├── VulnerabilityFilterFactory.java │ │ │ └── VulnerabilityFilterImpl.java │ │ └── request │ │ │ ├── OssIndexHttpClient.java │ │ │ ├── PackageRequestDto.java │ │ │ └── PackageRequestService.java │ │ └── gradle │ │ ├── AuditExclusion.java │ │ ├── AuditExtensions.java │ │ ├── OssIndexPlugin.java │ │ ├── audit │ │ ├── AuditorFactory.java │ │ ├── DependencyAuditor.java │ │ ├── MavenIdWrapper.java │ │ ├── MavenPackageDescriptor.java │ │ ├── OssIndexResultsWrapper.java │ │ └── Proxy.java │ │ ├── input │ │ ├── ArtifactGatherer.java │ │ └── GradleArtifact.java │ │ └── output │ │ ├── AuditResultReporter.java │ │ ├── JunitXmlReportWriter.java │ │ └── PackageTreeReporter.java └── resources │ └── META-INF │ └── gradle-plugins │ └── net.ossindex.audit.properties └── test ├── groovy └── net │ └── ossindex │ └── integrationtests │ ├── ExclusionTests.groovy │ └── OssIndexAuditPluginTests.groovy └── java └── net └── ossindex ├── common ├── PackageRequestTest.java ├── SmokeIT.java └── filter │ └── VulnerabilityFilterTests.java └── gradle └── audit ├── ProxyTests.java └── ProxyUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/eclipse,gradle,intellij,vim,java 3 | 4 | ### Eclipse ### 5 | 6 | .metadata 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | .recommenders 17 | 18 | # Eclipse Core 19 | .project 20 | 21 | # External tool builders 22 | .externalToolBuilders/ 23 | 24 | # Locally stored "Eclipse launch configurations" 25 | *.launch 26 | 27 | # PyDev specific (Python IDE for Eclipse) 28 | *.pydevproject 29 | 30 | # CDT-specific (C/C++ Development Tooling) 31 | .cproject 32 | 33 | # JDT-specific (Eclipse Java Development Tools) 34 | .classpath 35 | 36 | # Java annotation processor (APT) 37 | .factorypath 38 | 39 | # PDT-specific (PHP Development Tools) 40 | .buildpath 41 | 42 | # sbteclipse plugin 43 | .target 44 | 45 | # Tern plugin 46 | .tern-project 47 | 48 | # TeXlipse plugin 49 | .texlipse 50 | 51 | # STS (Spring Tool Suite) 52 | .springBeans 53 | 54 | # Code Recommenders 55 | .recommenders/ 56 | 57 | ### Intellij ### 58 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 59 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 60 | 61 | # User-specific stuff: 62 | .idea/**/workspace.xml 63 | .idea/**/tasks.xml 64 | 65 | # Sensitive or high-churn files: 66 | .idea/**/dataSources/ 67 | .idea/**/dataSources.ids 68 | .idea/**/dataSources.xml 69 | .idea/**/dataSources.local.xml 70 | .idea/**/sqlDataSources.xml 71 | .idea/**/dynamic.xml 72 | .idea/**/uiDesigner.xml 73 | 74 | # Gradle: 75 | .idea/**/gradle.xml 76 | .idea/**/libraries 77 | 78 | # Mongo Explorer plugin: 79 | .idea/**/mongoSettings.xml 80 | 81 | ## File-based project format: 82 | *.iws 83 | *.ipr 84 | 85 | ## Plugin-specific files: 86 | 87 | # IntelliJ 88 | out/ 89 | 90 | # mpeltonen/sbt-idea plugin 91 | .idea_modules/ 92 | 93 | # JIRA plugin 94 | atlassian-ide-plugin.xml 95 | 96 | # Crashlytics plugin (for Android Studio and IntelliJ) 97 | com_crashlytics_export_strings.xml 98 | crashlytics.properties 99 | crashlytics-build.properties 100 | fabric.properties 101 | 102 | ### Intellij Patch ### 103 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 104 | .idea/ 105 | *.iml 106 | 107 | ### Java ### 108 | # Compiled class file 109 | *.class 110 | 111 | # Log file 112 | *.log 113 | 114 | # BlueJ files 115 | *.ctxt 116 | 117 | # Mobile Tools for Java (J2ME) 118 | .mtj.tmp/ 119 | 120 | # Package Files # 121 | *.jar 122 | *.war 123 | *.ear 124 | *.zip 125 | *.tar.gz 126 | *.rar 127 | 128 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 129 | hs_err_pid* 130 | 131 | ### Vim ### 132 | # swap 133 | [._]*.s[a-v][a-z] 134 | [._]*.sw[a-p] 135 | [._]s[a-v][a-z] 136 | [._]sw[a-p] 137 | # session 138 | Session.vim 139 | # temporary 140 | .netrwhist 141 | *~ 142 | # auto-generated tag files 143 | tags 144 | 145 | ### Gradle ### 146 | .gradle 147 | build/ 148 | 149 | # Ignore Gradle GUI config 150 | gradle-app.setting 151 | 152 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 153 | !gradle-wrapper.jar 154 | 155 | # Cache of project 156 | .gradletasknamecache 157 | 158 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 159 | # gradle/wrapper/gradle-wrapper.properties 160 | 161 | # End of https://www.gitignore.io/api/eclipse,gradle,intellij,vim,java 162 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, OSS Index 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ossindex-gradle-plugin 2 | Audits a [gradle](https://gradle.org/) project using the [OSS Index REST API v3](https://ossindex.sonatype.org/rest) to identify known vulnerabilities in its dependencies. 3 | 4 | New Release Notes 5 | ------------- 6 | 7 | **This release uses the new OSS Index v3 API**. There are a few differences of note: 8 | 9 | * Vulnerability IDs have changed, they are now UUIDs instead of long integers. 10 | These should not change again: sorry for the inconvenience. 11 | * The new API has rate limiting, which is higher for authenticated users. Many 12 | users should be fine running unauthenticated, but if you start running 13 | into rate limit issues this can easily be resolved by getting a free OSS Index 14 | account and providing credentials (discussed below). 15 | * We have added results caching in an attempt to reduce server hits (and thus 16 | reduce rate limit problems). Setting the cache location is described below. 17 | 18 | Table of Contents 19 | ----------------- 20 | 21 | * [New Release Notes](#new-release-notes) 22 | * [Requirements](#requirements) 23 | * [Usage](#usage) 24 | + [Installing](#installing) 25 | + [Running the audit](#running-the-audit) 26 | + [Success output](#success-output) 27 | + [Error output](#error-output) 28 | + [Credentials](#credentials) 29 | + [Prevent error on exceeding rate limit](#prevent-error-on-exceeding-rate-limit) 30 | + [Dependency tree of vulnerabilities](#dependency-tree-of-vulnerabilities) 31 | + [Reporting in a Jenkins Pipeline](#reporting-in-a-jenkins-pipeline) 32 | + [Stages](#stages) 33 | + [Report Output](#report-output) 34 | * [Disable fail on error](#disable-fail-on-error) 35 | * [Ignore: Simple vulnerability management](#ignore--simple-vulnerability-management) 36 | * [Exclusions: Advanced vulnerability management](#-alpha--exclusions--advanced-vulnerability-management) 37 | * [Cache](#cache) 38 | 39 | Requirements 40 | ------------- 41 | 42 | * Gradle 43 | * An internet connection with access to https://ossindex.net 44 | 45 | Usage 46 | ----- 47 | 48 | ### Installing 49 | 50 | (NOTE: Versions < 1.0 are considered preview) 51 | 52 | The plugin is available at the [Gradle plugin repository](https://plugins.gradle.org/plugin/net.ossindex.audit). 53 | 54 | To use it either place (replace `THEVERSION` with the correct version) 55 | 56 | ``` 57 | plugins { 58 | id "net.ossindex.audit" version "THEVERSION" 59 | } 60 | ``` 61 | 62 | in your `build.gradle`. 63 | 64 | If you use an older Gradle version you can use 65 | 66 | ``` 67 | buildscript { 68 | repositories { 69 | maven { 70 | url "https://plugins.gradle.org/m2/" 71 | } 72 | } 73 | dependencies { 74 | classpath "gradle.plugin.net.ossindex:ossindex-gradle-plugin:THEVERSION" 75 | } 76 | } 77 | 78 | apply plugin: "net.ossindex.audit" 79 | ``` 80 | 81 | ### Running the audit 82 | 83 | To run the audit standalone specify the task `audit`: 84 | 85 | `gradle audit` 86 | 87 | To use it before compiling write 88 | 89 | `compile.dependsOn audit` 90 | 91 | into your buildscript. 92 | 93 | ### Success output 94 | This will run the OSS Index Auditor against the applicable maven project. A successful 95 | scan finding no errors will look something like this: 96 | 97 | ![Success](docs/NoVulnerabilities.PNG) 98 | 99 | 100 | ### Error output 101 | If a vulnerability is found that might impact your project, the output will resemble the 102 | following, where the package and vulnerability details depends on what is identified. 103 | 104 | ![Failure](docs/Vulnerabilities.PNG) 105 | 106 | ### Credentials 107 | 108 | The OSS Index API is rate limited. In many cases the limit is more than 109 | sufficient, however in heavier use cases an increased limit might be desired. 110 | This can be attained by creating a user account at 111 | [OSS Index](https://ossindex.sonatype.org) and supplying the username and `token` 112 | to the plugin. The `token` can be retrieved from the OSS Index settings page 113 | of the user. 114 | 115 | As you don't want credentials stored in a source repository, you use the 116 | gradle properties file to specify the cache folder. In the gradle properties, 117 | write this: 118 | 119 | ``` 120 | ossindexUser=user@example.com 121 | ossindexToken=ef40752eeb642ba1c3df1893d270c6f9fb7ab9e1 122 | ``` 123 | 124 | In your build.gradle file, add the following. 125 | 126 | ``` 127 | audit { 128 | user = "$ossindexUser" 129 | token = "$ossindexToken" 130 | } 131 | ``` 132 | 133 | The credentialed rate limit is 64 requests per hour, where each request can fetch 134 | information for up to 128 packages. (Strictly speaking, you actually have 64 requests which 135 | replenish at a rate of one per minute). 136 | 137 | ### Prevent error on exceeding rate limit 138 | It may be that you know you are going to exceed the rate limit, but you want 139 | a run to complete without failing. This is done by setting the "rateLimitAsError" 140 | option. 141 | 142 | ``` 143 | audit { 144 | rateLimitAsError = false 145 | } 146 | ``` 147 | 148 | By default the cache resets every 12 hours, which means if you exceed the 149 | credentialled limit, you can run multiple times a day (eg. once an hour) and cached packages 150 | WILL NOT be rechecked on the server and therefore rate limiting will not 151 | be affected by those packages. 152 | 153 | ### Dependency tree of vulnerabilities 154 | If the `--info` flag is provided to gradle it will output a dependency tree which shows the transitive dependencies which have vulnerabilities. 155 | 156 | ![Tree](docs/Tree.PNG) 157 | 158 | ### Reporting in a Jenkins Pipeline 159 | 160 | The gradle plugin supports writing out test reports in the correct XML format for the 161 | [Jenkins JUnit Reporting Plugin](https://wiki.jenkins.io/display/JENKINS/JUnit+Plugin). 162 | To switch on this reporting, set the path to the report in you project's build.gradle file using the **"junitReport"** element like so: 163 | 164 | ``` 165 | audit { 166 | failOnError = false 167 | ignore = [ 'ch.qos.logback:logback-core' ] 168 | junitReport = "./ossindex/junitReport.xml" 169 | } 170 | ``` 171 | 172 | This would create the file in an /ossindex folder in the project root. 173 | 174 | To access this using the JUnit plugin in a Jenkins pipeline: 175 | 176 | ``` 177 | stage('OSSIndex Scan') { 178 | steps { 179 | // Run the audit 180 | sh "./gradlew --no-daemon --stacktrace audit" 181 | } 182 | post { 183 | always { 184 | // Tell junit plugin to use report 185 | junit '**/ossindex/junitReport.xml' 186 | // Fail stage if ' \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /ossi-mapdb/build.gradle: -------------------------------------------------------------------------------- 1 | // Special shadow jar for mapdb to eliminate dependency collision 2 | import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation 3 | 4 | buildscript { 5 | repositories { 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.4' 10 | } 11 | } 12 | 13 | version '3.0.7' 14 | 15 | apply plugin: 'groovy' 16 | apply plugin: 'com.github.johnrengelman.shadow' 17 | 18 | repositories { 19 | mavenCentral() 20 | } 21 | 22 | dependencies { 23 | shadow localGroovy() 24 | shadow gradleApi() 25 | 26 | implementation 'org.mapdb:mapdb:3.0.7' 27 | } 28 | 29 | // Configuring Filtering for Relocation 30 | shadowJar { 31 | relocate 'kotlin', 'shadow.kotlin' 32 | relocate 'net.jpountz', 'shadow.net.jpountz' 33 | } 34 | 35 | configurations.archives.artifacts.clear() 36 | artifacts { 37 | add("archives", shadowJar) 38 | } 39 | 40 | shadowJar { 41 | classifier = null // don't add "-all" to the generated jar 42 | } 43 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ossindex-gradle-plugin' 2 | 3 | include 'ossi-mapdb' -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/IPackageRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Vör Security Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | package net.ossindex.common; 28 | 29 | import java.io.IOException; 30 | import java.util.Collection; 31 | import java.util.List; 32 | 33 | import net.ossindex.common.filter.IVulnerabilityFilter; 34 | 35 | /** Interface to be implemented for the package request API. 36 | * 37 | * @author Ken Duck 38 | * 39 | */ 40 | public interface IPackageRequest 41 | { 42 | 43 | /** Add a new artifact to search for. 44 | * 45 | * @param pm Name of the package manager 46 | * @param groupId Group ID for the package 47 | * @param artifactId Artifact ID for the package 48 | * @param version Version number for request 49 | * @return A package descriptor containing the information 50 | */ 51 | public OssiPackage add(String pm, String groupId, String artifactId, String version); 52 | 53 | /** Add a new package/version (with dependency information) to search for. This is useful for advanced 54 | * filtering. 55 | * 56 | * @param path A path from the root (including) package to the dependency package. The packager being checked 57 | * is the last one placed on the list (max index). 58 | * @return A package descriptor containing the information 59 | */ 60 | public OssiPackage add(List path); 61 | 62 | /** 63 | * Filter the request results using the specified filter(s) 64 | */ 65 | public void addVulnerabilityFilter(IVulnerabilityFilter filter); 66 | 67 | /** 68 | * Execute the request. 69 | * 70 | * @throws IOException 71 | */ 72 | public Collection run() throws IOException; 73 | 74 | /** 75 | * Set the file path to be used by the cache. 76 | * Set to null to disable the cache. 77 | */ 78 | public void setCacheFile(String path); 79 | 80 | /** 81 | * Set the credentials to be used in the OSS Index query 82 | */ 83 | public void setCredentials(String user, String token); 84 | 85 | /** 86 | * 87 | */ 88 | public void setMaximumPackagesPerRequest(int count); 89 | 90 | /** 91 | * Number of hours till the cache expires. Only affects new items in the cache. 92 | */ 93 | public void setCacheTimeout(int hours); 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/OssIndexApi.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Vör Security Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | package net.ossindex.common; 28 | 29 | import net.ossindex.common.request.OssIndexHttpClient; 30 | import net.ossindex.common.request.PackageRequestService; 31 | 32 | /** Main class for access of the OSS Index API. Use this to create request 33 | * objects. 34 | * 35 | * @author Ken Duck 36 | * 37 | */ 38 | public class OssIndexApi 39 | { 40 | private static OssIndexHttpClient client = new OssIndexHttpClient(); 41 | 42 | public static void addProxy(String protocol, String host, int port, String username, String password) { 43 | client.addProxy(protocol, host, port, username, password); 44 | } 45 | 46 | public void addProxy(String protocol, String host, int port, String username, String password, 47 | int socketTimeout, int connectTimeout, int connectionRequestTimeout) 48 | { 49 | client.addProxy(protocol, host, port, username, password, socketTimeout, connectTimeout, connectionRequestTimeout); 50 | } 51 | 52 | /** 53 | * Create a package request object. 54 | * 55 | * @return The new package request 56 | */ 57 | public static IPackageRequest createPackageRequest() { 58 | return new PackageRequestService(client); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/OssiPackage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Vör Security Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | package net.ossindex.common; 28 | 29 | import java.util.List; 30 | import org.sonatype.goodies.packageurl.PackageUrl; 31 | 32 | /** 33 | * Represents an OSS Index package. 34 | * 35 | * @author Ken Duck 36 | */ 37 | public class OssiPackage 38 | { 39 | 40 | private String coordinates; 41 | 42 | private String description; 43 | 44 | private String reference; 45 | 46 | private int unfilteredVulnerabilityMatches; 47 | 48 | private List vulnerabilities; 49 | 50 | private transient PackageUrl purl; 51 | 52 | /** 53 | * Create a package coordinate 54 | */ 55 | public OssiPackage(String type, String namespace, String artifactId, String version) { 56 | if (namespace == null || namespace.isEmpty()) { 57 | this.coordinates = "pkg:" + type + "/" + artifactId + "@" + version; 58 | } 59 | else { 60 | this.coordinates = "pkg:" + type + "/" + namespace + "/" + artifactId + "@" + version; 61 | } 62 | } 63 | 64 | private OssiPackage(final Builder builder) { 65 | coordinates = builder.coordinates; 66 | description = builder.description; 67 | reference = builder.reference; 68 | unfilteredVulnerabilityMatches = builder.unfilteredVulnerabilityMatches; 69 | vulnerabilities = builder.vulnerabilities; 70 | purl = builder.purl; 71 | } 72 | 73 | public static Builder newBuilder() { 74 | return new Builder(); 75 | } 76 | 77 | public static Builder newBuilder(final OssiPackage copy) { 78 | Builder builder = new Builder(); 79 | builder.coordinates = copy.coordinates; 80 | builder.description = copy.description; 81 | builder.reference = copy.reference; 82 | builder.unfilteredVulnerabilityMatches = copy.unfilteredVulnerabilityMatches; 83 | builder.vulnerabilities = copy.vulnerabilities; 84 | builder.purl = copy.purl; 85 | return builder; 86 | } 87 | 88 | public String getCoordinates() { 89 | return coordinates; 90 | } 91 | 92 | public String getDescription() { 93 | return description; 94 | } 95 | 96 | public String getReference() { 97 | return reference; 98 | } 99 | 100 | public List getVulnerabilities() { 101 | return vulnerabilities; 102 | } 103 | 104 | public int getUnfilteredVulnerabilityMatches() { 105 | return unfilteredVulnerabilityMatches; 106 | } 107 | 108 | public PackageUrl getPurl() { 109 | return purl; 110 | } 111 | 112 | public String getType() { 113 | if (purl == null) { 114 | purl = PackageUrl.parse(coordinates); 115 | } 116 | return purl.getType(); 117 | } 118 | 119 | public String getNamespace() { 120 | if (purl == null) { 121 | purl = PackageUrl.parse(coordinates); 122 | } 123 | return purl.getNamespaceAsString(); 124 | } 125 | 126 | public String getName() { 127 | if (purl == null) { 128 | purl = PackageUrl.parse(coordinates); 129 | } 130 | return purl.getName(); 131 | } 132 | 133 | public String getVersion() { 134 | if (purl == null) { 135 | purl = PackageUrl.parse(coordinates); 136 | } 137 | return purl.getVersion(); 138 | } 139 | 140 | public static final class Builder 141 | { 142 | private String coordinates; 143 | 144 | private String description; 145 | 146 | private String reference; 147 | 148 | private int unfilteredVulnerabilityMatches; 149 | 150 | private List vulnerabilities; 151 | 152 | private PackageUrl purl; 153 | 154 | private Builder() {} 155 | 156 | public Builder withCoordinates(final String val) { 157 | coordinates = val; 158 | return this; 159 | } 160 | 161 | public Builder withDescription(final String val) { 162 | description = val; 163 | return this; 164 | } 165 | 166 | public Builder withReference(final String val) { 167 | reference = val; 168 | return this; 169 | } 170 | 171 | public Builder withUnfilteredVulnerabilityMatches(final int val) { 172 | unfilteredVulnerabilityMatches = val; 173 | return this; 174 | } 175 | 176 | public Builder withVulnerabilities(final List val) { 177 | vulnerabilities = val; 178 | return this; 179 | } 180 | 181 | public Builder withPurl(final PackageUrl val) { 182 | purl = val; 183 | return this; 184 | } 185 | 186 | public OssiPackage build() { 187 | return new OssiPackage(this); 188 | } 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/OssiVulnerability.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Vör Security Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | package net.ossindex.common; 28 | 29 | import javax.xml.bind.annotation.XmlElement; 30 | 31 | /** Represents a vulnerability from OSS Index 32 | * 33 | * @author Ken Duck 34 | * 35 | */ 36 | public class OssiVulnerability 37 | { 38 | @XmlElement(name = "id") 39 | private String id; 40 | 41 | @XmlElement(name = "title") 42 | private String title; 43 | 44 | @XmlElement(name = "description") 45 | private String description; 46 | 47 | @XmlElement(name = "cvssScore") 48 | private Double cvssScore; 49 | 50 | @XmlElement(name = "cvssVector") 51 | private String cvssVector; 52 | 53 | @XmlElement(name = "reference") 54 | private String reference; 55 | 56 | public String getId() { 57 | return id; 58 | } 59 | 60 | public String getTitle() { 61 | return title; 62 | } 63 | 64 | public String getDescription() { 65 | return description; 66 | } 67 | 68 | public Double getCvssScore() { 69 | return cvssScore; 70 | } 71 | 72 | public String getCvssVector() { 73 | return cvssVector; 74 | } 75 | 76 | public String getReference() { 77 | return reference; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/PackageCoordinate.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.common; 2 | 3 | import java.util.Objects; 4 | 5 | import net.ossindex.version.IVersionRange; 6 | import net.ossindex.version.InvalidRangeException; 7 | import net.ossindex.version.VersionFactory; 8 | 9 | public class PackageCoordinate 10 | { 11 | public final String type; 12 | 13 | public final String namespace; 14 | 15 | public final String name; 16 | 17 | public final String version; 18 | 19 | private PackageCoordinate(final Builder builder) { 20 | type = builder.format; 21 | namespace = builder.namespace; 22 | name = builder.name; 23 | version = builder.version; 24 | } 25 | 26 | public static Builder newBuilder() { 27 | return new Builder(); 28 | } 29 | 30 | public static Builder newBuilder(final PackageCoordinate copy) { 31 | Builder builder = new Builder(); 32 | builder.format = copy.type; 33 | builder.namespace = copy.namespace; 34 | builder.name = copy.name; 35 | builder.version = copy.version; 36 | return builder; 37 | } 38 | 39 | public String getType() { 40 | return type; 41 | } 42 | 43 | public String getNamespace() { 44 | return namespace; 45 | } 46 | 47 | public String getName() { 48 | return name; 49 | } 50 | 51 | public String getVersion() { 52 | return version; 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | // DIRTY HACK: Don't include version in the hash because we want to be forced to compare coordinates with 58 | // different versions, cause if one coordinate specifies a version and the other does not then they are equivalent. 59 | // We also need to use some custom version comparison code, since one will be a range, and the other a specific 60 | // version and we will consider them equal if they overlap. This is a dirty misuse of the equals method. 61 | int hashcode = Objects.hash(type, namespace, name); 62 | return hashcode; 63 | } 64 | 65 | /** 66 | * For our purposes we will allow coordinates to be equal if they overlap, even though technically they are different 67 | * objects. This is a bit of a dirty hack to be able to force set to our will. 68 | */ 69 | @Override 70 | public boolean equals(final Object obj) { 71 | if (this == obj) { 72 | return true; 73 | } 74 | if (obj == null || getClass() != obj.getClass()) { 75 | return false; 76 | } 77 | final PackageCoordinate other = (PackageCoordinate) obj; 78 | if (Objects.equals(this.type, other.type) 79 | && Objects.equals(this.namespace, other.namespace) 80 | && Objects.equals(this.name, other.name)) { 81 | // Do versions overlap 82 | if (other.version == null || this.version == null) { 83 | return true; 84 | } 85 | try { 86 | IVersionRange orange = VersionFactory.getVersionFactory().getRange(other.version); 87 | IVersionRange range = VersionFactory.getVersionFactory().getRange(this.version); 88 | if (orange.intersects(range)) { 89 | return true; 90 | } 91 | } 92 | catch (InvalidRangeException e) { 93 | e.printStackTrace(); 94 | } 95 | } 96 | 97 | return false; 98 | } 99 | 100 | @Override 101 | public String toString() { 102 | return "PackageCoordinate{" + 103 | "type='" + type + '\'' + 104 | ", namespace='" + namespace + '\'' + 105 | ", name='" + name + '\'' + 106 | ", version='" + version + '\'' + 107 | '}'; 108 | } 109 | 110 | public static final class Builder 111 | { 112 | private String format; 113 | 114 | private String namespace; 115 | 116 | private String name; 117 | 118 | private String version; 119 | 120 | private Builder() {} 121 | 122 | public Builder withFormat(final String val) { 123 | format = val == null || val.trim().isEmpty() ? null : val; 124 | return this; 125 | } 126 | 127 | public Builder withNamespace(final String val) { 128 | namespace = val == null || val.trim().isEmpty() ? null : val; 129 | return this; 130 | } 131 | 132 | public Builder withName(final String val) { 133 | name = val == null || val.trim().isEmpty() ? null : val; 134 | return this; 135 | } 136 | 137 | public Builder withVersion(final String val) { 138 | version = val == null || val.trim().isEmpty() ? null : val; 139 | return this; 140 | } 141 | 142 | public PackageCoordinate build() { 143 | return new PackageCoordinate(this); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/filter/IVulnerabilityFilter.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.common.filter; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | import net.ossindex.common.PackageCoordinate; 7 | 8 | public interface IVulnerabilityFilter 9 | { 10 | /** 11 | * Ignore all vulnerabilities belonging to a specific package 12 | */ 13 | public void ignorePackage(final List pkgs); 14 | 15 | /** 16 | * Ignore a specific vulnerability 17 | */ 18 | public void ignoreVulnerability(final String vid); 19 | 20 | /** 21 | * Ignore a specific vulnerability which is included in a dependency path including ALL of the specified coordinates. 22 | * This can be used in may ways: 23 | * 24 | * 1. Broadly specify a vulnerability being included by a specific "introducing" package 25 | * 2. Specify a vulnerability being included by a specific "vulnerable" package, since the same vulnerability might 26 | * be assigned to multiple packages and we only want to filter it through one of the inclusions 27 | * 3. Specify a vulnerability which is included through a specific path, for example a specified including 28 | * package and the specific vulnerable package. 29 | */ 30 | public void ignoreVulnerability(final List pkg, final String vid); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/filter/VulnerabilityFilterFactory.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.common.filter; 2 | 3 | import java.util.List; 4 | 5 | import net.ossindex.common.PackageCoordinate; 6 | 7 | public class VulnerabilityFilterFactory 8 | { 9 | private static VulnerabilityFilterFactory instance; 10 | 11 | private VulnerabilityFilterFactory() { 12 | } 13 | 14 | public static synchronized VulnerabilityFilterFactory getInstance() { 15 | if (instance == null) { 16 | instance = new VulnerabilityFilterFactory(); 17 | } 18 | return instance; 19 | } 20 | 21 | public IVulnerabilityFilter createVulnerabilityFilter() { 22 | return new VulnerabilityFilterImpl(); 23 | } 24 | 25 | /** 26 | * Kind of filthy this being here. It is in lieu of making a separate utility class for now. It allows us to run 27 | * the filter without exposing private functionality to the users. 28 | */ 29 | public static boolean shouldFilter(final IVulnerabilityFilter filter, 30 | final List path, 31 | final String vid) { 32 | return ((VulnerabilityFilterImpl)filter).shouldFilter(path, vid); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/filter/VulnerabilityFilterImpl.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.common.filter; 2 | 3 | import java.util.Collection; 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | import net.ossindex.common.PackageCoordinate; 13 | 14 | class VulnerabilityFilterImpl implements IVulnerabilityFilter 15 | { 16 | private Map>> filteredPackages = new HashMap<>(); 17 | 18 | private Map>> filteredIssues = new HashMap<>(); 19 | 20 | @Override 21 | public void ignorePackage(final List pkgs) { 22 | if (pkgs != null && !pkgs.isEmpty()) { 23 | PackageCoordinate pkg = pkgs.get(pkgs.size() - 1); 24 | 25 | List> sets = null; 26 | if (!filteredPackages.containsKey(pkg)) { 27 | sets = new LinkedList<>(); 28 | filteredPackages.put(pkg, sets); 29 | } 30 | else { 31 | sets = filteredPackages.get(pkg); 32 | } 33 | Set set = new HashSet<>(); 34 | set.addAll(pkgs); 35 | sets.add(set); 36 | } 37 | } 38 | 39 | @Override 40 | public void ignoreVulnerability(final String vid) { 41 | ignoreVulnerability(null, vid); 42 | } 43 | 44 | @Override 45 | public void ignoreVulnerability(final List pkgs, final String vid) { 46 | if (vid == null) { 47 | ignorePackage(pkgs); 48 | } 49 | List> sets = null; 50 | if (!filteredIssues.containsKey(vid)) { 51 | sets = new LinkedList<>(); 52 | filteredIssues.put(vid, sets); 53 | } else { 54 | sets = filteredIssues.get(vid); 55 | } 56 | if (pkgs != null) { 57 | Set set = new HashSet<>(); 58 | set.addAll(pkgs); 59 | sets.add(set); 60 | } else { 61 | sets.add(Collections.EMPTY_SET); 62 | } 63 | } 64 | 65 | /** 66 | * @return true if the specified vulnerability should be filtered. 67 | */ 68 | boolean shouldFilter(final List path, final String vid) { 69 | Set pkgSet = new HashSet<>(); 70 | if (path != null) { 71 | pkgSet.addAll(path); 72 | } 73 | 74 | // Is the package outright filtered? 75 | if (path != null && !path.isEmpty()) { 76 | PackageCoordinate pkg = path.get(path.size() - 1); 77 | if (filteredPackages.containsKey(pkg)) { 78 | List> sets = filteredPackages.get(pkg); 79 | for (Set set: sets) { 80 | if (set.isEmpty() || pkgSet.containsAll(set)) { 81 | return true; 82 | } 83 | } 84 | } 85 | } 86 | 87 | if (vid != null) { 88 | if (filteredIssues.containsKey(vid)) { 89 | List> sets = filteredIssues.get(vid); 90 | for (Set set : sets) { 91 | // We either need to match an empty list (indicating a simple issue match), or we need all packages to match 92 | if (set.isEmpty() || pkgSet.containsAll(set)) { 93 | return true; 94 | } 95 | } 96 | } 97 | } 98 | return false; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/request/OssIndexHttpClient.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.common.request; 2 | 3 | import java.io.IOException; 4 | import java.net.ConnectException; 5 | import java.util.Arrays; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | import org.apache.http.HttpHeaders; 10 | import org.apache.http.HttpHost; 11 | import org.apache.http.ParseException; 12 | import org.apache.http.auth.AuthScope; 13 | import org.apache.http.auth.Credentials; 14 | import org.apache.http.auth.UsernamePasswordCredentials; 15 | import org.apache.http.client.AuthCache; 16 | import org.apache.http.client.CredentialsProvider; 17 | import org.apache.http.client.config.AuthSchemes; 18 | import org.apache.http.client.config.CookieSpecs; 19 | import org.apache.http.client.config.RequestConfig; 20 | import org.apache.http.client.methods.CloseableHttpResponse; 21 | import org.apache.http.client.methods.HttpPost; 22 | import org.apache.http.client.protocol.HttpClientContext; 23 | import org.apache.http.entity.StringEntity; 24 | import org.apache.http.impl.auth.BasicScheme; 25 | import org.apache.http.impl.client.BasicAuthCache; 26 | import org.apache.http.impl.client.BasicCredentialsProvider; 27 | import org.apache.http.impl.client.CloseableHttpClient; 28 | import org.apache.http.impl.client.HttpClientBuilder; 29 | import org.apache.http.impl.client.HttpClients; 30 | import org.apache.http.message.BasicHeader; 31 | import org.apache.http.util.EntityUtils; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | 35 | public class OssIndexHttpClient 36 | { 37 | private static final Logger logger = LoggerFactory.getLogger(OssIndexHttpClient.class); 38 | 39 | private List proxies = new LinkedList(); 40 | 41 | private static String SCHEME = "https"; 42 | 43 | private static String HOST = "ossindex.sonatype.org"; 44 | 45 | private static String VERSION = "api/v3"; 46 | 47 | private static String BASE_URL = SCHEME + "://" + HOST + "/" + VERSION + "/"; 48 | 49 | private static final int DEFAULT_PORT = 443; 50 | 51 | private static final String CONTENT_TYPE = "application/json"; 52 | 53 | private Credentials credentials; 54 | 55 | private static HttpHost targetHost = new HttpHost(HOST, DEFAULT_PORT, SCHEME); 56 | 57 | static { 58 | String scheme = System.getProperty("OSSINDEX_SCHEME"); 59 | if (scheme != null && !scheme.trim().isEmpty()) { 60 | SCHEME = scheme.trim(); 61 | } 62 | String host = System.getProperty("OSSINDEX_HOST"); 63 | if (host != null && !host.trim().isEmpty()) { 64 | HOST = host.trim(); 65 | } 66 | String version = System.getProperty("OSSINDEX_VERSION"); 67 | if (version != null && !version.trim().isEmpty()) { 68 | VERSION = version.trim(); 69 | } 70 | BASE_URL = SCHEME + "://" + HOST + "/" + VERSION + "/"; 71 | } 72 | 73 | private static int PROXY_SOCKET_TIMEOUT = 60000; 74 | 75 | private static int PROXY_CONNECT_TIMEOUT = 10000; 76 | 77 | private static int PROXY_CONNECTION_REQUEST_TIMEOUT = 10000; 78 | 79 | static { 80 | // Define the default proxy settings using environment variables 81 | String tmp = System.getenv("OSSINDEX_PROXY_SOCKET_TIMEOUT"); 82 | if (tmp != null && !tmp.trim().isEmpty()) { 83 | PROXY_SOCKET_TIMEOUT = Integer.parseInt(tmp.trim()); 84 | } 85 | tmp = System.getenv("OSSINDEX_PROXY_CONNECT_TIMEOUT"); 86 | if (tmp != null && !tmp.trim().isEmpty()) { 87 | PROXY_CONNECT_TIMEOUT = Integer.parseInt(tmp.trim()); 88 | } 89 | tmp = System.getenv("OSSINDEX_PROXY_CONNECTION_REQUEST_TIMEOUT"); 90 | if (tmp != null && !tmp.trim().isEmpty()) { 91 | PROXY_CONNECTION_REQUEST_TIMEOUT = Integer.parseInt(tmp.trim()); 92 | } 93 | } 94 | 95 | 96 | static { 97 | // Define the default proxy settings using properties 98 | String tmp = System.getProperty("OSSINDEX_PROXY_SOCKET_TIMEOUT"); 99 | if (tmp != null && !tmp.trim().isEmpty()) { 100 | PROXY_SOCKET_TIMEOUT = Integer.parseInt(tmp.trim()); 101 | } 102 | tmp = System.getProperty("OSSINDEX_PROXY_CONNECT_TIMEOUT"); 103 | if (tmp != null && !tmp.trim().isEmpty()) { 104 | PROXY_CONNECT_TIMEOUT = Integer.parseInt(tmp.trim()); 105 | } 106 | tmp = System.getProperty("OSSINDEX_PROXY_CONNECTION_REQUEST_TIMEOUT"); 107 | if (tmp != null && !tmp.trim().isEmpty()) { 108 | PROXY_CONNECTION_REQUEST_TIMEOUT = Integer.parseInt(tmp.trim()); 109 | } 110 | } 111 | 112 | private static final String USER = System.getProperty("OSSINDEX_USER"); 113 | 114 | private static final String TOKEN = System.getProperty("OSSINDEX_TOKEN"); 115 | 116 | /** 117 | * Override the server location. 118 | */ 119 | static void setHost(String scheme, String host, int port) { 120 | BASE_URL = scheme + "://" + host + ":" + port + "/" + VERSION + "/"; 121 | targetHost = new HttpHost(host, port, scheme); 122 | } 123 | 124 | public void setCredentials(final String user, final String token) { 125 | credentials = new UsernamePasswordCredentials(user, token); 126 | } 127 | 128 | /** 129 | * Perform the request with the given URL and JSON data. 130 | * 131 | * @param requestString Server request relative URL 132 | * @param data JSON data for the request 133 | * @return JSON results of the request 134 | * @throws IOException On query problems 135 | */ 136 | public String performPostRequest(String requestString, String data) throws IOException { 137 | HttpPost request = new HttpPost(getBaseUrl() + requestString); 138 | String json = null; 139 | 140 | request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE)); 141 | request.setHeader(new BasicHeader(HttpHeaders.ACCEPT, CONTENT_TYPE)); 142 | request.setHeader(new BasicHeader(HttpHeaders.CONTENT_ENCODING, CONTENT_TYPE)); 143 | 144 | HttpClientBuilder httpClientBuilder = HttpClients.custom() 145 | .setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); 146 | 147 | final HttpClientContext context = HttpClientContext.create(); 148 | if (credentials != null) { 149 | CredentialsProvider provider = new BasicCredentialsProvider(); 150 | provider.setCredentials(AuthScope.ANY, credentials); 151 | 152 | AuthCache authCache = new BasicAuthCache(); 153 | authCache.put(targetHost, new BasicScheme()); 154 | 155 | // Add AuthCache to the execution context 156 | context.setCredentialsProvider(provider); 157 | context.setAuthCache(authCache); 158 | } 159 | 160 | CloseableHttpClient httpClient = httpClientBuilder.build(); 161 | if (proxies.size() > 0) { 162 | // We only check the first proxy for now 163 | Proxy myProxy = proxies.get(0); 164 | HttpHost proxy = myProxy.getHttpHost(); 165 | RequestConfig config = RequestConfig.custom() 166 | .setProxy(proxy) 167 | .setSocketTimeout(myProxy.getProxySocketTimeout()) 168 | .setConnectTimeout(myProxy.getProxyConnectTimeout()) 169 | .setConnectionRequestTimeout(myProxy.getProxyConnectionRequestTimeout()) 170 | .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)) 171 | .build(); 172 | request.setConfig(config); 173 | } 174 | try { 175 | logger.debug("------------------------------------------------"); 176 | logger.debug("OSS Index POST: " + getBaseUrl() + requestString); 177 | logger.debug(data); 178 | logger.debug("------------------------------------------------"); 179 | 180 | request.setEntity(new StringEntity(data)); 181 | CloseableHttpResponse response = httpClient.execute(request, context); 182 | int code = response.getStatusLine().getStatusCode(); 183 | if (code < 200 || code > 299) { 184 | throw new ConnectException(response.getStatusLine().getReasonPhrase() + " (" + code + ")"); 185 | } 186 | json = EntityUtils.toString(response.getEntity(), "UTF-8"); 187 | } 188 | catch (ParseException e) { 189 | throw new IOException(e); 190 | } 191 | finally { 192 | httpClient.close(); 193 | } 194 | return json; 195 | } 196 | 197 | /** 198 | * Get the base URL for requests 199 | * 200 | * @return The base URL 201 | */ 202 | private String getBaseUrl() { 203 | return BASE_URL; 204 | } 205 | 206 | 207 | /** 208 | * Add a proxy server through which to make requests 209 | */ 210 | public void addProxy(String protocol, String host, int port, String username, String password) { 211 | Proxy proxy = new Proxy(protocol, host, port, username, password); 212 | proxies.add(proxy); 213 | } 214 | 215 | /** 216 | * Add a proxy server through which to make requests 217 | */ 218 | public void addProxy(String protocol, String host, int port, String username, String password, 219 | int socketTimeout, int connectTimeout, int connectionRequestTimeout) 220 | { 221 | Proxy proxy = new Proxy(protocol, host, port, username, password, socketTimeout, connectTimeout, 222 | connectionRequestTimeout); 223 | proxies.add(proxy); 224 | } 225 | 226 | public static final void main(final String[] args) throws IOException { 227 | if (args.length != 1) { 228 | System.err.println("Usage: OssIndexHttpClient '{\"coordinates\": [\"pkg:maven/poi/poi@1.2.3\"]}'"); 229 | return; 230 | } 231 | OssIndexHttpClient client = new OssIndexHttpClient(); 232 | String results = client.performPostRequest("component-report", args[args.length - 1]); 233 | System.err.println(results); 234 | } 235 | 236 | /** 237 | * Simple POJO for proxy information. 238 | */ 239 | class Proxy 240 | { 241 | private String protocol; 242 | 243 | private String host; 244 | 245 | private int port; 246 | 247 | private String username; 248 | 249 | private String password; 250 | 251 | private int proxySocketTimeout = PROXY_SOCKET_TIMEOUT; 252 | 253 | private int proxyConnectTimeout = PROXY_CONNECT_TIMEOUT; 254 | 255 | private int proxyConnectionRequestTimeout = PROXY_CONNECTION_REQUEST_TIMEOUT; 256 | 257 | public Proxy(String protocol, String host, int port, String username, String password) { 258 | this.protocol = protocol; 259 | this.host = host; 260 | this.port = port; 261 | this.username = username; 262 | this.password = password; 263 | } 264 | 265 | public int getProxyConnectionRequestTimeout() { 266 | return proxyConnectionRequestTimeout; 267 | } 268 | 269 | public int getProxyConnectTimeout() { 270 | return proxyConnectTimeout; 271 | } 272 | 273 | public int getProxySocketTimeout() { 274 | return proxySocketTimeout; 275 | } 276 | 277 | public Proxy(String protocol, String host, int port, String username, String password, 278 | int socketTimeout, int connectTimeout, int connectionRequestTimeout) 279 | { 280 | this.protocol = protocol; 281 | this.host = host; 282 | this.port = port; 283 | this.username = username; 284 | this.password = password; 285 | proxySocketTimeout = socketTimeout; 286 | proxyConnectTimeout = connectTimeout; 287 | proxyConnectionRequestTimeout = connectionRequestTimeout; 288 | } 289 | 290 | public HttpHost getHttpHost() { 291 | return new HttpHost(host, port, protocol); 292 | } 293 | 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/request/PackageRequestDto.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.common.request; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | public class PackageRequestDto 8 | { 9 | private String[] coordinates; 10 | 11 | private PackageRequestDto(final Builder builder) { 12 | setCoordinates(builder.coordinates.toArray(new String[builder.coordinates.size()])); 13 | } 14 | 15 | public static Builder newBuilder() { 16 | return new Builder(); 17 | } 18 | 19 | public static Builder newBuilder(final PackageRequestDto copy) { 20 | Builder builder = new Builder(); 21 | builder.coordinates = Arrays.asList(copy.coordinates); 22 | return builder; 23 | } 24 | 25 | public String[] getCoordinates() { 26 | return coordinates; 27 | } 28 | 29 | private void setCoordinates(final String[] coordinates) { 30 | this.coordinates = coordinates; 31 | } 32 | 33 | public static final class Builder 34 | { 35 | private List coordinates = new ArrayList<>(); 36 | 37 | private Builder() {} 38 | 39 | public Builder withCoordinate(final String val) { 40 | coordinates.add(val); 41 | return this; 42 | } 43 | 44 | public PackageRequestDto build() { 45 | return new PackageRequestDto(this); 46 | } 47 | 48 | public int size() { 49 | return coordinates.size(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/common/request/PackageRequestService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Vör Security Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | package net.ossindex.common.request; 28 | 29 | import java.io.File; 30 | import java.io.IOException; 31 | import java.lang.reflect.Type; 32 | import java.util.Collection; 33 | import java.util.Collections; 34 | import java.util.HashMap; 35 | import java.util.HashSet; 36 | import java.util.LinkedList; 37 | import java.util.List; 38 | import java.util.Map; 39 | import java.util.concurrent.TimeUnit; 40 | 41 | import com.google.gson.Gson; 42 | import com.google.gson.GsonBuilder; 43 | import com.google.gson.reflect.TypeToken; 44 | import net.ossindex.common.IPackageRequest; 45 | import net.ossindex.common.OssiPackage; 46 | import net.ossindex.common.OssiVulnerability; 47 | import net.ossindex.common.PackageCoordinate; 48 | import net.ossindex.common.filter.IVulnerabilityFilter; 49 | import net.ossindex.common.filter.VulnerabilityFilterFactory; 50 | import org.mapdb.DB; 51 | import org.mapdb.DBMaker; 52 | import org.mapdb.HTreeMap; 53 | import org.mapdb.Serializer; 54 | import org.slf4j.Logger; 55 | import org.slf4j.LoggerFactory; 56 | 57 | /** 58 | * Perform a package request. 59 | * 60 | * @author Ken Duck 61 | */ 62 | public class PackageRequestService 63 | implements IPackageRequest 64 | { 65 | private static final Logger LOG = LoggerFactory.getLogger(PackageRequestService.class); 66 | 67 | private static final long FILE_LOCK_WAIT = 1000; 68 | 69 | private static int MAX_PACKAGES_PER_QUERY = 128; 70 | 71 | private final Gson gson = new GsonBuilder().disableHtmlEscaping().create(); 72 | 73 | private final OssIndexHttpClient client; 74 | 75 | private boolean debug = false; 76 | 77 | /** 78 | * Packages returned from the server are in the same order as the packages SENT to the server. 79 | */ 80 | private PackageRequestDto.Builder packages = PackageRequestDto.newBuilder(); 81 | 82 | /** 83 | * Map of coordinates to their inclusion paths 84 | */ 85 | private Map> paths = new HashMap<>(); 86 | 87 | /** 88 | * List of all filters to apply 89 | */ 90 | private List filters = new LinkedList<>(); 91 | 92 | private File cacheFile; 93 | 94 | private int cacheTimeout = 12; 95 | 96 | public PackageRequestService(OssIndexHttpClient client) { 97 | this.client = client; 98 | File home = new File(System.getProperty("user.home")); 99 | File cacheDir = new File(home, ".ossindex"); 100 | cacheFile = new File(cacheDir, "gradle.cache"); 101 | } 102 | 103 | public void addVulnerabilityFilter(IVulnerabilityFilter filter) { 104 | filters.add(filter); 105 | } 106 | 107 | public void setCacheFile(String path) { 108 | cacheFile = path != null ? new File(path) : null; 109 | } 110 | 111 | @Override 112 | public void setCredentials(final String user, final String token) { 113 | client.setCredentials(user, token); 114 | } 115 | 116 | @Override 117 | public void setMaximumPackagesPerRequest(final int count) { 118 | MAX_PACKAGES_PER_QUERY = count; 119 | } 120 | 121 | @Override 122 | public void setCacheTimeout(final int hours) { 123 | cacheTimeout = hours; 124 | } 125 | 126 | /* 127 | * (non-Javadoc) 128 | * @see net.ossindex.common.IPackageRequest#add(java.lang.String, java.lang.String, java.lang.String, java.lang.String) 129 | */ 130 | @Override 131 | public OssiPackage add(String type, String namespace, String artifactId, String version) { 132 | PackageCoordinate pkg = PackageCoordinate.newBuilder() 133 | .withFormat(type) 134 | .withNamespace(namespace) 135 | .withName(artifactId) 136 | .withVersion(version) 137 | .build(); 138 | return add(Collections.singletonList(pkg)); 139 | } 140 | 141 | @Override 142 | public OssiPackage add(List path) { 143 | if (path != null && !path.isEmpty()) { 144 | PackageCoordinate pkg = path.get(path.size() - 1); 145 | 146 | // Build a default response for the query 147 | OssiPackage desc = new OssiPackage(pkg.getType(), pkg.getNamespace(), pkg.getName(), pkg.getVersion()); 148 | 149 | // Add the package path to the packages and path lists 150 | String coord = desc.getCoordinates(); 151 | packages.withCoordinate(coord); 152 | paths.put(coord.toLowerCase(), path); 153 | 154 | return desc; 155 | } 156 | return null; 157 | } 158 | 159 | /* 160 | * (non-Javadoc) 161 | * @see net.ossindex.common.IPackageRequest#run() 162 | */ 163 | @Override 164 | public synchronized Collection run() throws IOException { 165 | // Setup the cache 166 | if (cacheFile != null && !cacheFile.exists()) { 167 | File cacheDir = cacheFile.getParentFile(); 168 | if (!cacheDir.exists()) { 169 | cacheDir.mkdirs(); 170 | } 171 | } 172 | DB db = null; 173 | if (cacheFile != null) { 174 | try { 175 | db = DBMaker 176 | .fileDB(cacheFile) 177 | .transactionEnable() 178 | .fileLockWait(FILE_LOCK_WAIT) 179 | .make(); 180 | } 181 | catch (Exception e) { 182 | LOG.error("Could not create cache file (" + cacheFile + "): " + e.getMessage()); 183 | db = DBMaker.memoryDB().make(); 184 | } 185 | } 186 | else { 187 | db = DBMaker.memoryDB().make(); 188 | } 189 | 190 | HTreeMap cache = db.hashMap("cache") 191 | .keySerializer(Serializer.STRING) 192 | .valueSerializer(Serializer.STRING) 193 | .expireAfterCreate(cacheTimeout, TimeUnit.HOURS) 194 | .createOrOpen(); 195 | 196 | try { 197 | PackageRequestDto dto = packages.build(); 198 | String[] coords = dto.getCoordinates(); 199 | 200 | Collection results = new HashSet(); 201 | 202 | // Maximum number of packages per request is 128, so we may need to make multiple requests. The top level 203 | // loop ensures we check for all coordinates. 204 | int count = 0; 205 | while (count < coords.length) { 206 | 207 | PackageRequestDto.Builder usePackages = PackageRequestDto.newBuilder(); 208 | 209 | // The second loop only continues until we have enough coords for a single request 210 | while (count < coords.length && usePackages.size() < MAX_PACKAGES_PER_QUERY) { 211 | String coord = coords[count++]; 212 | // FIXME: Assumption that all coordinates are case insensitive 213 | coord = coord.toLowerCase(); 214 | if (cache.containsKey(coord)) { 215 | LOG.debug("Using cache: " + coord); 216 | OssiPackage pkg = gson.fromJson(cache.get(coord), OssiPackage.class); 217 | results.add(pkg); 218 | } 219 | else { 220 | LOG.debug("Add to request: " + coord); 221 | usePackages.withCoordinate(coord); 222 | } 223 | } 224 | 225 | PackageRequestDto useDto = usePackages.build(); 226 | if (useDto.getCoordinates().length > 0) { 227 | String data = gson.toJson(useDto); 228 | // Perform the OSS Index query 229 | String response = client.performPostRequest("component-report", data); 230 | 231 | // Convert the results to Java objects 232 | Type listType = new TypeToken>() { }.getType(); 233 | Collection newResults = gson.fromJson(response, listType); 234 | for (OssiPackage pkg : newResults) { 235 | try { 236 | results.add(pkg); 237 | String cacheCoord = pkg.getCoordinates().toLowerCase(); 238 | LOG.debug("Adding to cache: " + cacheCoord); 239 | cache.put(cacheCoord, gson.toJson(pkg)); 240 | db.commit(); 241 | } 242 | catch (Exception e) { 243 | db.rollback(); 244 | throw e; 245 | } 246 | } 247 | } 248 | } 249 | results = filterResults(results); // This will remove vulnerabilities from packages 250 | return results; 251 | } 252 | finally { 253 | db.close(); 254 | } 255 | } 256 | 257 | private Collection filterResults(final Collection pkgs) { 258 | if (!filters.isEmpty()) { 259 | List results = new LinkedList<>(); 260 | 261 | for (OssiPackage pkg : pkgs) { 262 | String coord = pkg.getCoordinates().toLowerCase(); 263 | if (paths.containsKey(coord)) { 264 | List path = paths.get(coord); 265 | results.add(filterPackage(pkg, path)); 266 | } 267 | else { 268 | LOG.error("WARNING: Could not find inclusion path for " + coord); 269 | results.add(filterPackage(pkg, Collections.emptyList())); 270 | } 271 | } 272 | 273 | return results; 274 | } 275 | else { 276 | return pkgs; 277 | } 278 | } 279 | 280 | private OssiPackage filterPackage(final OssiPackage pkg, final List path) 281 | { 282 | List vulns = pkg.getVulnerabilities(); 283 | if (vulns != null && !vulns.isEmpty()) { 284 | OssiPackage.Builder builder = OssiPackage.newBuilder() 285 | .withCoordinates(pkg.getCoordinates()) 286 | .withDescription(pkg.getDescription()) 287 | .withReference(pkg.getReference()) 288 | .withUnfilteredVulnerabilityMatches(vulns.size()); 289 | // Build a new list of vulnerabilities 290 | List filteredVulns = new LinkedList<>(); 291 | 292 | // See if any vulnerabilities are filtered 293 | for (OssiVulnerability vuln : vulns) { 294 | String vid = vuln.getId(); 295 | for (IVulnerabilityFilter filter : filters) { 296 | if (!VulnerabilityFilterFactory.shouldFilter(filter, path, vid)) { 297 | filteredVulns.add(vuln); 298 | } 299 | } 300 | } 301 | 302 | builder.withVulnerabilities(filteredVulns); 303 | return builder.build(); 304 | } 305 | 306 | // No vulns, just return the original 307 | return pkg; 308 | } 309 | 310 | public boolean isDebug() { 311 | return debug; 312 | } 313 | 314 | public void setDebug(final boolean debug) { 315 | this.debug = debug; 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/AuditExclusion.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | import net.ossindex.common.PackageCoordinate; 7 | import net.ossindex.common.PackageCoordinate.Builder; 8 | import net.ossindex.common.filter.IVulnerabilityFilter; 9 | 10 | public class AuditExclusion 11 | { 12 | private String vid; 13 | 14 | private List packages; 15 | 16 | public String getVid() { 17 | return vid; 18 | } 19 | 20 | public void setVid(final String vid) { 21 | this.vid = vid; 22 | } 23 | 24 | public List getPackages() { 25 | return packages; 26 | } 27 | 28 | public void setPackages(final List packages) { 29 | this.packages = packages; 30 | } 31 | 32 | /** 33 | * Add this exclusion to the filter 34 | */ 35 | public void apply(final IVulnerabilityFilter filter) { 36 | List list = getPackageList(); 37 | if (vid == null) { 38 | filter.ignorePackage(list); 39 | } 40 | else { 41 | filter.ignoreVulnerability(list, vid); 42 | } 43 | } 44 | 45 | private List getPackageList() { 46 | List list = new LinkedList<>(); 47 | if (packages != null) { 48 | for (String def : packages) { 49 | PackageCoordinate pkg = buildPackage(def); 50 | if (pkg != null) { 51 | list.add(pkg); 52 | } 53 | } 54 | } 55 | return list; 56 | } 57 | 58 | private PackageCoordinate buildPackage(final String def) { 59 | String[] tokens = def.split("/"); 60 | if (tokens.length >= 2) { 61 | String[] nameTokens = tokens[1].split(":"); 62 | Builder builder = PackageCoordinate.newBuilder() 63 | .withFormat("maven") 64 | .withNamespace(tokens[0]) 65 | .withName(nameTokens[0]); 66 | if (nameTokens.length > 1) { 67 | builder = builder.withVersion(nameTokens[1]); 68 | } 69 | return builder.build(); 70 | } 71 | return null; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return "AuditExclusion{" + 77 | "vid='" + vid + '\'' + 78 | ", packages=" + packages + 79 | '}'; 80 | } 81 | 82 | public boolean hasPackage(final String name) { 83 | if (packages != null) { 84 | for (String def : packages) { 85 | if (def.startsWith(name)) { 86 | return true; 87 | } 88 | } 89 | } 90 | return false; 91 | } 92 | 93 | public boolean hasVid(final String myVid) { 94 | if (vid != null) { 95 | return vid.equals(myVid); 96 | } 97 | return false; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/AuditExtensions.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle; 2 | 3 | import groovy.lang.Closure; 4 | import net.ossindex.gradle.audit.MavenPackageDescriptor; 5 | import org.gradle.api.Project; 6 | 7 | import java.io.File; 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.List; 11 | 12 | public class AuditExtensions 13 | { 14 | private final Project project; 15 | 16 | public String junitReport; 17 | 18 | public boolean failOnError = true; 19 | 20 | public List ignore = new ArrayList<>(); 21 | 22 | Collection exclusion = new ArrayList<>(); 23 | 24 | public String proxyScheme; 25 | 26 | public String proxyHost; 27 | 28 | public Integer proxyPort; 29 | 30 | public String proxyUser; 31 | 32 | public String proxyPassword; 33 | 34 | public String nonProxyHosts; 35 | 36 | public String cache; 37 | 38 | public String user; 39 | 40 | public String token; 41 | 42 | public Integer packagesPerRequest; 43 | 44 | public Boolean rateLimitAsError; 45 | 46 | public Integer cacheTimeout; 47 | 48 | public AuditExtensions(Project project) { 49 | this.project = project; 50 | } 51 | 52 | public boolean isIgnored(MavenPackageDescriptor descriptor) { 53 | return isWholeArtifactIgnored(descriptor) || isSpecificVersionIgnored(descriptor); 54 | } 55 | 56 | private boolean isSpecificVersionIgnored(MavenPackageDescriptor descriptor) { 57 | return ignore.stream().anyMatch(ignored -> ignored.equals(descriptor.getMavenVersionId())); 58 | } 59 | private boolean isWholeArtifactIgnored(MavenPackageDescriptor descriptor) { 60 | return ignore.stream().anyMatch(ignored -> ignored.equals(descriptor.getMavenPackageId())); 61 | } 62 | 63 | public AuditExclusion exclusion(Closure closure) { 64 | AuditExclusion exclusion = (AuditExclusion) project.configure(new AuditExclusion(), closure); 65 | this.exclusion.add(exclusion); 66 | return exclusion; 67 | } 68 | 69 | public Collection getExclusions() { 70 | return exclusion; 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/OssIndexPlugin.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle; 2 | 3 | import net.ossindex.gradle.audit.AuditorFactory; 4 | import net.ossindex.gradle.audit.DependencyAuditor; 5 | import net.ossindex.gradle.audit.MavenPackageDescriptor; 6 | import net.ossindex.gradle.audit.Proxy; 7 | import net.ossindex.gradle.input.ArtifactGatherer; 8 | import net.ossindex.gradle.input.GradleArtifact; 9 | import net.ossindex.gradle.output.AuditResultReporter; 10 | import net.ossindex.gradle.output.JunitXmlReportWriter; 11 | import net.ossindex.gradle.output.PackageTreeReporter; 12 | import org.gradle.api.GradleException; 13 | import org.gradle.api.Plugin; 14 | import org.gradle.api.Project; 15 | import org.gradle.api.Task; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.util.Collection; 20 | import java.util.LinkedList; 21 | import java.util.List; 22 | import java.util.Set; 23 | 24 | public class OssIndexPlugin implements Plugin { 25 | 26 | private JunitXmlReportWriter junitXmlReportWriter = new JunitXmlReportWriter(); 27 | public static String junitReport = null; 28 | 29 | private static AuditExtensions settings = null; 30 | private static final Logger logger = LoggerFactory.getLogger(OssIndexPlugin.class); 31 | private List proxies = new LinkedList<>(); 32 | private Project project = null; 33 | private AuditorFactory factory = new AuditorFactory(); 34 | public void setAuditorFactory(AuditorFactory factory) { 35 | this.factory = factory; 36 | } 37 | 38 | @Override 39 | public synchronized void apply(Project project) { 40 | logger.info("Apply OSS Index Plugin"); 41 | this.project = project; 42 | 43 | project.getExtensions().create("audit", AuditExtensions.class, project); 44 | Task audit = project.task("audit"); 45 | Proxy proxy = getProxy(project, "http"); 46 | 47 | if (proxy != null) { 48 | proxies.add(proxy); 49 | }proxy = getProxy(project, "https"); 50 | if (proxy != null) { 51 | proxies.add(proxy); 52 | } 53 | audit.doLast(this::doAudit); 54 | } 55 | 56 | /** 57 | * Get the proxy if it exists. Check 3 different places: 58 | * 59 | * 1. The build.gradle file 60 | * 2. The local properties 61 | * 3. The system properties 62 | */ 63 | private Proxy getProxy(Project project, String scheme) { 64 | Proxy proxy = new Proxy(); 65 | // Try build.gradle properties 66 | if (settings != null && scheme.equals(settings.proxyScheme) && settings.proxyHost != null) { 67 | proxy = new Proxy(); 68 | proxy.setHost(settings.proxyHost); 69 | proxy.setPort(settings.proxyPort); 70 | proxy.setUser(settings.proxyUser); 71 | proxy.setPassword(settings.proxyPassword); 72 | proxy.setNonProxyHosts(settings.nonProxyHosts); 73 | } 74 | 75 | // Try the local properties 76 | if (!proxy.isValid() && project.hasProperty(scheme + ".proxyHost")) { 77 | proxy = new Proxy(); 78 | proxy.setHost((String) project.findProperty(scheme + ".proxyHost")); 79 | Object port = project.findProperty(scheme + ".proxyPort"); 80 | proxy.setPort(port == null ? null : Integer.parseInt((String) port)); 81 | proxy.setUser((String) project.findProperty(scheme + ".proxyUser")); 82 | proxy.setPassword((String) project.findProperty(scheme + ".proxyPassword")); 83 | proxy.setNonProxyHosts((String) project.findProperty(scheme + ".nonProxyHosts")); 84 | } 85 | 86 | // Look for the system properties 87 | if (!proxy.isValid() && project.hasProperty("systemProp." + scheme + ".proxyHost")) { 88 | proxy = new Proxy(); 89 | proxy.setHost((String) project.findProperty("systemProp." + scheme + ".proxyHost")); 90 | Object port = project.findProperty("systemProp." + scheme + ".proxyPort"); 91 | proxy.setPort(port == null ? null : Integer.parseInt((String) port)); 92 | proxy.setUser((String) project.findProperty("systemProp." + scheme + ".proxyUser")); 93 | proxy.setPassword((String) project.findProperty("systemProp." + scheme + ".proxyPassword")); 94 | proxy.setNonProxyHosts((String) project.findProperty("systemProp." + scheme + ".nonProxyHosts")); 95 | } 96 | if (proxy.isValid()) { 97 | return proxy; 98 | } else { 99 | return null; 100 | } 101 | } 102 | 103 | private synchronized void doAudit(Task task) { 104 | if (this.settings == null) { 105 | this.settings = getAuditExtensions(task.getProject()); 106 | } 107 | // Mocked tests may not have settings 108 | junitReport = settings != null ? settings.junitReport : null; 109 | if (settings != null) { 110 | junitXmlReportWriter.init(junitReport); 111 | } 112 | 113 | ArtifactGatherer gatherer = factory.getGatherer(); 114 | Set gradleArtifacts = gatherer != null ? gatherer.gatherResolvedArtifacts(task.getProject()) : null; 115 | 116 | AuditExtensions auditConfig = getAuditExtensions(task.getProject()); 117 | DependencyAuditor auditor = factory.getDependencyAuditor(auditConfig, gradleArtifacts, proxies); 118 | 119 | String tmpTask = project.getDisplayName().split(" ")[1].replaceAll("\'","") + ":audit"; 120 | AuditResultReporter reporter = new AuditResultReporter(gradleArtifacts, 121 | getAuditExtensions(task.getProject()), 122 | junitXmlReportWriter, 123 | project.getDisplayName().split(" ")[1].replaceAll("\'","") + ":audit"); 124 | 125 | logger.info(String.format("Found %s gradleArtifacts to audit", gradleArtifacts.size())); 126 | 127 | Collection packagesWithVulnerabilities = auditor.runAudit(); 128 | 129 | try { 130 | reporter.reportResult(packagesWithVulnerabilities); 131 | } catch (GradleException e) { 132 | if (shouldFailOnError(task.getProject())) { 133 | throw e; 134 | } 135 | } finally { 136 | PackageTreeReporter treeReporter = new PackageTreeReporter(auditConfig); 137 | treeReporter.reportDependencyTree(gradleArtifacts, packagesWithVulnerabilities); 138 | if (junitReport != null) { 139 | try { 140 | junitXmlReportWriter.writeXmlReport(junitReport); 141 | junitXmlReportWriter = null; 142 | } catch (Exception e) { 143 | logger.error("ERROR: Failed to create JUnit Plugin report: " + e.getMessage()); 144 | } 145 | } 146 | } 147 | } 148 | 149 | private boolean shouldFailOnError(Project project) { 150 | return getAuditExtensions(project).failOnError; 151 | } 152 | 153 | private AuditExtensions getAuditExtensions(Project project) { 154 | return (AuditExtensions) project.getExtensions().getByName("audit"); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/audit/AuditorFactory.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.audit; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | import net.ossindex.gradle.AuditExtensions; 7 | import net.ossindex.gradle.input.ArtifactGatherer; 8 | import net.ossindex.gradle.input.GradleArtifact; 9 | 10 | /** 11 | * Using a factory to make testing easier. 12 | */ 13 | public class AuditorFactory 14 | { 15 | public DependencyAuditor getDependencyAuditor(final AuditExtensions auditConfig, 16 | final Set gradleArtifacts, 17 | final List proxies) { 18 | return new DependencyAuditor(auditConfig, gradleArtifacts, proxies); 19 | } 20 | 21 | public ArtifactGatherer getGatherer() { 22 | ArtifactGatherer gatherer = new ArtifactGatherer(); 23 | return gatherer; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/audit/DependencyAuditor.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.audit; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.Arrays; 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | import com.google.common.base.Strings; 15 | import net.ossindex.common.IPackageRequest; 16 | import net.ossindex.common.OssIndexApi; 17 | import net.ossindex.common.OssiPackage; 18 | import net.ossindex.common.PackageCoordinate; 19 | import net.ossindex.common.filter.IVulnerabilityFilter; 20 | import net.ossindex.common.filter.VulnerabilityFilterFactory; 21 | import net.ossindex.gradle.AuditExclusion; 22 | import net.ossindex.gradle.AuditExtensions; 23 | import net.ossindex.gradle.input.GradleArtifact; 24 | import org.gradle.api.GradleException; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | public class DependencyAuditor 29 | { 30 | private static final Logger logger = LoggerFactory.getLogger(DependencyAuditor.class); 31 | 32 | private final AuditExtensions config; 33 | 34 | private Map parents = new HashMap<>(); 35 | 36 | private IPackageRequest request; 37 | 38 | public DependencyAuditor(final AuditExtensions auditConfig, 39 | Set gradleArtifacts, 40 | final List proxies) 41 | { 42 | this.config = auditConfig; 43 | switch(proxies.size()) { 44 | case 0: 45 | logger.info("Direct OSSI connection (" + proxies.size() + " proxies configured)"); 46 | break; 47 | default: 48 | logger.info("Using proxy (" + proxies.size() + ")"); 49 | for (Proxy proxy : proxies) { 50 | logger.info(" * " + proxy); 51 | OssIndexApi.addProxy(proxy.getScheme(), proxy.getHost(), proxy.getPort(), proxy.getUser(), proxy.getPassword()); 52 | } 53 | break; 54 | } 55 | 56 | request = OssIndexApi.createPackageRequest(); 57 | configure(); 58 | addArtifactsToAudit(gradleArtifacts); 59 | } 60 | 61 | private void configure() { 62 | if (config != null) { 63 | // Filter configuration 64 | IVulnerabilityFilter filter = VulnerabilityFilterFactory.getInstance().createVulnerabilityFilter(); 65 | Collection exclusions = config.getExclusions(); 66 | for (AuditExclusion exclusion : exclusions) { 67 | exclusion.apply(filter); 68 | } 69 | request.addVulnerabilityFilter(filter); 70 | 71 | // Cache configuration 72 | if (!Strings.isNullOrEmpty(config.cache)) { 73 | File file = new File(config.cache); 74 | if (file.exists()) { 75 | if (!file.isFile()) { 76 | throw new GradleException("cache option must specify a file (" + config.cache + ")"); 77 | } 78 | if (!file.canWrite()) { 79 | throw new GradleException("cannot write to the specified cache file (" + config.cache + ")"); 80 | } 81 | } 82 | File parentDir = file.getParentFile(); 83 | if (parentDir.exists()) { 84 | if (!parentDir.canWrite()) { 85 | throw new GradleException("cannot write to cache dir (" + config.cache + ")"); 86 | } 87 | if (!parentDir.canExecute()) { 88 | throw new GradleException( 89 | "cannot access cache dir, need execute permissions on dir (" + config.cache + ")"); 90 | } 91 | } 92 | else { 93 | if (!parentDir.mkdirs()) { 94 | throw new GradleException("cannot create dir for cache (" + config.cache + ")"); 95 | } 96 | } 97 | request.setCacheFile(file.getAbsolutePath()); 98 | } 99 | 100 | // Credentials configuration 101 | if (!Strings.isNullOrEmpty(config.user) && !Strings.isNullOrEmpty(config.token)) { 102 | request.setCredentials(config.user, config.token); 103 | } 104 | 105 | if (config.packagesPerRequest != null) { 106 | request.setMaximumPackagesPerRequest(config.packagesPerRequest); 107 | } 108 | 109 | if (config.cacheTimeout != null) { 110 | request.setCacheTimeout(config.cacheTimeout); 111 | } 112 | } 113 | } 114 | 115 | public Collection runAudit() { 116 | try { 117 | Set results = new HashSet<>(); 118 | Collection packages = request.run(); 119 | for (OssiPackage pkg : packages) { 120 | MavenPackageDescriptor mvnPkg = new MavenPackageDescriptor(pkg); 121 | if (parents.containsKey(pkg)) { 122 | OssiPackage parent = parents.get(pkg); 123 | if (parent != null) { 124 | mvnPkg.setParent(new MavenIdWrapper(parent)); 125 | } 126 | } 127 | if (mvnPkg.getAllVulnerabilityCount() > 0) { 128 | results.add(mvnPkg); 129 | } 130 | } 131 | return results; 132 | } 133 | catch (IOException e) { 134 | if (e.getMessage().contains("(429)")) { 135 | // Non-registered users should be told about registering 136 | if(Strings.isNullOrEmpty(config.user)) { 137 | throw new GradleException("Too many requests (429): Use OSS Index credentials for increased rate limit.", e); 138 | } 139 | // Registered users can choose whether this is an error or not 140 | else { 141 | // If the user has decided that 429 is a warning 142 | if (Boolean.FALSE.equals(config.rateLimitAsError)) { 143 | logger.info("Too many requests (429) trying to get audit results. Current results have been cached,"); 144 | logger.info("wait 60+ minutes then run again to audit more packages. If you run builds once a day"); 145 | logger.info("and always see this message, you may want to run the build more often or increase the"); 146 | logger.info("cache timeout to 48 or more hours."); 147 | return Collections.emptyList(); 148 | } 149 | // Otherwise 429 is an error 150 | else { 151 | throw new GradleException("Error trying to get audit results: " + e.getMessage(), e); 152 | } 153 | } 154 | } 155 | // Unknown exceptions are errors 156 | else { 157 | throw new GradleException("Error trying to get audit results: " + e.getMessage(), e); 158 | } 159 | } 160 | } 161 | 162 | private void addArtifactsToAudit(Set gradleArtifacts) { 163 | gradleArtifacts.forEach(this::addArtifact); 164 | } 165 | 166 | private void addArtifact(GradleArtifact gradleArtifact) { 167 | PackageCoordinate parentCoordinate = buildCoordinate(gradleArtifact); 168 | OssiPackage parent = request.add(Collections.singletonList(parentCoordinate)); 169 | parents.put(parent, null); 170 | gradleArtifact.getAllChildren().forEach(c -> addPackageDependencies(parent, parentCoordinate, c)); 171 | } 172 | 173 | private void addPackageDependencies(OssiPackage parent, 174 | PackageCoordinate parentCoordinate, 175 | GradleArtifact gradleArtifact) 176 | { 177 | OssiPackage pkgDep = new OssiPackage("maven", gradleArtifact.getGroup(), gradleArtifact.getName(), 178 | gradleArtifact.getVersion()); 179 | if (!parents.containsKey(pkgDep)) { 180 | PackageCoordinate childCoordinate = buildCoordinate(gradleArtifact); 181 | pkgDep = request.add(Arrays.asList(new PackageCoordinate[]{parentCoordinate, childCoordinate})); 182 | parents.put(pkgDep, parent); 183 | } 184 | } 185 | 186 | private PackageCoordinate buildCoordinate(final GradleArtifact gradleArtifact) { 187 | return PackageCoordinate.newBuilder() 188 | .withFormat("maven") 189 | .withNamespace(gradleArtifact.getGroup()) 190 | .withName(gradleArtifact.getName()) 191 | .withVersion(gradleArtifact.getVersion()) 192 | .build(); 193 | } 194 | 195 | private String toString(PackageCoordinate pkg) { 196 | return pkg.getNamespace() + ":" + pkg.getNamespace() + ":" + pkg.getName() + ":" + pkg.getVersion(); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/audit/MavenIdWrapper.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.audit; 2 | 3 | import net.ossindex.common.OssiPackage; 4 | 5 | public class MavenIdWrapper { 6 | 7 | protected String groupId; 8 | protected String artifactId; 9 | protected String version; 10 | 11 | @Override 12 | public boolean equals(Object o) { 13 | if (this == o) return true; 14 | if (o == null || getClass() != o.getClass()) return false; 15 | 16 | MavenIdWrapper that = (MavenIdWrapper) o; 17 | 18 | return getMavenVersionId() != null ? getMavenVersionId().equals(that.getMavenVersionId()) : that.getMavenVersionId() == null; 19 | } 20 | 21 | @Override 22 | public int hashCode() { 23 | return getMavenVersionId() != null ? getMavenVersionId().hashCode() : 0; 24 | } 25 | 26 | /** 27 | * Required for serialization 28 | */ 29 | public MavenIdWrapper() { 30 | 31 | } 32 | 33 | public MavenIdWrapper(OssiPackage pkg) { 34 | this.setGroupId(pkg.getNamespace()); 35 | this.setArtifactId(pkg.getName()); 36 | this.setVersion(pkg.getVersion()); 37 | } 38 | 39 | /** 40 | * @return the groupId 41 | */ 42 | public String getGroupId() { 43 | return groupId; 44 | } 45 | 46 | /** 47 | * @param groupId the groupId to set 48 | */ 49 | public void setGroupId(String groupId) { 50 | this.groupId = groupId; 51 | } 52 | 53 | /** 54 | * @return the artifactId 55 | */ 56 | public String getArtifactId() { 57 | return artifactId; 58 | } 59 | 60 | /** 61 | * @param artifactId the artifactId to set 62 | */ 63 | public void setArtifactId(String artifactId) { 64 | this.artifactId = artifactId; 65 | } 66 | 67 | /** 68 | * @return the version 69 | */ 70 | public String getVersion() { 71 | return version; 72 | } 73 | 74 | /** 75 | * @param version the version to set 76 | */ 77 | public void setVersion(String version) { 78 | this.version = version; 79 | } 80 | 81 | /** 82 | * Get the Maven ID excluding the version 83 | * 84 | * @return the Maven ID 85 | */ 86 | public String getMavenPackageId() { 87 | StringBuilder sb = new StringBuilder(); 88 | if (groupId != null) { 89 | sb.append(groupId); 90 | } 91 | sb.append(":"); 92 | if (artifactId != null) { 93 | sb.append(artifactId); 94 | } 95 | return sb.toString(); 96 | } 97 | 98 | /** 99 | * Get the maven ID including the version 100 | * 101 | * @return the maven ID 102 | */ 103 | public String getMavenVersionId() { 104 | StringBuilder sb = new StringBuilder(); 105 | if (groupId != null) { 106 | sb.append(groupId); 107 | } 108 | sb.append(":"); 109 | if (artifactId != null) { 110 | sb.append(artifactId); 111 | } 112 | sb.append(":"); 113 | if (version != null) { 114 | sb.append(version); 115 | } 116 | return sb.toString(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/audit/MavenPackageDescriptor.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.audit; 2 | 3 | import net.ossindex.common.OssiPackage; 4 | import net.ossindex.common.OssiVulnerability; 5 | import org.gradle.internal.impldep.com.google.gson.annotations.SerializedName; 6 | 7 | import javax.xml.bind.annotation.XmlElement; 8 | import javax.xml.bind.annotation.XmlElementWrapper; 9 | import java.util.List; 10 | import java.util.Objects; 11 | 12 | public class MavenPackageDescriptor extends MavenIdWrapper { 13 | 14 | private MavenIdWrapper parent; 15 | 16 | @XmlElement(name = "unfiltered-vulnerability-count") 17 | @SerializedName("unfiltered-vulnerability-count") 18 | private int unfilteredVulnerabilityCount; 19 | 20 | @XmlElement(name = "vulnerability-matches") 21 | @SerializedName("vulnerability-matches") 22 | private int vulnerabilityMatches; 23 | 24 | @XmlElementWrapper(name = "vulnerabilities") 25 | @XmlElement(name = "vulnerability") 26 | private List vulnerabilities; 27 | 28 | /** 29 | * Constructor required by jaxb 30 | */ 31 | public MavenPackageDescriptor() { 32 | 33 | } 34 | 35 | public MavenPackageDescriptor(OssiPackage pkg) { 36 | groupId = pkg.getNamespace(); 37 | artifactId = pkg.getName(); 38 | version = pkg.getVersion(); 39 | vulnerabilities = pkg.getVulnerabilities(); 40 | unfilteredVulnerabilityCount = pkg.getUnfilteredVulnerabilityMatches(); 41 | vulnerabilityMatches = vulnerabilities.size(); 42 | } 43 | 44 | public void setParent(MavenIdWrapper parent) { 45 | this.parent = parent; 46 | } 47 | 48 | public MavenIdWrapper getParent() { 49 | return parent; 50 | } 51 | 52 | /** 53 | * Get the number of vulnerabilities matching the supplied version, prior to any exclusions and filtering. 54 | * 55 | * @return Total number of vulnerabilities. 56 | */ 57 | public int getAllVulnerabilityCount() { 58 | return unfilteredVulnerabilityCount; 59 | } 60 | 61 | /** 62 | * Get the total number of vulnerabilities matching the supplied version. 63 | * 64 | * @return Number of matching vulnerabilities 65 | */ 66 | public int getVulnerabilityMatches() { 67 | return vulnerabilityMatches; 68 | } 69 | 70 | /** 71 | * Get vulnerabilities belonging to this package. 72 | * 73 | * @return all vulnerabilities 74 | */ 75 | public List getVulnerabilities() { 76 | return vulnerabilities; 77 | } 78 | 79 | @Override 80 | public int hashCode() {return 31 * super.hashCode() + Objects.hash(parent);} 81 | 82 | @Override 83 | public boolean equals(final Object obj) { 84 | if (this == obj) { 85 | return true; 86 | } 87 | if (obj == null || getClass() != obj.getClass()) { 88 | return false; 89 | } 90 | if (!super.equals(obj)) { 91 | return false; 92 | } 93 | final MavenPackageDescriptor other = (MavenPackageDescriptor) obj; 94 | return Objects.equals(this.parent, other.parent); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/audit/OssIndexResultsWrapper.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.audit; 2 | 3 | import net.ossindex.gradle.audit.MavenPackageDescriptor; 4 | 5 | import javax.xml.bind.annotation.XmlElement; 6 | import javax.xml.bind.annotation.XmlElementWrapper; 7 | import javax.xml.bind.annotation.XmlRootElement; 8 | import java.util.Collection; 9 | 10 | @XmlRootElement(name = "OssIndex") 11 | public class OssIndexResultsWrapper { 12 | 13 | private Collection packages; 14 | 15 | public OssIndexResultsWrapper() { 16 | 17 | } 18 | 19 | public OssIndexResultsWrapper(Collection results) { 20 | this.setPackages(results); 21 | } 22 | 23 | public Collection getPackages() { 24 | return packages; 25 | } 26 | 27 | @XmlElementWrapper(name="packages") 28 | @XmlElement(name = "package") 29 | public void setPackages(Collection packages) { 30 | this.packages = packages; 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/audit/Proxy.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.audit; 2 | 3 | import java.util.Objects; 4 | 5 | public class Proxy 6 | { 7 | private String scheme; 8 | 9 | private String host; 10 | 11 | private Integer port; 12 | 13 | private String user; 14 | 15 | private String password; 16 | 17 | private String[] nonProxyHosts; 18 | 19 | public String getScheme() { 20 | return scheme; 21 | } 22 | 23 | public String getHost() { 24 | return host; 25 | } 26 | 27 | public int getPort() { 28 | if (port == null) { 29 | switch (scheme) { 30 | case "http": 31 | return 80; 32 | case "https": 33 | return 443; 34 | default: 35 | throw new IllegalArgumentException("Unknown proxy scheme: " + scheme); 36 | } 37 | } 38 | return port; 39 | } 40 | 41 | public String getUser() { 42 | return user; 43 | } 44 | 45 | public String getPassword() { 46 | return password; 47 | } 48 | 49 | public void setScheme(final String scheme) { 50 | this.scheme = scheme; 51 | } 52 | 53 | public void setHost(final String host) { 54 | this.host = host; 55 | } 56 | 57 | public void setPort(final Integer port) { 58 | this.port = port; 59 | } 60 | 61 | public void setUser(final String user) { 62 | this.user = user; 63 | } 64 | 65 | public void setPassword(final String password) { 66 | this.password = password; 67 | } 68 | 69 | public void setNonProxyHosts(final String nonProxyHosts) { 70 | if (nonProxyHosts != null) { 71 | this.nonProxyHosts = nonProxyHosts.split("|"); 72 | } 73 | } 74 | 75 | public boolean isValid() { 76 | if (host == null) { 77 | return false; 78 | } 79 | 80 | if (nonProxyHosts != null) { 81 | for (String nonProxyHost: nonProxyHosts) { 82 | if (host.equals(nonProxyHost)) { 83 | return false; 84 | } 85 | } 86 | } 87 | 88 | return true; 89 | } 90 | 91 | @Override 92 | public String toString() { 93 | return user + "@" + host + ":" + getPort(); 94 | } 95 | 96 | @Override 97 | public boolean equals(Object o) { 98 | if (o instanceof Proxy) { 99 | Proxy p = (Proxy)o; 100 | return Objects.equals(scheme, p.scheme) && 101 | Objects.equals(host, p.host) && 102 | Objects.equals(port, p.port) && 103 | Objects.equals(user, p.user) && 104 | Objects.equals(password, p.password) && 105 | Objects.equals(nonProxyHosts, p.nonProxyHosts); 106 | } 107 | return false; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/input/ArtifactGatherer.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.input; 2 | 3 | import org.gradle.api.Project; 4 | import org.gradle.api.artifacts.Configuration; 5 | 6 | import java.util.Set; 7 | import java.util.stream.Collectors; 8 | 9 | public class ArtifactGatherer { 10 | public Set gatherResolvedArtifacts(Project project) { 11 | return project 12 | .getConfigurations() 13 | .stream() 14 | .filter(Configuration::isCanBeResolved) 15 | .flatMap(c -> c.getResolvedConfiguration().getFirstLevelModuleDependencies().stream()) 16 | .map(it -> new GradleArtifact(null, it)) 17 | .collect(Collectors.toSet()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/input/GradleArtifact.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.input; 2 | 3 | import net.ossindex.gradle.OssIndexPlugin; 4 | import org.gradle.api.artifacts.ResolvedDependency; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | public class GradleArtifact { 12 | private static final Logger logger = LoggerFactory.getLogger(GradleArtifact.class); 13 | 14 | private final GradleArtifact parent; 15 | private final Set children; 16 | private final String group; 17 | private final String name; 18 | 19 | public String getGroup() { 20 | return group; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public String getVersion() { 28 | return version; 29 | } 30 | 31 | public Set getAllArtifacts() { 32 | Set allGradleArtifacts = new HashSet<>(); 33 | allGradleArtifacts.add(this); 34 | allGradleArtifacts.addAll(getAllChildren()); 35 | return allGradleArtifacts; 36 | } 37 | 38 | public Set getAllChildren() { 39 | Set allChildren = new HashSet<>(); 40 | for (GradleArtifact child : children) { 41 | allChildren.add(child); 42 | allChildren.addAll(child.children); 43 | } 44 | return allChildren; 45 | } 46 | 47 | private final String version; 48 | 49 | public GradleArtifact(GradleArtifact parent, ResolvedDependency gradleArtifact) { 50 | this.parent = parent; 51 | children = new HashSet<>(); 52 | name = gradleArtifact.getModule().getId().getName(); 53 | group = gradleArtifact.getModule().getId().getGroup(); 54 | version = gradleArtifact.getModule().getId().getVersion(); 55 | addChildren(gradleArtifact); 56 | } 57 | 58 | public GradleArtifact getTopMostParent() { 59 | if (parent == null) return this; 60 | GradleArtifact currentParent = parent; 61 | Set visited = new HashSet<>(); 62 | while (currentParent.parent != null) { 63 | currentParent = currentParent.parent; 64 | 65 | if (visited.contains(currentParent)) { 66 | logger.warn("artifact loop detected, starting with " + currentParent.getGroup() + 67 | ":" + currentParent.getName() + 68 | ":" + currentParent.getVersion()); 69 | break; 70 | } 71 | 72 | visited.add(currentParent); 73 | } 74 | return currentParent; 75 | } 76 | 77 | public boolean containsPackage(String fullQualifiedName) { 78 | return getAllArtifacts().stream().anyMatch(c -> c.getFullDescription().equals(fullQualifiedName)); 79 | } 80 | 81 | public String getFullDescription() { 82 | return String.format("%s:%s:%s", group, name, version); 83 | } 84 | 85 | private boolean ancestorIsAlso(ResolvedDependency gradleArtifact) { 86 | if (parent == null) return false; 87 | GradleArtifact currentParent = parent; 88 | while(currentParent != null) { 89 | if(isEquivalent(gradleArtifact, currentParent)) { 90 | return true; 91 | } 92 | currentParent = currentParent.parent; 93 | } 94 | 95 | return false; 96 | } 97 | 98 | private void addChildren(ResolvedDependency gradleArtifact) { 99 | gradleArtifact.getChildren().forEach(c -> { 100 | if(!ancestorIsAlso(c)){ 101 | GradleArtifact child = new GradleArtifact(this, c); 102 | children.add(child); 103 | } 104 | } 105 | ); 106 | } 107 | 108 | @Override 109 | public boolean equals(Object o) { 110 | if (this == o) return true; 111 | if (o == null || getClass() != o.getClass()) return false; 112 | 113 | GradleArtifact gradleArtifact = (GradleArtifact) o; 114 | 115 | return getFullDescription() != null ? getFullDescription().equals(gradleArtifact.getFullDescription()) : gradleArtifact.getFullDescription() == null; 116 | } 117 | 118 | private boolean isEquivalent(ResolvedDependency dependency, GradleArtifact artifact) { 119 | return dependency.getModule().getId().getName().equals(artifact.getName()) 120 | && dependency.getModule().getId().getGroup().equals(artifact.getGroup()) 121 | && dependency.getModule().getId().getVersion().equals(artifact.getVersion()); 122 | } 123 | 124 | @Override 125 | public int hashCode() { 126 | return getFullDescription() != null ? getFullDescription().hashCode() : 0; 127 | } 128 | 129 | public Set getChildren() { 130 | return children; 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/output/AuditResultReporter.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.output; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Set; 8 | import java.util.stream.Collectors; 9 | 10 | import net.ossindex.common.OssiVulnerability; 11 | import net.ossindex.gradle.AuditExtensions; 12 | import net.ossindex.gradle.audit.MavenPackageDescriptor; 13 | import net.ossindex.gradle.input.GradleArtifact; 14 | import org.gradle.api.GradleException; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | public class AuditResultReporter 19 | { 20 | private static final Logger logger = LoggerFactory.getLogger(AuditResultReporter.class); 21 | 22 | private static final String OSSI_VULN_PREFIX = "https://ossindex.sonatype.org/vuln/"; 23 | 24 | private final Set resolvedTopLevelArtifacts; 25 | 26 | private final AuditExtensions settings; 27 | 28 | private Set allGradleArtifacts; 29 | 30 | private String currentVulnerableArtifact = null; 31 | 32 | ArrayList currentVulnerabilityList = new ArrayList<>(); 33 | 34 | private String currentVulnerabilityTotals = null; 35 | 36 | private String thisTask; 37 | 38 | private JunitXmlReportWriter junitXmlReportWriter; 39 | 40 | public AuditResultReporter(Set resolvedTopLevelArtifacts, 41 | AuditExtensions settings, 42 | JunitXmlReportWriter junitXmlReportWriter, 43 | String thisTask) 44 | { 45 | this.resolvedTopLevelArtifacts = resolvedTopLevelArtifacts; 46 | this.settings = settings; 47 | this.junitXmlReportWriter = junitXmlReportWriter; 48 | this.thisTask = thisTask; 49 | } 50 | 51 | public void reportResult(Collection results) { 52 | int unfilteredVulnerabilities = 0; 53 | int totalVunerabilities = 0; 54 | boolean failReported = false; 55 | 56 | { 57 | // Put in a block to ensure these variables are not used elsewhere. They cause confusion. 58 | int unExcludedVulnerabilities = getSumOfUnfilteredVulnerabilities(results); 59 | int excludedVulnerabilities = getSumOfFilteredVulnerabilities(results); 60 | int ignoredVulnerabilities = getIgnoredVulnerabilityCount(results); 61 | 62 | // All excluded and ignored count 63 | unfilteredVulnerabilities = unExcludedVulnerabilities - ignoredVulnerabilities; 64 | 65 | // All non-excluded and ignored 66 | totalVunerabilities = unfilteredVulnerabilities + excludedVulnerabilities + ignoredVulnerabilities; 67 | } 68 | 69 | if (totalVunerabilities == 0) { 70 | return; 71 | } 72 | 73 | currentVulnerabilityTotals = String.format("%s unignored (of %s total) vulnerabilities found", 74 | unfilteredVulnerabilities, 75 | totalVunerabilities); 76 | logger.error(currentVulnerabilityTotals); 77 | 78 | allGradleArtifacts = getAllDependencies(); 79 | 80 | 81 | for (MavenPackageDescriptor descriptor : results) { 82 | 83 | if (descriptor.getVulnerabilities() == null) { 84 | logger.info("No vulnerabilities in " + descriptor.getMavenVersionId()); 85 | continue; 86 | } 87 | if (settings.isIgnored(descriptor)) { 88 | logger.info(descriptor.getMavenVersionId() + " is ignored due to settings"); 89 | continue; 90 | } 91 | 92 | // Now bail if exclusions cause all issues in this package to be ignored 93 | if (descriptor.getVulnerabilityMatches() == 0) { 94 | logger.info("Vulnerabilities in " + descriptor.getMavenVersionId() + " are excluded due to settings"); 95 | continue; 96 | } 97 | 98 | GradleArtifact importingGradleArtifact = null; 99 | try { 100 | importingGradleArtifact = findImportingArtifactFor(descriptor); 101 | } 102 | catch (GradleException ignore) { 103 | // This seems to mean that this is a top level artifact 104 | } 105 | reportVulnerableArtifact(importingGradleArtifact, descriptor); 106 | reportIntroducedVulnerabilities(descriptor); 107 | 108 | // Update the JUnit plugin XML report object 109 | writeTestcaseXml(); 110 | failReported = true; 111 | } 112 | 113 | // If we have not reported any fails on this module, then we should report a pass 114 | if (!failReported) { 115 | writeTestcaseXml(); 116 | } 117 | 118 | if (unfilteredVulnerabilities > 0) { 119 | throw new GradleException("Too many vulnerabilities (" + unfilteredVulnerabilities + ") found."); 120 | } 121 | } 122 | 123 | private void writeTestcaseXml() { 124 | junitXmlReportWriter.updateJunitReport(currentVulnerabilityTotals, 125 | thisTask, 126 | currentVulnerableArtifact, 127 | currentVulnerabilityList); 128 | } 129 | 130 | private void reportVulnerableArtifact(GradleArtifact importingArtifact, MavenPackageDescriptor descriptor) { 131 | currentVulnerableArtifact = importingArtifact == null ? 132 | String.format("%s introduces %s which has %s vulnerabilities", 133 | descriptor.getMavenVersionId(), 134 | descriptor.getMavenVersionId(), 135 | descriptor.getVulnerabilityMatches()) : 136 | String.format("%s introduces %s which has %s vulnerabilities", 137 | importingArtifact.getFullDescription(), 138 | descriptor.getMavenVersionId(), 139 | descriptor.getVulnerabilityMatches()); 140 | logger.error(currentVulnerableArtifact); 141 | } 142 | 143 | private int reportIntroducedVulnerabilities(MavenPackageDescriptor descriptor) { 144 | currentVulnerabilityList.clear(); 145 | List vulns = descriptor.getVulnerabilities(); 146 | vulns.forEach(v -> reportVulnerability(String.format("=> %s (see %s)", v.getTitle(), getUriString(v)))); 147 | return vulns.size(); 148 | } 149 | 150 | private String getUriString(final OssiVulnerability v) { 151 | return OSSI_VULN_PREFIX + v.getId(); 152 | } 153 | 154 | private void reportVulnerability(String line) { 155 | logger.error(line); 156 | currentVulnerabilityList.add(line); 157 | } 158 | 159 | private GradleArtifact findImportingArtifactFor(MavenPackageDescriptor mavenPackageDescriptor) { 160 | return allGradleArtifacts 161 | .stream() 162 | .filter(a -> a.getFullDescription().equals(mavenPackageDescriptor.getMavenVersionId())) 163 | .map(GradleArtifact::getTopMostParent) 164 | .findAny() 165 | .orElseThrow(() -> new GradleException( 166 | "Couldn't find importing artifact for " + mavenPackageDescriptor.getMavenVersionId())); 167 | } 168 | 169 | /** 170 | * Recursively get all dependencies 171 | */ 172 | private Set getAllDependencies() { 173 | Set results = new HashSet<>(); 174 | for (GradleArtifact artifact: resolvedTopLevelArtifacts) { 175 | buildDependencies(results, artifact); 176 | } 177 | return results; 178 | } 179 | 180 | /** 181 | * Recursively get all dependencies 182 | */ 183 | private void buildDependencies(final Set results, final GradleArtifact parent) { 184 | results.add(parent); 185 | Set children = parent.getChildren(); 186 | for (GradleArtifact child: children) { 187 | if (!results.contains(child)) { 188 | buildDependencies(results, child); 189 | } 190 | } 191 | } 192 | 193 | private int getSumOfUnfilteredVulnerabilities(Collection results) { 194 | return results.stream().mapToInt(MavenPackageDescriptor::getVulnerabilityMatches).sum(); 195 | } 196 | 197 | private int getSumOfFilteredVulnerabilities(Collection results) { 198 | int count = 0; 199 | for (MavenPackageDescriptor pkg : results) { 200 | count += (pkg.getAllVulnerabilityCount() - pkg.getVulnerabilityMatches()); 201 | } 202 | return count; 203 | } 204 | 205 | private int getIgnoredVulnerabilityCount(Collection results) { 206 | int count = 0; 207 | for (MavenPackageDescriptor pkg : results) { 208 | if (settings.isIgnored(pkg)) { 209 | count += pkg.getVulnerabilityMatches(); 210 | continue; 211 | } 212 | } 213 | return count; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/output/JunitXmlReportWriter.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.output; 2 | 3 | import java.io.*; 4 | import java.nio.ByteBuffer; 5 | import java.nio.CharBuffer; 6 | import java.nio.channels.FileChannel; 7 | import java.nio.channels.FileLock; 8 | import java.nio.charset.Charset; 9 | import java.nio.file.Files; 10 | import java.nio.file.LinkOption; 11 | import java.nio.file.Paths; 12 | import java.nio.file.attribute.PosixFilePermission; 13 | import java.util.ArrayList; 14 | import java.util.Set; 15 | import java.util.concurrent.locks.Lock; 16 | import java.util.concurrent.locks.ReentrantLock; 17 | 18 | import javax.xml.parsers.DocumentBuilder; 19 | import javax.xml.parsers.DocumentBuilderFactory; 20 | import javax.xml.parsers.ParserConfigurationException; 21 | import javax.xml.transform.OutputKeys; 22 | import javax.xml.transform.Transformer; 23 | import javax.xml.transform.TransformerConfigurationException; 24 | import javax.xml.transform.TransformerException; 25 | import javax.xml.transform.TransformerFactory; 26 | import javax.xml.transform.dom.DOMSource; 27 | import javax.xml.transform.stream.StreamResult; 28 | 29 | import org.apache.commons.lang3.SystemUtils; 30 | 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | import org.w3c.dom.Attr; 34 | import org.w3c.dom.Document; 35 | import org.w3c.dom.Element; 36 | import org.w3c.dom.NamedNodeMap; 37 | import org.w3c.dom.Node; 38 | import org.xml.sax.InputSource; 39 | import org.xml.sax.SAXException; 40 | 41 | public class JunitXmlReportWriter 42 | { 43 | private static final Logger logger = LoggerFactory.getLogger(JunitXmlReportWriter.class); 44 | 45 | public static String pathToReport; 46 | 47 | private boolean skip = false; 48 | 49 | private Element testSuite; 50 | 51 | private Long startSeconds = null; 52 | 53 | /** 54 | * The DocResource class does a file lock preventing concurrent access from other processes, but we still need 55 | * to prevent concurrent access of the document in *THIS* class. 56 | */ 57 | private static Lock lock = new ReentrantLock(); 58 | 59 | public void init(String junitReport) { 60 | 61 | if (junitReport == null) { 62 | skip = true; 63 | return; 64 | } 65 | 66 | this.pathToReport = junitReport; 67 | // Metrics 68 | setStartTime(); 69 | } 70 | 71 | public void setStartTime() { 72 | startSeconds = java.time.Instant.now().getEpochSecond(); 73 | } 74 | 75 | public void updateJunitReport(String totals, 76 | String task, 77 | String artifact, 78 | ArrayList currentVulnerabilityList) 79 | { 80 | 81 | if (skip) { 82 | return; 83 | } 84 | 85 | lock.lock(); 86 | try (DocResource docResource = new DocResource(pathToReport)) { 87 | Document doc = docResource.getDocument(); 88 | 89 | // Get a new testcase ID 90 | Integer testCaseId = getTotalOfElementsByName(doc, "testcase"); 91 | testCaseId++; 92 | 93 | // Change to empty string for text, add name tag set to 94 | Element testCase = addChildElement(doc, testSuite, "testcase", ""); 95 | addElementAttribute(doc, testCase, "name", task + " - " + totals); 96 | addElementAttribute(doc, testCase, "id", testCaseId.toString()); 97 | Long elapsedTime = java.time.Instant.now().getEpochSecond() - startSeconds; 98 | addElementAttribute(doc, testCase, "time", elapsedTime.toString()); 99 | 100 | if (artifact != null) { 101 | if (!artifact.substring(0, 1).equals("0")) { 102 | Element failure = addChildElement(doc, testCase, "failure", buildFailureString(currentVulnerabilityList)); 103 | addElementAttribute(doc, failure, "message", artifact); 104 | } 105 | } 106 | } 107 | catch (IOException e) { 108 | logger.error("Exception writing log: " + e); 109 | e.printStackTrace(); 110 | } 111 | finally { 112 | lock.unlock(); 113 | } 114 | } 115 | 116 | public void writeXmlReport(String pathToReport) throws Exception { 117 | if (skip) { 118 | return; 119 | } 120 | 121 | lock.lock(); 122 | try (DocResource docResource = new DocResource(pathToReport)) { 123 | Document doc = docResource.getDocument(); 124 | String testCount = getTotalOfElementsByName(doc, "testcase").toString(); 125 | modifyElementAttribute(doc, "testsuites", 0, "tests", testCount); 126 | String failureCount = getTotalOfElementsByName(doc, "failure").toString(); 127 | modifyElementAttribute(doc, "testsuites", 0, "failures", failureCount); 128 | } 129 | finally { 130 | lock.unlock(); 131 | } 132 | } 133 | 134 | private Element addChildElement(Document doc, Element parent, String name, String data) { 135 | Element elem = doc.createElement(name); 136 | elem.appendChild(doc.createTextNode(data)); 137 | parent.appendChild(elem); 138 | return elem; 139 | } 140 | 141 | private void addElementAttribute(Document doc, Element parent, String name, String value) { 142 | Attr attr = doc.createAttribute(name); 143 | attr.setValue(value); 144 | parent.setAttributeNode(attr); 145 | } 146 | 147 | private String buildFailureString(ArrayList currentVulnerabilityList) { 148 | String failureString = ""; 149 | for (String tmp : currentVulnerabilityList) { 150 | failureString = failureString + tmp + "\n"; 151 | } 152 | return failureString.trim(); 153 | } 154 | 155 | private Integer getTotalOfElementsByName(Document doc, String name) { 156 | return doc.getElementsByTagName(name).getLength(); 157 | } 158 | 159 | private void modifyElementAttribute(Document doc, String tagName, Integer index, String attrName, String value) { 160 | Node target = doc.getElementsByTagName(tagName).item(index); 161 | NamedNodeMap attr = target.getAttributes(); 162 | Node nodeAttr = attr.getNamedItem(attrName); 163 | nodeAttr.setTextContent(value); 164 | } 165 | 166 | /** 167 | * Manage the lock and writing of the report document. 168 | * 169 | * Every document edit is performed by: 170 | * 171 | * 1. Locking the file 172 | * 2. Creating/Loading the document 173 | * 3. Performing the document changes 174 | * 4. Writing the file 175 | * 5. Unlocking the file 176 | * 177 | * This is done to ensure that parallel builds can all work on the same report file. 178 | */ 179 | class DocResource 180 | implements AutoCloseable 181 | { 182 | 183 | private String path; 184 | 185 | private Document doc; 186 | 187 | private FileLock fileLock; 188 | 189 | private FileChannel fc; 190 | 191 | public DocResource(final String path) { 192 | try { 193 | if (!parentDirIsWritable(new File(path))) { 194 | throw new IOException(("Report directory is not writable: " + path)); 195 | } 196 | 197 | File f = new File(path); 198 | this.path = f.getAbsolutePath(); 199 | 200 | RandomAccessFile randomAccessFile = new RandomAccessFile(path, "rw"); 201 | fc = randomAccessFile.getChannel(); 202 | 203 | fileLock = fc.lock(); 204 | if (fc.size() == 0) { 205 | // If this is a new file, then initialize it. 206 | doc = createDocument(); 207 | } 208 | else { 209 | doc = loadDocument(); 210 | } 211 | } 212 | catch (IOException e) { 213 | logger.error("Exception writing log", e); 214 | } 215 | 216 | } 217 | 218 | public Document getDocument() { 219 | return doc; 220 | } 221 | 222 | @Override 223 | public void close() throws IOException { 224 | writeDocument(doc); 225 | fileLock.close(); 226 | fc.close(); 227 | } 228 | 229 | /** 230 | * Called only if the file does not exist. Creates the basic document structure and writes to the file. 231 | */ 232 | private Document createDocument() throws IOException { 233 | File f = new File(pathToReport); 234 | Document doc = getDocBuilder().newDocument(); 235 | Element rootElement = doc.createElement("testsuites"); 236 | doc.appendChild(rootElement); 237 | addElementAttribute(doc, rootElement, "id", "1"); 238 | addElementAttribute(doc, rootElement, "failures", "0"); 239 | addElementAttribute(doc, rootElement, "tests", "0"); 240 | // Top level test suite 241 | testSuite = addChildElement(doc, rootElement, "testsuite", ""); 242 | addElementAttribute(doc, testSuite, "id", "1"); 243 | addElementAttribute(doc, testSuite, "name", "OSSIndex"); 244 | 245 | writeDocument(doc); 246 | return doc; 247 | } 248 | 249 | /** 250 | * Load the existing report file. 251 | */ 252 | private Document loadDocument() { 253 | Document doc = null; 254 | try { 255 | StringBuilder sb = new StringBuilder(); 256 | ByteBuffer buf = ByteBuffer.allocate(512); 257 | Charset charset = Charset.forName("UTF-8"); 258 | 259 | fc.position(0); 260 | 261 | int count = fc.read(buf); 262 | while (count != -1) { 263 | buf.flip(); 264 | CharBuffer chbuf = charset.decode(buf); 265 | for ( int i = 0; i < count; i++ ) { 266 | sb.append(chbuf.get()); 267 | } 268 | buf.clear(); 269 | count = fc.read(buf); 270 | } 271 | 272 | InputSource is = new InputSource(new StringReader(sb.toString())); 273 | doc = getDocBuilder().parse(is); 274 | } 275 | catch (SAXException | IOException e) { 276 | e.printStackTrace(); 277 | System.exit(1); 278 | } 279 | testSuite = (Element) doc.getElementsByTagName("testsuite").item(0); 280 | return doc; 281 | } 282 | 283 | private DocumentBuilder getDocBuilder() { 284 | DocumentBuilder docBuilder = null; 285 | DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 286 | try { 287 | docBuilder = docFactory.newDocumentBuilder(); 288 | } 289 | catch (ParserConfigurationException e) { 290 | e.printStackTrace(); 291 | System.exit(1); 292 | } 293 | return docBuilder; 294 | } 295 | 296 | /** 297 | * Write the report file 298 | */ 299 | private void writeDocument(Document doc) throws IOException { 300 | if (!parentDirIsWritable(new File(path))) { 301 | throw new IOException(("Report directory is not writable: " + path)); 302 | } 303 | try { 304 | TransformerFactory transformerFactory = TransformerFactory.newInstance(); 305 | Transformer transformer = transformerFactory.newTransformer(); 306 | DOMSource source = new DOMSource(doc); 307 | StringWriter writer = new StringWriter(); 308 | StreamResult result = new StreamResult(writer); 309 | 310 | transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 311 | transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); 312 | 313 | transformer.transform(source, result); 314 | String s = writer.toString(); 315 | fc.truncate(0); 316 | 317 | ByteBuffer buf = ByteBuffer.allocate(s.length()); 318 | buf.clear(); 319 | buf.put(s.getBytes()); 320 | 321 | buf.flip(); 322 | 323 | while(buf.hasRemaining()) { 324 | fc.write(buf); 325 | } 326 | } 327 | catch (TransformerConfigurationException e) { 328 | throw new IOException(e); 329 | } 330 | catch (TransformerException e) { 331 | throw new IOException(e); 332 | } 333 | } 334 | } 335 | 336 | private Boolean parentDirIsWritable(File path) throws IOException { 337 | File parentDir = path.getParentFile(); 338 | if (!parentDir.exists()) { 339 | if (!parentDir.mkdirs()) { 340 | logger.error("Failed to create directory: " + parentDir.getAbsolutePath()); 341 | } 342 | } 343 | if (parentDir.exists()) { 344 | if (SystemUtils.IS_OS_WINDOWS) { 345 | // Special code for windows 346 | return parentDir.canWrite(); 347 | } 348 | else { 349 | // Leave the fancy code for unix based systems. We don't want to disturb any magic that 350 | // may be running here, so don't remove for now. 351 | Set permissions = Files 352 | .getPosixFilePermissions(Paths.get(parentDir.getAbsolutePath()), LinkOption.NOFOLLOW_LINKS); 353 | 354 | return (permissions.contains(PosixFilePermission.OTHERS_WRITE) || 355 | permissions.contains(PosixFilePermission.GROUP_WRITE) || 356 | permissions.contains(PosixFilePermission.OWNER_WRITE) 357 | ); 358 | } 359 | } 360 | return false; 361 | } 362 | } 363 | 364 | -------------------------------------------------------------------------------- /src/main/java/net/ossindex/gradle/output/PackageTreeReporter.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.output; 2 | 3 | import net.ossindex.gradle.AuditExtensions; 4 | import net.ossindex.gradle.audit.MavenPackageDescriptor; 5 | import net.ossindex.gradle.input.GradleArtifact; 6 | import org.gradle.api.invocation.Gradle; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.*; 11 | 12 | public class PackageTreeReporter { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(PackageTreeReporter.class); 15 | private final AuditExtensions settings; 16 | 17 | public PackageTreeReporter(AuditExtensions extensions) { 18 | settings = extensions; 19 | } 20 | 21 | public void reportDependencyTree(Set topLevelArtifacts, Collection descriptorsWithVulnerabilities) { 22 | if (!logger.isInfoEnabled()) return; 23 | 24 | Map> artifactsWithVulnerabilities = getArtifactsContainingVulnerabilities(topLevelArtifacts, descriptorsWithVulnerabilities); 25 | 26 | artifactsWithVulnerabilities.forEach(this::printDependencyTree); 27 | } 28 | 29 | private Map> getArtifactsContainingVulnerabilities(Set artifacts, Collection descriptors) { 30 | Map> map = new HashMap<>(); 31 | for (GradleArtifact artifact : artifacts) { 32 | for (MavenPackageDescriptor descriptor : descriptors) { 33 | if (artifact.containsPackage(descriptor.getMavenVersionId())) { 34 | if (!map.containsKey(artifact)) { 35 | map.put(artifact, new ArrayList<>()); 36 | } 37 | map.get(artifact).add(descriptor); 38 | } 39 | } 40 | } 41 | 42 | return map; 43 | } 44 | 45 | private void printDependencyTree(GradleArtifact artifact, List descriptors) { 46 | StringBuilder builder = new StringBuilder(); 47 | builder.append(artifact.getFullDescription()); 48 | if (hasVulnerabilities(artifact, descriptors)) { 49 | builder.append("\033[31m <-- vulnerability\033[0m"); 50 | } 51 | builder.append("\n"); 52 | 53 | for (GradleArtifact child : artifact.getChildren()) { 54 | printChild(child, descriptors, builder, "-"); 55 | } 56 | 57 | logger.info(builder.toString()); 58 | } 59 | 60 | private void printChild(GradleArtifact artifact, List descriptors, StringBuilder builder, String prefix) { 61 | builder.append("|"); 62 | builder.append(prefix); 63 | builder.append(artifact.getFullDescription()); 64 | if (hasVulnerabilities(artifact, descriptors)) { 65 | builder.append("\033[31m <-- vulnerability\033[0m"); 66 | } 67 | builder.append("\n"); 68 | 69 | for (GradleArtifact child : artifact.getChildren()) { 70 | printChild(child, descriptors, builder, prefix + "-"); 71 | } 72 | } 73 | 74 | private boolean hasVulnerabilities(GradleArtifact artifact, List descriptors) { 75 | return descriptors 76 | .stream() 77 | .filter(d -> !settings.isIgnored(d)) 78 | .anyMatch(d -> d.getMavenVersionId().equals(artifact.getFullDescription())); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/net.ossindex.audit.properties: -------------------------------------------------------------------------------- 1 | implementation-class=net.ossindex.gradle.OssIndexPlugin -------------------------------------------------------------------------------- /src/test/groovy/net/ossindex/integrationtests/ExclusionTests.groovy: -------------------------------------------------------------------------------- 1 | package net.ossindex.integrationtests 2 | 3 | import org.gradle.testkit.runner.GradleRunner 4 | import org.junit.Rule 5 | import org.junit.rules.TemporaryFolder 6 | import spock.lang.Specification 7 | 8 | import static org.gradle.testkit.runner.TaskOutcome.FAILED 9 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS 10 | 11 | class ExclusionTests extends Specification 12 | { 13 | @Rule 14 | final TemporaryFolder testProjectDir = new TemporaryFolder() 15 | File buildFile 16 | 17 | def setup() { 18 | buildFile = testProjectDir.newFile("build.gradle") 19 | } 20 | 21 | def "a project which ignores two specific vulnerabilities"() { 22 | given: "A project with 2 vulnerable artifacts with two vulnerabilities ignored from one" 23 | buildFile << """ 24 | plugins { 25 | id 'net.ossindex.audit' 26 | id 'java' 27 | } 28 | 29 | repositories { 30 | mavenCentral() 31 | } 32 | 33 | dependencies { 34 | compile 'org.grails:grails:2.0.1' 35 | compile 'com.squareup.okhttp3:mockwebserver:3.7.0' 36 | } 37 | 38 | audit { 39 | exclusion { 40 | vid = "8396562328" 41 | } 42 | exclusion { 43 | vid = "8402763844" 44 | } 45 | } 46 | """ 47 | 48 | when: 49 | def result = GradleRunner.create() 50 | .withProjectDir(testProjectDir.root) 51 | .withArguments("audit") 52 | .withPluginClasspath() 53 | .buildAndFail() 54 | 55 | then: 56 | result.task(":audit").outcome.is(FAILED) 57 | result.output.contains("which has 8 vulnerabilities") 58 | !result.output.contains("which has 2 vulnerabilities") 59 | result.output.contains("8 unignored (of 10 total) vulnerabilities found") 60 | } 61 | 62 | def "a project which ignores a specific artifact but has another vulnerability should fail"() { 63 | given: "A project with 2 vulnerable artifacts and one package exclusion" 64 | buildFile << """ 65 | plugins { 66 | id 'net.ossindex.audit' 67 | id 'java' 68 | } 69 | 70 | repositories { 71 | mavenCentral() 72 | } 73 | 74 | dependencies { 75 | compile 'org.grails:grails:2.0.1' 76 | compile 'com.squareup.okhttp3:mockwebserver:3.7.0' 77 | } 78 | 79 | audit { 80 | exclusion { 81 | packages = [ 'org.grails:grails' ] 82 | } 83 | } 84 | """ 85 | 86 | when: 87 | def result = GradleRunner.create() 88 | .withProjectDir(testProjectDir.root) 89 | .withArguments("audit") 90 | .withPluginClasspath() 91 | .buildAndFail() 92 | 93 | then: 94 | result.task(":audit").outcome.is(FAILED) 95 | // This is only valid so long as no new vulnerabilities are found in these packages. 96 | result.output.contains("2 unignored (of 10 total) vulnerabilities found") 97 | } 98 | 99 | def "a project which ignores a vulnerable artifact and a vulnerability besides"() { 100 | given: "A project with 2 vulnerable artifacts and two package exclusions" 101 | buildFile << """ 102 | plugins { 103 | id 'net.ossindex.audit' 104 | id 'java' 105 | } 106 | 107 | repositories { 108 | mavenCentral() 109 | } 110 | 111 | dependencies { 112 | compile 'org.grails:grails:2.0.1' 113 | compile 'com.squareup.okhttp3:mockwebserver:3.7.0' 114 | } 115 | 116 | audit { 117 | exclusion { 118 | packages = [ 'org.grails:grails' ] 119 | } 120 | exclusion { 121 | vid = '8396562328' 122 | } 123 | } 124 | """ 125 | 126 | when: 127 | def result = GradleRunner.create() 128 | .withProjectDir(testProjectDir.root) 129 | .withArguments("audit") 130 | .withPluginClasspath() 131 | .buildAndFail() 132 | 133 | then: 134 | result.task(":audit").outcome.is(FAILED) 135 | // This is only valid so long as no new vulnerabilities are found in these packages. 136 | result.output.contains("1 unignored (of 10 total) vulnerabilities found") 137 | } 138 | 139 | def "a project which ignores two vulnerable artifacts"() { 140 | given: "A project with 2 vulnerable artifacts and two package exclusions" 141 | buildFile << """ 142 | plugins { 143 | id 'net.ossindex.audit' 144 | id 'java' 145 | } 146 | 147 | repositories { 148 | mavenCentral() 149 | } 150 | 151 | dependencies { 152 | compile 'org.grails:grails:2.0.1' 153 | compile 'com.squareup.okhttp3:mockwebserver:3.7.0' 154 | } 155 | 156 | audit { 157 | exclusion { 158 | packages = [ 'org.grails:grails' ] 159 | } 160 | exclusion { 161 | packages = [ 'org.bouncycastle:bcprov-jdk15on:1.50' ] 162 | } 163 | } 164 | """ 165 | 166 | when: 167 | def result = GradleRunner.create() 168 | .withProjectDir(testProjectDir.root) 169 | .withArguments("audit") 170 | .withPluginClasspath() 171 | .build() 172 | 173 | then: 174 | result.task(":audit").outcome.is(SUCCESS) 175 | // This is only valid so long as no new vulnerabilities are found in these packages. 176 | result.output.contains("0 unignored (of 10 total) vulnerabilities found") 177 | } 178 | 179 | def "a project which ignores a different version of an artifact"() { 180 | given: "A project with 2 vulnerable artifacts, excluding a different version of an artifact" 181 | buildFile << """ 182 | plugins { 183 | id 'net.ossindex.audit' 184 | id 'java' 185 | } 186 | 187 | repositories { 188 | mavenCentral() 189 | } 190 | 191 | dependencies { 192 | compile 'org.grails:grails:2.0.1' 193 | compile 'com.squareup.okhttp3:mockwebserver:3.7.0' 194 | } 195 | 196 | audit { 197 | exclusion { 198 | packages = [ 'org.grails:grails:1.5.3' ] 199 | } 200 | } 201 | """ 202 | 203 | when: 204 | def result = GradleRunner.create() 205 | .withProjectDir(testProjectDir.root) 206 | .withArguments("audit") 207 | .withPluginClasspath() 208 | .buildAndFail() 209 | 210 | then: 211 | result.task(":audit").outcome.is(FAILED) 212 | // This is only valid so long as no new vulnerabilities are found in these packages. 213 | result.output.contains("10 unignored (of 10 total) vulnerabilities found") 214 | } 215 | 216 | def "a project which ignores vulnerabilities through a parent package"() { 217 | given: "A project with 2 vulnerable artifacts, excluding a parent of a vulnerable package" 218 | buildFile << """ 219 | plugins { 220 | id 'net.ossindex.audit' 221 | id 'java' 222 | } 223 | 224 | repositories { 225 | mavenCentral() 226 | } 227 | 228 | dependencies { 229 | compile 'org.grails:grails:2.0.1' 230 | compile 'com.squareup.okhttp3:mockwebserver:3.7.0' 231 | } 232 | 233 | audit { 234 | exclusion { 235 | vid = '8396562328' 236 | packages = [ 'com.squareup.okhttp3:mockwebserver:3.7.0' ] 237 | } 238 | exclusion { 239 | vid = '8402763844' 240 | packages = [ 'groupid:artifactid:1.50' ] 241 | } 242 | } 243 | """ 244 | 245 | when: 246 | def result = GradleRunner.create() 247 | .withProjectDir(testProjectDir.root) 248 | .withArguments("audit") 249 | .withPluginClasspath() 250 | .buildAndFail() 251 | 252 | then: 253 | result.task(":audit").outcome.is(FAILED) 254 | // This is only valid so long as no new vulnerabilities are found in these packages. 255 | result.output.contains("9 unignored (of 10 total) vulnerabilities found") 256 | } 257 | 258 | def "Ignore all vulnerabilities by versionless path"() { 259 | given: "A project with 2 vulnerable artifacts, excluding a parent of a vulnerable package" 260 | buildFile << """ 261 | plugins { 262 | id 'net.ossindex.audit' 263 | id 'java' 264 | } 265 | 266 | repositories { 267 | mavenCentral() 268 | } 269 | 270 | dependencies { 271 | compile 'com.squareup.okhttp3:mockwebserver:3.7.0' 272 | } 273 | 274 | audit { 275 | exclusion { 276 | packages = [ 'com.squareup.okhttp3:mockwebserver', 'org.bouncycastle:bcprov-jdk15on' ] 277 | } 278 | } 279 | """ 280 | 281 | when: 282 | def result = GradleRunner.create() 283 | .withProjectDir(testProjectDir.root) 284 | .withArguments("audit") 285 | .withPluginClasspath() 286 | .build() 287 | 288 | then: 289 | result.task(":audit").outcome.is(SUCCESS) 290 | // This is only valid so long as no new vulnerabilities are found in these packages. 291 | result.output.contains("0 unignored (of 2 total) vulnerabilities found") 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/test/groovy/net/ossindex/integrationtests/OssIndexAuditPluginTests.groovy: -------------------------------------------------------------------------------- 1 | package net.ossindex.integrationtests 2 | 3 | import org.gradle.testkit.runner.GradleRunner 4 | import org.junit.Rule 5 | import org.junit.rules.TemporaryFolder 6 | import spock.lang.Specification 7 | 8 | import static org.gradle.testkit.runner.TaskOutcome.FAILED 9 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS 10 | 11 | class OssIndexAuditPluginTests extends Specification { 12 | @Rule 13 | final TemporaryFolder testProjectDir = new TemporaryFolder() 14 | File buildFile 15 | 16 | def setup() { 17 | buildFile = testProjectDir.newFile("build.gradle") 18 | } 19 | 20 | def "a project with no dependencies should not fail"() { 21 | given: "A project with no dependencies" 22 | buildFile << """ 23 | plugins { 24 | id 'net.ossindex.audit' 25 | id 'java' 26 | } 27 | """ 28 | 29 | when: 30 | def result = GradleRunner.create() 31 | .withProjectDir(testProjectDir.root) 32 | .withArguments("audit") 33 | .withPluginClasspath() 34 | .build() 35 | 36 | then: 37 | result.task(":audit").outcome.is(SUCCESS) 38 | } 39 | 40 | def "a project with vulnerable dependencies should fail"() { 41 | given: "A project with vulnerable dependencies" 42 | buildFile << """ 43 | plugins { 44 | id 'net.ossindex.audit' 45 | id 'java' 46 | } 47 | 48 | repositories { 49 | mavenCentral() 50 | } 51 | 52 | dependencies { 53 | compile 'org.grails:grails:2.0.1' 54 | } 55 | """ 56 | 57 | when: 58 | def result = GradleRunner.create() 59 | .withProjectDir(testProjectDir.root) 60 | .withArguments("audit") 61 | .withPluginClasspath() 62 | .buildAndFail() 63 | 64 | then: 65 | result.task(":audit").outcome.is(FAILED) 66 | result.output.contains("vulnerabilities found") 67 | } 68 | 69 | def "a project with vulnerable dependencies which should continue on error should pass"() { 70 | given: "A project with vulnerable dependencies" 71 | buildFile << """ 72 | plugins { 73 | id 'net.ossindex.audit' 74 | id 'java' 75 | } 76 | 77 | repositories { 78 | mavenCentral() 79 | } 80 | 81 | dependencies { 82 | compile 'org.grails:grails:2.0.1' 83 | } 84 | 85 | audit { 86 | failOnError = false 87 | } 88 | """ 89 | 90 | when: 91 | def result = GradleRunner.create() 92 | .withProjectDir(testProjectDir.root) 93 | .withArguments("audit") 94 | .withPluginClasspath() 95 | .build() 96 | 97 | then: 98 | result.task(":audit").outcome.is(SUCCESS) 99 | result.output.contains("vulnerabilities found") 100 | } 101 | 102 | def "a project which ignores a specific artifact and version should pass"() { 103 | given: "A project which ignores the vulnerability with specific version" 104 | buildFile << """ 105 | plugins { 106 | id 'net.ossindex.audit' 107 | id 'java' 108 | } 109 | 110 | repositories { 111 | mavenCentral() 112 | } 113 | 114 | dependencies { 115 | compile 'org.grails:grails:2.0.1' 116 | } 117 | 118 | audit { 119 | ignore = [ 'org.grails:grails:2.0.1' ] 120 | } 121 | """ 122 | 123 | when: 124 | def result = GradleRunner.create() 125 | .withProjectDir(testProjectDir.root) 126 | .withArguments("audit") 127 | .withPluginClasspath() 128 | .build() 129 | 130 | then: 131 | result.task(":audit").outcome.is(SUCCESS) 132 | } 133 | 134 | def "a project which ignores a specific artifact and all versions should pass"() { 135 | given: "A project which ignores the vulnerability with all versions" 136 | buildFile << """ 137 | plugins { 138 | id 'net.ossindex.audit' 139 | id 'java' 140 | } 141 | 142 | repositories { 143 | mavenCentral() 144 | } 145 | 146 | dependencies { 147 | compile 'org.grails:grails:2.0.1' 148 | } 149 | 150 | audit { 151 | ignore = [ 'org.grails:grails' ] 152 | } 153 | """ 154 | 155 | when: 156 | def result = GradleRunner.create() 157 | .withProjectDir(testProjectDir.root) 158 | .withArguments("audit") 159 | .withPluginClasspath() 160 | .build() 161 | 162 | then: 163 | result.task(":audit").outcome.is(SUCCESS) 164 | } 165 | 166 | 167 | def "a project which ignores a specific artifact but has another vulnerability should fail"() { 168 | given: "A project with 2 vulnerable artifacts and one ignore" 169 | buildFile << """ 170 | plugins { 171 | id 'net.ossindex.audit' 172 | id 'java' 173 | } 174 | 175 | repositories { 176 | mavenCentral() 177 | } 178 | 179 | dependencies { 180 | compile 'org.grails:grails:2.0.1' 181 | compile 'com.squareup.okhttp3:mockwebserver:3.7.0' 182 | } 183 | 184 | audit { 185 | ignore = [ 'org.grails:grails' ] 186 | } 187 | """ 188 | 189 | when: 190 | def result = GradleRunner.create() 191 | .withProjectDir(testProjectDir.root) 192 | .withArguments("audit") 193 | .withPluginClasspath() 194 | .buildAndFail() 195 | 196 | then: 197 | result.task(":audit").outcome.is(FAILED) 198 | // This is only valid so long as no new vulnerabilities are found in these packages. 199 | result.output.contains("2 unignored (of 10 total) vulnerabilities found") 200 | result.output.contains("which has 2 vulnerabilities") 201 | !result.output.contains("which has 8 vulnerabilities") 202 | } 203 | 204 | def "a project with no dependencies and a junitReport element should not fail"() { 205 | given: "A project with no dependencies" 206 | buildFile << """ 207 | plugins { 208 | id 'net.ossindex.audit' 209 | id 'java' 210 | } 211 | 212 | audit { 213 | junitReport = './junitReport.xml' 214 | } 215 | """ 216 | 217 | when: 218 | def result = GradleRunner.create() 219 | .withProjectDir(testProjectDir.root) 220 | .withArguments("audit") 221 | .withPluginClasspath() 222 | .build() 223 | 224 | then: 225 | result.task(":audit").outcome.is(SUCCESS) 226 | } 227 | 228 | def "a project with vulnerable dependencies and a junitReport element should fail"() { 229 | given: "A project with vulnerable dependencies" 230 | buildFile << """ 231 | plugins { 232 | id 'net.ossindex.audit' 233 | id 'java' 234 | } 235 | 236 | repositories { 237 | mavenCentral() 238 | } 239 | 240 | dependencies { 241 | compile 'org.grails:grails:2.0.1' 242 | } 243 | 244 | audit { 245 | junitReport = './junitReport.xml' 246 | } 247 | """ 248 | 249 | when: 250 | def result = GradleRunner.create() 251 | .withProjectDir(testProjectDir.root) 252 | .withArguments("audit") 253 | .withPluginClasspath() 254 | .buildAndFail() 255 | 256 | then: 257 | result.task(":audit").outcome.is(FAILED) 258 | result.output.contains("vulnerabilities found") 259 | } 260 | } 261 | 262 | 263 | -------------------------------------------------------------------------------- /src/test/java/net/ossindex/common/PackageRequestTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Vör Security Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | package net.ossindex.common; 28 | 29 | import java.io.IOException; 30 | import java.util.Arrays; 31 | import java.util.Collection; 32 | import java.util.Collections; 33 | import java.util.List; 34 | 35 | import net.ossindex.common.filter.IVulnerabilityFilter; 36 | import net.ossindex.common.filter.VulnerabilityFilterFactory; 37 | import net.ossindex.common.request.OssIndexHttpClient; 38 | import net.ossindex.common.request.PackageRequestService; 39 | import org.junit.Before; 40 | import org.junit.Test; 41 | 42 | import static org.junit.Assert.assertEquals; 43 | import static org.mockito.ArgumentMatchers.anyString; 44 | import static org.mockito.Mockito.mock; 45 | import static org.mockito.Mockito.when; 46 | 47 | /** 48 | * Test the package requests. 49 | * 50 | * @author Ken Duck 51 | */ 52 | public class PackageRequestTest 53 | { 54 | private IPackageRequest request; 55 | 56 | private IVulnerabilityFilter filter; 57 | 58 | private PackageCoordinate vulnerablePkg; 59 | 60 | private PackageCoordinate nonVulnerablePkg; 61 | 62 | private PackageCoordinate anotherPkg; 63 | 64 | private PackageCoordinate vulnerablePkgVersion; 65 | 66 | private PackageCoordinate nonVulnerablePkgVersion; 67 | 68 | private PackageCoordinate anotherPkgVersion; 69 | 70 | @Before 71 | public void before() throws IOException { 72 | OssIndexHttpClient client = mock(OssIndexHttpClient.class); 73 | when(client.performPostRequest(anyString(), anyString())).thenReturn(getPostResponse()); 74 | request = new PackageRequestService(client); 75 | filter = VulnerabilityFilterFactory.getInstance().createVulnerabilityFilter(); 76 | 77 | vulnerablePkg = buildTestPackage(); 78 | nonVulnerablePkg = buildNonVulnerablePackage(); 79 | anotherPkg = buildAnotherPackage(); 80 | 81 | vulnerablePkgVersion = PackageCoordinate.newBuilder(vulnerablePkg).withVersion("1.2.3").build(); 82 | nonVulnerablePkgVersion = PackageCoordinate.newBuilder(nonVulnerablePkg).withVersion("2.3.4").build(); 83 | anotherPkgVersion = PackageCoordinate.newBuilder(anotherPkg).withVersion("3.4.5").build(); 84 | } 85 | 86 | @Test 87 | public void singlePackageRequest() throws IOException { 88 | request.add("maven", "org.webjars.npm", "jQuery", "1.2.3"); 89 | Collection packages = request.run(); 90 | assertEquals(1, packages.size()); 91 | OssiPackage actualPkg = packages.iterator().next(); 92 | 93 | List vulns = actualPkg.getVulnerabilities(); 94 | assertEquals(3, vulns.size()); 95 | } 96 | 97 | @Test 98 | public void singleFilteredPackageRequest() throws IOException { 99 | filter.ignorePackage(Collections.singletonList(vulnerablePkg)); 100 | request.addVulnerabilityFilter(filter); 101 | 102 | request.add("maven", "org.webjars.npm", "jQuery", "1.2.3"); 103 | Collection packages = request.run(); 104 | assertEquals(1, packages.size()); 105 | OssiPackage actualPkg = packages.iterator().next(); 106 | 107 | List vulns = actualPkg.getVulnerabilities(); 108 | assertEquals(0, vulns.size()); 109 | } 110 | 111 | @Test 112 | public void unFilteredPackageRequest() throws IOException { 113 | filter.ignorePackage(Collections.singletonList(nonVulnerablePkg)); 114 | request.addVulnerabilityFilter(filter); 115 | 116 | request.add("maven", "org.webjars.npm", "jQuery", "1.2.3"); 117 | Collection packages = request.run(); 118 | assertEquals(1, packages.size()); 119 | OssiPackage actualPkg = packages.iterator().next(); 120 | 121 | List vulns = actualPkg.getVulnerabilities(); 122 | assertEquals(3, vulns.size()); 123 | } 124 | 125 | @Test 126 | public void singleFilteredIssueRequest() throws IOException { 127 | filter.ignoreVulnerability("49da4413-af2b-4e55-acc0-9c752e30dde4"); 128 | request.addVulnerabilityFilter(filter); 129 | 130 | request.add("maven", "org.webjars.npm", "jQuery", "1.2.3"); 131 | Collection packages = request.run(); 132 | assertEquals(1, packages.size()); 133 | OssiPackage actualPkg = packages.iterator().next(); 134 | 135 | List vulns = actualPkg.getVulnerabilities(); 136 | assertEquals(2, vulns.size()); 137 | } 138 | 139 | @Test 140 | public void unFilteredIssueRequest() throws IOException { 141 | filter.ignoreVulnerability("1234567890"); 142 | request.addVulnerabilityFilter(filter); 143 | 144 | request.add("maven", "org.webjars.npm", "jQuery", "1.2.3"); 145 | Collection packages = request.run(); 146 | assertEquals(1, packages.size()); 147 | OssiPackage actualPkg = packages.iterator().next(); 148 | 149 | List vulns = actualPkg.getVulnerabilities(); 150 | assertEquals(3, vulns.size()); 151 | } 152 | 153 | @Test 154 | public void singleFilteredPackageIssueRequest() throws IOException { 155 | filter.ignoreVulnerability(Collections.singletonList(vulnerablePkg), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 156 | request.addVulnerabilityFilter(filter); 157 | 158 | request.add("maven", "org.webjars.npm", "jQuery", "1.2.3"); 159 | Collection packages = request.run(); 160 | assertEquals(1, packages.size()); 161 | OssiPackage actualPkg = packages.iterator().next(); 162 | 163 | List vulns = actualPkg.getVulnerabilities(); 164 | assertEquals(2, vulns.size()); 165 | } 166 | 167 | @Test 168 | public void unFilteredPackageIssueRequest1() throws IOException { 169 | filter.ignoreVulnerability(Collections.singletonList(nonVulnerablePkg), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 170 | request.addVulnerabilityFilter(filter); 171 | 172 | request.add("maven", "org.webjars.npm", "jQuery", "1.2.3"); 173 | Collection packages = request.run(); 174 | assertEquals(1, packages.size()); 175 | OssiPackage actualPkg = packages.iterator().next(); 176 | 177 | List vulns = actualPkg.getVulnerabilities(); 178 | assertEquals(3, vulns.size()); 179 | } 180 | 181 | @Test 182 | public void unFilteredPackageIssueRequest2() throws IOException { 183 | filter.ignoreVulnerability(Collections.singletonList(vulnerablePkg), "1234567890"); 184 | request.addVulnerabilityFilter(filter); 185 | 186 | request.add("maven", "org.webjars.npm", "jQuery", "1.2.3"); 187 | Collection packages = request.run(); 188 | assertEquals(1, packages.size()); 189 | OssiPackage actualPkg = packages.iterator().next(); 190 | 191 | List vulns = actualPkg.getVulnerabilities(); 192 | assertEquals(3, vulns.size()); 193 | } 194 | 195 | @Test 196 | public void filterVulnerabilityInImmediateDependency() throws IOException { 197 | filter.ignoreVulnerability(Collections.singletonList(vulnerablePkg), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 198 | request.addVulnerabilityFilter(filter); 199 | 200 | request.add(Collections.singletonList(vulnerablePkgVersion)); 201 | Collection packages = request.run(); 202 | assertEquals(1, packages.size()); 203 | OssiPackage actualPkg = packages.iterator().next(); 204 | 205 | List vulns = actualPkg.getVulnerabilities(); 206 | assertEquals(2, vulns.size()); 207 | } 208 | 209 | @Test 210 | public void filterVulnerabilityByChildOnly() throws IOException { 211 | filter.ignoreVulnerability(Collections.singletonList(vulnerablePkg), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 212 | request.addVulnerabilityFilter(filter); 213 | 214 | request.add(Arrays.asList(new PackageCoordinate[] {anotherPkgVersion, vulnerablePkgVersion})); 215 | Collection packages = request.run(); 216 | assertEquals(1, packages.size()); 217 | OssiPackage actualPkg = packages.iterator().next(); 218 | 219 | List vulns = actualPkg.getVulnerabilities(); 220 | assertEquals(2, vulns.size()); 221 | } 222 | 223 | @Test 224 | public void filterVulnerabilityByCompletePath() throws IOException { 225 | filter.ignoreVulnerability(Arrays.asList(new PackageCoordinate[] {anotherPkg, vulnerablePkg}), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 226 | request.addVulnerabilityFilter(filter); 227 | 228 | request.add(Arrays.asList(new PackageCoordinate[] {anotherPkgVersion, vulnerablePkgVersion})); 229 | Collection packages = request.run(); 230 | assertEquals(1, packages.size()); 231 | OssiPackage actualPkg = packages.iterator().next(); 232 | 233 | List vulns = actualPkg.getVulnerabilities(); 234 | assertEquals(2, vulns.size()); 235 | } 236 | 237 | @Test 238 | public void filterVulnerabilityByCompletePathWithVersion() throws IOException { 239 | filter.ignoreVulnerability(Arrays.asList(new PackageCoordinate[] {anotherPkgVersion, vulnerablePkgVersion}), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 240 | request.addVulnerabilityFilter(filter); 241 | 242 | request.add(Arrays.asList(new PackageCoordinate[] {anotherPkgVersion, vulnerablePkgVersion})); 243 | Collection packages = request.run(); 244 | assertEquals(1, packages.size()); 245 | OssiPackage actualPkg = packages.iterator().next(); 246 | 247 | List vulns = actualPkg.getVulnerabilities(); 248 | assertEquals(2, vulns.size()); 249 | } 250 | 251 | @Test 252 | public void dontFilterVulnerabilityByIncorrectPath() throws IOException { 253 | filter.ignoreVulnerability(Arrays.asList(new PackageCoordinate[] {nonVulnerablePkg, vulnerablePkg}), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 254 | request.addVulnerabilityFilter(filter); 255 | 256 | request.add(Arrays.asList(new PackageCoordinate[] {anotherPkgVersion, vulnerablePkgVersion})); 257 | Collection packages = request.run(); 258 | assertEquals(1, packages.size()); 259 | OssiPackage actualPkg = packages.iterator().next(); 260 | 261 | List vulns = actualPkg.getVulnerabilities(); 262 | assertEquals(3, vulns.size()); 263 | } 264 | 265 | @Test 266 | public void filterVulnerabilityInChildByParentPackage() throws IOException { 267 | filter.ignoreVulnerability(Arrays.asList(new PackageCoordinate[] {anotherPkg}), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 268 | request.addVulnerabilityFilter(filter); 269 | 270 | request.add(Arrays.asList(new PackageCoordinate[] {anotherPkgVersion, vulnerablePkgVersion})); 271 | Collection packages = request.run(); 272 | assertEquals(1, packages.size()); 273 | OssiPackage actualPkg = packages.iterator().next(); 274 | 275 | List vulns = actualPkg.getVulnerabilities(); 276 | assertEquals(2, vulns.size()); 277 | } 278 | 279 | @Test 280 | public void filterVulnerabilitiesInChildByParentPackage() throws IOException { 281 | filter.ignoreVulnerability(Arrays.asList(new PackageCoordinate[] {anotherPkg}), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 282 | request.addVulnerabilityFilter(filter); 283 | 284 | request.add(Arrays.asList(new PackageCoordinate[] {anotherPkgVersion, vulnerablePkgVersion})); 285 | Collection packages = request.run(); 286 | assertEquals(1, packages.size()); 287 | OssiPackage actualPkg = packages.iterator().next(); 288 | 289 | List vulns = actualPkg.getVulnerabilities(); 290 | assertEquals(2, vulns.size()); 291 | } 292 | 293 | @Test 294 | public void filterVulnerabilityInChildByParentPackageVersion() throws IOException { 295 | filter.ignoreVulnerability(Arrays.asList(new PackageCoordinate[] {anotherPkgVersion}), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 296 | request.addVulnerabilityFilter(filter); 297 | 298 | request.add(Arrays.asList(new PackageCoordinate[] {anotherPkgVersion, vulnerablePkgVersion})); 299 | Collection packages = request.run(); 300 | assertEquals(1, packages.size()); 301 | OssiPackage actualPkg = packages.iterator().next(); 302 | 303 | List vulns = actualPkg.getVulnerabilities(); 304 | assertEquals(2, vulns.size()); 305 | } 306 | 307 | @Test 308 | public void filterVulnerabilityInChildByParentPackageWithOddVersion() throws IOException { 309 | PackageCoordinate filterParent = PackageCoordinate.newBuilder() 310 | .withFormat("maven") 311 | .withNamespace("scot.disclosure") 312 | .withName("aps-unit-test-utils") 313 | .build(); 314 | 315 | PackageCoordinate filterChild = PackageCoordinate.newBuilder() 316 | .withFormat("maven") 317 | .withNamespace("javax.servlet") 318 | .withName("jstl") 319 | .withVersion("1.2") 320 | .build(); 321 | 322 | filter.ignoreVulnerability(Arrays.asList(new PackageCoordinate[] {filterParent, filterChild}), "52f593c8-7729-435c-b9df-a7bb9ded8589"); 323 | filter.ignoreVulnerability(Arrays.asList(new PackageCoordinate[] {filterParent, filterChild}), "49da4413-af2b-4e55-acc0-9c752e30dde4"); 324 | filter.ignoreVulnerability(Arrays.asList(new PackageCoordinate[] {filterParent, filterChild}), "3b3ba2f8-9c2c-4afe-b593-75c6b3fd4bb7"); 325 | request.addVulnerabilityFilter(filter); 326 | 327 | PackageCoordinate parent = PackageCoordinate.newBuilder() 328 | .withFormat("maven") 329 | .withNamespace("scot.disclosure") 330 | .withName("aps-unit-test-utils") 331 | .withVersion("0.3.0-20180517102126-f776111-46") 332 | .build(); 333 | 334 | PackageCoordinate child = PackageCoordinate.newBuilder() 335 | .withFormat("maven") 336 | .withNamespace("javax.servlet") 337 | .withName("jstl") 338 | .withVersion("1.2") 339 | .build(); 340 | 341 | request.add(Arrays.asList(new PackageCoordinate[] {parent, child})); 342 | Collection packages = request.run(); 343 | assertEquals(1, packages.size()); 344 | OssiPackage actualPkg = packages.iterator().next(); 345 | 346 | List vulns = actualPkg.getVulnerabilities(); 347 | assertEquals(0, vulns.size()); 348 | } 349 | 350 | 351 | private PackageCoordinate buildTestPackage() { 352 | PackageCoordinate pkg = PackageCoordinate.newBuilder() 353 | .withFormat("maven") 354 | .withNamespace("org.webjars.npm") 355 | .withName("jQuery") 356 | .build(); 357 | return pkg; 358 | } 359 | 360 | private PackageCoordinate buildNonVulnerablePackage() { 361 | PackageCoordinate pkg = PackageCoordinate.newBuilder() 362 | .withFormat("maven") 363 | .withNamespace("org.webjars.npm") 364 | .withName("jQuery-patched") 365 | .build(); 366 | return pkg; 367 | } 368 | 369 | private PackageCoordinate buildAnotherPackage() { 370 | PackageCoordinate pkg = PackageCoordinate.newBuilder() 371 | .withFormat("maven") 372 | .withNamespace("my.project") 373 | .withName("MyProg") 374 | .build(); 375 | return pkg; 376 | } 377 | 378 | public String getPostResponse() { 379 | return "[\n" + 380 | " {\n" + 381 | " \"coordinates\": \"maven:org.webjars.npm/jquery@1.2.3\",\n" + 382 | " \"description\": \"WebJar for jquery\",\n" + 383 | " \"reference\": \"https://ossindex.sonatype.org/component/maven:org.webjars.npm/jquery@1.2.3\",\n" + 384 | " \"vulnerabilities\": [\n" + 385 | " {\n" + 386 | " \"id\": \"49da4413-af2b-4e55-acc0-9c752e30dde4\",\n" + 387 | " \"title\": \"CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')\",\n" + 388 | " \"description\": \"The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.\",\n" + 389 | " \"cvssScore\": 7.2,\n" + 390 | " \"cvssVector\": \"CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N\",\n" + 391 | " \"cwe\": \"CWE-79\",\n" + 392 | " \"reference\": \"https://ossindex.sonatype.org/vuln/49da4413-af2b-4e55-acc0-9c752e30dde4\"\n" + 393 | " },\n" + 394 | " {\n" + 395 | " \"id\": \"52f593c8-7729-435c-b9df-a7bb9ded8589\",\n" + 396 | " \"title\": \"CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')\",\n" + 397 | " \"description\": \"The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.\",\n" + 398 | " \"cvssScore\": 6.1,\n" + 399 | " \"cvssVector\": \"CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N\",\n" + 400 | " \"cwe\": \"CWE-79\",\n" + 401 | " \"reference\": \"https://ossindex.sonatype.org/vuln/52f593c8-7729-435c-b9df-a7bb9ded8589\"\n" + 402 | " },\n" + 403 | " {\n" + 404 | " \"id\": \"3b3ba2f8-9c2c-4afe-b593-75c6b3fd4bb7\",\n" + 405 | " \"title\": \"[CVE-2015-9251] jQuery before 3.0.0 is vulnerable to Cross-site Scripting (XSS) attacks when a c...\",\n" + 406 | " \"description\": \"jQuery before 3.0.0 is vulnerable to Cross-site Scripting (XSS) attacks when a cross-domain Ajax request is performed without the dataType option, causing text/javascript responses to be executed.\",\n" + 407 | " \"cvssScore\": 6.1,\n" + 408 | " \"cvssVector\": \"CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N\",\n" + 409 | " \"reference\": \"https://ossindex.sonatype.org/vuln/3b3ba2f8-9c2c-4afe-b593-75c6b3fd4bb7\"\n" + 410 | " }\n" + 411 | " ]\n" + 412 | " }\n" + 413 | "]"; 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /src/test/java/net/ossindex/common/SmokeIT.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.common; 2 | 3 | import java.io.IOException; 4 | import java.net.ConnectException; 5 | import java.util.Collection; 6 | import java.util.Collections; 7 | 8 | import org.junit.Test; 9 | 10 | import static junit.framework.TestCase.assertFalse; 11 | import static org.junit.Assert.assertTrue; 12 | import static org.junit.Assert.fail; 13 | 14 | public class SmokeIT 15 | { 16 | @Test 17 | public void simpleTest() throws IOException { 18 | IPackageRequest client = getVulnerableCoordinateRequest(); 19 | client.setCacheFile(null); 20 | Collection results = client.run(); 21 | assertFalse(results.isEmpty()); 22 | } 23 | 24 | @Test 25 | public void badCredentialsTest() throws IOException { 26 | IPackageRequest client = getVulnerableCoordinateRequest(); 27 | client.setCredentials("invalidUser", "invalidToken"); 28 | client.setCacheFile(null); 29 | try { 30 | client.run(); 31 | // Bad credentials should mean we never get here 32 | fail(); 33 | } catch (ConnectException e) { 34 | assertTrue(e.getMessage().contains("401")); 35 | } 36 | } 37 | 38 | private IPackageRequest getVulnerableCoordinateRequest() { 39 | IPackageRequest client = OssIndexApi.createPackageRequest(); 40 | PackageCoordinate coord = PackageCoordinate.newBuilder() 41 | .withFormat("maven") 42 | .withNamespace("poi") 43 | .withName("poi") 44 | .withVersion("1.2.3") 45 | .build(); 46 | client.add(Collections.singletonList(coord)); 47 | return client; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/net/ossindex/common/filter/VulnerabilityFilterTests.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.common.filter; 2 | 3 | import java.util.Collections; 4 | 5 | import net.ossindex.common.PackageCoordinate; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertFalse; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | public class VulnerabilityFilterTests 12 | { 13 | @Test 14 | public void filterNothing() { 15 | PackageCoordinate filterPkg = fakeCoordinate(); 16 | VulnerabilityFilterImpl filter = new VulnerabilityFilterImpl(); 17 | filter.ignorePackage(Collections.singletonList(filterPkg)); 18 | 19 | assertFalse(filter.shouldFilter(null, "12345")); 20 | assertFalse(filter.shouldFilter(Collections.EMPTY_LIST, "12345")); 21 | PackageCoordinate pkg = PackageCoordinate.newBuilder().build(); 22 | assertFalse(filter.shouldFilter(Collections.singletonList(pkg), "12345")); 23 | } 24 | 25 | @Test 26 | public void filterPackage() { 27 | PackageCoordinate filterPkg = fakeCoordinate(); 28 | VulnerabilityFilterImpl filter = new VulnerabilityFilterImpl(); 29 | filter.ignorePackage(Collections.singletonList(filterPkg)); 30 | 31 | PackageCoordinate pkg = PackageCoordinate.newBuilder(filterPkg).build(); 32 | 33 | assertTrue(filter.shouldFilter(Collections.singletonList(pkg), "12345")); 34 | } 35 | 36 | @Test 37 | public void filterIssue() { 38 | VulnerabilityFilterImpl filter = new VulnerabilityFilterImpl(); 39 | filter.ignoreVulnerability("12345"); 40 | 41 | assertTrue(filter.shouldFilter(Collections.singletonList(fakeCoordinate()), "12345")); 42 | } 43 | 44 | 45 | @Test 46 | public void filterSimplePackageIssue() { 47 | PackageCoordinate filterPkg = fakeCoordinate(); 48 | VulnerabilityFilterImpl filter = new VulnerabilityFilterImpl(); 49 | filter.ignoreVulnerability(Collections.singletonList(filterPkg), "12345"); 50 | 51 | PackageCoordinate pkg = PackageCoordinate.newBuilder(filterPkg).build(); 52 | 53 | assertTrue(filter.shouldFilter(Collections.singletonList(pkg), "12345")); 54 | } 55 | 56 | @Test 57 | public void filterExactVersionMatch() { 58 | PackageCoordinate filterPkg = fakeCoordinate(); 59 | filterPkg = PackageCoordinate.newBuilder(filterPkg).withVersion("1.2.3").build(); 60 | VulnerabilityFilterImpl filter = new VulnerabilityFilterImpl(); 61 | filter.ignoreVulnerability(Collections.singletonList(filterPkg), "12345"); 62 | 63 | PackageCoordinate pkg = PackageCoordinate.newBuilder(filterPkg).build(); 64 | 65 | assertTrue(filter.shouldFilter(Collections.singletonList(pkg), "12345")); 66 | } 67 | 68 | @Test 69 | public void filterRangeMatch() { 70 | PackageCoordinate filterPkg = fakeCoordinate(); 71 | filterPkg = PackageCoordinate.newBuilder(filterPkg).withVersion(">=1.0.0 <=2.0.0").build(); 72 | VulnerabilityFilterImpl filter = new VulnerabilityFilterImpl(); 73 | filter.ignoreVulnerability(Collections.singletonList(filterPkg), "12345"); 74 | 75 | PackageCoordinate pkg = PackageCoordinate.newBuilder(filterPkg) 76 | .withVersion("1.2.3") 77 | .build(); 78 | 79 | assertTrue(filter.shouldFilter(Collections.singletonList(pkg), "12345")); 80 | } 81 | 82 | @Test 83 | public void filterSetRangeMatch() { 84 | PackageCoordinate filterPkg = fakeCoordinate(); 85 | filterPkg = PackageCoordinate.newBuilder(filterPkg).withVersion("[1.0.0,2.0.0)").build(); 86 | VulnerabilityFilterImpl filter = new VulnerabilityFilterImpl(); 87 | filter.ignoreVulnerability(Collections.singletonList(filterPkg), "12345"); 88 | 89 | PackageCoordinate pkg = PackageCoordinate.newBuilder(filterPkg) 90 | .withVersion("1.2.3") 91 | .build(); 92 | 93 | assertTrue(filter.shouldFilter(Collections.singletonList(pkg), "12345")); 94 | } 95 | 96 | @Test 97 | public void filterSetRangeMismatchHigh() { 98 | PackageCoordinate filterPkg = fakeCoordinate(); 99 | filterPkg = PackageCoordinate.newBuilder(filterPkg).withVersion("(1.2.3,2.0.0)").build(); 100 | VulnerabilityFilterImpl filter = new VulnerabilityFilterImpl(); 101 | filter.ignoreVulnerability(Collections.singletonList(filterPkg), "12345"); 102 | 103 | PackageCoordinate pkg = PackageCoordinate.newBuilder(filterPkg) 104 | .withVersion("1.2.3") 105 | .build(); 106 | 107 | assertFalse(filter.shouldFilter(Collections.singletonList(pkg), "12345")); 108 | } 109 | 110 | @Test 111 | public void filterSetRangeMismatchLow() { 112 | PackageCoordinate filterPkg = fakeCoordinate(); 113 | filterPkg = PackageCoordinate.newBuilder(filterPkg).withVersion("(1.0.0,1.2.3)").build(); 114 | VulnerabilityFilterImpl filter = new VulnerabilityFilterImpl(); 115 | filter.ignoreVulnerability(Collections.singletonList(filterPkg), "12345"); 116 | 117 | PackageCoordinate pkg = PackageCoordinate.newBuilder(filterPkg) 118 | .withVersion("1.2.3") 119 | .build(); 120 | 121 | assertFalse(filter.shouldFilter(Collections.singletonList(pkg), "12345")); 122 | } 123 | 124 | //-------- The following code should be replaced by proper faker code 125 | private static final String ALPHA_NUMERIC_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; 126 | 127 | private PackageCoordinate fakeCoordinate() { 128 | PackageCoordinate pkg = PackageCoordinate.newBuilder() 129 | .withFormat("maven") 130 | .withNamespace(randomNamespace()) 131 | .withName(randomName()) 132 | .build(); 133 | return pkg; 134 | } 135 | 136 | private static String randomName() { 137 | int count = 15; 138 | StringBuilder builder = new StringBuilder(); 139 | while (count-- != 0) { 140 | int character = (int)(Math.random()*ALPHA_NUMERIC_STRING.length()); 141 | builder.append(ALPHA_NUMERIC_STRING.charAt(character)); 142 | } 143 | return builder.toString(); 144 | } 145 | 146 | private static String randomNamespace() { 147 | StringBuilder builder = new StringBuilder(); 148 | for (int i = 0; i < 3; i++) { 149 | builder.append(randomName()).append("."); 150 | } 151 | return builder.toString(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/test/java/net/ossindex/gradle/audit/ProxyTests.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.audit; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | import java.util.Collections; 7 | 8 | import net.ossindex.gradle.AuditExtensions; 9 | import net.ossindex.gradle.OssIndexPlugin; 10 | import net.ossindex.gradle.input.ArtifactGatherer; 11 | import org.gradle.api.Project; 12 | import org.gradle.api.Task; 13 | import org.gradle.api.plugins.ExtensionContainer; 14 | import org.junit.Test; 15 | 16 | import static org.gradle.internal.impldep.org.junit.Assert.assertEquals; 17 | import static org.gradle.internal.impldep.org.testng.Assert.assertNotNull; 18 | import static org.mockito.ArgumentMatchers.any; 19 | import static org.mockito.ArgumentMatchers.eq; 20 | import static org.mockito.Mockito.mock; 21 | import static org.mockito.Mockito.verify; 22 | import static org.mockito.Mockito.when; 23 | 24 | /** 25 | * A series of tests that simulate the operation of gradle and ensure that appropriate proxy arguments are 26 | * passed to the DependencyAuditor. 27 | */ 28 | public class ProxyTests 29 | { 30 | private static final String PROXY_HOST = "example.com"; 31 | 32 | private static final Integer PROXY_PORT = 8080; 33 | 34 | private static final String PROXY_USER = "username"; 35 | 36 | private static final String PROXY_PASS = "password"; 37 | 38 | private static final String NON_PROXY_HOSTS = "ossindex.net|ossindex.sonatype.org"; 39 | 40 | /** 41 | * Ensure that OssIndexPlugin properly assembles the proxy argument and passes it to the DependencyAuditor. 42 | * If the dependency auditor receives proxy information it is passed directly to the OSS Index request 43 | * library. 44 | */ 45 | @Test 46 | public void noProxyTest() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { 47 | Project project = mockProject(); 48 | 49 | OssIndexPlugin plugin = new OssIndexPlugin(); 50 | AuditorFactory factory = mockAuditorFactory(); 51 | plugin.setAuditorFactory(factory); 52 | 53 | // Simulate the process the gradle runs 54 | runGradleSimulation(project, plugin); 55 | 56 | verify(factory).getDependencyAuditor(null, Collections.EMPTY_SET, Collections.EMPTY_LIST); 57 | } 58 | 59 | /** 60 | * Ensure that OssIndexPlugin properly assembles the proxy argument and passes it to the DependencyAuditor. 61 | */ 62 | @Test 63 | public void settingsProxyIT() 64 | throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException 65 | { 66 | Project project = mockProject(); 67 | AuditExtensions settings = new AuditExtensions(project); 68 | settings.proxyScheme = "http"; 69 | settings.proxyHost = PROXY_HOST; 70 | settings.proxyPort = PROXY_PORT; 71 | settings.proxyUser = PROXY_USER; 72 | settings.proxyPassword = PROXY_PASS; 73 | settings.nonProxyHosts = null; 74 | 75 | OssIndexPlugin plugin = new OssIndexPlugin(); 76 | Field field = OssIndexPlugin.class.getDeclaredField("settings"); 77 | try { 78 | field.setAccessible(true); 79 | field.set(plugin, settings); 80 | 81 | AuditorFactory factory = mockAuditorFactory(); 82 | plugin.setAuditorFactory(factory); 83 | 84 | // Simulate the process the gradle runs 85 | runGradleSimulation(project, plugin); 86 | 87 | verify(factory) 88 | .getDependencyAuditor(null, Collections.EMPTY_SET, Collections.singletonList(getExpectedProxy("http"))); 89 | } finally { 90 | field.set(plugin, null); 91 | } 92 | } 93 | 94 | /** 95 | * Ensure that OssIndexPlugin properly assembles the proxy argument and passes it to the DependencyAuditor. 96 | */ 97 | @Test 98 | public void httpLocalProxyTest() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { 99 | Project project = mockProject(); 100 | 101 | // Mock the proxy being provided as project properties 102 | mockLocalProxy(project, "http"); 103 | 104 | OssIndexPlugin plugin = new OssIndexPlugin(); 105 | AuditorFactory factory = mockAuditorFactory(); 106 | plugin.setAuditorFactory(factory); 107 | 108 | // Simulate the process the gradle runs 109 | runGradleSimulation(project, plugin); 110 | 111 | verify(factory).getDependencyAuditor(null, Collections.EMPTY_SET, Collections.singletonList(getExpectedProxy("http"))); 112 | } 113 | 114 | /** 115 | * Ensure that OssIndexPlugin properly assembles the proxy argument and passes it to the DependencyAuditor. 116 | */ 117 | @Test 118 | public void httpsLocalProxyTest() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { 119 | Project project = mockProject(); 120 | 121 | // Mock the proxy being provided as project properties 122 | mockLocalProxy(project, "https"); 123 | 124 | OssIndexPlugin plugin = new OssIndexPlugin(); 125 | AuditorFactory factory = mockAuditorFactory(); 126 | plugin.setAuditorFactory(factory); 127 | 128 | // Simulate the process the gradle runs 129 | runGradleSimulation(project, plugin); 130 | 131 | verify(factory).getDependencyAuditor(null, Collections.EMPTY_SET, Collections.singletonList(getExpectedProxy("https"))); 132 | } 133 | 134 | /** 135 | * Ensure that OssIndexPlugin properly assembles the proxy argument and passes it to the DependencyAuditor. 136 | */ 137 | @Test 138 | public void httpSystemProxyTest() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { 139 | Project project = mockProject(); 140 | 141 | // Mock the proxy being provided as project properties 142 | mockSystemProxy(project, "http"); 143 | 144 | OssIndexPlugin plugin = new OssIndexPlugin(); 145 | AuditorFactory factory = mockAuditorFactory(); 146 | plugin.setAuditorFactory(factory); 147 | 148 | // Simulate the process the gradle runs 149 | runGradleSimulation(project, plugin); 150 | 151 | verify(factory).getDependencyAuditor(null, Collections.EMPTY_SET, Collections.singletonList(getExpectedProxy("http"))); 152 | } 153 | 154 | /** 155 | * Ensure that OssIndexPlugin properly assembles the proxy argument and passes it to the DependencyAuditor. 156 | */ 157 | @Test 158 | public void httpsSystemProxyTest() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { 159 | Project project = mockProject(); 160 | 161 | // Mock the proxy being provided as project properties 162 | mockSystemProxy(project, "https"); 163 | 164 | OssIndexPlugin plugin = new OssIndexPlugin(); 165 | AuditorFactory factory = mockAuditorFactory(); 166 | plugin.setAuditorFactory(factory); 167 | 168 | // Simulate the process the gradle runs 169 | runGradleSimulation(project, plugin); 170 | 171 | verify(factory).getDependencyAuditor(null, Collections.EMPTY_SET, Collections.singletonList(getExpectedProxy("https"))); 172 | } 173 | 174 | /** 175 | * Mock the factory used by the auditor. The factory was specifically created to simplify unit tests. The factory 176 | * allows the assembly of: 177 | * 178 | * - The artifact gatherer. This is normally provided by gradle, but needs mocking for testing purposes. 179 | * - The dependency auditor. For normal usage this is directly instantiated. 180 | */ 181 | private AuditorFactory mockAuditorFactory() { 182 | AuditorFactory factory = mock(AuditorFactory.class); 183 | 184 | ArtifactGatherer gatherer = mock(ArtifactGatherer.class); 185 | when(gatherer.gatherResolvedArtifacts(any())).thenReturn(Collections.EMPTY_SET); 186 | when(factory.getGatherer()).thenReturn(gatherer); 187 | 188 | DependencyAuditor auditor = mock(DependencyAuditor.class); 189 | when(factory.getDependencyAuditor(eq(null), any(), any())).thenReturn(auditor); 190 | return factory; 191 | } 192 | 193 | /** 194 | * Assemble the gradle project with appropriate task. 195 | */ 196 | private Project mockProject() { 197 | Project project = mock(Project.class); 198 | 199 | ExtensionContainer extension = mock(ExtensionContainer.class); 200 | when(project.getExtensions()).thenReturn(extension); 201 | when(project.getDisplayName()).thenReturn("Mock Mock"); 202 | 203 | Task audit = mock(Task.class); 204 | when(audit.getProject()).thenReturn(project); 205 | when(project.task("audit")).thenReturn(audit); 206 | 207 | return project; 208 | } 209 | 210 | /** 211 | * Mock the project properties for a specified proxy 212 | */ 213 | private void mockLocalProxy(final Project project, final String scheme) { 214 | when(project.hasProperty(scheme + ".proxyHost")).thenReturn(true); 215 | when(project.findProperty(scheme + ".proxyHost")).thenReturn(PROXY_HOST); 216 | when(project.findProperty(scheme + ".proxyPort")).thenReturn(PROXY_PORT.toString()); 217 | when(project.findProperty(scheme + ".proxyUser")).thenReturn(PROXY_USER); 218 | when(project.findProperty(scheme + ".proxyPassword")).thenReturn(PROXY_PASS); 219 | when(project.findProperty(scheme + ".nonProxyHosts")).thenReturn(null); 220 | } 221 | 222 | /** 223 | * Mock the project properties for a specified proxy 224 | */ 225 | private void mockSystemProxy(final Project project, final String scheme) { 226 | when(project.hasProperty("systemProp." + scheme + ".proxyHost")).thenReturn(true); 227 | when(project.findProperty("systemProp." + scheme + ".proxyHost")).thenReturn(PROXY_HOST); 228 | when(project.findProperty("systemProp." + scheme + ".proxyPort")).thenReturn(PROXY_PORT.toString()); 229 | when(project.findProperty("systemProp." + scheme + ".proxyUser")).thenReturn(PROXY_USER); 230 | when(project.findProperty("systemProp." + scheme + ".proxyPassword")).thenReturn(PROXY_PASS); 231 | when(project.findProperty("systemProp." + scheme + ".nonProxyHosts")).thenReturn(null); 232 | } 233 | 234 | /** 235 | * The proxy expected as part of the test 236 | */ 237 | private Proxy getExpectedProxy(final String scheme) { 238 | Proxy proxy = new Proxy(); 239 | proxy.setHost(PROXY_HOST); 240 | proxy.setPort(PROXY_PORT); 241 | proxy.setUser(PROXY_USER); 242 | proxy.setPassword(PROXY_PASS); 243 | proxy.setNonProxyHosts(null); 244 | return proxy; 245 | } 246 | 247 | 248 | /** 249 | * Simulate gradle operations 250 | * 251 | * This is a terrible hack that shortcuts the internal workings of gradle to allow some simple tests to work 252 | */ 253 | private void runGradleSimulation(final Project project, final OssIndexPlugin plugin) 254 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException 255 | { 256 | plugin.apply(project); 257 | 258 | Method method = OssIndexPlugin.class.getDeclaredMethod("doAudit", Task.class); 259 | method.setAccessible(true); 260 | method.invoke(plugin, project.task("audit")); 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /src/test/java/net/ossindex/gradle/audit/ProxyUnitTest.java: -------------------------------------------------------------------------------- 1 | package net.ossindex.gradle.audit; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | 7 | import net.ossindex.gradle.AuditExtensions; 8 | import net.ossindex.gradle.OssIndexPlugin; 9 | import org.gradle.api.Project; 10 | import org.gradle.api.Task; 11 | import org.gradle.api.plugins.ExtensionContainer; 12 | import org.junit.Test; 13 | 14 | import static org.gradle.internal.impldep.org.junit.Assert.assertEquals; 15 | import static org.gradle.internal.impldep.org.testng.Assert.assertNotNull; 16 | import static org.mockito.Mockito.mock; 17 | import static org.mockito.Mockito.when; 18 | 19 | public class ProxyUnitTest 20 | { 21 | private static final String PROXY_HOST = "example.com"; 22 | 23 | private static final Integer PROXY_PORT = 8080; 24 | 25 | private static final String PROXY_USER = "username"; 26 | 27 | private static final String PROXY_PASS = "password"; 28 | 29 | /** 30 | * Ensure that OssIndexPlugin properly assembles the proxy argument and passes it to the DependencyAuditor. 31 | */ 32 | @Test 33 | public void settingsProxyTest() 34 | throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException 35 | { 36 | Project project = mockProject(); 37 | AuditExtensions settings = new AuditExtensions(project); 38 | settings.proxyScheme = "http"; 39 | settings.proxyHost = PROXY_HOST; 40 | settings.proxyPort = PROXY_PORT; 41 | settings.proxyUser = PROXY_USER; 42 | settings.proxyPassword = PROXY_PASS; 43 | settings.nonProxyHosts = null; 44 | 45 | OssIndexPlugin plugin = new OssIndexPlugin(); 46 | Field field = OssIndexPlugin.class.getDeclaredField("settings"); 47 | try { 48 | field.setAccessible(true); 49 | field.set(plugin, settings); 50 | 51 | Method method = OssIndexPlugin.class.getDeclaredMethod("getProxy", Project.class, String.class); 52 | method.setAccessible(true); 53 | Proxy proxy = (Proxy) method.invoke(plugin, project, "http"); 54 | assertNotNull(proxy); 55 | assertEquals(getExpectedProxy("http"), proxy); 56 | } finally { 57 | field.set(plugin, null); 58 | } 59 | } 60 | 61 | /** 62 | * Assemble the gradle project with appropriate task. 63 | */ 64 | private Project mockProject() { 65 | Project project = mock(Project.class); 66 | 67 | ExtensionContainer extension = mock(ExtensionContainer.class); 68 | when(project.getExtensions()).thenReturn(extension); 69 | when(project.getDisplayName()).thenReturn("Mock Mock"); 70 | 71 | Task audit = mock(Task.class); 72 | when(audit.getProject()).thenReturn(project); 73 | when(project.task("audit")).thenReturn(audit); 74 | 75 | return project; 76 | } 77 | 78 | /** 79 | * The proxy expected as part of the test 80 | */ 81 | private Proxy getExpectedProxy(final String scheme) { 82 | Proxy proxy = new Proxy(); 83 | proxy.setHost(PROXY_HOST); 84 | proxy.setPort(PROXY_PORT); 85 | proxy.setUser(PROXY_USER); 86 | proxy.setPassword(PROXY_PASS); 87 | proxy.setNonProxyHosts(null); 88 | return proxy; 89 | } 90 | } 91 | --------------------------------------------------------------------------------