Initial commit

This commit is contained in:
retanoj
2020-07-28 15:39:10 +08:00
commit e38227f6d5
34 changed files with 1815 additions and 0 deletions

110
.gitignore vendored Normal file
View File

@@ -0,0 +1,110 @@
# Created by https://www.gitignore.io/api/gradle,intellij+all
# Edit at https://www.gitignore.io/?templates=gradle,intellij+all
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Gradle ###
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
### Gradle Patch ###
**/build/
# End of https://www.gitignore.io/api/gradle,intellij+all

34
CHANGES Normal file
View File

@@ -0,0 +1,34 @@
Changelog
=========
Verson 1.1.3
- deps add gson
- feature add Renderer
- test add Unittests
Version 1.1.2
- feature default BUILD FAIL when found vulnerable
Version 1.1.1
- feature BUILD FAILED when found vulnerable && parameter support
Version 1.1.0
- feature add projectType param, support Maven & Android project
Version 1.0.2
- feature warning detail add title and cve
Version 1.0.1
- feature add onlyProvenance parameter
- feature add http timeout
Version 1.0.0
- Init

13
LICENSE Normal file
View File

@@ -0,0 +1,13 @@
Copyright 2020 momosecurity.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

73
README.md Normal file
View File

@@ -0,0 +1,73 @@
# MOSEC-GRADLE-PLUGIN
用于检测gradle项目的第三方依赖组件是否存在安全漏洞。
该项目灵感来自 [snyk-gradle-plugin](https://github.com/snyk/snyk-gradle-plugin.git) 。
## 版本支持
Gradle >= 3.0
## 安装
向顶层build.gradle中增加如下配置
```groovy
// file: build.gradle
buildscript {
repositories {
maven { url "https://raw.github.com/momosecurity/mosec-gradle-plugin/master/mvn-repo/" }
}
dependencies {
classpath 'com.immomo.momosec:mosec-gradle-plugin:1.1.3'
}
}
allprojects {
apply plugin: 'mosec'
}
```
## 使用
首先运行 [MOSEC-X-PLUGIN Backend](https://github.com/momosecurity/mosec-x-plugin-backend.git)
#### 命令行使用
```shell script
# 对于所有projectsMaven项目
> MOSEC_ENDPOINT=http://127.0.0.1:9000/api/plugin \
./gradlew --no-parallel \
mosec -PprojectType=Maven -PonlyProvenance=true
# 对于单个project (如 demo)Maven项目
> MOSEC_ENDPOINT=http://127.0.0.1:9000/api/plugin \
./gradlew --no-parallel \
:demo:mosec -PprojectType=Maven -PonlyProvenance=true
# 对于 Android 项目,使用 confAttr 参数
> MOSEC_ENDPOINT=http://127.0.0.1:9000/api/plugin \
./gradlew --no-parallel \
mosec -PprojectType=Android -PconfAttr=buildtype:release -PonlyProvenance=true
```
## 开发
#### Intellij 远程调试 Gradle 插件
1.将mosec-gradle-plugin安装至本地仓库
2.git clone mosec-gradle-plugin
3.Intellij 中新建 Remote Configuration 并填入如下信息
![remote-configuration](https://github.com/momosecurity/mosec-gradle-plugin/blob/master/static/remote-configuration.jpg)
4.在另一个gradle工程中执行如下命令
```shell script
> ./gradlew --no-parallel --no-daemon mosec -Dorg.gradle.debug=true
```
5.回到Intellij中下断点开始Debug

43
build.gradle Normal file
View File

@@ -0,0 +1,43 @@
plugins {
id 'groovy'
id 'maven'
}
group 'com.immomo.momosec'
version '1.1.3'
repositories {
mavenCentral()
}
dependencies {
compile gradleApi()
compile localGroovy()
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.10'
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.28.2'
testImplementation gradleTestKit()
}
// local maven repo install
install {
repositories.mavenInstaller {
pom.groupId = "$project.group"
pom.artifactId = "$project.name"
pom.version = "$project.version"
}
}
// remote private maven repo deploy
uploadArchives {
repositories.mavenDeployer {
repository(url: "file://mvn-repo")
pom.groupId = "$project.group"
pom.artifactId = "$project.name"
pom.version = "$project.version"
}
}

2
gradle.properties Normal file
View File

@@ -0,0 +1,2 @@
NEXUS_USERNAME=
NEXUS_PASSWORD=

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Tue May 26 14:27:38 CST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

172
gradlew vendored Executable file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1 @@
63a54e2e32ae94d5efdd5d474a47fae5

View File

@@ -0,0 +1 @@
f28f856789fbe4385c027d4f26ed8ab8c7e746f0

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.immomo.momosec</groupId>
<artifactId>mosec-gradle-plugin</artifactId>
<version>1.1.3</version>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.28.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1 @@
e092d0c7163e87bb55fabe6ae4fe2bf0

View File

@@ -0,0 +1 @@
6fbbb8db08cec3acd0ba30c14503e01b59f184bb

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>com.immomo.momosec</groupId>
<artifactId>mosec-gradle-plugin</artifactId>
<versioning>
<release>1.1.3</release>
<versions>
<version>1.1.3</version>
</versions>
<lastUpdated>20200728045957</lastUpdated>
</versioning>
</metadata>

View File

@@ -0,0 +1 @@
cee7d6bf5c0a3d11b4d432fcacf1b8ab

View File

@@ -0,0 +1 @@
64fb6c894314270126bbdcc2975fcfd86e194878

2
settings.gradle Normal file
View File

@@ -0,0 +1,2 @@
rootProject.name = 'mosec-gradle-plugin'

View File

@@ -0,0 +1,126 @@
package com.immomo.momosec.gradle.plugins
import com.immomo.momosec.gradle.plugins.exceptions.NetworkErrorException
import groovy.json.JsonOutput
import org.apache.http.HttpEntity
import org.apache.http.HttpResponse
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.gradle.api.Plugin
import org.gradle.api.Project
class Mosec implements Plugin<Project> {
/**
* https://developer.android.com/studio/build/dependencies#variant_aware
* -PconfAttr=buildtype:debug,usage:java-runtime
*/
private List confAttrSpec = null
/**
* 过滤configuration
* -Pconfiguration=confNameRegex or -Pconfiguration=spcificConfName
*/
private String confNameFilter = ".*"
/**
* 威胁等级 [High|Medium|Low]
*/
private String severityLevel = 'High'
/**
* 仅检查直接依赖
*/
private Boolean onlyProvenance = false
/**
* 发现漏洞即编译失败
*/
private Boolean failOnVuln = true
/**
* 项目类型 [Maven|Android]
* 决定了使用的检索漏洞库
*/
private String projectType = 'Android'
private def allowProjectType = ['Maven', 'Android']
/**
* 上报API
*/
private String endpoint
@Override
void apply(Project project) {
project.task('mosec').doLast {
if (project.hasProperty('projectType')) {
projectType = project.property('projectType').toString()
if (!allowProjectType.contains(projectType)) {
throw new Exception(Constants.ERROR_ON_PROJECT_TYPE)
}
}
if (project.hasProperty('endpoint')) {
endpoint = project.property('endpoint').toString()
}
def endpoint_env = System.getenv(Constants.MOSEC_ENDPOINT_ENV)
if (endpoint_env != null) {
endpoint = endpoint_env
}
if (endpoint == null) {
throw new RuntimeException(Constants.ERROR_ON_NULL_ENDPOINT)
}
if (project.hasProperty('confAttr')) {
confAttrSpec = project.property('confAttr').toString().toLowerCase().split(',').collect { it.split(':') }
}
if (project.hasProperty('configuration')) {
confNameFilter = String.format("%s", project.property('configuration').toString().toLowerCase())
}
if (project.hasProperty('severityLevel')) {
severityLevel = project.property('severityLevel')
}
if (project.hasProperty('onlyProvenance')) {
onlyProvenance = new Boolean(project.property('onlyProvenance').toString())
}
if (project.hasProperty('failOnVuln')) {
failOnVuln = new Boolean(project.property('failOnVuln').toString())
}
ProjectDependencyCollector collector = new ProjectDependencyCollector(project, confAttrSpec, confNameFilter, onlyProvenance)
Map depsTree = collector.collect()
if (depsTree == null) { return }
depsTree['type'] = projectType
depsTree['language'] = Constants.PROJECT_LANGUAGE
depsTree['severityLevel'] = severityLevel
HttpPost request = new HttpPost(endpoint)
request.addHeader("Content-Type", Constants.CONTENT_TYPE_JSON)
HttpEntity entity = new StringEntity(JsonOutput.toJson(depsTree))
request.setEntity(entity)
HttpClientHelper httpClientHelper = new HttpClientHelper()
HttpClient client = httpClientHelper.buildHttpClient()
HttpResponse response = client.execute(request)
if (response.getStatusLine().getStatusCode() >= 400) {
throw new NetworkErrorException(response.getStatusLine().getReasonPhrase())
}
Renderer renderer = new Renderer(project.logger, failOnVuln)
renderer.renderResponse(response.getEntity().getContent())
}
}
}

View File

@@ -0,0 +1,182 @@
package com.immomo.momosec.gradle.plugins
import groovy.json.JsonOutput
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.attributes.Attribute
class ProjectDependencyCollector {
private final Project project
private final List confAttrSpec
private final String confNameFilter
private final Boolean onlyProvenance
private final MosecLogHelper logHelper = new MosecLogHelper()
private final def mosecMergedDepsConf = 'mosecMergedDepsConf'
ProjectDependencyCollector(Project project, List confAttrSpec, String confNameFilter, Boolean onlyProvenance) {
this.project = project
this.confAttrSpec = confAttrSpec
this.confNameFilter = confNameFilter
this.onlyProvenance = onlyProvenance
}
def collect() {
def projName = project.name
def projVersion = project.version
def logger = project.logger
logger.info logHelper.strongInfo('MOSEC: ') + 'task is executing via doLast on ' + projName
def depsTree = [
'name': projName,
'version': projVersion,
'from': [projName + '@' + projVersion],
'dependencies': [:],
]
Map<Attribute<?>, Set<?>> allConfigurationAttributes = new HashMap<>()
Map<String, Set<String>> attributesAsStrings = new HashMap<>()
project.allprojects.each { proj ->
proj.configurations.findAll({
it.name != mosecMergedDepsConf &&
it.name =~ confNameFilter &&
matchesAttributeFilter.call(it)
}).each { conf ->
if (!conf.hasProperty('attributes')) {
// Gradle before version 3 does not support attributes
return
}
def attrs = conf.attributes
attrs.keySet().each({ attr ->
def value = attrs.getAttribute(attr as Attribute<Object>)
if (!allConfigurationAttributes.containsKey(attr)) {
allConfigurationAttributes[attr] = new HashSet()
attributesAsStrings[attr.name] = new HashSet()
}
allConfigurationAttributes[attr].add(value)
attributesAsStrings[attr.name].add(value.toString())
})
}
}
logger.debug 'MOSEC: JSON Attr ' + JsonOutput.toJson(attributesAsStrings)
def mosecConf = null
def mergeableConfs = project.configurations.findAll({it.name != mosecMergedDepsConf && it.name =~ confNameFilter})
if (confAttrSpec != null) {
mergeableConfs = mergeableConfs.findAll(matchesAttributeFilter)
}
if (mergeableConfs.size() == 0 && project.configurations.size() > 0) {
throw new RuntimeException('MOSEC: Matching configurations not found: ' + confNameFilter +
', availabie configurations for project ' + project + ': ' +
project.configurations.collect({ it.name }))
} else if (mergeableConfs.size() == 1) {
mosecConf = mergeableConfs.first()
} else if (mergeableConfs.size() > 1) {
logger.info logHelper.strongInfo('MOSEC: ') + 'constructing merged configuration from ' + mergeableConfs.collect({conf -> conf.name})
mosecConf = project.configurations.create(mosecMergedDepsConf)
mergeableConfs.each { mosecConf.extendsFrom(it) }
if (mosecConf.hasProperty('attributes')) {
allConfigurationAttributes.each({ attr, valueSet ->
if (valueSet.size() == 1) {
mosecConf.attributes.attribute(attr, valueSet.head())
}
})
}
}
if (mosecConf != null) {
logger.info logHelper.strongInfo('MOSEC: ') + 'resolving configuration ' + mosecConf.name
Set<ResolvedDependency> gradleDeps = mosecConf.resolvedConfiguration.firstLevelModuleDependencies
logger.debug 'MOSEC: converting dependency graph to DepTree'
ArrayList<String> from = new ArrayList<String>(){{ add(projName + '@' + projVersion) }}
depsTree['dependencies'] = depsToDict.call(gradleDeps, from, onlyProvenance)
simplifyDeps.call(depsTree)
logger.debug 'MOSEC: depsTree ' + JsonOutput.prettyPrint(JsonOutput.toJson(depsTree))
return depsTree
} else {
logger.error('MOSEC: no configuration found.')
}
}
def depsToDict = { Set<ResolvedDependency> deps, ArrayList<String> currentChain, Boolean onlyProvenance ->
def res = [:]
deps.each { d ->
def depName = d.moduleGroup + ':' + d.moduleName
def depNameVersion = depName + '@' + d.moduleVersion
if (!currentChain.contains(depNameVersion)) {
currentChain.add(depNameVersion)
def row = ['name': depName, 'version': d.moduleVersion, 'from': currentChain.clone()]
def subDeps = [:]
if (!onlyProvenance) {
subDeps = depsToDict.call(d.children, currentChain, onlyProvenance)
}
currentChain.remove(depNameVersion)
if (subDeps.size() > 0) {
row['dependencies'] = subDeps
} else {
row['dependencies'] = [:]
}
res[depName] = row
}
}
return res
}
def simplifyDeps = { deps ->
def q = [] as Queue
def s = new HashSet<String>()
q.add(deps)
def elem = [:]
while ( (elem = q.poll()) != null) {
def removeNames = []
elem['dependencies'].each { name, d ->
def depFull = d['name'] + '@' + d['version']
if (s.contains(depFull)) {
removeNames.add(name)
return
}
s.add(depFull as String)
q.add(d)
}
removeNames.each {name ->
elem['dependencies'].remove(name)
}
}
}
def matchesAttributeFilter = {Configuration conf ->
if (!conf.hasProperty('attributes')) {
// Gradle before version 3 does not support attributes
return true
}
def matches = true
def attrs = conf.attributes
attrs.keySet().each({ attr ->
def attrValueAsString = attrs.getAttribute(attr as Attribute<Object>).toString().toLowerCase()
for(String[] keyValueFilter : confAttrSpec) {
if (attr.name.toLowerCase().contains(keyValueFilter[0])
&& attrValueAsString != keyValueFilter[1]) {
matches = false
}
}
})
return matches
}
}

View File

@@ -0,0 +1,18 @@
package com.immomo.momosec.gradle.plugins;
public class Constants {
public static final String ERROR_ON_PROJECT_TYPE = "Project type not allow. Use [Maven | Android].";
public static final String ERROR_ON_VULNERABLE = "Dependency Vulnerable Found!";
public static final String ERROR_ON_API = "API return data format error.";
public static final String ERROR_ON_NULL_ENDPOINT = "API endpoint not setting. Setting by MOSEC_ENDPOINT env.";
public static final String CONTENT_TYPE_JSON = "application/json";
public static final String PROJECT_LANGUAGE = "java";
public static final String MOSEC_ENDPOINT_ENV = "MOSEC_ENDPOINT";
}

View File

@@ -0,0 +1,26 @@
package com.immomo.momosec.gradle.plugins;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
public class HttpClientHelper {
private final int timeout = 15 * 1000;
public HttpClient buildHttpClient() {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create()
.setDefaultRequestConfig(config)
.setRedirectStrategy(new LaxRedirectStrategy())
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
return httpClientBuilder.build();
}
}

View File

@@ -0,0 +1,21 @@
package com.immomo.momosec.gradle.plugins;
public class MosecLogHelper {
private static final String YELLOW = "\033[1;33m";
private static final String LIGHT_RED = "\033[1;31m";
private static final String LIGHT_GREEN = "\033[1;32m";
private static final String CANCEL_COLOR = "\033[0m";
public String strongWarning(String content) {
return YELLOW + content + CANCEL_COLOR;
}
public String strongError(String content) {
return LIGHT_RED + content + CANCEL_COLOR;
}
public String strongInfo(String content) {
return LIGHT_GREEN + content + CANCEL_COLOR;
}
}

View File

@@ -0,0 +1,78 @@
package com.immomo.momosec.gradle.plugins;
import com.immomo.momosec.gradle.plugins.exceptions.FoundVulnerableException;
import com.immomo.momosec.gradle.plugins.exceptions.NetworkErrorException;
import org.gradle.api.logging.Logger;
import com.google.gson.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Renderer {
private final MosecLogHelper logHelper = new MosecLogHelper();
private final Logger log;
private final Boolean failOnVuln;
public Renderer(Logger log, Boolean failOnVuln) {
this.log = log;
this.failOnVuln = failOnVuln;
}
public void renderResponse(InputStream in) {
JsonParser parser = new JsonParser();
JsonObject responseJson;
try {
responseJson = parser.parse(new BufferedReader(new InputStreamReader(in))).getAsJsonObject();
} catch (Exception e) {
throw new NetworkErrorException(Constants.ERROR_ON_API);
}
if(responseJson.get("ok") != null && responseJson.get("ok").getAsBoolean()) {
String ok = "✓ Tested %s dependencies, no vulnerable found.";
getLog().warn(logHelper.strongInfo(String.format(ok, responseJson.get("dependencyCount").getAsString())));
} else if (responseJson.get("vulnerabilities") != null) {
JsonArray vulns = (JsonArray) responseJson.get("vulnerabilities");
for (JsonElement vuln : vulns) {
printSingleVuln(vuln.getAsJsonObject());
}
String fail = "Tested %s dependencies, found %d vulnerable pathes.";
getLog().warn(logHelper.strongWarning(String.format(fail, responseJson.get("dependencyCount").getAsString(), vulns.size())));
if (Boolean.TRUE.equals(failOnVuln)) {
throw new FoundVulnerableException(Constants.ERROR_ON_VULNERABLE);
}
}
}
private void printSingleVuln(JsonObject vuln) {
String vulnWarn = "✗ %s severity (%s - %s) found on %s@%s";
getLog().warn(logHelper.strongError(String.format(vulnWarn,
vuln.get("severity").getAsString(),
vuln.get("title").getAsString(),
vuln.get("cve").getAsString(),
vuln.get("packageName").getAsString(),
vuln.get("version").getAsString()
)));
if(vuln.get("from") != null) {
JsonArray fromArr = vuln.get("from").getAsJsonArray();
StringBuilder fromStrb = new StringBuilder();
for(int i = 0; i < fromArr.size(); i++) {
fromStrb.append(fromArr.get(i).getAsString());
fromStrb.append(" > ");
}
getLog().warn(String.format("- Path: %s" ,fromStrb.substring(0, fromStrb.length() - 3)));
}
if (vuln.get("target_version").getAsJsonArray().size() >= 0) {
getLog().warn(logHelper.strongInfo(String.format("! Fix version %s", vuln.get("target_version").getAsJsonArray())));
}
getLog().warn("");
}
private Logger getLog() {
return log;
}
}

View File

@@ -0,0 +1,8 @@
package com.immomo.momosec.gradle.plugins.exceptions;
public class FoundVulnerableException extends RuntimeException {
public FoundVulnerableException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,8 @@
package com.immomo.momosec.gradle.plugins.exceptions;
public class NetworkErrorException extends RuntimeException {
public NetworkErrorException(String message) {
super(message);
}
}

View File

@@ -0,0 +1 @@
implementation-class=com.immomo.momosec.gradle.plugins.Mosec

View File

@@ -0,0 +1,183 @@
package com.immomo.momosec.gradle.plugins
import com.immomo.momosec.gradle.plugins.stubs.MyConfiguration
import com.immomo.momosec.gradle.plugins.stubs.MyResolvedDependency
import org.gradle.api.Project
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.ResolvedConfiguration
import org.gradle.api.logging.Logger
import org.junit.Assert
import org.junit.Test
import static org.mockito.Mockito.*
class TestProjectDependencyCollector {
private final Project project = mock(Project.class)
@Test
void collectTest() {
def spyLog = spy(mock(Logger.class))
def confNameFilter = /.*/
def spyCollector = spy(new ProjectDependencyCollector(project, null, confNameFilter, true))
def confContainer = mock(ConfigurationContainer.class)
// for spy log
doNothing().when(spyLog).info(anyString())
doNothing().when(spyLog).debug(anyString())
doNothing().when(spyLog).warn(anyString())
doNothing().when(spyLog).error(anyString())
// for project.configuration.findAll
def spyConf = spy(new MyConfiguration())
doReturn([spyConf].toSet()).when(confContainer).findAll(any(Closure.class))
// for mosecConf.resolvedConfiguration.firstLevelModuleDependencies
def resolvedConf = mock(ResolvedConfiguration.class)
doReturn(resolvedConf).when(spyConf).getResolvedConfiguration()
def parent = new MyResolvedDependency('com.study.parent', 'parent', '1.0.0')
parent.setChildren(new MyResolvedDependency('com.study.child', 'child', '1.0.0'))
doReturn(parent.getChildren()).when(resolvedConf).getFirstLevelModuleDependencies()
// for spy project
doReturn('com.study.parent:parent').when(project).getName()
doReturn('1.0.0').when(project).getVersion()
doReturn(spyLog).when(project).getLogger()
doReturn([project].toSet()).when(project).getAllprojects()
doReturn(confContainer).when(project).getConfigurations()
def depsTree = spyCollector.collect()
verify(spyCollector, times(1)).collect()
verify(project, times(1)).getAllprojects()
verify(project, atLeastOnce()).getConfigurations()
verify(spyConf, times(1)).getResolvedConfiguration()
verify(resolvedConf, times(1)).getFirstLevelModuleDependencies()
Assert.assertEquals('com.study.parent:parent', depsTree.get('name'))
Assert.assertEquals('1.0.0', depsTree.get('version'))
Assert.assertEquals(2,
(((depsTree.get('dependencies') as Map)
.get('com.study.child:child') as Map)
.get('from') as List)
.size())
}
@Test
void depsToDictTest() {
def parent = new MyResolvedDependency('com.study.parent', 'parent', '1.0.0')
def child = new MyResolvedDependency('com.study.child', 'child', '1.0.0')
def child_child = new MyResolvedDependency('com.study.child_child', 'child_child', '1.0.0')
parent.setChildren(child)
child.setChildren(child_child)
child_child.setParent(child)
child.setParent(parent)
def collector = new ProjectDependencyCollector(project, null, null, true)
def chain = ['com.study.parent:parent@1.0.0']
def childOnlyProvenanceDepsTree = collector.depsToDict.call(parent.getChildren(), chain as ArrayList, true)
def expectChildOnlyProvenanceTree = [
'com.study.child:child': [
'name': 'com.study.child:child',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0', 'com.study.child:child@1.0.0'],
'dependencies': [:]
]
]
Assert.assertEquals(expectChildOnlyProvenanceTree, childOnlyProvenanceDepsTree)
def childNotOnlyProvenanceDepsTree = collector.depsToDict.call(parent.getChildren(), chain as ArrayList, false)
def expectChildNotOnlyProvenanceTree = [
'com.study.child:child': [
'name': 'com.study.child:child',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0', 'com.study.child:child@1.0.0'],
'dependencies': [
'com.study.child_child:child_child': [
'name': 'com.study.child_child:child_child',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0', 'com.study.child:child@1.0.0', 'com.study.child_child:child_child@1.0.0'],
'dependencies': [:]
]
]
]
]
Assert.assertEquals(expectChildNotOnlyProvenanceTree, childNotOnlyProvenanceDepsTree)
}
@Test
void simplifyDepsTest() {
def depsTree = [
'name': 'com.study.parent:parent',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0'],
'dependencies': [
'com.study.child1:child1': [
'name': 'com.study.child1:child1',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0', 'com.study.child1:child1@1.0.0'],
'dependencies': [:]
],
'com.study.child2:child2': [
'name': 'com.study.child2:child2',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0', 'com.study.child2:child2@1.0.0'],
'dependencies': [
'com.study.child1:child1': [
'name': 'com.study.child1:child1',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0', 'com.study.child2:child2@1.0.0', 'com.study.child1:child1@1.0.0'],
'dependencies': [:]
]
]
]
]
]
def expectDepsTree = [
'name': 'com.study.parent:parent',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0'],
'dependencies': [
'com.study.child1:child1': [
'name': 'com.study.child1:child1',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0', 'com.study.child1:child1@1.0.0'],
'dependencies': [:]
],
'com.study.child2:child2': [
'name': 'com.study.child2:child2',
'version': '1.0.0',
'from': ['com.study.parent:parent@1.0.0', 'com.study.child2:child2@1.0.0'],
'dependencies': [:]
]
]
]
def collector = new ProjectDependencyCollector(project, null, null, true)
collector.simplifyDeps.call(depsTree)
Assert.assertEquals(expectDepsTree, depsTree)
}
@Test
void matchesAttributeFilter() {
def match
def conf = new MyConfiguration() // has usage:java-runtime attributes
String matchAttrSpec = "usage:java-runtime"
def collector = new ProjectDependencyCollector(project, matchAttrSpec.split(',').collect{ it.split(':') }, null, true)
match = collector.matchesAttributeFilter.call(conf)
Assert.assertTrue(match)
String notMatchAttrSpec = "usage:imnotexist"
def collectorWithWrongAttrSpec = new ProjectDependencyCollector(project, notMatchAttrSpec.split(',').collect{ it.split(':') }, null, true)
match = collectorWithWrongAttrSpec.matchesAttributeFilter.call(conf)
Assert.assertFalse(match)
}
}

View File

@@ -0,0 +1,351 @@
package com.immomo.momosec.gradle.plugins.stubs;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.artifacts.*;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.attributes.AttributeContainer;
import org.gradle.api.attributes.Usage;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileTree;
import org.gradle.api.internal.attributes.DefaultImmutableAttributesFactory;
import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.TaskDependency;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MyConfiguration implements Configuration {
@Override
public ResolutionStrategy getResolutionStrategy() {
return null;
}
@Override
public Configuration resolutionStrategy(Closure closure) {
return null;
}
@Override
public Configuration resolutionStrategy(Action<? super ResolutionStrategy> action) {
return null;
}
@Override
public State getState() {
return null;
}
@Override
public String getName() {
return null;
}
@Override
public boolean isVisible() {
return false;
}
@Override
public Configuration setVisible(boolean b) {
return null;
}
@Override
public Set<Configuration> getExtendsFrom() {
return null;
}
@Override
public Configuration setExtendsFrom(Iterable<Configuration> iterable) {
return null;
}
@Override
public Configuration extendsFrom(Configuration... configurations) {
return null;
}
@Override
public boolean isTransitive() {
return false;
}
@Override
public Configuration setTransitive(boolean b) {
return null;
}
@Nullable
@Override
public String getDescription() {
return null;
}
@Override
public Configuration setDescription(@Nullable String s) {
return null;
}
@Override
public Set<Configuration> getHierarchy() {
return null;
}
@Override
public Set<File> resolve() {
return null;
}
@Override
public Set<File> files(Closure closure) {
return null;
}
@Override
public Set<File> files(Spec<? super Dependency> spec) {
return null;
}
@Override
public Set<File> files(Dependency... dependencies) {
return null;
}
@Override
public FileCollection fileCollection(Spec<? super Dependency> spec) {
return null;
}
@Override
public FileCollection fileCollection(Closure closure) {
return null;
}
@Override
public FileCollection fileCollection(Dependency... dependencies) {
return null;
}
@Override
public ResolvedConfiguration getResolvedConfiguration() {
return null;
}
@Override
public String getUploadTaskName() {
return null;
}
@Override
public TaskDependency getBuildDependencies() {
return null;
}
@Override
public TaskDependency getTaskDependencyFromProjectDependency(boolean b, String s) {
return null;
}
@Override
public DependencySet getDependencies() {
return null;
}
@Override
public DependencySet getAllDependencies() {
return null;
}
@Override
public DependencyConstraintSet getDependencyConstraints() {
return null;
}
@Override
public DependencyConstraintSet getAllDependencyConstraints() {
return null;
}
@Override
public PublishArtifactSet getArtifacts() {
return null;
}
@Override
public PublishArtifactSet getAllArtifacts() {
return null;
}
@Override
public Set<ExcludeRule> getExcludeRules() {
return null;
}
@Override
public Configuration exclude(Map<String, String> map) {
return null;
}
@Override
public Configuration defaultDependencies(Action<? super DependencySet> action) {
return null;
}
@Override
public Configuration withDependencies(Action<? super DependencySet> action) {
return null;
}
@Override
public Set<Configuration> getAll() {
return null;
}
@Override
public ResolvableDependencies getIncoming() {
return null;
}
@Override
public ConfigurationPublications getOutgoing() {
return null;
}
@Override
public void outgoing(Action<? super ConfigurationPublications> action) {
}
@Override
public Configuration copy() {
return null;
}
@Override
public Configuration copyRecursive() {
return null;
}
@Override
public Configuration copy(Spec<? super Dependency> spec) {
return null;
}
@Override
public Configuration copyRecursive(Spec<? super Dependency> spec) {
return null;
}
@Override
public Configuration copy(Closure closure) {
return null;
}
@Override
public Configuration copyRecursive(Closure closure) {
return null;
}
@Override
public void setCanBeConsumed(boolean b) {
}
@Override
public boolean isCanBeConsumed() {
return false;
}
@Override
public void setCanBeResolved(boolean b) {
}
@Override
public boolean isCanBeResolved() {
return false;
}
@Override
public Configuration attributes(Action<? super AttributeContainer> action) {
return null;
}
@Override
public AttributeContainer getAttributes() {
DefaultImmutableAttributesFactory attributesFactory = new DefaultImmutableAttributesFactory(null, null);
AttributeContainer attrContainer = attributesFactory.mutable();
attrContainer.attribute(Attribute.of("usage", String.class), Usage.JAVA_RUNTIME);
return attrContainer;
}
@Override
public File getSingleFile() throws IllegalStateException {
return null;
}
@Override
public Set<File> getFiles() {
return null;
}
@Override
public boolean contains(File file) {
return false;
}
@Override
public String getAsPath() {
return null;
}
@Override
public FileCollection plus(FileCollection fileCollection) {
return null;
}
@Override
public FileCollection minus(FileCollection fileCollection) {
return null;
}
@Override
public FileCollection filter(Closure closure) {
return null;
}
@Override
public FileCollection filter(Spec<? super File> spec) {
return null;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public FileTree getAsFileTree() {
return null;
}
@Override
public void addToAntBuilder(Object o, String s, AntType antType) {
}
@Override
public Object addToAntBuilder(Object o, String s) {
return null;
}
@Override
public Iterator<File> iterator() {
return null;
}
}

View File

@@ -0,0 +1,95 @@
package com.immomo.momosec.gradle.plugins.stubs;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.artifacts.ResolvedModuleVersion;
import java.util.HashSet;
import java.util.Set;
public class MyResolvedDependency implements ResolvedDependency {
private String group;
private String name;
private String version;
private Set<ResolvedDependency> children = new HashSet<>();
private Set<ResolvedDependency> parent = new HashSet<>();
public MyResolvedDependency(String group, String name, String version) {
this.group = group;
this.name = name;
this.version = version;
}
@Override
public String getName() {
return null;
}
@Override
public String getModuleGroup() {
return group;
}
@Override
public String getModuleName() {
return name;
}
@Override
public String getModuleVersion() {
return version;
}
@Override
public String getConfiguration() {
return null;
}
@Override
public ResolvedModuleVersion getModule() {
return null;
}
@Override
public Set<ResolvedDependency> getChildren() {
return children;
}
public void setChildren(ResolvedDependency dependency) {
this.children.add(dependency);
}
@Override
public Set<ResolvedDependency> getParents() {
return parent;
}
public void setParent(ResolvedDependency dependency) {
this.parent.add(dependency);
}
@Override
public Set<ResolvedArtifact> getModuleArtifacts() {
return null;
}
@Override
public Set<ResolvedArtifact> getAllModuleArtifacts() {
return null;
}
@Override
public Set<ResolvedArtifact> getParentArtifacts(ResolvedDependency resolvedDependency) {
return null;
}
@Override
public Set<ResolvedArtifact> getArtifacts(ResolvedDependency resolvedDependency) {
return null;
}
@Override
public Set<ResolvedArtifact> getAllArtifacts(ResolvedDependency resolvedDependency) {
return null;
}
}

View File

@@ -0,0 +1,127 @@
package com.immomo.momosec.gradle.plugins;
import com.immomo.momosec.gradle.plugins.exceptions.FoundVulnerableException;
import com.immomo.momosec.gradle.plugins.exceptions.NetworkErrorException;
import org.gradle.api.logging.Logger;
import org.junit.*;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
public class TestRenderer {
@Rule
public ExpectedException exceptionRule = ExpectedException.none();
private final Logger log = mock(Logger.class);
private final Logger spyLog = Mockito.spy(log);
private final MosecLogHelper logHelper = new MosecLogHelper();
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;
private final String no_vulnerable_response =
"{" +
" \"ok\": true," +
" \"dependencyCount\": 3" +
"}";
private final String vulnerable_response =
"{" +
" \"ok\": false," +
" \"dependencyCount\": 3," +
" \"vulnerabilities\": [{" +
" \"severity\": \"High\"," +
" \"title\": \"Fake Vulnerable\"," +
" \"cve\": \"CVE-0001-0001\"," +
" \"packageName\": \"com.study.foo:bar\"," +
" \"version\": \"1.0.0\"," +
" \"target_version\": [\"1.1\"]" +
" }]" +
"}";
@Before
public void setUpStreams() {
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));
}
@Before
public void mockLog() {
doAnswer(invocation -> {
System.out.println((String)invocation.getArgument(0));
return null;
}).when(spyLog).info(anyString());
doAnswer(invocation -> {
System.out.println((String)invocation.getArgument(0));
return null;
}).when(spyLog).debug(anyString());
doAnswer(invocation -> {
System.out.println((String)invocation.getArgument(0));
return null;
}).when(spyLog).warn(anyString());
doAnswer(invocation -> {
System.out.println((String)invocation.getArgument(0));
return null;
}).when(spyLog).error(anyString());
}
@After
public void restoreStreams() {
System.setOut(originalOut);
System.setErr(originalErr);
}
@Test
public void renderResponseTest_ErrorJson() throws Exception {
exceptionRule.expect(NetworkErrorException.class);
exceptionRule.expectMessage(Constants.ERROR_ON_API);
Renderer renderer = new Renderer(log, true);
renderer.renderResponse(new ByteArrayInputStream("_".getBytes()));
}
@Test
public void renderResponseTest_NotFoundVuln() throws Exception {
Renderer renderer = new Renderer(spyLog, true);
renderer.renderResponse(new ByteArrayInputStream(no_vulnerable_response.getBytes()));
String expect = logHelper.strongInfo("✓ Tested 3 dependencies, no vulnerable found.") + "\n";
Assert.assertEquals(expect, outContent.toString());
}
@Test
public void renderResponseTest_FoundVulnWithFailOnVuln() throws Exception {
exceptionRule.expect(FoundVulnerableException.class);
exceptionRule.expectMessage(Constants.ERROR_ON_VULNERABLE);
Renderer renderer = new Renderer(log, true);
renderer.renderResponse(new ByteArrayInputStream(vulnerable_response.getBytes()));
}
@Test
public void renderResponseTest_FoundVulnWithoutFailOnVuln() throws Exception {
Renderer renderer = new Renderer(spyLog, false);
renderer.renderResponse(new ByteArrayInputStream(vulnerable_response.getBytes()));
String expect =
logHelper.strongError("✗ High severity (Fake Vulnerable - CVE-0001-0001) found on com.study.foo:bar@1.0.0") + "\n" +
logHelper.strongInfo("! Fix version [\"1.1\"]") + "\n" +
"\n" +
logHelper.strongWarning("Tested 3 dependencies, found 1 vulnerable pathes.") + "\n";
Assert.assertEquals(expect, outContent.toString());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB