├── src └── main │ ├── webapp │ ├── help-phost.html │ ├── help-useproxy.html │ ├── help-pport.html │ ├── help-ncwebsiteid.html │ ├── help-ncprofileid.html │ ├── help-acxserverurl.html │ ├── help-puser.html │ ├── help-ppassword.html │ ├── help-ncapitoken.html │ ├── help-ncconfirmed.html │ ├── help-ncignoreriskaccepted.html │ ├── help-ncignorefalsepositive.html │ ├── help-ncabortscan.html │ ├── help-ncseverity.html │ ├── help-ncstopscan.html │ ├── help-credentialsid.html │ ├── help-ncscanType.html │ └── scripts │ │ └── arrive.js │ ├── resources │ ├── index.jelly │ └── com │ │ └── acunetix │ │ └── plugin │ │ ├── Messages.properties │ │ ├── ACXScanBuilder │ │ ├── config.properties │ │ ├── global.jelly │ │ └── config.jelly │ │ └── ACXScanResultAction │ │ └── index.jelly │ └── java │ └── com │ └── acunetix │ ├── model │ ├── ScanType.java │ ├── WebsiteProfileModel.java │ ├── ScanTaskState.java │ ├── ReportType.java │ ├── WebsiteModel.java │ ├── ScanCancelRequest.java │ ├── ScanCancelRequestResult.java │ ├── ProxyBlock.java │ ├── ScanRequestBase.java │ ├── ScanInfoRequest.java │ ├── WebsiteModelRequest.java │ ├── ScanRequest.java │ ├── ScanInfoRequestResult.java │ ├── VCSCommit.java │ ├── ScanReport.java │ ├── IgnoredVulnerabilityStateFilters.java │ └── ScanRequestResult.java │ ├── plugin │ ├── ACXScanSCMAction.java │ ├── ACXScanSCMListener.java │ ├── ACXScanResultAction.java │ └── ACXScanBuilder.java │ └── utility │ └── AppCommon.java ├── ss ├── jenkins_scan_report.png ├── jenkins_scan_settings.png ├── jenkins_global_settings.png └── NE_jenkins_new_integration.png ├── spotbugs-exclude.xml ├── Jenkinsfile ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── LICENSE ├── .gitignore ├── Readme.md ├── pom.xml └── acunetix-360-scan.iml /src/main/webapp/help-phost.html: -------------------------------------------------------------------------------- 1 |
2 | Enter the proxy host. 3 |
-------------------------------------------------------------------------------- /src/main/webapp/help-useproxy.html: -------------------------------------------------------------------------------- 1 |
2 | Selecting this checkbox enables Proxy. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-pport.html: -------------------------------------------------------------------------------- 1 |
2 | Enter the port number if the proxy host has a port. 3 |
-------------------------------------------------------------------------------- /src/main/webapp/help-ncwebsiteid.html: -------------------------------------------------------------------------------- 1 |
2 | This address will be scanned. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-ncprofileid.html: -------------------------------------------------------------------------------- 1 |
2 | This profile will be used in the scan. 3 |
4 | -------------------------------------------------------------------------------- /ss/jenkins_scan_report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/acunetix-360-scan-plugin/master/ss/jenkins_scan_report.png -------------------------------------------------------------------------------- /ss/jenkins_scan_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/acunetix-360-scan-plugin/master/ss/jenkins_scan_settings.png -------------------------------------------------------------------------------- /ss/jenkins_global_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/acunetix-360-scan-plugin/master/ss/jenkins_global_settings.png -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Plugin for starting Acunetix 360 Scans. 4 |
5 | -------------------------------------------------------------------------------- /ss/NE_jenkins_new_integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/acunetix-360-scan-plugin/master/ss/NE_jenkins_new_integration.png -------------------------------------------------------------------------------- /src/main/webapp/help-acxserverurl.html: -------------------------------------------------------------------------------- 1 |
2 | like https://online.acunetix360.com. 3 |
4 | -------------------------------------------------------------------------------- /spotbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | buildPlugin(configurations:[ 2 | [platform:"windows", jdk:"21", jenkins:"2.516.3"], 3 | [platform:"linux", jdk:"21", jenkins:"2.516.3"] 4 | ]) -------------------------------------------------------------------------------- /src/main/webapp/help-puser.html: -------------------------------------------------------------------------------- 1 |
2 | If the proxy server is password protected, enter your username and password in the Username and Password fields. 3 |
-------------------------------------------------------------------------------- /src/main/webapp/help-ppassword.html: -------------------------------------------------------------------------------- 1 |
2 | If the proxy server is password protected, enter your username and password in the Username and Password fields. 3 |
-------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanType.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | public enum ScanType { 4 | Incremental, FullWithPrimaryProfile, FullWithSelectedProfile 5 | } 6 | -------------------------------------------------------------------------------- /src/main/webapp/help-ncapitoken.html: -------------------------------------------------------------------------------- 1 |
2 | It can be found at "Your Account > API Settings" page in the Acunetix 360. User must have "Start Scans" permission for the target website. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-ncconfirmed.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
-------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic", 3 | "files.exclude": { 4 | "**/.classpath": true, 5 | "**/.project": true, 6 | "**/.settings": true, 7 | "**/.factorypath": true 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/webapp/help-ncignoreriskaccepted.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | -------------------------------------------------------------------------------- /src/main/webapp/help-ncignorefalsepositive.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | -------------------------------------------------------------------------------- /src/main/webapp/help-ncabortscan.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | -------------------------------------------------------------------------------- /src/main/webapp/help-ncseverity.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | -------------------------------------------------------------------------------- /src/main/webapp/help-ncstopscan.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
9 | -------------------------------------------------------------------------------- /src/main/webapp/help-credentialsid.html: -------------------------------------------------------------------------------- 1 |

2 | Acunetix 360 Server URL - API Token Pair. Note that setting this credentials overrides jenkins' global settings.

3 | Credential
4 | Kind = Username with password
5 | Scope = Global *(This must be defined as global)
6 | Username = Server Url like https://online.acunetix360.com
7 | Password = Api Token
8 |

-------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/WebsiteProfileModel.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | public class WebsiteProfileModel { 4 | private String id; 5 | private String Name; 6 | 7 | public String getId() { 8 | return id; 9 | } 10 | 11 | public void setId(String id) { 12 | this.id = id; 13 | } 14 | 15 | public String getName() { 16 | return Name; 17 | } 18 | 19 | public void setName(String name) { 20 | Name = name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "java", 9 | "name": "Debug (Attach)", 10 | "request": "attach", 11 | "hostName": "localhost", 12 | "port": 8000, 13 | "preLaunchTask": "mvnDebug" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanTaskState.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | public enum ScanTaskState { 4 | Queued(0), Scanning(1), Archiving(2), Complete(3), Failed(4), Cancelled(5), Delayed( 5 | 6), Pausing(7), Paused(8), Resuming(9); 6 | 7 | private final int number; 8 | 9 | ScanTaskState(final int number) { 10 | this.number = number; 11 | } 12 | 13 | public int getNumber() { 14 | return number; 15 | } 16 | 17 | public String getNumberAsString() { 18 | return String.valueOf(number); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ReportType.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | public enum ReportType { 4 | ScanDetail(3), 5 | OwaspTopTen2013(5), 6 | HIPAACompliance(6), 7 | PCICompliance(7), 8 | KnowledgeBase(8), 9 | ExecutiveSummary(9), 10 | OwaspTopTen2017(10), 11 | SansTop25(11), 12 | WASC(12), 13 | Iso27001Compliance(13), 14 | FullScanDetail(14); 15 | 16 | private final int number; 17 | 18 | ReportType(final int number) { 19 | this.number = number; 20 | } 21 | 22 | public int getNumber() { 23 | return number; 24 | } 25 | 26 | public String getNumberAsString() { 27 | return String.valueOf(number); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/webapp/help-ncscanType.html: -------------------------------------------------------------------------------- 1 |
2 | 13 |
14 | -------------------------------------------------------------------------------- /src/main/resources/com/acunetix/plugin/Messages.properties: -------------------------------------------------------------------------------- 1 | ACXScanBuilder.DescriptorImpl.errors.missingApiURL=The parameter 'Acunetix 360 Server URL' must be specified. 2 | ACXScanBuilder.DescriptorImpl.errors.invalidApiURL=The parameter 'Acunetix 360 Server URL' is invalid. 3 | ACXScanBuilder.DescriptorImpl.errors.missingApiToken=The parameter 'API Token' must be specified. 4 | ACXScanBuilder.DescriptorImpl.errors.invalidScanType=The parameter 'Scan Type' is invalid. 5 | ACXScanBuilder.DescriptorImpl.errors.invalidWebsiteId=The parameter 'Website URL' is invalid. 6 | ACXScanBuilder.DescriptorImpl.errors.invalidProfileId=The parameter 'Profile Name' is invalid. 7 | ACXScanBuilder.DescriptorImpl.DisplayName=Acunetix 360 Scan -------------------------------------------------------------------------------- /src/main/java/com/acunetix/plugin/ACXScanSCMAction.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.plugin; 2 | 3 | import com.acunetix.model.VCSCommit; 4 | import hudson.model.Action; 5 | 6 | import edu.umd.cs.findbugs.annotations.CheckForNull; 7 | 8 | public class ACXScanSCMAction implements Action { 9 | 10 | private final VCSCommit vcsCommit; 11 | 12 | public ACXScanSCMAction(VCSCommit vcsCommit) { 13 | this.vcsCommit = vcsCommit; 14 | } 15 | 16 | public VCSCommit getVcsCommit() { 17 | return vcsCommit; 18 | } 19 | 20 | @CheckForNull 21 | @Override 22 | public String getIconFileName() { 23 | return null; 24 | } 25 | 26 | @CheckForNull 27 | @Override 28 | public String getDisplayName() { 29 | return "ACXScanSCMAction"; 30 | } 31 | 32 | @CheckForNull 33 | @Override 34 | public String getUrlName() { 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/resources/com/acunetix/plugin/ACXScanBuilder/config.properties: -------------------------------------------------------------------------------- 1 | acxServerURL=NC Server URL 2 | acxServerURLDescr=URL, like https://online.acunetix360.com. 3 | apiToken=API Token 4 | apiTokenDescr=It can be found at "API Settings" page in the Acunetix 360 which is reachable under "Your Accounts" section in Acunetix 360 Site. Token's user must have "start scan permission" on to be scanned website. 5 | ncScanType=Scan Type 6 | scanTypeDescr= 7 | ncReportType=Report Type 8 | reportTypeDescr= 9 | ncTargetURL=Website Deploy URL 10 | targetURLDescr=Website deploy URL, like http://www.example.com. This address will be scanned. 11 | ncProfileName=Profile Name 12 | profileNameDescr= 13 | ncBaseScanId=Base Scan Id: 14 | baseScanIdDescr=Base scan ID like, 8539ae41-a816-4d43-b91e-3b4296002d72. Base scan ID is a scan ID which can be retrieved from "/scans/list" endpoint of Acunetix 360 API. 15 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/WebsiteModel.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class WebsiteModel { 6 | private String id; 7 | private String name; 8 | private String url; 9 | private ArrayList profiles; 10 | 11 | public WebsiteModel() { 12 | profiles = new ArrayList<>(); 13 | } 14 | 15 | public String getId() { 16 | return id; 17 | } 18 | 19 | public void setId(String id) { 20 | this.id = id; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public void setName(String name) { 28 | this.name = name; 29 | } 30 | 31 | public String getUrl() { 32 | return url; 33 | } 34 | 35 | public void setUrl(String url) { 36 | this.url = url; 37 | } 38 | 39 | public String getDisplayName() { 40 | return getName() + " (" + getUrl() + ")"; 41 | } 42 | 43 | public ArrayList getProfiles() { 44 | return profiles; 45 | } 46 | 47 | public void setProfiles(ArrayList profiles) { 48 | this.profiles = profiles; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Acunetix Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/plugin/ACXScanSCMListener.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.plugin; 2 | 3 | import com.acunetix.model.VCSCommit; 4 | import hudson.Extension; 5 | import hudson.model.*; 6 | import hudson.model.listeners.SCMListener; 7 | import hudson.scm.ChangeLogSet; 8 | import hudson.scm.SCM; 9 | 10 | /** 11 | * This class registers an {@link SCMListener} with Jenkins which allows us to create the "Checkout 12 | * successful" event. 13 | */ 14 | @Extension 15 | public class ACXScanSCMListener extends SCMListener { 16 | /** 17 | * Invoked right after the source code for the build has been checked out. It will NOT be called 18 | * if a checkout fails. 19 | * 20 | * @param build - Current build 21 | * @param scm - Configured SCM 22 | * @param listener - Current build listener 23 | * @param changelog - Changelog 24 | * @throws Exception if an error is encountered 25 | */ 26 | @Override 27 | public void onChangeLogParsed(Run build, SCM scm, TaskListener listener, 28 | ChangeLogSet changelog) throws Exception { 29 | super.onChangeLogParsed(build, scm, listener, changelog); 30 | 31 | build.replaceAction(new ACXScanSCMAction(new VCSCommit(build, changelog))); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "mvnClean", 8 | "type": "shell", 9 | "command": "mvn clean", 10 | "isBackground": true, 11 | "problemMatcher": [ 12 | { 13 | "pattern": [ 14 | { 15 | "regexp": "\\b\\B", 16 | "file": 1, 17 | "location": 2, 18 | "message": 3 19 | } 20 | ], 21 | "background": { 22 | "activeOnStart": true, 23 | "beginsPattern": "^.*Preparing to execute Maven clean.*", 24 | "endsPattern": "^.*End of maven clean*" 25 | } 26 | } 27 | ] 28 | }, 29 | { 30 | "label": "mvnDebug", 31 | "type": "shell", 32 | "dependsOn": ["mvnClean"], 33 | "command": "mvnDebug hpi:run -D jenkins.version=2.516.3", 34 | "isBackground": true, 35 | "problemMatcher": [ 36 | { 37 | "pattern": [ 38 | { 39 | "regexp": "\\b\\B", 40 | "file": 1, 41 | "location": 2, 42 | "message": 3 43 | } 44 | ], 45 | "background": { 46 | "activeOnStart": true, 47 | "beginsPattern": "^.*Preparing to execute Maven in debug mode.*", 48 | "endsPattern": "^.*Listening for transport dt_socket at address.*" 49 | } 50 | } 51 | ] 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # Ignore bin directory (generated build output, not needed in version control) 10 | bin/ 11 | 12 | # BlueJ files 13 | *.ctxt 14 | 15 | # Mobile Tools for Java (J2ME) 16 | .mtj.tmp/ 17 | 18 | # Package Files # 19 | *.jar 20 | *.war 21 | *.ear 22 | *.zip 23 | *.tar.gz 24 | *.rar 25 | 26 | work/ 27 | target/ 28 | pom.xml.tag 29 | pom.xml.releaseBackup 30 | pom.xml.versionsBackup 31 | pom.xml.next 32 | release.properties 33 | dependency-reduced-pom.xml 34 | buildNumber.properties 35 | .mvn/timing.properties 36 | 37 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 38 | !/.mvn/wrapper/maven-wrapper.jar 39 | 40 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 41 | hs_err_pid* 42 | ### Windows template 43 | # Windows thumbnail cache files 44 | Thumbs.db 45 | ehthumbs.db 46 | ehthumbs_vista.db 47 | 48 | # Dump file 49 | *.stackdump 50 | 51 | # Folder config file 52 | [Dd]esktop.ini 53 | 54 | # Recycle Bin used on file shares 55 | $RECYCLE.BIN/ 56 | 57 | # Windows Installer files 58 | *.cab 59 | *.msi 60 | *.msm 61 | *.msp 62 | 63 | # Windows shortcuts 64 | *.lnk 65 | 66 | # CMake 67 | cmake-build-debug/ 68 | 69 | ## File-based project format: 70 | *.iws 71 | 72 | ## Plugin-specific files: 73 | 74 | # IntelliJ 75 | out/ 76 | .idea/ 77 | 78 | # JIRA plugin 79 | atlassian-ide-plugin.xml 80 | 81 | # Crashlytics plugin (for Android Studio and IntelliJ) 82 | com_crashlytics_export_strings.xml 83 | crashlytics.properties 84 | crashlytics-build.properties 85 | fabric.properties 86 | 87 | *.classpath 88 | *.factorypath 89 | *.project 90 | *.prefs -------------------------------------------------------------------------------- /src/main/resources/com/acunetix/plugin/ACXScanResultAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 34 |
35 |
36 |
37 |
38 | 39 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanCancelRequest.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import hudson.util.Secret; 4 | import org.apache.hc.core5.http.HttpHeaders; 5 | import org.apache.hc.core5.http.io.entity.StringEntity; 6 | import org.apache.hc.core5.http.ClassicHttpResponse; 7 | import org.apache.hc.client5.http.classic.methods.HttpPost; 8 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 9 | 10 | import java.io.IOException; 11 | import java.net.MalformedURLException; 12 | import java.net.URI; 13 | import java.net.URISyntaxException; 14 | import java.net.URL; 15 | import org.apache.hc.core5.http.HttpEntity; 16 | import org.apache.hc.core5.http.ContentType; 17 | 18 | public class ScanCancelRequest extends ScanRequestBase { 19 | public ScanCancelRequest(String apiURL, Secret apiToken, String scanTaskId, 20 | ProxyBlock proxy) throws MalformedURLException, NullPointerException, URISyntaxException { 21 | super(apiURL, apiToken, proxy); 22 | this.scanTaskId = scanTaskId; 23 | this.scanCancelUri = new URL(ApiURL, "api/1.0/scans/CancelScanForPlugin/").toURI(); 24 | this.contentLength = "0"; 25 | this.content = ""; 26 | } 27 | 28 | public final String scanTaskId; 29 | public final URI scanCancelUri; 30 | public final String contentLength; 31 | public final String content; 32 | 33 | public ClassicHttpResponse scanCancelRequest() throws IOException { 34 | CloseableHttpClient client = getHttpClient(); 35 | HttpPost httpPost = new HttpPost(scanCancelUri + scanTaskId); 36 | HttpEntity stringEntity = new StringEntity(content,ContentType.APPLICATION_JSON); 37 | httpPost.setEntity(stringEntity); 38 | httpPost.setHeader("Accept", json); 39 | httpPost.setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader()); 40 | 41 | return (ClassicHttpResponse) client.execute(httpPost); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/com/acunetix/plugin/ACXScanBuilder/global.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 34 | 39 |
40 |
-------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Acunetix 360 Scan Plugin 2 | ==================== 3 | 4 | [![Jenkins Plugin](https://img.shields.io/jenkins/plugin/v/acunetix-360-scan.svg?color=red)](https://plugins.jenkins.io/acunetix-360-scan) 5 | [![Jenkins Plugin Installs](https://img.shields.io/jenkins/plugin/i/acunetix-360-scan.svg?color=red)](https://plugins.jenkins.io/acunetix-360-scan) 6 | 7 | ## About this plugin 8 | 9 | Allows users to start security scans via Acunetix 360 and see their 10 | reports in Jenkins  11 | 12 | ## Features 13 | 14 | ### Global Settings 15 | 16 | Acunetix 360 plugin needs the admin user to define the API settings 17 | only once. 18 | 19 | ![](ss/jenkins_global_settings.png) 20 | 21 | ### Global Settings Override 22 | 23 | Global settings can be overridden in pipeline scripts by 24 | giving ncApiToken and/or acxServerURL parameters. 25 | 26 | #### Example Script 27 | 28 | step([$class: 'ACXScanBuilder', ncScanType: 'FullWithPrimaryProfile', ncWebsiteId: '19011b1b-4141-4331-8514-ab4102a4c135']) 29 | 30 | ![](ss/NE_jenkins_new_integration.png) 31 | 32 | ### Scan Settings 33 | 34 | Once you define global API settings, the plugin retrieves available 35 | scan settings such as scannable website list and scan profile names. You 36 | can easily select relevant settings. 37 | 38 | ![](ss/jenkins_scan_settings.png) 39 | 40 | ### Scan Report 41 | 42 |  Once your initiated scan is completed, you can easily see your 43 | executive scan report on the build result window. 44 | 45 | ![](ss/jenkins_scan_report.png) 46 | 47 | ## Requirements 48 | 49 | In order to use the Acunetix 360 scan plugin, following requirements 50 | needs to be satisfied: 51 | 52 | - The user must have API token which has permission to start security 53 | scan. 54 | 55 | - The token belongs to the Acunetix 360 account must have at least one 56 | registered website.  57 | 58 | ## User Guide 59 | 60 | Acunetix 360 Jenkins Plugin documentation is available at: 61 | 62 | 63 | 64 | Acunetix 360 SDLC documentation is available at: 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/plugin/ACXScanResultAction.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.plugin; 2 | 3 | import com.acunetix.model.ScanRequestResult; 4 | import hudson.model.Action; 5 | import hudson.model.Run; 6 | import hudson.util.HttpResponses; 7 | import jenkins.model.RunAction2; 8 | import org.kohsuke.stapler.HttpResponse; 9 | import org.kohsuke.stapler.verb.GET; 10 | 11 | import edu.umd.cs.findbugs.annotations.CheckForNull; 12 | 13 | public class ACXScanResultAction implements Action, RunAction2 { 14 | 15 | private ScanRequestResult scanRequestResult; 16 | 17 | private transient Run run; 18 | 19 | public ACXScanResultAction(ScanRequestResult scanRequestResult) { 20 | this.scanRequestResult = scanRequestResult; 21 | } 22 | 23 | public ScanRequestResult getScanRequestResult() { 24 | return scanRequestResult; 25 | } 26 | 27 | public void setScanRequestResult(ScanRequestResult scanRequestResult) { 28 | this.scanRequestResult = scanRequestResult; 29 | } 30 | 31 | public String getError() { 32 | if (scanRequestResult.isError()) { 33 | return "true"; 34 | } else { 35 | return "false"; 36 | } 37 | } 38 | 39 | public String getReportGenerated() { 40 | if (scanRequestResult.isReportGenerated()) { 41 | return "true"; 42 | } else { 43 | return "false"; 44 | } 45 | } 46 | 47 | @GET 48 | public HttpResponse doGetContent() { 49 | String content = scanRequestResult.getReport().getContent(); 50 | return HttpResponses.html(content); 51 | } 52 | 53 | @CheckForNull 54 | @Override 55 | public String getIconFileName() { 56 | return "document.png"; 57 | } 58 | 59 | @CheckForNull 60 | @Override 61 | public String getDisplayName() { 62 | return "Acunetix 360 Report"; 63 | } 64 | 65 | @CheckForNull 66 | @Override 67 | public String getUrlName() { 68 | return "acunetixreport"; 69 | } 70 | 71 | @Override 72 | public void onAttached(Run run) { 73 | this.run = run; 74 | } 75 | 76 | @Override 77 | public void onLoad(Run run) { 78 | this.run = run; 79 | } 80 | 81 | public Run getRun() { 82 | return run; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanCancelRequestResult.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import org.apache.hc.core5.http.ClassicHttpResponse; 4 | import org.json.simple.parser.ParseException; 5 | 6 | import java.io.IOException; 7 | import java.net.MalformedURLException; 8 | import java.net.URISyntaxException; 9 | 10 | import com.acunetix.utility.AppCommon; 11 | 12 | public class ScanCancelRequestResult extends ScanRequestBase { 13 | public static ScanCancelRequestResult errorResult(final String errorMessage) { 14 | return new ScanCancelRequestResult(errorMessage); 15 | } 16 | 17 | private final int httpStatusCode; 18 | private String data; 19 | private String scanTaskID; 20 | private boolean isError; 21 | private String errorMessage; 22 | 23 | private ScanCancelRequestResult(final String errorMessage) { 24 | super(); 25 | this.errorMessage = errorMessage; 26 | httpStatusCode = 0; 27 | isError = true; 28 | data = ""; 29 | } 30 | 31 | public ScanCancelRequestResult(final ClassicHttpResponse response) throws MalformedURLException, URISyntaxException { 32 | super(); 33 | httpStatusCode = response.getCode(); 34 | isError = httpStatusCode != 200; 35 | 36 | if (!isError) { 37 | try { 38 | data = AppCommon.parseResponseToString(response); 39 | isError = !(boolean) AppCommon.parseJsonValue(data, "IsValid"); 40 | if (!isError) { 41 | scanTaskID = (String) AppCommon.parseJsonValue(data, "ScanTaskId"); 42 | } else { 43 | errorMessage = (String) AppCommon.parseJsonValue(data, "ErrorMessage"); 44 | } 45 | } catch (final ParseException ex) { 46 | isError = true; 47 | errorMessage = "Scan info request result is not parsable::: " + ex.toString(); 48 | } catch (final IOException ex) { 49 | isError = true; 50 | errorMessage = "Scan info request result is not readable::: " + ex.toString(); 51 | } 52 | } 53 | } 54 | 55 | public String getScanTaskId() { 56 | return scanTaskID; 57 | } 58 | 59 | public int getHttpStatusCode() { 60 | return httpStatusCode; 61 | } 62 | 63 | public String getErrorMessage() { 64 | return errorMessage; 65 | } 66 | 67 | public boolean isError() { 68 | return isError; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ProxyBlock.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import org.kohsuke.stapler.DataBoundConstructor; 4 | 5 | /** 6 | * The ProxyBlock class corresponds to an "optionalBlock" global.jelly element that 7 | * represents proxy configuration data. 8 | * 9 | */ 10 | public class ProxyBlock { 11 | 12 | private final Boolean _useProxy; 13 | private final String _pHost; 14 | private final String _pPort; 15 | private final String _pUser; 16 | private final String _pPassword; 17 | 18 | /** 19 | * Corresponds to the {@code useProxy} identifier referenced in a global.jelly file. 20 | * 21 | * @return a {@link java.lang.Boolean} object. 22 | */ 23 | public Boolean getUseProxy() { 24 | return this._useProxy; 25 | } 26 | 27 | /** 28 | * Corresponds to the {@code pHost} identifier referenced in a global.jelly file. 29 | * 30 | * @return a {@link java.lang.String} object. 31 | */ 32 | public String getpHost() { 33 | return this._pHost; 34 | } 35 | 36 | /** 37 | * Corresponds to the {@code pPort} identifier referenced in a global.jelly file. 38 | * 39 | * @return a {@link java.lang.String} object. 40 | */ 41 | public String getpPort() { 42 | return this._pPort; 43 | } 44 | 45 | /** 46 | * Corresponds to the {@code pUser} identifier referenced in a global.jelly file. 47 | * 48 | * @return a {@link java.lang.String} object. 49 | */ 50 | public String getpUser() { 51 | return this._pUser; 52 | } 53 | 54 | /** 55 | * Corresponds to the {@code pPassword} identifier referenced in a global.jelly file. 56 | * 57 | * @return a {@link java.lang.String} object. 58 | */ 59 | public String getpPassword() { 60 | return this._pPassword; 61 | } 62 | 63 | 64 | /** 65 | * Called by Jenkins with form data. 66 | * 67 | * @param useProxy a {@link java.lang.Boolean} object. 68 | * @param pHost a {@link java.lang.String} object. 69 | * @param pPort a {@link java.lang.String} object. 70 | * @param pUser a {@link java.lang.String} object. 71 | * @param pPassword a {@link java.lang.String} object. 72 | */ 73 | @DataBoundConstructor 74 | public ProxyBlock(Boolean useProxy, String pHost, String pPort, String pUser, String pPassword) { 75 | this._useProxy = useProxy; 76 | this._pHost = pHost; 77 | this._pPort = pPort; 78 | this._pUser = pUser; 79 | this._pPassword = pPassword; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanRequestBase.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import com.acunetix.utility.AppCommon; 4 | import hudson.util.Secret; 5 | import org.apache.commons.codec.binary.Base64; 6 | import org.apache.hc.client5.http.auth.AuthScope; 7 | import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; 8 | import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; 9 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 10 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 11 | import org.apache.hc.core5.http.HttpHost; 12 | 13 | import java.net.MalformedURLException; 14 | import java.net.URL; 15 | import java.nio.charset.StandardCharsets; 16 | 17 | public abstract class ScanRequestBase { 18 | protected static final String json = "application/json"; 19 | public final URL ApiURL; 20 | public final Secret ApiToken; 21 | public final ProxyBlock proxy; 22 | 23 | // Called from server-side 24 | public ScanRequestBase(String apiURL, Secret apiToken, ProxyBlock proxy) throws MalformedURLException { 25 | this.ApiURL = AppCommon.getBaseURL(apiURL); 26 | this.ApiToken = apiToken; 27 | this.proxy = proxy; 28 | } 29 | 30 | public ScanRequestBase() { 31 | this.ApiURL = null; 32 | this.ApiToken = null; 33 | this.proxy = null; 34 | } 35 | 36 | protected CloseableHttpClient getHttpClient() { 37 | 38 | if (proxy != null && proxy.getUseProxy()) { 39 | int proxyPort = Integer.parseInt(proxy.getpPort()); 40 | HttpHost proxyHttpHost = new HttpHost(proxy.getpHost(), proxyPort); 41 | 42 | BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); 43 | AuthScope authScope = new AuthScope(proxyHttpHost); 44 | UsernamePasswordCredentials credentials = new UsernamePasswordCredentials( 45 | proxy.getpUser(), proxy.getpPassword().toCharArray()); 46 | credsProvider.setCredentials(authScope, credentials); 47 | 48 | return HttpClientBuilder 49 | .create() 50 | .setProxy(proxyHttpHost) 51 | .setDefaultCredentialsProvider(credsProvider) 52 | .disableRedirectHandling() 53 | .build(); 54 | } 55 | else { 56 | return HttpClientBuilder 57 | .create() 58 | .disableRedirectHandling() 59 | .build(); 60 | } 61 | } 62 | 63 | protected String getAuthHeader() { 64 | String auth = ":" + ApiToken.getPlainText(); 65 | byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); 66 | String authHeader = "Basic " + new String(encodedAuth, StandardCharsets.ISO_8859_1); 67 | 68 | return authHeader; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanInfoRequest.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import hudson.util.Secret; 4 | import org.apache.hc.core5.http.HttpHeaders; 5 | import org.apache.hc.core5.http.ClassicHttpResponse; 6 | import org.apache.hc.client5.http.classic.HttpClient; 7 | import org.apache.hc.client5.http.classic.methods.HttpGet; 8 | import org.apache.hc.client5.http.classic.methods.HttpPost; 9 | import org.apache.hc.core5.http.ContentType; 10 | import org.apache.hc.core5.http.io.entity.StringEntity; 11 | import java.io.IOException; 12 | import java.net.MalformedURLException; 13 | import java.net.URI; 14 | import java.net.URISyntaxException; 15 | import java.net.URL; 16 | 17 | public class ScanInfoRequest extends ScanRequestBase { 18 | public ScanInfoRequest(String apiURL, Secret apiToken,String scanTaskId, Boolean doNotFail, Boolean isConfirmed, 19 | IgnoredVulnerabilityStateFilters filters, ProxyBlock proxy) throws MalformedURLException, NullPointerException, URISyntaxException { 20 | super(apiURL, apiToken, proxy); 21 | this.scanTaskId = scanTaskId; 22 | this.scanInfoUri = new URL(ApiURL, "api/1.0/scans/ScanInfoForPlugin/").toURI(); 23 | this.filters = filters; 24 | this.doNotFail = doNotFail; 25 | this.isConfirmed = isConfirmed; 26 | } 27 | 28 | public final String scanTaskId; 29 | public final URI scanInfoUri; 30 | public final IgnoredVulnerabilityStateFilters filters; 31 | public final Boolean doNotFail; 32 | public final Boolean isConfirmed; 33 | 34 | public ClassicHttpResponse scanInfoRequestOld() throws IOException { 35 | HttpClient client = getHttpClient(); 36 | final HttpGet httpGet = new HttpGet(scanInfoUri + scanTaskId); 37 | httpGet.setHeader("Accept", json); 38 | httpGet.setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader()); 39 | 40 | return (ClassicHttpResponse) client.execute(httpGet); 41 | } 42 | 43 | public ClassicHttpResponse scanInfoRequest() throws IOException { 44 | HttpClient httpClient = getHttpClient(); 45 | 46 | try { 47 | HttpPost request = new HttpPost(scanInfoUri); 48 | 49 | StringBuilder jsonString = new StringBuilder(); 50 | jsonString.append("{"); 51 | jsonString.append("'ScanId':'").append(scanTaskId).append("',"); 52 | jsonString.append("'DoNotFail':").append(doNotFail).append(","); 53 | jsonString.append("'IsConfirmed':").append(isConfirmed).append(","); 54 | filters.setFiltersString(); 55 | jsonString.append("'IgnoredVulnerabilityStateFilters':").append(filters.getFiltersString()); 56 | jsonString.append("}"); 57 | StringEntity bodyEntity = new StringEntity(jsonString.toString(), ContentType.APPLICATION_JSON); 58 | // send a JSON data 59 | request.setEntity(bodyEntity); 60 | request.addHeader("Accept","application/json"); 61 | request.setHeader("Content-Type", "application/json"); 62 | request.setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader()); 63 | 64 | return (ClassicHttpResponse) httpClient.execute(request); 65 | }catch (Exception ex) { 66 | Exception e = ex; 67 | } 68 | return null; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/WebsiteModelRequest.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import com.acunetix.utility.AppCommon; 4 | import hudson.util.Secret; 5 | 6 | import org.apache.hc.core5.http.ClassicHttpResponse; 7 | import org.apache.hc.core5.http.HttpHeaders; 8 | import org.apache.hc.client5.http.classic.HttpClient; 9 | import org.apache.hc.client5.http.classic.methods.HttpGet; 10 | import org.json.simple.JSONArray; 11 | import org.json.simple.JSONObject; 12 | import org.json.simple.parser.JSONParser; 13 | import org.json.simple.parser.ParseException; 14 | 15 | import java.io.IOException; 16 | import java.net.MalformedURLException; 17 | import java.net.URI; 18 | import java.net.URISyntaxException; 19 | import java.net.URL; 20 | import java.util.ArrayList; 21 | 22 | public class WebsiteModelRequest extends ScanRequestBase { 23 | private ArrayList websiteModels = new ArrayList<>(); 24 | 25 | public WebsiteModelRequest(String apiURL, Secret apiToken, 26 | ProxyBlock proxy) throws MalformedURLException, NullPointerException, URISyntaxException { 27 | super(apiURL, apiToken, proxy); 28 | pluginWebSiteModelsUri = new URL(ApiURL, "api/1.0/scans/PluginWebSiteModels").toURI(); 29 | } 30 | 31 | private final URI pluginWebSiteModelsUri; 32 | 33 | public ArrayList getWebsiteModels() { 34 | return websiteModels; 35 | } 36 | 37 | public ClassicHttpResponse getPluginWebSiteModels() throws IOException, ParseException { 38 | final HttpClient httpClient = getHttpClient(); 39 | final HttpGet httpGet = new HttpGet(pluginWebSiteModelsUri); 40 | httpGet.setHeader("Accept", json); 41 | httpGet.setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader()); 42 | 43 | ClassicHttpResponse response = (ClassicHttpResponse) httpClient.execute(httpGet); 44 | if (response.getCode() == 200) { 45 | parseWebsiteData(response); 46 | } 47 | return response; 48 | } 49 | 50 | private void parseWebsiteData(final ClassicHttpResponse response) throws ParseException, IOException { 51 | String data = AppCommon.parseResponseToString(response); 52 | 53 | JSONParser parser = new JSONParser(); 54 | Object jsonData = parser.parse(data); 55 | 56 | JSONArray WebsiteModelObjects = (JSONArray) jsonData; 57 | websiteModels = new ArrayList<>(); 58 | 59 | for (Object wmo : WebsiteModelObjects) { 60 | if (wmo instanceof JSONObject) { 61 | JSONObject websiteModelObject = (JSONObject) wmo; 62 | 63 | WebsiteModel websiteModel = new WebsiteModel(); 64 | websiteModel.setId((String) websiteModelObject.get("Id")); 65 | websiteModel.setName((String) websiteModelObject.get("Name")); 66 | websiteModel.setUrl((String) websiteModelObject.get("Url")); 67 | 68 | JSONArray WebsiteProfileModelObjects = 69 | (JSONArray) websiteModelObject.get("WebsiteProfiles"); 70 | ArrayList profiles = new ArrayList<>(); 71 | for (Object wmpo : WebsiteProfileModelObjects) { 72 | JSONObject websiteProfileModelObject = (JSONObject) wmpo; 73 | 74 | WebsiteProfileModel websiteProfileModel = new WebsiteProfileModel(); 75 | websiteProfileModel.setId((String) websiteProfileModelObject.get("Id")); 76 | websiteProfileModel.setName((String) websiteProfileModelObject.get("Name")); 77 | 78 | profiles.add(websiteProfileModel); 79 | } 80 | 81 | websiteModel.setProfiles(profiles); 82 | websiteModels.add(websiteModel); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanRequest.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import hudson.util.Secret; 4 | import org.apache.hc.core5.http.HttpHeaders; 5 | import org.apache.hc.core5.http.ClassicHttpResponse; 6 | import org.apache.hc.core5.http.NameValuePair; 7 | import org.apache.hc.client5.http.classic.HttpClient; 8 | import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; 9 | import org.apache.hc.client5.http.classic.methods.HttpPost; 10 | import org.apache.hc.core5.http.message.BasicNameValuePair; 11 | 12 | import java.io.IOException; 13 | import java.net.MalformedURLException; 14 | import java.net.URI; 15 | import java.net.URISyntaxException; 16 | import java.net.URL; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | public class ScanRequest extends ScanRequestBase { 21 | public ScanRequest(String apiURL, Secret apiToken, String scanType, String websiteId, String profileId, VCSCommit vcsCommit, 22 | ProxyBlock proxy) 23 | throws MalformedURLException, NullPointerException, URISyntaxException { 24 | super(apiURL, apiToken, proxy); 25 | this.scanType = ScanType.valueOf(scanType); 26 | this.websiteId = websiteId; 27 | this.profileId = profileId; 28 | this.vcsCommit = vcsCommit; 29 | scanUri = new URL(ApiURL, "api/1.0/scans/CreateFromPluginScanRequest").toURI(); 30 | testUri = new URL(ApiURL, "api/1.0/scans/VerifyPluginScanRequest").toURI(); 31 | } 32 | 33 | public final ScanType scanType; 34 | public final String websiteId; 35 | public final String profileId; 36 | public final VCSCommit vcsCommit; 37 | public final URI scanUri; 38 | public final URI testUri; 39 | 40 | public ClassicHttpResponse scanRequest() throws IOException { 41 | HttpClient client = getHttpClient(); 42 | final HttpPost httpPost = new HttpPost(scanUri); 43 | httpPost.setHeader("Accept", json); 44 | httpPost.setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader()); 45 | 46 | List params = new ArrayList<>(); 47 | setScanParams(params); 48 | vcsCommit.addVcsCommitInfo(params); 49 | httpPost.setEntity(new UrlEncodedFormEntity(params)); 50 | 51 | return (ClassicHttpResponse) client.execute(httpPost); 52 | } 53 | 54 | public ClassicHttpResponse testRequest() throws IOException { 55 | HttpClient client = getHttpClient(); 56 | final HttpPost httpPost = new HttpPost(testUri); 57 | httpPost.setHeader("Accept", json); 58 | httpPost.setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader()); 59 | 60 | List params = new ArrayList<>(); 61 | setScanParams(params); 62 | httpPost.setEntity(new UrlEncodedFormEntity(params)); 63 | 64 | return (ClassicHttpResponse) client.execute(httpPost); 65 | } 66 | 67 | private void setScanParams(List params) { 68 | switch (scanType) { 69 | case Incremental: 70 | params.add(new BasicNameValuePair("WebsiteId", websiteId)); 71 | params.add(new BasicNameValuePair("ProfileId", profileId)); 72 | params.add(new BasicNameValuePair("ScanType", "Incremental")); 73 | break; 74 | case FullWithPrimaryProfile: 75 | params.add(new BasicNameValuePair("WebsiteId", websiteId)); 76 | params.add(new BasicNameValuePair("ScanType", "FullWithPrimaryProfile")); 77 | break; 78 | case FullWithSelectedProfile: 79 | params.add(new BasicNameValuePair("WebsiteId", websiteId)); 80 | params.add(new BasicNameValuePair("ProfileId", profileId)); 81 | params.add(new BasicNameValuePair("ScanType", "FullWithSelectedProfile")); 82 | break; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanInfoRequestResult.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import com.google.gson.Gson; 4 | import com.acunetix.utility.AppCommon; 5 | import org.apache.hc.core5.http.ClassicHttpResponse; 6 | import org.json.simple.parser.ParseException; 7 | 8 | import java.io.IOException; 9 | import java.net.MalformedURLException; 10 | import java.net.URISyntaxException; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class ScanInfoRequestResult extends ScanRequestBase { 15 | public static ScanInfoRequestResult errorResult(final String errorMessage) { 16 | return new ScanInfoRequestResult(errorMessage); 17 | } 18 | 19 | private final int httpStatusCode; 20 | private String data; 21 | private String scanTaskID; 22 | private ScanTaskState scanTaskState; 23 | private HashMap FoundedSeverityAndCounts; 24 | private boolean isError; 25 | private String errorMessage; 26 | 27 | private ScanInfoRequestResult(final String errorMessage) { 28 | super(); 29 | this.errorMessage = errorMessage; 30 | httpStatusCode = 0; 31 | FoundedSeverityAndCounts = new HashMap(); 32 | isError = true; 33 | data = ""; 34 | } 35 | 36 | public ScanInfoRequestResult(final ClassicHttpResponse response) throws MalformedURLException, URISyntaxException { 37 | super(); 38 | httpStatusCode = response.getCode(); 39 | isError = httpStatusCode != 200; 40 | 41 | if (!isError) { 42 | try { 43 | data = AppCommon.parseResponseToString(response); 44 | isError = !(boolean) AppCommon.parseJsonValue(data, "IsValid"); 45 | if (!isError) { 46 | scanTaskID = (String) AppCommon.parseJsonValue(data, "ScanTaskId"); 47 | 48 | final String sTaskState = (String) AppCommon.parseJsonValue(data, "State"); 49 | scanTaskState = ScanTaskState.valueOf(sTaskState); 50 | 51 | org.json.simple.JSONObject foundedSeverityInfo = (org.json.simple.JSONObject) AppCommon 52 | .parseJsonValue(data, "FoundedSeverityAndCounts"); 53 | 54 | if (foundedSeverityInfo != null) { 55 | FoundedSeverityAndCounts = new Gson().fromJson(foundedSeverityInfo.toString(), HashMap.class); 56 | } 57 | 58 | if(FoundedSeverityAndCounts == null){ 59 | FoundedSeverityAndCounts = new HashMap(); 60 | } 61 | 62 | } else { 63 | errorMessage = (String) AppCommon.parseJsonValue(data, "ErrorMessage"); 64 | } 65 | } catch (final ParseException ex) { 66 | isError = true; 67 | errorMessage = "Scan info request result is not parsable::: " + ex.toString(); 68 | } catch (final IOException ex) { 69 | isError = true; 70 | errorMessage = "Scan info request result is not readable::: " + ex.toString(); 71 | } 72 | } 73 | } 74 | 75 | public String getScanTaskId() { 76 | return scanTaskID; 77 | } 78 | 79 | public ScanTaskState getScanTaskState() { 80 | return scanTaskState; 81 | } 82 | 83 | public HashMap getFoundedSeverityAndCounts() { 84 | return FoundedSeverityAndCounts; 85 | } 86 | 87 | public int getHttpStatusCode() { 88 | return httpStatusCode; 89 | } 90 | 91 | public String getErrorMessage() { 92 | return errorMessage; 93 | } 94 | 95 | public boolean isError() { 96 | return isError; 97 | } 98 | 99 | public boolean checkSeverity(final String ncSeverity) { 100 | if (isError()) { 101 | return false; 102 | } else if (ncSeverity == null) { 103 | return false; 104 | } else { 105 | for (Map.Entry entry : this.getFoundedSeverityAndCounts().entrySet()) { 106 | String foundedSeverityLevel = entry.getKey(); 107 | if (ncSeverity.contains(foundedSeverityLevel)) { 108 | return true; 109 | } 110 | } 111 | 112 | return false; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/VCSCommit.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import com.acunetix.utility.AppCommon; 4 | import hudson.model.Run; 5 | import hudson.model.User; 6 | import hudson.scm.ChangeLogSet; 7 | import hudson.util.VersionNumber; 8 | import jenkins.model.Jenkins; 9 | import org.apache.hc.core5.http.NameValuePair; 10 | import org.apache.hc.core5.http.message.BasicNameValuePair; 11 | 12 | import java.text.SimpleDateFormat; 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | public class VCSCommit { 17 | 18 | private final String ciBuildServerVersion; 19 | private final String ciNcPluginVersion; 20 | private final String buildId; 21 | private final String buildConfigurationName; 22 | private final String buildURL; 23 | private final boolean buildHasChange; 24 | private final String versionControlName; 25 | private final String committer; 26 | private final String vcsVersion; 27 | private final String ciTimestamp; 28 | private String rootURL = ""; 29 | 30 | public VCSCommit(Run build, ChangeLogSet changelog) { 31 | buildId = String.valueOf(build.number); 32 | buildConfigurationName = build.getParent().getName(); 33 | buildURL = getBuildURL(build); 34 | 35 | SimpleDateFormat iso8601DateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); 36 | 37 | buildHasChange = changelog != null && !changelog.isEmptySet(); 38 | if (buildHasChange) { 39 | versionControlName = changelog.getKind(); 40 | 41 | final ChangeLogSet.Entry change = (ChangeLogSet.Entry) changelog.getItems()[0]; 42 | long ciTimestampInMilliseconds = change.getTimestamp(); 43 | ciTimestamp = iso8601DateTimeFormat.format(new Date(ciTimestampInMilliseconds)); 44 | 45 | final User author = change.getAuthor(); 46 | vcsVersion = change.getCommitId(); 47 | 48 | String fullName = author.getFullName(); 49 | String displayName = author.getDisplayName(); 50 | if (AppCommon.isValidEmailAddress(fullName)) { 51 | committer = fullName; 52 | } else if (AppCommon.isValidEmailAddress(displayName)) { 53 | committer = displayName; 54 | } else { 55 | committer = fullName; 56 | } 57 | 58 | } else { 59 | versionControlName = ""; 60 | ciTimestamp = iso8601DateTimeFormat.format(new Date()); 61 | vcsVersion = ""; 62 | committer = ""; 63 | } 64 | 65 | VersionNumber versionNumber = Jenkins.getVersion(); 66 | ciBuildServerVersion = versionNumber != null ? versionNumber.toString() : "Not found."; 67 | ciNcPluginVersion = null; // don't add plugin version number 68 | } 69 | 70 | public static VCSCommit empty(Run build) { 71 | return new VCSCommit(build, null); 72 | } 73 | 74 | public void setRootURL(String rootURL) { 75 | if (rootURL == null) { 76 | this.rootURL = ""; 77 | return; 78 | } 79 | this.rootURL = rootURL; 80 | } 81 | 82 | public void addVcsCommitInfo(List params) { 83 | params.add(new BasicNameValuePair("VcsCommitInfoModel.CiBuildId", buildId)); 84 | params.add(new BasicNameValuePair("VcsCommitInfoModel.IntegrationSystem", "Jenkins")); 85 | params.add(new BasicNameValuePair("VcsCommitInfoModel.CiBuildServerVersion", 86 | ciBuildServerVersion)); 87 | params.add( 88 | new BasicNameValuePair("VcsCommitInfoModel.CiNcPluginVersion", ciNcPluginVersion)); 89 | params.add(new BasicNameValuePair("VcsCommitInfoModel.CiBuildConfigurationName", 90 | buildConfigurationName)); 91 | params.add(new BasicNameValuePair("VcsCommitInfoModel.CiBuildUrl", rootURL + buildURL)); 92 | params.add(new BasicNameValuePair("VcsCommitInfoModel.CiBuildHasChange", 93 | String.valueOf(buildHasChange))); 94 | params.add(new BasicNameValuePair("VcsCommitInfoModel.CiTimestamp", ciTimestamp)); 95 | params.add(new BasicNameValuePair("VcsCommitInfoModel.VcsName", versionControlName)); 96 | params.add(new BasicNameValuePair("VcsCommitInfoModel.VcsVersion", vcsVersion)); 97 | params.add(new BasicNameValuePair("VcsCommitInfoModel.Committer", committer)); 98 | } 99 | 100 | private String getBuildURL(Run build) { 101 | 102 | try { 103 | return build.getUrl(); 104 | } catch (Exception ex) { 105 | return ""; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanReport.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import com.acunetix.utility.AppCommon; 4 | import org.apache.hc.core5.http.ClassicHttpResponse; 5 | import org.json.simple.JSONObject; 6 | import org.json.simple.parser.JSONParser; 7 | import org.json.simple.parser.ParseException; 8 | 9 | import java.io.IOException; 10 | 11 | public class ScanReport { 12 | private final ClassicHttpResponse reportRequestResponse; 13 | private final boolean scanRequestHasError; 14 | private final String scanRequestErrorMessage; 15 | private final boolean reportRequestHasError; 16 | private final String reportRequestErrorMessage; 17 | private final String requestURI; 18 | private static String reportHtmlAsString = null; 19 | 20 | public ScanReport(ClassicHttpResponse reportRequestResponse, String requestURI) { 21 | this.reportRequestResponse = reportRequestResponse; 22 | this.scanRequestHasError = false; 23 | this.scanRequestErrorMessage = ""; 24 | this.reportRequestHasError = false; 25 | this.reportRequestErrorMessage = ""; 26 | this.requestURI = requestURI; 27 | } 28 | 29 | public ScanReport(boolean scanRequestHasError, String scanRequestErrorMessage, 30 | boolean reportRequestHasError, String reportRequestErrorMessage, String requestURI) { 31 | this.reportRequestResponse = null; 32 | this.scanRequestHasError = scanRequestHasError; 33 | this.scanRequestErrorMessage = scanRequestErrorMessage; 34 | this.reportRequestHasError = reportRequestHasError; 35 | this.reportRequestErrorMessage = reportRequestErrorMessage; 36 | this.requestURI = requestURI; 37 | } 38 | 39 | private String getContentType() { 40 | return reportRequestResponse.getHeaders("Content-Type")[0].getValue(); 41 | } 42 | 43 | public boolean isReportGenerated() { 44 | // when report stored, it will be loaded from disk for later requests. There is an exception 45 | // potential. 46 | try { 47 | return getContentType().equalsIgnoreCase("text/html"); 48 | } catch (Exception ex) { 49 | return false; 50 | } 51 | } 52 | 53 | public static void setReportHtmlAsStringField(String reportHtml) { 54 | ScanReport.reportHtmlAsString = reportHtml; 55 | } 56 | 57 | public void setReportHtmlAsString(String reportHtml) { 58 | setReportHtmlAsStringField(reportHtml); 59 | } 60 | 61 | public String getContent() { 62 | String content = ""; 63 | try { 64 | if (scanRequestHasError) { 65 | content = ExceptionContent(content, scanRequestErrorMessage); 66 | } else if (reportRequestHasError) { 67 | content = ExceptionContent(content, reportRequestErrorMessage); 68 | } else { 69 | 70 | String contentData = null; 71 | 72 | try { 73 | contentData = AppCommon.parseResponseToString(reportRequestResponse); 74 | 75 | setReportHtmlAsString(contentData); 76 | } catch (IOException ex) { 77 | contentData = reportHtmlAsString; 78 | } 79 | 80 | if (isReportGenerated()) { 81 | content = contentData; 82 | } else { 83 | JSONParser parser = new JSONParser(); 84 | JSONObject obj = (JSONObject) parser.parse(contentData); 85 | content = (String) obj.get("Message"); 86 | } 87 | } 88 | } catch (ParseException ex) { 89 | content = ExceptionContent("Report result is not parsable.", ex.toString()); 90 | } catch (Exception ex) { 91 | content = ExceptionContent(content, ex.toString()); 92 | } 93 | 94 | return content; 95 | } 96 | 97 | private String ExceptionContent(String content, String ExceptionMessage) { 98 | if (content != null && !content.isEmpty()) { 99 | content = "

" + content + "

"; 100 | } else { 101 | content = "

Something went wrong.

"; 102 | } 103 | if (requestURI != null) { 104 | content = content + "

Request URL: " + requestURI + "

"; 105 | } 106 | if (reportRequestResponse != null) { 107 | content = content + "

HttpStatusCode: " 108 | + reportRequestResponse.getCode() + "

"; 109 | } 110 | if (ExceptionMessage != null) { 111 | content = content + "

Exception Message:: " + ExceptionMessage + "

"; 112 | } 113 | 114 | return content; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/IgnoredVulnerabilityStateFilters.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import org.json.simple.JSONObject; 4 | import org.json.simple.parser.JSONParser; 5 | import org.json.simple.parser.ParseException; 6 | 7 | public class IgnoredVulnerabilityStateFilters{ 8 | 9 | private String filtersString; 10 | private JSONObject filtersJsonObject; 11 | private Boolean present; 12 | private Boolean fixedUnconfirmed; 13 | private Boolean fixedCantRetest; 14 | private Boolean fixedConfirmed; 15 | private Boolean revived; 16 | private Boolean scanning; 17 | private Boolean ignored; 18 | private Boolean acceptedRisk; 19 | private Boolean falsePositive; 20 | 21 | public IgnoredVulnerabilityStateFilters(){ 22 | assignAllToFalse(); 23 | } 24 | 25 | // GETTERS & SETTERS 26 | 27 | public String getFiltersString(){ 28 | return filtersString; 29 | } 30 | 31 | public void setFiltersString() { 32 | JSONObject object = new JSONObject(); 33 | object.put("present" , this.present); 34 | object.put("fixedUnconfirmed" , this.fixedUnconfirmed); 35 | object.put("fixedCantRetest" , this.fixedCantRetest); 36 | object.put("fixedConfirmed" , this.fixedConfirmed); 37 | object.put("revived" , this.revived); 38 | object.put("scanning" , this.scanning); 39 | object.put("ignored" , this.ignored); 40 | object.put("acceptedRisk" , this.acceptedRisk); 41 | object.put("falsePositive" , this.falsePositive); 42 | this.filtersJsonObject = object; 43 | this.filtersString = object.toJSONString(); 44 | } 45 | 46 | public JSONObject getFiltersJsonObject() { 47 | return filtersJsonObject; 48 | } 49 | 50 | public void setFiltersJsonObject() throws ParseException { 51 | JSONParser jsonParser = new JSONParser(); 52 | this.filtersJsonObject = (JSONObject) jsonParser.parse(getFiltersString()); 53 | } 54 | 55 | public Boolean getPresent() { 56 | return present; 57 | } 58 | 59 | public void setPresent(Boolean present) { 60 | this.present = present; 61 | } 62 | 63 | public Boolean getFixedUnconfirmed() { 64 | return fixedUnconfirmed; 65 | } 66 | 67 | public void setFixedUnconfirmed(Boolean fixedUnconfirmed) { 68 | this.fixedUnconfirmed = fixedUnconfirmed; 69 | } 70 | 71 | public Boolean getFixedCantRetest() { 72 | return fixedCantRetest; 73 | } 74 | 75 | public void setFixedCantRetest(Boolean fixedCantRetest) { 76 | this.fixedCantRetest = fixedCantRetest; 77 | } 78 | 79 | public Boolean getFixedConfirmed() { 80 | return fixedConfirmed; 81 | } 82 | 83 | public void setFixedConfirmed(Boolean fixedConfirmed) { 84 | this.fixedConfirmed = fixedConfirmed; 85 | } 86 | 87 | public Boolean getRevived() { 88 | return revived; 89 | } 90 | 91 | public void setRevived(Boolean revived) { 92 | this.revived = revived; 93 | } 94 | 95 | public Boolean getScanning() { 96 | return scanning; 97 | } 98 | 99 | public void setScanning(Boolean scanning) { 100 | this.scanning = scanning; 101 | } 102 | 103 | public Boolean getIgnored() { 104 | return ignored; 105 | } 106 | 107 | public void setIgnored(Boolean ignored) { 108 | this.ignored = ignored; 109 | } 110 | 111 | public Boolean getAcceptedRisk() { 112 | return acceptedRisk; 113 | } 114 | 115 | public void setAcceptedRisk(Boolean acceptedRisk) { 116 | this.acceptedRisk = acceptedRisk; 117 | } 118 | 119 | public Boolean getFalsePositive() { 120 | return falsePositive; 121 | } 122 | 123 | public void setFalsePositive(Boolean falsePositive) { 124 | this.falsePositive = falsePositive; 125 | } 126 | 127 | private void assignAllToFalse(){ 128 | this.present = false; 129 | this.fixedUnconfirmed = false; 130 | this.fixedCantRetest = false; 131 | this.fixedConfirmed = false; 132 | this.revived = false; 133 | this.scanning = false; 134 | this.ignored = false; 135 | this.acceptedRisk = false; 136 | this.falsePositive = false; 137 | } 138 | 139 | private void assignAllToTrue(){ 140 | this.present = true; 141 | this.fixedUnconfirmed = true; 142 | this.fixedCantRetest = true; 143 | this.fixedConfirmed = true; 144 | this.revived = true; 145 | this.scanning = true; 146 | this.ignored = true; 147 | this.acceptedRisk = true; 148 | this.falsePositive = true; 149 | } 150 | 151 | } 152 | 153 | 154 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/model/ScanRequestResult.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.model; 2 | 3 | import com.acunetix.utility.AppCommon; 4 | import hudson.util.Secret; 5 | import org.apache.hc.core5.http.HttpHeaders; 6 | import org.apache.hc.core5.http.ClassicHttpResponse; 7 | import org.apache.hc.client5.http.classic.HttpClient; 8 | import org.apache.hc.client5.http.classic.methods.HttpGet; 9 | import org.json.simple.parser.ParseException; 10 | 11 | import java.io.IOException; 12 | import java.net.MalformedURLException; 13 | import java.net.URI; 14 | import java.net.URISyntaxException; 15 | import java.net.URL; 16 | import java.util.Date; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public class ScanRequestResult extends ScanRequestBase { 21 | public static ScanRequestResult errorResult(String errorMessage) { 22 | return new ScanRequestResult(errorMessage); 23 | } 24 | 25 | private String scanReportEndpoint; 26 | 27 | private final int httpStatusCode; 28 | private String data; 29 | private String scanTaskID; 30 | private boolean isError; 31 | private String errorMessage; 32 | 33 | // Response from Acunetix 360 API 34 | private ScanReport report = null; 35 | private Date previousRequestTime; 36 | 37 | private ScanRequestResult(String errorMessage) { 38 | super(); 39 | this.errorMessage = errorMessage; 40 | httpStatusCode = 0; 41 | isError = true; 42 | data = ""; 43 | } 44 | 45 | public ScanRequestResult(ClassicHttpResponse response, String apiURL, Secret apiToken, String ncReportType, 46 | ProxyBlock proxy) 47 | throws MalformedURLException, URISyntaxException { 48 | super(apiURL, apiToken, proxy); 49 | httpStatusCode = response.getCode(); 50 | isError = httpStatusCode != 201; 51 | 52 | if (!isError) { 53 | try { 54 | data = AppCommon.parseResponseToString(response); 55 | isError = !(boolean) AppCommon.parseJsonValue(data, "IsValid"); 56 | if (!isError) { 57 | scanTaskID = (String) AppCommon.parseJsonValue(data, "ScanTaskId"); 58 | } else { 59 | errorMessage = (String) AppCommon.parseJsonValue(data, "ErrorMessage"); 60 | } 61 | } catch (ParseException ex) { 62 | isError = true; 63 | errorMessage = "Scan request result is not parsable::: " + ex.toString(); 64 | } catch (IOException ex) { 65 | isError = true; 66 | errorMessage = "Scan request result is not readable::: " + ex.toString(); 67 | } 68 | } 69 | 70 | String scanReportRelativeUrl = "api/1.0/scans/report/"; 71 | URI scanReportEndpointUri = new URL(ApiURL, scanReportRelativeUrl).toURI(); 72 | 73 | Map queryparams = new HashMap<>(); 74 | String reportType = ncReportType == null || ncReportType.equals("null") ? "ExecutiveSummary" : ncReportType; 75 | queryparams.put("Type", reportType); 76 | queryparams.put("Format", "Html"); 77 | queryparams.put("Id", scanTaskID); 78 | 79 | scanReportEndpoint = 80 | scanReportEndpointUri.toString() + "?" + AppCommon.mapToQueryString(queryparams); 81 | } 82 | 83 | public String getScanTaskId() { 84 | return scanTaskID; 85 | } 86 | 87 | public int getHttpStatusCode() { 88 | return httpStatusCode; 89 | } 90 | 91 | public String getErrorMessage() { 92 | return errorMessage; 93 | } 94 | 95 | public boolean isError() { 96 | return isError; 97 | } 98 | 99 | public boolean isReportGenerated() { 100 | // If scan request is failed we don't need additional check. 101 | if (isError()) { 102 | return false; 103 | } else if (isReportAvailable()) { 104 | return true; 105 | } else if (canAskForReportFromNCCloud()) {// If report is not requested or report wasn't 106 | // ready in previous request we must check again. 107 | try { 108 | final ScanReport report = getReport(); 109 | return report.isReportGenerated(); 110 | } catch (Exception ex) { 111 | return false; 112 | } 113 | } else { 114 | return false; 115 | } 116 | } 117 | 118 | public ScanReport getReport() { 119 | // if report is not generated and requested yet, request it from server. 120 | if (canAskForReportFromNCCloud()) { 121 | final ScanReport reportFromNcCloud = getReportFromNcCloud(); 122 | previousRequestTime = new Date(); 123 | 124 | this.report = reportFromNcCloud; 125 | 126 | return this.report; 127 | } 128 | 129 | return report; 130 | } 131 | 132 | private boolean canAskForReportFromNCCloud() { 133 | Date now = new Date(); 134 | // Is report not requested or have request threshold passed 135 | // And report isn't generated yet 136 | boolean isTimeThresholdPassed = previousRequestTime == null 137 | || now.getTime() - previousRequestTime.getTime() >= 60 * 1000;// 1 min 138 | 139 | return isTimeThresholdPassed || !isReportAvailable(); 140 | } 141 | 142 | private boolean isReportAvailable() { 143 | return report != null && report.isReportGenerated(); 144 | } 145 | 146 | private ScanReport getReportFromNcCloud() { 147 | ScanReport reportFromApi; 148 | 149 | if (!isError) { 150 | try { 151 | final HttpClient httpClient = getHttpClient(); 152 | final HttpGet httpGet = new HttpGet(scanReportEndpoint); 153 | httpGet.setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader()); 154 | 155 | ClassicHttpResponse response = (ClassicHttpResponse) httpClient.execute(httpGet); 156 | 157 | reportFromApi = new ScanReport(response, scanReportEndpoint); 158 | } catch (IOException ex) { 159 | String reportRequestErrorMessage = 160 | "Report result is not readable::: " + ex.toString(); 161 | reportFromApi = new ScanReport(false, "", true, reportRequestErrorMessage, 162 | scanReportEndpoint); 163 | } 164 | } else { 165 | reportFromApi = new ScanReport(true, errorMessage, false, "", scanReportEndpoint); 166 | } 167 | 168 | this.report = reportFromApi; 169 | 170 | return reportFromApi; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/utility/AppCommon.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.utility; 2 | 3 | import com.cloudbees.hudson.plugins.folder.Folder; 4 | import com.cloudbees.plugins.credentials.CredentialsMatcher; 5 | import com.cloudbees.plugins.credentials.CredentialsMatchers; 6 | import com.cloudbees.plugins.credentials.CredentialsProvider; 7 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 8 | import com.cloudbees.plugins.credentials.domains.DomainRequirement; 9 | import hudson.model.Item; 10 | import hudson.model.ItemGroup; 11 | import hudson.security.ACL; 12 | import jenkins.model.Jenkins; 13 | import org.apache.commons.validator.routines.UrlValidator; 14 | import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; 15 | import org.apache.hc.core5.http.ClassicHttpResponse; 16 | import org.json.simple.JSONArray; 17 | import org.json.simple.JSONObject; 18 | import org.json.simple.parser.JSONParser; 19 | import org.json.simple.parser.ParseException; 20 | 21 | import jakarta.mail.internet.AddressException; 22 | import jakarta.mail.internet.InternetAddress; 23 | import java.io.*; 24 | import java.net.MalformedURLException; 25 | import java.net.URL; 26 | import java.net.URLDecoder; 27 | import java.net.URLEncoder; 28 | import java.nio.charset.StandardCharsets; 29 | import java.util.*; 30 | 31 | 32 | public class AppCommon { 33 | public static List getNames(Class> e) { 34 | String[] enumNames = 35 | Arrays.toString(e.getEnumConstants()).replaceAll("^.|.$", "").split(", "); 36 | return Arrays.asList(enumNames); 37 | } 38 | 39 | public static boolean isUrlValid(String url) { 40 | String[] schemes = {"http", "https"}; // DEFAULT schemes = "http", "https", "ftp" 41 | UrlValidator urlValidator = new UrlValidator(schemes, UrlValidator.ALLOW_LOCAL_URLS); 42 | 43 | if (urlValidator.isValid(url)) { 44 | return true; 45 | } else { 46 | return false; 47 | } 48 | } 49 | 50 | public static boolean isValidEmailAddress(String email) { 51 | boolean result = true; 52 | try { 53 | InternetAddress emailAddr = new InternetAddress(email); 54 | emailAddr.validate(); 55 | } catch (AddressException ex) { 56 | result = false; 57 | } 58 | return result; 59 | } 60 | 61 | public static boolean isGUIDValid(String guid) { 62 | try { 63 | if (guid == null) { 64 | return false; 65 | } 66 | UUID.fromString( 67 | // fixes the guid if it doesn't contain hypens 68 | guid.replaceFirst( 69 | "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", 70 | "$1-$2-$3-$4-$5")); 71 | return true; 72 | } catch (IllegalArgumentException exception) { 73 | return false; 74 | } 75 | } 76 | 77 | public static URL getBaseURL(String url) throws MalformedURLException { 78 | return new URL(new URL(url), "/"); 79 | } 80 | 81 | public static String mapToQueryString(Map map) { 82 | StringBuilder stringBuilder = new StringBuilder(); 83 | String key; 84 | String value; 85 | for (Map.Entry entry : map.entrySet()) { 86 | if (stringBuilder.length() > 0) { 87 | stringBuilder.append("&"); 88 | } 89 | try { 90 | key = entry.getKey(); 91 | value = entry.getValue(); 92 | stringBuilder.append((key != null ? URLEncoder.encode(key, "UTF-8") : "")); 93 | stringBuilder.append("="); 94 | stringBuilder.append(value != null ? URLEncoder.encode(value, "UTF-8") : ""); 95 | } catch (UnsupportedEncodingException e) { 96 | throw new RuntimeException("This method requires UTF-8 encoding support", e); 97 | } 98 | } 99 | 100 | return stringBuilder.toString(); 101 | } 102 | 103 | public static Map queryStringToMap(String input) { 104 | Map map = new HashMap<>(); 105 | 106 | String[] nameValuePairs = input.split("&"); 107 | for (String nameValuePair : nameValuePairs) { 108 | String[] nameValue = nameValuePair.split("="); 109 | try { 110 | map.put(URLDecoder.decode(nameValue[0], "UTF-8"), 111 | nameValue.length > 1 ? URLDecoder.decode(nameValue[1], "UTF-8") : ""); 112 | } catch (UnsupportedEncodingException e) { 113 | throw new RuntimeException("This method requires UTF-8 encoding support", e); 114 | } 115 | } 116 | 117 | return map; 118 | } 119 | 120 | public static Object parseJsonValue(String Data, String key) throws ParseException { 121 | JSONParser parser = new JSONParser(); 122 | Object parsedData = parser.parse(Data); 123 | Object value; 124 | if (parsedData instanceof JSONArray) { 125 | JSONArray array = (JSONArray) parsedData; 126 | JSONObject object = (JSONObject) array.get(0); 127 | value = object.get(key); 128 | } else { 129 | JSONObject obj = (JSONObject) parsedData; 130 | value = obj.get(key); 131 | } 132 | return value; 133 | } 134 | 135 | public static String parseResponseToString(ClassicHttpResponse response) throws IOException { 136 | final BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler(); 137 | 138 | return handler.handleResponse(response); 139 | } 140 | 141 | public static StandardUsernamePasswordCredentials findCredentialsById( 142 | final String credentialsId, final String descriptorUrlOrJobUrl) 143 | throws UnsupportedEncodingException { 144 | final Jenkins jenkins = Jenkins.get(); 145 | 146 | /* 147 | * This case is without folder plugin "job/Project Name" 148 | */ 149 | ItemGroup credentialsContext = (ItemGroup) jenkins; 150 | 151 | List folders = jenkins.getItems(Folder.class); 152 | 153 | /* 154 | * if folders are used then find project's context first 155 | */ 156 | if (folders != null && folders.size() > 0) { 157 | 158 | List folderNames = getFolderNames(descriptorUrlOrJobUrl); 159 | 160 | folderNames.removeIf(item -> item == null || "".equals(item)); 161 | 162 | // last one is project name so remove it 163 | folderNames.remove(folderNames.size() - 1); 164 | 165 | Folder deepestFolder = getDeepestFolder(folders, folderNames); 166 | 167 | if (folders.size() > 0 && deepestFolder != null 168 | && CredentialsProvider.hasStores(deepestFolder)) { 169 | credentialsContext = (ItemGroup) deepestFolder; 170 | } 171 | } 172 | 173 | List matches = 174 | CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, 175 | credentialsContext, ACL.SYSTEM, (DomainRequirement) null); 176 | 177 | final CredentialsMatcher matcher = CredentialsMatchers.withId(credentialsId); 178 | final StandardUsernamePasswordCredentials result = 179 | CredentialsMatchers.firstOrNull(matches, matcher); 180 | 181 | return result; 182 | } 183 | 184 | private static List getFolderNames(final String descriptorUrlOrJobUrl) 185 | throws UnsupportedEncodingException { 186 | List folderNames; 187 | String projectFullUrl = descriptorUrlOrJobUrl; 188 | 189 | // if its descriptorUrl 190 | if (descriptorUrlOrJobUrl.contains("/job/")) { 191 | /* 192 | * Format is like: 193 | * 194 | * "job/Folder Name/job/Project Name" or 195 | * "job/Folder Name/job/FolderInsideFolder/job/Project Name" 196 | */ 197 | projectFullUrl = 198 | URLDecoder.decode(descriptorUrlOrJobUrl, StandardCharsets.UTF_8.toString()); 199 | 200 | // substring url to make it starts with "job" 201 | if (!projectFullUrl.startsWith("/job")) { 202 | projectFullUrl = projectFullUrl.substring(projectFullUrl.indexOf("/job")); 203 | } 204 | 205 | projectFullUrl = projectFullUrl.replace("/job/", "/"); 206 | 207 | } 208 | 209 | // then it's a job url similar to this 210 | // "Folder 1/Folder 1 - Folder 1/Folder 1 - Folder 1 - Folder 1/Child Project" 211 | 212 | folderNames = new java.util.ArrayList<>(java.util.Arrays.asList(projectFullUrl.split("/"))); 213 | 214 | return folderNames; 215 | } 216 | 217 | private static Folder getDeepestFolder(List folders, List folderNames) { 218 | if (folders.size() == 0 || folderNames.size() == 0) { 219 | return null; 220 | } 221 | Folder deepestFolder = folders.get(0); 222 | Folder tempFolder; 223 | 224 | Optional f = 225 | folders.stream().filter(w -> w.getName().equals(folderNames.get(0))).findFirst(); 226 | if (f.isPresent()) { 227 | tempFolder = f.get(); 228 | if (CredentialsProvider.hasStores(tempFolder)) { 229 | deepestFolder = tempFolder; 230 | } 231 | 232 | for (int i = 1; i < folderNames.size(); i++) { 233 | if(tempFolder != null) { 234 | tempFolder = (Folder) tempFolder.getItem(folderNames.get(i)); 235 | if (tempFolder != null && CredentialsProvider.hasStores(tempFolder)) { 236 | deepestFolder = tempFolder; 237 | } 238 | } 239 | } 240 | } 241 | 242 | return deepestFolder; 243 | } 244 | 245 | public static List findCredentials(Item own) { 246 | 247 | final List matches = 248 | CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, 249 | own, ACL.SYSTEM, (DomainRequirement) null); 250 | return matches; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.jenkins-ci.plugins 6 | plugin 7 | 5.26 8 | 9 | 10 | org.jenkins-ci.plugins 11 | acunetix-360-scan 12 | 2.1.20-SNAPSHOT 13 | hpi 14 | 15 | 16 | 2.516.3 17 | 21 | 22 | Acunetix 360 Scan Plugin 23 | Allows users to start security scans via Acunetix 360. 24 | 25 | 26 | 27 | 28 | 29 | org.apache.maven.plugins 30 | maven-compiler-plugin 31 | 3.13.0 32 | 33 | 34 | 35 | 36 | 37 | org.jenkins-ci.tools 38 | maven-hpi-plugin 39 | true 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 45 | 17 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | io.jenkins.tools.bom 55 | bom-2.516.x 56 | 5353.v3dec76e40169 57 | import 58 | pom 59 | 60 | 61 | 62 | 63 | 64 | io.jenkins.plugins 65 | jakarta-mail-api 66 | 67 | 68 | io.jenkins.plugins 69 | jakarta-activation-api 70 | 71 | 72 | org.jenkins-ci 73 | symbol-annotation 74 | 1.23 75 | 76 | 77 | org.kohsuke.stapler 78 | stapler-adjunct-jquery 79 | 1.12.4-0 80 | 81 | 82 | org.kohsuke.stapler 83 | stapler 84 | 85 | 86 | 87 | 88 | org.kohsuke.stapler 89 | json-lib 90 | 2.4-jenkins-8 91 | 92 | 93 | commons-logging 94 | commons-logging 95 | 96 | 97 | commons-lang 98 | commons-lang 99 | 100 | 101 | 102 | 103 | org.jenkins-ci.plugins 104 | credentials 105 | 106 | 107 | org.jenkins-ci.plugins 108 | cloudbees-folder 109 | 110 | 111 | io.jenkins.plugins 112 | apache-httpcomponents-client-5-api 113 | 5.2.1-1.0 114 | 115 | 116 | commons-codec 117 | commons-codec 118 | 1.15 119 | 120 | 121 | commons-beanutils 122 | commons-beanutils 123 | 1.11.0 124 | 125 | 126 | commons-logging 127 | commons-logging 128 | 129 | 130 | 131 | 132 | org.apache.commons 133 | commons-collections4 134 | 4.4 135 | 136 | 137 | commons-validator 138 | commons-validator 139 | 1.7 140 | 141 | 142 | commons-logging 143 | commons-logging 144 | 145 | 146 | 147 | 148 | com.googlecode.json-simple 149 | json-simple 150 | 1.1.1 151 | 152 | 153 | org.jenkins-ci.plugins 154 | structs 155 | 156 | 157 | org.jenkins-ci.plugins.workflow 158 | workflow-step-api 159 | 160 | 161 | org.jenkins-ci.plugins.workflow 162 | workflow-cps 163 | 164 | 165 | org.jenkins-ci.plugins.workflow 166 | workflow-job 167 | 168 | 169 | org.jenkins-ci.plugins.workflow 170 | workflow-api 171 | 172 | 173 | org.jenkins-ci.plugins.workflow 174 | workflow-basic-steps 175 | 176 | 177 | org.jenkins-ci.plugins.workflow 178 | workflow-support 179 | 180 | 181 | org.jenkins-ci.plugins 182 | command-launcher 183 | 184 | 185 | org.ogce 186 | xpp3 187 | 1.1.6 188 | 189 | 190 | org.jenkins-ci.plugins 191 | script-security 192 | 193 | 194 | com.google.code.gson 195 | gson 196 | 2.9.1 197 | 198 | 199 | org.apache.commons 200 | commons-digester3 201 | 3.2 202 | 203 | 204 | commons-logging 205 | commons-logging 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | central 214 | https://repo.maven.apache.org/maven2/ 215 | 216 | 217 | repo.jenkins-ci.org 218 | https://repo.jenkins-ci.org/public/ 219 | 220 | 221 | 222 | 223 | central 224 | https://repo.maven.apache.org/maven2/ 225 | 226 | 227 | repo.jenkins-ci.org 228 | https://repo.jenkins-ci.org/public/ 229 | 230 | 231 | 232 | scm:git:https://github.com/jenkinsci/${project.artifactId}-plugin.git 233 | scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git 234 | https://github.com/jenkinsci/acunetix-360-scan-plugin 235 | acunetix-360-scan-2.1.19 236 | 237 | 238 | 239 | MIT License 240 | https://raw.githubusercontent.com/jenkinsci/acunetix-360-scan-plugin/master/LICENSE 241 | 242 | 243 | 244 | https://github.com/jenkinsci/acunetix-360-scan-plugin 245 | 246 | 247 | 248 | acunetix360 249 | Acunetix360 250 | support.integration@acunetix.com 251 | 252 | 253 | -------------------------------------------------------------------------------- /src/main/resources/com/acunetix/plugin/ACXScanBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /src/main/webapp/scripts/arrive.js: -------------------------------------------------------------------------------- 1 | /*globals jQuery,Window,HTMLElement,HTMLDocument,HTMLCollection,NodeList,MutationObserver */ 2 | /*exported Arrive*/ 3 | /*jshint latedef:false */ 4 | 5 | /* 6 | * arrive.js 7 | * v2.5.2 8 | * https://github.com/uzairfarooq/arrive 9 | * MIT licensed 10 | * 11 | * Copyright (c) 2014-2024 Uzair Farooq 12 | */ 13 | var Arrive = (function(window, $, undefined) { 14 | 15 | "use strict"; 16 | 17 | if(!window.MutationObserver || typeof HTMLElement === 'undefined'){ 18 | return; //for unsupported browsers 19 | } 20 | 21 | var arriveUniqueId = 0; 22 | 23 | var utils = (function() { 24 | var matches = HTMLElement.prototype.matches || HTMLElement.prototype.webkitMatchesSelector || HTMLElement.prototype.mozMatchesSelector 25 | || HTMLElement.prototype.msMatchesSelector; 26 | 27 | return { 28 | matchesSelector: function(elem, selector) { 29 | return elem instanceof HTMLElement && matches.call(elem, selector); 30 | }, 31 | // to enable function overloading - By John Resig (MIT Licensed) 32 | addMethod: function (object, name, fn) { 33 | var old = object[ name ]; 34 | object[ name ] = function(){ 35 | if ( fn.length == arguments.length ) { 36 | return fn.apply( this, arguments ); 37 | } 38 | else if ( typeof old == 'function' ) { 39 | return old.apply( this, arguments ); 40 | } 41 | }; 42 | }, 43 | callCallbacks: function(callbacksToBeCalled, registrationData, mutationEvents) { 44 | if (!callbacksToBeCalled.length) return; 45 | 46 | if (registrationData && registrationData.options.onceOnly) { 47 | // as onlyOnce param is true, make sure we fire the event for only one item 48 | callbacksToBeCalled = [callbacksToBeCalled[0]]; 49 | 50 | // unbind event after first callback as onceOnly is true. 51 | registrationData.me.unbindEventWithSelectorAndCallback.call( 52 | registrationData.target, registrationData.selector, registrationData.callback); 53 | } 54 | 55 | for (var i = 0, cb; (cb = callbacksToBeCalled[i]); i++) { 56 | if (cb && cb.callback) { 57 | cb.callback.call(cb.elem, cb.elem); 58 | } 59 | } 60 | 61 | if (registrationData && registrationData.callback && mutationEvents) { 62 | mutationEvents.addTimeoutHandler(registrationData.target, registrationData.selector, registrationData.callback, registrationData.options, registrationData.data); 63 | } 64 | }, 65 | // traverse through all descendants of a node to check if event should be fired for any descendant 66 | checkChildNodesRecursively: function(nodes, registrationData, matchFunc, callbacksToBeCalled) { 67 | // check each new node if it matches the selector 68 | for (var i=0, node; (node = nodes[i]); i++) { 69 | if (matchFunc(node, registrationData, callbacksToBeCalled)) { 70 | callbacksToBeCalled.push({ callback: registrationData.callback, elem: node }); 71 | } 72 | 73 | if (node.childNodes.length > 0) { 74 | utils.checkChildNodesRecursively(node.childNodes, registrationData, matchFunc, callbacksToBeCalled); 75 | } 76 | } 77 | }, 78 | mergeArrays: function(firstArr, secondArr){ 79 | // Overwrites default options with user-defined options. 80 | var options = {}, 81 | attrName; 82 | for (attrName in firstArr) { 83 | if (firstArr.hasOwnProperty(attrName)) { 84 | options[attrName] = firstArr[attrName]; 85 | } 86 | } 87 | for (attrName in secondArr) { 88 | if (secondArr.hasOwnProperty(attrName)) { 89 | options[attrName] = secondArr[attrName]; 90 | } 91 | } 92 | return options; 93 | }, 94 | toElementsArray: function (elements) { 95 | // check if object is an array (or array like object) 96 | // Note: window object has .length property but it's not array of elements so don't consider it an array 97 | if (typeof elements !== "undefined" && (typeof elements.length !== "number" || elements === window)) { 98 | elements = [elements]; 99 | } 100 | return elements; 101 | } 102 | }; 103 | })(); 104 | 105 | 106 | // Class to maintain state of all registered events of a single type 107 | var EventsBucket = (function() { 108 | var EventsBucket = function() { 109 | // holds all the events 110 | 111 | this._eventsBucket = []; 112 | // function to be called while adding an event, the function should do the event initialization/registration 113 | this._beforeAdding = null; 114 | // function to be called while removing an event, the function should do the event destruction 115 | this._beforeRemoving = null; 116 | }; 117 | 118 | EventsBucket.prototype.addEvent = function(target, selector, options, callback, data) { 119 | var newEvent = { 120 | target: target, 121 | selector: selector, 122 | options: options, 123 | callback: callback, 124 | data: data, 125 | firedElems: [] 126 | }; 127 | 128 | if (this._beforeAdding) { 129 | this._beforeAdding(newEvent); 130 | } 131 | 132 | this._eventsBucket.push(newEvent); 133 | return newEvent; 134 | }; 135 | 136 | EventsBucket.prototype.removeEvent = function(compareFunction) { 137 | for (var i=this._eventsBucket.length - 1, registeredEvent; (registeredEvent = this._eventsBucket[i]); i--) { 138 | if (compareFunction(registeredEvent)) { 139 | if (this._beforeRemoving) { 140 | this._beforeRemoving(registeredEvent); 141 | } 142 | 143 | if (registeredEvent.data && registeredEvent.data.timeoutId) { 144 | clearTimeout(registeredEvent.data.timeoutId); 145 | } 146 | 147 | // mark callback as null so that even if an event mutation was already triggered it does not call callback 148 | var removedEvents = this._eventsBucket.splice(i, 1); 149 | if (removedEvents && removedEvents.length) { 150 | removedEvents[0].callback = null; 151 | } 152 | } 153 | } 154 | }; 155 | 156 | EventsBucket.prototype.beforeAdding = function(beforeAdding) { 157 | this._beforeAdding = beforeAdding; 158 | }; 159 | 160 | EventsBucket.prototype.beforeRemoving = function(beforeRemoving) { 161 | this._beforeRemoving = beforeRemoving; 162 | }; 163 | 164 | return EventsBucket; 165 | })(); 166 | 167 | 168 | /** 169 | * @constructor 170 | * General class for binding/unbinding arrive and leave events 171 | */ 172 | var MutationEvents = function(getObserverConfig, onMutation) { 173 | var eventsBucket = new EventsBucket(), 174 | me = this; 175 | 176 | var defaultOptions = { 177 | fireOnAttributesModification: false 178 | }; 179 | 180 | // actual event registration before adding it to bucket 181 | eventsBucket.beforeAdding(function(registrationData) { 182 | var 183 | target = registrationData.target, 184 | observer; 185 | 186 | // mutation observer does not work on window or document 187 | if (target === window.document || target === window) { 188 | target = document.getElementsByTagName("html")[0]; 189 | } 190 | 191 | // Create an observer instance 192 | observer = new MutationObserver(function(e) { 193 | onMutation.call(this, e, registrationData); 194 | }); 195 | 196 | var config = getObserverConfig(registrationData.options); 197 | 198 | observer.observe(target, config); 199 | 200 | registrationData.observer = observer; 201 | registrationData.me = me; 202 | }); 203 | 204 | // cleanup/unregister before removing an event 205 | eventsBucket.beforeRemoving(function (eventData) { 206 | eventData.observer.disconnect(); 207 | }); 208 | 209 | this.bindEvent = function(selector, options, callback) { 210 | options = utils.mergeArrays(defaultOptions, options); 211 | 212 | var elements = utils.toElementsArray(this); 213 | 214 | for (var i = 0; i < elements.length; i++) { 215 | const data = {}; 216 | 217 | // Add timeout handling 218 | me.addTimeoutHandler(elements[i], selector, callback, options, data); 219 | 220 | eventsBucket.addEvent(elements[i], selector, options, callback, data); 221 | } 222 | }; 223 | 224 | this.unbindEvent = function() { 225 | var elements = utils.toElementsArray(this); 226 | eventsBucket.removeEvent(function(eventObj) { 227 | for (var i = 0; i < elements.length; i++) { 228 | if (this === undefined || eventObj.target === elements[i]) { 229 | return true; 230 | } 231 | } 232 | return false; 233 | }); 234 | }; 235 | 236 | this.unbindEventWithSelectorOrCallback = function(selector) { 237 | var elements = utils.toElementsArray(this), 238 | callback = selector, 239 | compareFunction; 240 | 241 | if (typeof selector === "function") { 242 | compareFunction = function(eventObj) { 243 | for (var i = 0; i < elements.length; i++) { 244 | if ((this === undefined || eventObj.target === elements[i]) && eventObj.callback === callback) { 245 | return true; 246 | } 247 | } 248 | return false; 249 | }; 250 | } 251 | else { 252 | compareFunction = function(eventObj) { 253 | for (var i = 0; i < elements.length; i++) { 254 | if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector) { 255 | return true; 256 | } 257 | } 258 | return false; 259 | }; 260 | } 261 | eventsBucket.removeEvent(compareFunction); 262 | }; 263 | 264 | this.unbindEventWithSelectorAndCallback = function(selector, callback) { 265 | var elements = utils.toElementsArray(this); 266 | eventsBucket.removeEvent(function(eventObj) { 267 | for (var i = 0; i < elements.length; i++) { 268 | if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector && eventObj.callback === callback) { 269 | return true; 270 | } 271 | } 272 | return false; 273 | }); 274 | }; 275 | 276 | this.addTimeoutHandler = function(target, selector, callback, options, data) { 277 | if (!options.timeout || options.timeout <= 0) { 278 | return; 279 | } 280 | 281 | if (data.timeoutId) { 282 | clearTimeout(data.timeoutId); 283 | } 284 | 285 | data.timeoutId = setTimeout(() => { 286 | me.unbindEventWithSelectorAndCallback.call(target, selector, callback); 287 | callback.call(null, null); 288 | }, options.timeout); 289 | } 290 | 291 | return this; 292 | }; 293 | 294 | 295 | /** 296 | * @constructor 297 | * Processes 'arrive' events 298 | */ 299 | var ArriveEvents = function() { 300 | // Default options for 'arrive' event 301 | var arriveDefaultOptions = { 302 | fireOnAttributesModification: false, 303 | onceOnly: false, 304 | existing: false, 305 | timeout: 0 // default 0 (no timeout) 306 | }; 307 | 308 | function getArriveObserverConfig(options) { 309 | var config = { 310 | attributes: false, 311 | childList: true, 312 | subtree: true 313 | }; 314 | 315 | if (options.fireOnAttributesModification) { 316 | config.attributes = true; 317 | } 318 | 319 | return config; 320 | } 321 | 322 | function onArriveMutation(mutations, registrationData) { 323 | mutations.forEach(function( mutation ) { 324 | var newNodes = mutation.addedNodes, 325 | targetNode = mutation.target, 326 | callbacksToBeCalled = [], 327 | node; 328 | 329 | // If new nodes are added 330 | if( newNodes !== null && newNodes.length > 0 ) { 331 | utils.checkChildNodesRecursively(newNodes, registrationData, nodeMatchFunc, callbacksToBeCalled); 332 | } 333 | else if (mutation.type === "attributes") { 334 | if (nodeMatchFunc(targetNode, registrationData, callbacksToBeCalled)) { 335 | callbacksToBeCalled.push({ callback: registrationData.callback, elem: targetNode }); 336 | } 337 | } 338 | 339 | utils.callCallbacks(callbacksToBeCalled, registrationData, arriveEvents); 340 | }); 341 | } 342 | 343 | function nodeMatchFunc(node, registrationData, callbacksToBeCalled) { 344 | // check a single node to see if it matches the selector 345 | if (utils.matchesSelector(node, registrationData.selector)) { 346 | if(node._id === undefined) { 347 | node._id = arriveUniqueId++; 348 | } 349 | // make sure the arrive event is not already fired for the element 350 | if (registrationData.firedElems.indexOf(node._id) == -1) { 351 | registrationData.firedElems.push(node._id); 352 | 353 | return true; 354 | } 355 | } 356 | 357 | return false; 358 | } 359 | 360 | arriveEvents = new MutationEvents(getArriveObserverConfig, onArriveMutation); 361 | 362 | var mutationBindEvent = arriveEvents.bindEvent; 363 | 364 | // override bindEvent function 365 | arriveEvents.bindEvent = function(selector, arg2, arg3) { 366 | 367 | var options = (typeof arg2 === 'object') ? utils.mergeArrays(arriveDefaultOptions, arg2) : { ...arriveDefaultOptions }; 368 | var callback = (typeof arg3 === 'function') ? arg3 : (typeof arg2 === 'function') ? arg2 : undefined; 369 | var elements = utils.toElementsArray(this); 370 | 371 | // For promise and async support, we can only do onceOnly=true 372 | if (!callback) 373 | options.onceOnly = true; 374 | 375 | if (options.existing) { 376 | var existing = []; 377 | 378 | for (var i = 0; i < elements.length; i++) { 379 | var nodes = elements[i].querySelectorAll(selector); 380 | for (var j = 0; j < nodes.length; j++) { 381 | existing.push({ callback: callback, elem: nodes[j] }); 382 | } 383 | } 384 | 385 | // no need to bind event if the callback has to be fired only once and we have already found the element 386 | if (options.onceOnly && existing.length) { 387 | if (callback) { 388 | return callback.call(existing[0].elem, existing[0].elem); 389 | } else { 390 | return Promise.resolve(existing[0].elem); 391 | } 392 | } 393 | 394 | setTimeout(utils.callCallbacks, 1, existing); 395 | } 396 | 397 | if (callback) { 398 | mutationBindEvent.call(this, selector, options, callback); 399 | } else { 400 | var a = this; 401 | return new Promise(resolve => mutationBindEvent.call(a, selector, options, resolve)); 402 | } 403 | }; 404 | 405 | return arriveEvents; 406 | }; 407 | 408 | 409 | /** 410 | * @constructor 411 | * Processes 'leave' events 412 | */ 413 | var LeaveEvents = function() { 414 | // Default options for 'leave' event 415 | var leaveDefaultOptions = { 416 | onceOnly: false, 417 | timeout: 0, // default 0 (no timeout) 418 | }; 419 | 420 | function getLeaveObserverConfig() { 421 | var config = { 422 | childList: true, 423 | subtree: true 424 | }; 425 | 426 | return config; 427 | } 428 | 429 | function onLeaveMutation(mutations, registrationData) { 430 | mutations.forEach(function( mutation ) { 431 | var removedNodes = mutation.removedNodes, 432 | callbacksToBeCalled = []; 433 | 434 | if( removedNodes !== null && removedNodes.length > 0 ) { 435 | utils.checkChildNodesRecursively(removedNodes, registrationData, nodeMatchFunc, callbacksToBeCalled); 436 | } 437 | 438 | utils.callCallbacks(callbacksToBeCalled, registrationData, leaveEvents); 439 | }); 440 | } 441 | 442 | function nodeMatchFunc(node, registrationData) { 443 | return utils.matchesSelector(node, registrationData.selector); 444 | } 445 | 446 | leaveEvents = new MutationEvents(getLeaveObserverConfig, onLeaveMutation); 447 | 448 | var mutationBindEvent = leaveEvents.bindEvent; 449 | 450 | // override bindEvent function 451 | leaveEvents.bindEvent = function(selector, arg2, arg3) { 452 | 453 | var options = (typeof arg2 === 'object') ? utils.mergeArrays(leaveDefaultOptions, arg2) : { ...leaveDefaultOptions }; 454 | var callback = (typeof arg3 === 'function') ? arg3 : (typeof arg2 === 'function') ? arg2 : undefined; 455 | 456 | if (callback) { 457 | mutationBindEvent.call(this, selector, options, callback); 458 | } else { 459 | // For promise and async support, we can only do onceOnly=true 460 | options.onceOnly = true; 461 | 462 | var a = this; 463 | return new Promise(resolve => mutationBindEvent.call(a, selector, options, resolve)); 464 | } 465 | }; 466 | 467 | return leaveEvents; 468 | }; 469 | 470 | 471 | 472 | var arriveEvents = new ArriveEvents(), 473 | leaveEvents = new LeaveEvents(); 474 | 475 | function exposeUnbindApi(eventObj, exposeTo, funcName) { 476 | // expose unbind function with function overriding 477 | utils.addMethod(exposeTo, funcName, eventObj.unbindEvent); 478 | utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorOrCallback); 479 | utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorAndCallback); 480 | } 481 | 482 | /*** expose APIs ***/ 483 | function exposeApi(exposeTo) { 484 | exposeTo.arrive = arriveEvents.bindEvent; 485 | exposeUnbindApi(arriveEvents, exposeTo, "unbindArrive"); 486 | 487 | exposeTo.leave = leaveEvents.bindEvent; 488 | exposeUnbindApi(leaveEvents, exposeTo, "unbindLeave"); 489 | } 490 | 491 | if ($) { 492 | exposeApi($.fn); 493 | } 494 | exposeApi(HTMLElement.prototype); 495 | exposeApi(NodeList.prototype); 496 | exposeApi(HTMLCollection.prototype); 497 | exposeApi(HTMLDocument.prototype); 498 | exposeApi(Window.prototype); 499 | 500 | var Arrive = {}; 501 | // expose functions to unbind all arrive/leave events 502 | exposeUnbindApi(arriveEvents, Arrive, "unbindAllArrive"); 503 | exposeUnbindApi(leaveEvents, Arrive, "unbindAllLeave"); 504 | 505 | return Arrive; 506 | 507 | })(window, typeof jQuery === 'undefined' ? null : jQuery, undefined); 508 | -------------------------------------------------------------------------------- /acunetix-360-scan.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /src/main/java/com/acunetix/plugin/ACXScanBuilder.java: -------------------------------------------------------------------------------- 1 | package com.acunetix.plugin; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | import java.io.StringWriter; 6 | import java.net.MalformedURLException; 7 | import java.net.URISyntaxException; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | 11 | import com.cloudbees.plugins.credentials.CredentialsProvider; 12 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel; 13 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 14 | import com.acunetix.model.IgnoredVulnerabilityStateFilters; 15 | import com.acunetix.model.ReportType; 16 | import com.acunetix.model.ScanCancelRequest; 17 | import com.acunetix.model.ScanCancelRequestResult; 18 | import com.acunetix.model.ScanInfoRequest; 19 | import com.acunetix.model.ScanInfoRequestResult; 20 | import com.acunetix.model.ScanRequest; 21 | import com.acunetix.model.ScanRequestResult; 22 | import com.acunetix.model.ScanTaskState; 23 | import com.acunetix.model.ScanType; 24 | import com.acunetix.model.VCSCommit; 25 | import com.acunetix.model.WebsiteModel; 26 | import com.acunetix.model.WebsiteModelRequest; 27 | import com.acunetix.model.WebsiteProfileModel; 28 | import com.acunetix.model.ProxyBlock; 29 | import com.acunetix.utility.AppCommon; 30 | 31 | import org.apache.commons.lang.StringUtils; 32 | import org.apache.hc.core5.http.ClassicHttpResponse; 33 | import org.jenkinsci.Symbol; 34 | import org.json.simple.parser.ParseException; 35 | import org.kohsuke.stapler.AncestorInPath; 36 | import org.kohsuke.stapler.DataBoundConstructor; 37 | import org.kohsuke.stapler.DataBoundSetter; 38 | import org.kohsuke.stapler.QueryParameter; 39 | import org.kohsuke.stapler.StaplerRequest; 40 | import org.kohsuke.stapler.bind.JavaScriptMethod; 41 | import org.kohsuke.stapler.verb.POST; 42 | import hudson.Extension; 43 | import hudson.FilePath; 44 | import hudson.Launcher; 45 | import hudson.model.AbstractProject; 46 | import hudson.model.Item; 47 | import hudson.model.ParameterValue; 48 | import hudson.model.ParametersAction; 49 | import hudson.model.Run; 50 | import hudson.model.TaskListener; 51 | import hudson.tasks.BuildStepDescriptor; 52 | import hudson.tasks.Builder; 53 | import hudson.util.FormValidation; 54 | import hudson.util.ListBoxModel; 55 | import hudson.util.Secret; 56 | import hudson.util.ListBoxModel.Option; 57 | import jenkins.model.Jenkins; 58 | import jenkins.tasks.SimpleBuildStep; 59 | import net.sf.json.JSONObject; 60 | 61 | public class ACXScanBuilder extends Builder implements SimpleBuildStep { 62 | 63 | private String ncScanType; 64 | private String ncWebsiteId; 65 | private String ncProfileId; 66 | private Secret ncApiToken; 67 | private String acxServerURL; 68 | private String credentialsId; 69 | private String ncSeverity; 70 | private Boolean ncStopScan; 71 | private Boolean ncConfirmed; 72 | private Boolean ncDoNotFail; 73 | private Boolean ncIgnoreFalsePositive; 74 | private Boolean ncIgnoreRiskAccepted; 75 | private IgnoredVulnerabilityStateFilters ncFilters = new IgnoredVulnerabilityStateFilters(); 76 | private String ncReportType; 77 | private String ncScanTaskId; 78 | private Boolean ncAbortScan; 79 | private Boolean ncCancelEventFired; 80 | private Boolean useProxy; 81 | private String pHost; 82 | private String pPort; 83 | private String pUser; 84 | private String pPassword; 85 | 86 | private static final String apiTokenBuildParameterName = "APITOKEN"; 87 | 88 | // Fields in config.jelly must match the parameter names in the 89 | // "DataBoundConstructor" 90 | // this ctor called when project's settings save method called 91 | @DataBoundConstructor 92 | public ACXScanBuilder(String ncScanType, String ncWebsiteId, String ncProfileId, Boolean ncDoNotFail, String ncReportType) { 93 | this.ncScanType = ncScanType == null ? "" : ncScanType; 94 | this.ncWebsiteId = ncWebsiteId == null ? "" : ncWebsiteId; 95 | this.ncProfileId = ncProfileId == null ? "" : ncProfileId; 96 | this.ncDoNotFail = ncDoNotFail; 97 | this.ncReportType = ncReportType == null || ncReportType.equals("null") ? "ExecutiveSummary" : ncReportType; 98 | } 99 | 100 | public String getNcSeverity() { 101 | return ncSeverity; 102 | } 103 | 104 | public Boolean getNcStopScan() { 105 | return ncStopScan; 106 | } 107 | 108 | public Boolean getNcConfirmed(){ 109 | return ncConfirmed; 110 | } 111 | 112 | public Boolean getNcDoNotFail(){ 113 | return ncDoNotFail; 114 | } 115 | 116 | public String getNcScanType() { 117 | return ncScanType; 118 | } 119 | 120 | public String getCredentialsId() { 121 | return credentialsId; 122 | } 123 | 124 | public IgnoredVulnerabilityStateFilters getFilters(){ 125 | return ncFilters; 126 | } 127 | 128 | public void setFilters(){ 129 | this.ncFilters = new IgnoredVulnerabilityStateFilters(); 130 | this.ncFilters.setFalsePositive(this.ncIgnoreFalsePositive); 131 | this.ncFilters.setAcceptedRisk(this.ncIgnoreRiskAccepted); 132 | } 133 | 134 | @DataBoundSetter 135 | public void setCredentialsId(String credentialsId) { 136 | this.credentialsId = credentialsId; 137 | } 138 | 139 | public void setNcScanType(String ncScanType) { 140 | this.ncScanType = ncScanType; 141 | } 142 | 143 | public String getNcWebsiteId() { 144 | return ncWebsiteId; 145 | } 146 | 147 | public void setNcWebsiteId(String ncTargetURL) { 148 | this.ncWebsiteId = ncTargetURL; 149 | } 150 | 151 | public String getNcProfileId() { 152 | return ncProfileId; 153 | } 154 | 155 | public void setNcProfileId(String ncProfileId) { 156 | this.ncProfileId = ncProfileId; 157 | } 158 | 159 | public String getAcxServerURL() { 160 | return acxServerURL; 161 | } 162 | 163 | @DataBoundSetter 164 | public void setNcSeverity(String ncSeverity) { 165 | this.ncSeverity = ncSeverity; 166 | } 167 | 168 | @DataBoundSetter 169 | public void setNcStopScan(Boolean ncStopScan) { 170 | this.ncStopScan = ncStopScan; 171 | } 172 | 173 | @DataBoundSetter 174 | public void setNcConfirmed(Boolean ncConfirmed){ 175 | this.ncConfirmed = ncConfirmed; 176 | } 177 | 178 | @DataBoundSetter 179 | public void setNcDoNotFail(Boolean ncDoNotFail){ 180 | this.ncDoNotFail = ncDoNotFail; 181 | } 182 | 183 | @DataBoundSetter 184 | public void setAcxServerURL(String acxServerURL) { 185 | this.acxServerURL = acxServerURL; 186 | } 187 | 188 | public Secret getNcApiToken() { 189 | if (ncApiToken == null) { 190 | ncApiToken = getDescriptor().getNcApiToken(); 191 | } 192 | return ncApiToken; 193 | } 194 | 195 | @DataBoundSetter 196 | public void setNcApiToken(Object ncApiToken) { 197 | if (ncApiToken.getClass() == String.class) { 198 | this.ncApiToken = Secret.fromString((String) ncApiToken); 199 | } 200 | if (ncApiToken.getClass() == Secret.class) { 201 | this.ncApiToken = (Secret) ncApiToken; 202 | } 203 | } 204 | 205 | public Boolean getNcIgnoreFalsePositive() { 206 | return ncIgnoreFalsePositive; 207 | } 208 | 209 | @DataBoundSetter 210 | public void setNcIgnoreFalsePositive(Boolean ncIgnoreFalsePositive) { 211 | this.ncIgnoreFalsePositive = ncIgnoreFalsePositive; 212 | } 213 | 214 | public Boolean getNcIgnoreRiskAccepted() { 215 | return ncIgnoreRiskAccepted; 216 | } 217 | 218 | @DataBoundSetter 219 | public void setNcIgnoreRiskAccepted(Boolean ncIgnoreRiskAccepted) { 220 | this.ncIgnoreRiskAccepted = ncIgnoreRiskAccepted; 221 | } 222 | 223 | public String getNcReportType(){ 224 | return ncReportType; 225 | } 226 | 227 | @DataBoundSetter 228 | public void setNcReportType(String ncReportType) { 229 | this.ncReportType = ncReportType; 230 | } 231 | 232 | public void setScanTaskId(String ncScanTaskId) { 233 | this.ncScanTaskId = ncScanTaskId; 234 | } 235 | 236 | public String getScanTaskId() { 237 | return ncScanTaskId; 238 | } 239 | 240 | public void setCancelState(Boolean ncCancelEventFired) { 241 | this.ncCancelEventFired = ncCancelEventFired; 242 | } 243 | 244 | public Boolean getCancelState() { 245 | return ncCancelEventFired; 246 | } 247 | 248 | @DataBoundSetter 249 | public void setNcAbortScan(Boolean ncAbortScan) { 250 | this.ncAbortScan = ncAbortScan; 251 | } 252 | 253 | public Boolean getNcAbortScan() { 254 | return ncAbortScan; 255 | } 256 | 257 | @DataBoundSetter 258 | public void setUseProxy(Boolean useProxy) { 259 | this.useProxy = useProxy; 260 | } 261 | 262 | public Boolean getUseProxy() { 263 | return useProxy; 264 | } 265 | 266 | @DataBoundSetter 267 | public void setpHost(String pHost) { 268 | this.pHost = pHost; 269 | } 270 | 271 | public String getpHost() { 272 | return pHost; 273 | } 274 | 275 | @DataBoundSetter 276 | public void setpPort(String pPort) { 277 | this.pPort = pPort; 278 | } 279 | 280 | public String getpPort() { 281 | return pPort; 282 | } 283 | 284 | @DataBoundSetter 285 | public void setpUser(String pUser) { 286 | this.pUser = pUser; 287 | } 288 | 289 | public String getpUser() { 290 | return pUser; 291 | } 292 | 293 | @DataBoundSetter 294 | public void setpPassword(String pPassword) { 295 | this.pPassword = pPassword; 296 | } 297 | 298 | public String getpPassword() { 299 | return pPassword; 300 | } 301 | 302 | @Override 303 | public void perform(Run build, FilePath workspace, Launcher launcher, 304 | TaskListener listener) throws InterruptedException, IOException { 305 | logInfo("Scan step created...", listener); 306 | 307 | ACXScanSCMAction scmAction = build.getAction(ACXScanSCMAction.class); 308 | VCSCommit commit = scmAction == null ? VCSCommit.empty(build) : scmAction.getVcsCommit(); 309 | 310 | try { 311 | ScanRequestHandler(build, commit, listener); 312 | } 313 | catch (RuntimeException e) { 314 | logInfo(e.getMessage(), listener); 315 | throw e; // Rethrow RuntimeException to handle it outside if necessary 316 | } 317 | catch(hudson.AbortException e) 318 | { 319 | try { 320 | 321 | DescriptorImpl descriptor = getDescriptor(); 322 | 323 | String acxServerURL = StringUtils.isBlank(getAcxServerURL()) ? descriptor.getAcxServerURL() 324 | : getAcxServerURL(); 325 | 326 | if (!StringUtils.isEmpty(credentialsId)) { 327 | 328 | // build.getEnvironment().get("job_name") 329 | // "Folder 1/Folder 1 - Folder 1/Folder 1 - Folder 1 - Folder 1/Child Project" 330 | final StandardUsernamePasswordCredentials credential = AppCommon.findCredentialsById( 331 | credentialsId, build.getEnvironment(listener).get("job_name")); 332 | 333 | if (credential != null) { 334 | acxServerURL = credential.getUsername(); 335 | ncApiToken = credential.getPassword(); 336 | } 337 | } 338 | 339 | // if token is not set, try to get from global variable or selected credential 340 | // from settings 341 | if (ncApiToken == null || ncApiToken.getPlainText().isEmpty()) { 342 | ncApiToken = 343 | getNcApiToken() != null && StringUtils.isBlank(getNcApiToken().getPlainText()) 344 | ? descriptor.getNcApiToken() 345 | : getNcApiToken(); 346 | } 347 | 348 | if (Secret.toString(ncApiToken) == ("$" + apiTokenBuildParameterName)) { 349 | ncApiToken = GetApiTokenFromBuildParameters(build); 350 | } 351 | 352 | ProxyBlock proxy = null; 353 | String pHost = null; 354 | String pPort = null; 355 | String pUser = null; 356 | String pPassword = null; 357 | 358 | Boolean useProxy = getUseProxy() == null ? descriptor.getUseProxy() 359 | : getUseProxy(); 360 | 361 | if (useProxy != null && useProxy) { 362 | pHost = StringUtils.isBlank(getpHost()) ? descriptor.getpHost() 363 | : getpHost(); 364 | 365 | pPort = StringUtils.isBlank(getpPort()) ? descriptor.getpPort() 366 | : getpPort(); 367 | 368 | pUser = StringUtils.isBlank(getpUser()) ? descriptor.getpUser() 369 | : getpUser(); 370 | 371 | pPassword = StringUtils.isBlank(getpPassword()) ? descriptor.getpPassword() 372 | : getpPassword(); 373 | 374 | proxy = new ProxyBlock(useProxy, pHost, pPort, pUser, pPassword); 375 | } 376 | 377 | Boolean cancelScanWhenUserAbortsOperation = getNcAbortScan(); 378 | 379 | if (cancelScanWhenUserAbortsOperation && !getCancelState()) { 380 | CancelScan(acxServerURL, ncApiToken, proxy, getScanTaskId() , listener); 381 | } 382 | } 383 | catch (RuntimeException ex) { 384 | logInfo(ex.getMessage(), listener); 385 | throw ex; // Rethrow RuntimeException to handle it outside if necessary 386 | } 387 | catch(Exception ex) 388 | { 389 | logInfo(ex.getMessage(), listener); 390 | } 391 | 392 | logInfo(e.getMessage(), listener); 393 | } 394 | catch (Exception e) { 395 | try { 396 | build.replaceAction(new ACXScanResultAction( 397 | ScanRequestResult.errorResult("Scan Request Failed:: " + e.getMessage()))); 398 | } catch (Exception ex) { 399 | build.addAction(new ACXScanResultAction( 400 | ScanRequestResult.errorResult("Scan Request Failed:: " + e.getMessage()))); 401 | } 402 | 403 | build.setResult(hudson.model.Result.FAILURE); 404 | } 405 | } 406 | 407 | private Secret GetApiTokenFromBuildParameters(Run build) { 408 | Secret secret = null; 409 | 410 | ParametersAction parametersAction = build.getAction(ParametersAction.class); 411 | if (parametersAction != null) { 412 | 413 | ParameterValue parameter = parametersAction.getAllParameters().stream() 414 | .filter(p -> p.getName().contains(apiTokenBuildParameterName)).findAny() 415 | .orElse(null); 416 | 417 | if (parameter != null && parameter.getValue() != null) { 418 | Object value = parameter.getValue(); 419 | if (value != null && value.getClass() != null) { 420 | if (value.getClass() == Secret.class) { 421 | secret = (Secret) value; 422 | } else if (value.getClass() == String.class) { 423 | secret = Secret.fromString((String) value); 424 | } 425 | } 426 | } 427 | } 428 | 429 | return secret; 430 | } 431 | 432 | private void ScanRequestHandler(Run build, VCSCommit commit, TaskListener listener) 433 | throws Exception { 434 | 435 | DescriptorImpl descriptor = getDescriptor(); 436 | String acxServerURL = StringUtils.isBlank(getAcxServerURL()) ? descriptor.getAcxServerURL() 437 | : getAcxServerURL(); 438 | 439 | Secret ncApiToken = null; 440 | 441 | ProxyBlock proxy = null; 442 | String pHost = null; 443 | String pPort = null; 444 | String pUser = null; 445 | String pPassword = null; 446 | 447 | Boolean useProxy = getUseProxy() == null ? descriptor.getUseProxy() 448 | : getUseProxy(); 449 | 450 | if (useProxy != null && useProxy) { 451 | pHost = StringUtils.isBlank(getpHost()) ? descriptor.getpHost() 452 | : getpHost(); 453 | 454 | pPort = StringUtils.isBlank(getpPort()) ? descriptor.getpPort() 455 | : getpPort(); 456 | 457 | pUser = StringUtils.isBlank(getpUser()) ? descriptor.getpUser() 458 | : getpUser(); 459 | 460 | pPassword = StringUtils.isBlank(getpPassword()) ? descriptor.getpPassword() 461 | : getpPassword(); 462 | 463 | proxy = new ProxyBlock(useProxy, pHost, pPort, pUser, pPassword); 464 | } 465 | 466 | // jenkin's server url 467 | String rootUrl = null; 468 | 469 | if (!StringUtils.isEmpty(credentialsId)) { 470 | 471 | // build.getEnvironment().get("job_name") 472 | // "Folder 1/Folder 1 - Folder 1/Folder 1 - Folder 1 - Folder 1/Child Project" 473 | final StandardUsernamePasswordCredentials credential = AppCommon.findCredentialsById( 474 | credentialsId, build.getEnvironment(listener).get("job_name")); 475 | 476 | if (credential != null) { 477 | acxServerURL = credential.getUsername(); 478 | ncApiToken = credential.getPassword(); 479 | } 480 | } 481 | 482 | // if token is not set, try to get from global variable or selected credential 483 | // from settings 484 | if (ncApiToken == null || ncApiToken.getPlainText().isEmpty()) { 485 | ncApiToken = 486 | getNcApiToken() != null && StringUtils.isBlank(getNcApiToken().getPlainText()) 487 | ? descriptor.getNcApiToken() 488 | : getNcApiToken(); 489 | } 490 | 491 | if (Secret.toString(ncApiToken) == ("$" + apiTokenBuildParameterName)) { 492 | ncApiToken = GetApiTokenFromBuildParameters(build); 493 | } 494 | 495 | // StringUtils.isEmpty checks null or empty 496 | if (StringUtils.isEmpty(rootUrl)) { 497 | rootUrl = descriptor.getRootURL(); 498 | } 499 | 500 | commit.setRootURL(rootUrl); 501 | 502 | ScanRequest scanRequest = new ScanRequest(acxServerURL, ncApiToken, ncScanType, ncWebsiteId, 503 | ncProfileId, commit, proxy); 504 | 505 | logInfo("Requesting scan...", listener); 506 | ClassicHttpResponse scanRequestResponse = scanRequest.scanRequest(); 507 | logInfo("Response status code: " + scanRequestResponse.getCode(), 508 | listener); 509 | 510 | ScanRequestResult scanRequestResult = 511 | new ScanRequestResult(scanRequestResponse, acxServerURL, ncApiToken, ncReportType, 512 | proxy); 513 | build.replaceAction(new ACXScanResultAction(scanRequestResult)); 514 | 515 | setFilters(); 516 | 517 | setScanTaskId(scanRequestResult.getScanTaskId()); 518 | 519 | // HTTP status code 201 refers to created. This means our request added to 520 | // queue. Otherwise it is failed. 521 | if (scanRequestResult.getHttpStatusCode() == 201 && !scanRequestResult.isError()) { 522 | ScanRequestSuccessHandler(acxServerURL, ncApiToken, proxy, 523 | scanRequestResult, scanRequestResult.getScanTaskId(), ncDoNotFail, ncConfirmed, 524 | ncFilters, listener); 525 | } else { 526 | ScanRequestFailureHandler(scanRequestResult, listener); 527 | } 528 | } 529 | 530 | private void ScanRequestSuccessHandler(String acxServerURL, Secret ncApiToken, ProxyBlock proxy, 531 | ScanRequestResult scanRequestResult, String scanTaskId, Boolean doNotFail, Boolean isConfirmed, 532 | IgnoredVulnerabilityStateFilters filters, TaskListener listener) 533 | throws IOException, URISyntaxException, InterruptedException { 534 | logInfo("Scan requested successfully.", listener); 535 | ScanTaskState scanStatus = ScanTaskState.Queued; 536 | Boolean scanAbortedExternally = false; 537 | Boolean scanInfoConnectionError = false; 538 | Boolean isScanStarted = false; 539 | Boolean isSeverityBreaked = false; 540 | 541 | try { 542 | while (!scanStatus.equals(ScanTaskState.Complete)) { 543 | ScanInfoRequest scanInfoRequest = 544 | new ScanInfoRequest(acxServerURL, ncApiToken, scanTaskId, ncDoNotFail, ncConfirmed, ncFilters, proxy); 545 | 546 | logInfo("Requesting scan info...", listener); 547 | ClassicHttpResponse scanInfoRequestResponse = scanInfoRequest.scanInfoRequest(); 548 | logInfo("Response scan info status code: " 549 | + scanInfoRequestResponse.getCode(), listener); 550 | 551 | ScanInfoRequestResult scanInfoRequestResult = new ScanInfoRequestResult(scanInfoRequestResponse); 552 | 553 | if (scanInfoRequestResult.isError()) { 554 | scanInfoConnectionError = true; 555 | logInfo("Get scan info error", listener); 556 | logError(scanInfoRequestResult.getErrorMessage(), listener); 557 | throw new hudson.AbortException("Error when getting scan info!"); 558 | } 559 | 560 | scanStatus = scanInfoRequestResult.getScanTaskState(); 561 | 562 | if (scanInfoRequestResult.checkSeverity(ncSeverity)) { 563 | isSeverityBreaked = true; 564 | String severityText = SeverityOptionsForBuildFailMesssage().get(ncSeverity); 565 | String failMessage = "Build failed because scan contains " + severityText + " severity!"; 566 | logInfo(failMessage, listener); 567 | throw new hudson.AbortException(failMessage); 568 | } 569 | 570 | if (scanStatus.equals(ScanTaskState.Scanning) && !isScanStarted) { 571 | isScanStarted = true; 572 | logInfo("Scan started...", listener); 573 | } else if (scanStatus.equals(ScanTaskState.Failed) 574 | || scanStatus.equals(ScanTaskState.Cancelled) 575 | || scanStatus.equals(ScanTaskState.Paused) 576 | || scanStatus.equals(ScanTaskState.Pausing)) { 577 | scanAbortedExternally = true; 578 | logInfo("Scan aborted because state is " + scanStatus.toString(), listener); 579 | throw new hudson.AbortException("The scan was aborted outside of this instance"); 580 | } 581 | 582 | Thread.sleep(10000); 583 | } 584 | logInfo("Scan completed...", listener); 585 | } catch (hudson.AbortException e) { 586 | Boolean isCancel = (scanInfoConnectionError || ((ncStopScan != null && ncStopScan) 587 | && (isSeverityBreaked || !scanAbortedExternally))); 588 | if (isCancel){ 589 | CancelScan(acxServerURL, ncApiToken, proxy, scanTaskId, listener); 590 | 591 | setCancelState(true); 592 | } 593 | else{ 594 | setCancelState(false); 595 | } 596 | throw new hudson.AbortException("The build was aborted"); 597 | } catch (Exception e) { 598 | StringWriter errors = new StringWriter(); 599 | e.printStackTrace(new PrintWriter(errors)); 600 | logInfo(errors.toString(), listener); 601 | setCancelState(false); 602 | throw new hudson.AbortException("The build was aborted"); 603 | } 604 | } 605 | 606 | private HashMap SeverityOptionsForBuildFailMesssage() { 607 | HashMap options = new HashMap(); 608 | options.put("DoNotFail", "Do not fail the build"); 609 | options.put("Critical", "Critical"); 610 | options.put("Critical,High", "High or above"); 611 | options.put("Critical,High,Medium", "Medium or above"); 612 | options.put("Critical,High,Medium,Low", "Low or above"); 613 | options.put("Critical,High,Medium,Low,Best Practice", "Best Practices or above"); 614 | return options; 615 | } 616 | 617 | private void CancelScan(String acxServerURL, Secret ncApiToken, ProxyBlock proxy, 618 | String scanTaskId, TaskListener listener) 619 | throws IOException, MalformedURLException, NullPointerException, URISyntaxException { 620 | 621 | ScanCancelRequest scanCancelRequest = new ScanCancelRequest(acxServerURL, ncApiToken, scanTaskId, proxy); 622 | 623 | logInfo("Requesting scan cancel...", listener); 624 | ClassicHttpResponse scanCancelRequestResponse = scanCancelRequest.scanCancelRequest(); 625 | logInfo("Response scan cancel status code: " 626 | + scanCancelRequestResponse.getCode(), listener); 627 | 628 | ScanCancelRequestResult scanCancelRequestResult = new ScanCancelRequestResult(scanCancelRequestResponse); 629 | 630 | if (scanCancelRequestResult.isError()) { 631 | logInfo("Scan cancel error", listener); 632 | logError(scanCancelRequestResult.getErrorMessage(), listener); 633 | throw new hudson.AbortException("Error when scan cancel!"); 634 | } else { 635 | logInfo("Scan canceled Id:" + scanTaskId, listener); 636 | } 637 | } 638 | 639 | private void ScanRequestFailureHandler( 640 | ScanRequestResult scanRequestResult, TaskListener listener) throws Exception { 641 | logError("Scan request failed. Error Message: " + scanRequestResult.getErrorMessage(), listener); 642 | 643 | throw new Exception("Acunetix 360 Plugin: Failed to start the scan. Response status code: " 644 | + scanRequestResult.getHttpStatusCode()); 645 | } 646 | 647 | private void logInfo(String message, TaskListener listener) { 648 | listener.getLogger().println("> Acunetix 360 Plugin: " + message); 649 | } 650 | 651 | private void logError(String message, TaskListener listener) { 652 | listener.error("> Acunetix 360 Plugin: " + message); 653 | } 654 | 655 | @Override 656 | public DescriptorImpl getDescriptor() { 657 | return (DescriptorImpl) super.getDescriptor(); 658 | } 659 | 660 | @Symbol("ACXScanBuilder") 661 | @Extension 662 | public static final class DescriptorImpl extends BuildStepDescriptor { 663 | private long lastEditorId = 0; 664 | private ArrayList websiteModels = new ArrayList<>(); 665 | 666 | private String acxServerURL; 667 | private Secret ncApiToken; 668 | private String rootURL; 669 | private Boolean useProxy; 670 | private String pHost; 671 | private String pPort; 672 | private String pUser; 673 | private String pPassword; 674 | 675 | public DescriptorImpl() { 676 | super(ACXScanBuilder.class); 677 | load(); 678 | 679 | java.util.logging.Logger.getLogger(DescriptorImpl.class.getName()).info("Netsparker Descriptor loaded"); 680 | } 681 | 682 | public String getAcxServerURL() { 683 | return acxServerURL; 684 | } 685 | 686 | public void setAcxServerURL(String acxServerURL) { 687 | this.acxServerURL = acxServerURL; 688 | } 689 | 690 | 691 | public Secret getNcApiToken() { 692 | return ncApiToken; 693 | } 694 | 695 | public void setNcApiToken(String ncApiToken) { 696 | this.ncApiToken = Secret.fromString(ncApiToken); 697 | } 698 | 699 | public String getRootURL() { 700 | return rootURL; 701 | } 702 | 703 | public Boolean getUseProxy() { 704 | return useProxy; 705 | } 706 | 707 | public void setUseProxy(Boolean useProxy) { 708 | this.useProxy = useProxy; 709 | } 710 | 711 | public String getpHost() { 712 | return pHost; 713 | } 714 | 715 | public void setpHost(String pHost) { 716 | this.pHost = pHost; 717 | } 718 | 719 | public String getpPort() { 720 | return pPort; 721 | } 722 | 723 | public void setpPort(String pPort) { 724 | this.pPort = pPort; 725 | } 726 | 727 | public String getpUser() { 728 | return pUser; 729 | } 730 | 731 | public void setpUser(String pUser) { 732 | this.pUser = pUser; 733 | } 734 | 735 | public String getpPassword() { 736 | return pPassword; 737 | } 738 | 739 | public void setpPassword(String pPassword) { 740 | this.pPassword = pPassword; 741 | } 742 | 743 | @Override 744 | public boolean isApplicable(Class aClass) { 745 | // Indicates that this builder can be used with all kinds of project types 746 | java.util.logging.Logger.getLogger(DescriptorImpl.class.getName()).info("NCScanBuilder descriptor isApplicable called"); 747 | return true; 748 | } 749 | 750 | @Override 751 | public String getDisplayName() { 752 | java.util.logging.Logger.getLogger(DescriptorImpl.class.getName()).info("NCScanBuilder getDisplayName called"); 753 | return Messages.ACXScanBuilder_DescriptorImpl_DisplayName(); 754 | } 755 | 756 | @Override 757 | // Invoked when the global configuration page is submitted. 758 | public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { 759 | req.bindParameters(this); 760 | this.acxServerURL = formData.getString("acxServerURL"); 761 | this.ncApiToken = Secret.fromString(formData.getString("ncApiToken")); 762 | this.useProxy = formData.getBoolean("useProxy"); 763 | this.pHost = formData.getString("pHost"); 764 | this.pPort = formData.getString("pPort"); 765 | this.pUser = formData.getString("pUser"); 766 | this.pPassword = formData.getString("pPassword"); 767 | this.rootURL = Jenkins.get().getRootUrl(); 768 | 769 | // To persist global configuration information, set properties and call save(). 770 | save(); 771 | return super.configure(req, formData); 772 | } 773 | 774 | @Override 775 | public String getConfigPage() { 776 | ProxyBlock proxy = null; 777 | if (useProxy != null && useProxy) { 778 | proxy = new ProxyBlock(useProxy, pHost, pPort, pUser, pPassword); 779 | } 780 | 781 | try { 782 | updateWebsiteModels(acxServerURL, ncApiToken, proxy); 783 | } catch (Exception e) { 784 | } 785 | 786 | return super.getConfigPage(); 787 | } 788 | 789 | @SuppressWarnings("unused") 790 | public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item owner, 791 | @QueryParameter String credentialsId) { 792 | StandardListBoxModel listBoxModel = new StandardListBoxModel(); 793 | listBoxModel.includeEmptyValue(); 794 | 795 | if (owner == null) { 796 | if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { 797 | return listBoxModel.includeCurrentValue(credentialsId); 798 | } 799 | } else { 800 | if (!owner.hasPermission(Item.EXTENDED_READ) 801 | && !owner.hasPermission(CredentialsProvider.USE_ITEM)) { 802 | return listBoxModel.includeCurrentValue(credentialsId); 803 | } 804 | } 805 | 806 | for (StandardUsernamePasswordCredentials credentialToAdd : AppCommon 807 | .findCredentials(owner)) { 808 | listBoxModel.with(credentialToAdd); 809 | } 810 | 811 | listBoxModel.includeCurrentValue(credentialsId); 812 | 813 | return listBoxModel; 814 | } 815 | 816 | @JavaScriptMethod 817 | @SuppressWarnings("unused") 818 | public synchronized String createEditorId() { 819 | return String.valueOf(lastEditorId++); 820 | } 821 | 822 | /* 823 | * methods named "doFill{fieldname}Items" need to be implemented for to fill select boxes in 824 | * UI 825 | */ 826 | @SuppressWarnings("unused") 827 | public ListBoxModel doFillNcScanTypeItems() { 828 | ListBoxModel model = new ListBoxModel(); 829 | model.add("Please Select Scan Type", ""); 830 | model.add("Incremental", "Incremental"); 831 | model.add("Full (With primary profile)", "FullWithPrimaryProfile"); 832 | model.add("Full (With selected profile)", "FullWithSelectedProfile"); 833 | 834 | return model; 835 | } 836 | 837 | @SuppressWarnings("unused") 838 | public ListBoxModel doFillNcWebsiteIdItems(@QueryParameter String credentialsId) { 839 | 840 | 841 | if (!StringUtils.isEmpty(credentialsId)) { 842 | doTestConnection(credentialsId); 843 | } 844 | 845 | ListBoxModel model = new ListBoxModel(); 846 | model.add("Please select website",""); 847 | for (WebsiteModel websiteModel : websiteModels) { 848 | model.add(websiteModel.getDisplayName(), websiteModel.getId()); 849 | } 850 | 851 | return model; 852 | } 853 | 854 | @SuppressWarnings("unused") 855 | public ListBoxModel doFillNcProfileIdItems(@QueryParameter String ncWebsiteId) { 856 | WebsiteModel websiteModel = new WebsiteModel(); 857 | 858 | for (WebsiteModel wm : websiteModels) { 859 | if (ncWebsiteId != null && wm.getId().equals(ncWebsiteId)) { 860 | websiteModel = wm; 861 | break; 862 | } 863 | } 864 | 865 | String placeholderText; 866 | final ArrayList websiteProfileModels = websiteModel.getProfiles(); 867 | ListBoxModel model = new ListBoxModel(); 868 | 869 | if (websiteProfileModels.isEmpty()) { 870 | placeholderText = "-- No profile found --"; 871 | model.add(placeholderText, ""); 872 | } else { 873 | model.add("Please Select Scan Profile",""); 874 | for (WebsiteProfileModel websiteProfileModel : websiteProfileModels) { 875 | boolean isSelected = websiteProfileModels.size() == 1 ? true : false; 876 | model.add(new Option(websiteProfileModel.getName(), websiteProfileModel.getId(),isSelected)); 877 | } 878 | } 879 | 880 | return model; 881 | } 882 | 883 | @SuppressWarnings("unused") 884 | public ListBoxModel doFillNcReportTypeItems() { 885 | ListBoxModel model = new ListBoxModel(); 886 | model.add("Please Select Report Type",""); 887 | model.add("Detailed Scan Report","ScanDetail"); 888 | model.add("Executive Summary","ExecutiveSummary"); 889 | model.add("Full Scan Detail","FullScanDetail"); 890 | model.add("HIPAA Compliance","HIPAACompliance"); 891 | model.add("ISO 27001 Compliance","Iso27001Compliance"); 892 | model.add("Knowledge Base", "KnowledgeBase"); 893 | model.add("OWASP Top Ten 2013","OwaspTopTen2013"); 894 | model.add("OWASP Top Ten 2017","OwaspTopTen2017"); 895 | model.add("PCI DSS Compliance","PCICompliance"); 896 | model.add("SANS Top 25", "SansTop25"); 897 | model.add("WASC Threat Classification","WASC"); 898 | 899 | return model; 900 | } 901 | 902 | private int updateWebsiteModels(final String acxServerURL, final Secret ncApiToken, 903 | ProxyBlock proxy) throws IOException, URISyntaxException, ParseException { 904 | WebsiteModelRequest websiteModelRequest = 905 | new WebsiteModelRequest(acxServerURL, ncApiToken, proxy); 906 | final ClassicHttpResponse response = websiteModelRequest.getPluginWebSiteModels(); 907 | int statusCode = response.getCode(); 908 | 909 | if (statusCode == 200) { 910 | websiteModels = new ArrayList<>(); 911 | websiteModels.addAll(websiteModelRequest.getWebsiteModels()); 912 | }else if(statusCode == 401){ 913 | websiteModels.clear(); 914 | } 915 | 916 | return statusCode; 917 | } 918 | 919 | private FormValidation validateConnection(final String acxServerURL, final Secret ncApiToken, 920 | final ProxyBlock proxy) { 921 | 922 | try { 923 | int statusCode = updateWebsiteModels(acxServerURL, ncApiToken, proxy); 924 | if (statusCode == 200) { 925 | return FormValidation 926 | .ok("Successfully connected to the Acunetix 360."); 927 | } else { 928 | return FormValidation 929 | .error("Acunetix 360 rejected the request. HTTP status code: " 930 | + statusCode); 931 | } 932 | } catch (Exception e) { 933 | return FormValidation 934 | .error("Failed to connect to the Acunetix 360. : " + e.toString()); 935 | } 936 | } 937 | 938 | @POST 939 | @SuppressWarnings("unused") 940 | public FormValidation doValidateAPI(@QueryParameter final String acxServerURL, @QueryParameter final Secret ncApiToken, 941 | @QueryParameter final Boolean useProxy, 942 | @QueryParameter final String pHost, 943 | @QueryParameter final String pPort, 944 | @QueryParameter final String pUser, 945 | @QueryParameter final String pPassword) { 946 | Jenkins.get().checkPermission(Jenkins.ADMINISTER); 947 | 948 | ProxyBlock proxy = null; 949 | if (useProxy != null && useProxy) { 950 | proxy = new ProxyBlock(useProxy, pHost, pPort, pUser, pPassword); 951 | } 952 | 953 | return validateConnection(acxServerURL, ncApiToken, proxy); 954 | } 955 | 956 | @SuppressWarnings("unused") 957 | public FormValidation doTestConnection(@QueryParameter final String credentialsId) { 958 | 959 | final String errorTemplate = "Error: %s"; 960 | 961 | try { 962 | 963 | String descriptorUrl = getCurrentDescriptorByNameUrl(); 964 | 965 | final StandardUsernamePasswordCredentials credential = 966 | AppCommon.findCredentialsById(credentialsId, descriptorUrl); 967 | 968 | if (credential == null) { 969 | return FormValidation.error(errorTemplate, "Credentials not found."); 970 | } 971 | 972 | String serverURL = credential.getUsername(); 973 | Secret apiToken = credential.getPassword(); 974 | 975 | ProxyBlock proxy = null; 976 | if (this.useProxy != null && this.useProxy) { 977 | proxy = new ProxyBlock(this.useProxy, this.pHost, this.pPort, this.pUser, this.pPassword); 978 | } 979 | 980 | return validateConnection(serverURL, apiToken, proxy); 981 | 982 | } catch (Exception e) { 983 | return FormValidation.error(e, errorTemplate, e.getMessage()); 984 | } 985 | } 986 | 987 | @SuppressWarnings("unused") 988 | public FormValidation doCheckAcxServerURL(@QueryParameter String value) { 989 | if (value.length() == 0) { 990 | return FormValidation 991 | .error(Messages.ACXScanBuilder_DescriptorImpl_errors_missingApiURL()); 992 | } else if (!AppCommon.isUrlValid(value)) { 993 | return FormValidation 994 | .error(Messages.ACXScanBuilder_DescriptorImpl_errors_invalidApiURL()); 995 | } 996 | 997 | return FormValidation.ok(); 998 | } 999 | 1000 | @POST 1001 | @SuppressWarnings("unused") 1002 | public FormValidation doCheckNcApiToken(@QueryParameter String value) { 1003 | if (value.length() == 0) { 1004 | return FormValidation 1005 | .error(Messages.ACXScanBuilder_DescriptorImpl_errors_missingApiToken()); 1006 | } 1007 | 1008 | return FormValidation.ok(); 1009 | } 1010 | 1011 | @SuppressWarnings("unused") 1012 | public FormValidation doCheckNcScanType(@QueryParameter String value) { 1013 | try { 1014 | ScanType.valueOf(value); 1015 | } catch (Exception ex) { 1016 | return FormValidation 1017 | .error(Messages.ACXScanBuilder_DescriptorImpl_errors_invalidScanType()); 1018 | } 1019 | 1020 | return FormValidation.ok(); 1021 | } 1022 | 1023 | @SuppressWarnings("unused") 1024 | public FormValidation doCheckNcWebsiteId(@QueryParameter String value) { 1025 | 1026 | if (!AppCommon.isGUIDValid(value)) { 1027 | return FormValidation 1028 | .error(Messages.ACXScanBuilder_DescriptorImpl_errors_invalidWebsiteId()); 1029 | } 1030 | 1031 | return FormValidation.ok(); 1032 | } 1033 | 1034 | @SuppressWarnings("unused") 1035 | public FormValidation doCheckNcProfileId(@QueryParameter String value, 1036 | @QueryParameter String ncScanType) { 1037 | 1038 | boolean isRequired; 1039 | 1040 | try { 1041 | ScanType type = ScanType.valueOf(ncScanType); 1042 | isRequired = type != ScanType.FullWithPrimaryProfile; 1043 | } catch (Exception ex) { 1044 | return FormValidation 1045 | .error(Messages.ACXScanBuilder_DescriptorImpl_errors_invalidProfileId()); 1046 | } 1047 | 1048 | if (isRequired && !AppCommon.isGUIDValid(value)) { 1049 | return FormValidation 1050 | .error(Messages.ACXScanBuilder_DescriptorImpl_errors_invalidProfileId()); 1051 | } 1052 | 1053 | return FormValidation.ok(); 1054 | } 1055 | 1056 | @SuppressWarnings("unused") 1057 | public ListBoxModel doFillNcSeverityItems() throws IOException { 1058 | ListBoxModel items = new ListBoxModel(); 1059 | items.add("Do not fail the build", "DoNotFail"); 1060 | items.add("Critical", "Critical"); 1061 | items.add("High or above", "Critical,High"); 1062 | items.add("Medium or above", "Critical,High,Medium"); 1063 | items.add("Low or above", "Critical,High,Medium,Low"); 1064 | items.add("Best Practices or above", "Critical,High,Medium,Low,Best Practice"); 1065 | return items; 1066 | } 1067 | } 1068 | } 1069 | --------------------------------------------------------------------------------