├── .github
├── CODEOWNERS
├── release-drafter.yml
├── dependabot.yml
└── workflows
│ ├── release-drafter.yml
│ └── jenkins-security-scan.yml
├── .mvn
├── maven.config
└── extensions.xml
├── .gitignore
├── src
└── main
│ ├── resources
│ ├── index.jelly
│ └── io
│ │ └── jenkins
│ │ └── plugins
│ │ └── appdome
│ │ └── validate
│ │ └── to
│ │ └── secure
│ │ └── AppdomeValidator
│ │ └── config.jelly
│ └── java
│ └── io
│ └── jenkins
│ └── plugins
│ └── appdome
│ └── validate
│ └── to
│ └── secure
│ ├── AppdomeValidateConstants.java
│ ├── Utils.java
│ └── AppdomeValidator.java
├── Jenkinsfile
├── README.md
├── LICENSE.md
└── pom.xml
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @jenkinsci/appdome-validate-2secure-plugin-developers
2 |
--------------------------------------------------------------------------------
/.mvn/maven.config:
--------------------------------------------------------------------------------
1 | -Pconsume-incrementals
2 | -Pmight-produce-incrementals
3 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | _extends: .github
2 | tag-template: appdome-validate-2secure-$NEXT_MINOR_VERSION
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 |
3 | # mvn hpi:run
4 | work
5 |
6 | # IntelliJ IDEA project files
7 | *.iml
8 | *.iws
9 | *.ipr
10 | .idea
11 |
12 | # Eclipse project files
13 | .settings
14 | .classpath
15 | .project
16 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | Appdome Validate-2secure integrates Jenkins with
4 |
Appdome
5 |
6 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | /*
2 | See the documentation for more options:
3 | https://github.com/jenkins-infra/pipeline-library/
4 | */
5 | buildPlugin(
6 | useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests
7 | configurations: [
8 | [platform: 'linux', jdk: 21],
9 | [platform: 'windows', jdk: 17],
10 | ])
11 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates
2 |
3 | version: 2
4 | updates:
5 | - package-ecosystem: maven
6 | directory: /
7 | schedule:
8 | interval: monthly
9 | - package-ecosystem: github-actions
10 | directory: /
11 | schedule:
12 | interval: monthly
13 |
--------------------------------------------------------------------------------
/.mvn/extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | io.jenkins.tools.incrementals
4 | git-changelist-maven-extension
5 | 1.7
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | # Automates creation of Release Drafts using Release Drafter
2 | # More Info: https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc
3 |
4 | on:
5 | push:
6 | branches:
7 | - master
8 | - main
9 |
10 | jobs:
11 | update_release_draft:
12 | runs-on: ubuntu-latest
13 | steps:
14 | # Drafts your next Release notes as Pull Requests are merged into the default branch
15 | - uses: release-drafter/release-drafter@v5
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appdome/validate/to/secure/AppdomeValidateConstants.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appdome.validate.to.secure;
2 |
3 | public interface AppdomeValidateConstants {
4 | /**
5 | * Environment variables
6 | **/
7 | String APP_PATH = "VALIDATE_APP_PATH";
8 |
9 | String APPDOME_HEADER_ENV_NAME = "APPDOME_CLIENT_HEADER";
10 | String APPDOME_BUILDE2SECURE_VERSION = "Jenkins/1.2";
11 |
12 | /**
13 | * FLAGS
14 | **/
15 | String KEY_FLAG = " --api_key ";
16 |
17 | String APP_FLAG = " --app ";
18 | String OUTPUT_FLAG = " --output ";
19 |
20 | String VALIDATION_RESULTS_NAME = "validation_results.json";
21 | }
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # appdome-validate-2secure
2 |
3 | ## Introduction
4 |
5 | Appdome Validation plugin - troubleshoot app signing in secured android & iOS apps.
6 | The service also verifies that the app wasn’t tampered with in any way that may prevent it from running on any mobile device.
7 |
8 | ## Getting started
9 |
10 | For documentation on how to use the plugin, see the [Appdome Validate-2secure plugin](https://www.appdome.com/how-to/devsecops-automation-mobile-cicd/mobile-app-security-anti-fraud-cicd/use-appdome-validate-2secure-plugin-for-jenkins/).
11 |
12 | ## Issues
13 |
14 | Please report issues and enhancements through the [Github issue tracker](https://github.com/jenkinsci/appdome-validate-2secure-plugin/issues/new/choose).
15 |
16 |
--------------------------------------------------------------------------------
/.github/workflows/jenkins-security-scan.yml:
--------------------------------------------------------------------------------
1 | # More information about the Jenkins security scan can be found at the developer docs: https://www.jenkins.io/redirect/jenkins-security-scan/
2 |
3 | name: Jenkins Security Scan
4 | on:
5 | push:
6 | branches:
7 | - "master"
8 | - "main"
9 | pull_request:
10 | types: [ opened, synchronize, reopened ]
11 | workflow_dispatch:
12 |
13 | permissions:
14 | security-events: write
15 | contents: read
16 | actions: read
17 |
18 | jobs:
19 | security-scan:
20 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2
21 | with:
22 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
23 | java-version: 11 # What version of Java to set up for the build.
24 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appdome/validate/to/secure/AppdomeValidator/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright 2023
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.jenkins-ci.plugins
6 | plugin
7 | 4.73
8 |
9 |
10 | io.jenkins.plugins
11 | appdome-validate-2secure
12 | ${revision}${changelist}
13 | hpi
14 | Appdome Validate-2secure
15 | https://github.com/jenkinsci/${project.artifactId}-plugin
16 |
17 |
18 | MIT License
19 | https://opensource.org/license/mit/
20 |
21 |
22 |
23 | scm:git:https://github.com/${gitHubRepo}
24 | scm:git:https://github.com/${gitHubRepo}
25 | ${scmTag}
26 | https://github.com/${gitHubRepo}
27 |
28 |
29 | 1.0.1
30 | -SNAPSHOT
31 |
32 | 2.361.4
33 | jenkinsci/${project.artifactId}-plugin
34 | false
35 |
36 |
37 |
38 |
39 |
40 | io.jenkins.tools.bom
41 | bom-2.387.x
42 | 2465.va_e76ed7b_3061
43 | pom
44 | import
45 |
46 |
47 |
48 |
49 |
50 |
51 | repo.jenkins-ci.org
52 | https://repo.jenkins-ci.org/public/
53 |
54 |
55 |
56 |
57 | repo.jenkins-ci.org
58 | https://repo.jenkins-ci.org/public/
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appdome/validate/to/secure/Utils.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appdome.validate.to.secure;
2 |
3 | import hudson.EnvVars;
4 | import hudson.FilePath;
5 | import hudson.Launcher;
6 | import hudson.Util;
7 | import hudson.model.TaskListener;
8 | import hudson.util.ArgumentListBuilder;
9 | import java.io.File;
10 | import java.io.IOException;
11 | import java.util.InputMismatchException;
12 |
13 | public class Utils {
14 | public static boolean isHttpUrl(String urlString) {
15 | String regex = "^https?://.*$";
16 | return urlString.matches(regex);
17 | }
18 |
19 | static String UseEnvironmentVariable(EnvVars env, String envName, String fieldValue, String filedName) {
20 | if (fieldValue == null
21 | || fieldValue.isEmpty()
22 | && (env.get(envName) != null && !(Util.fixEmptyAndTrim(env.get(envName)) == null))) {
23 | return env.get(envName, fieldValue);
24 | }
25 | throw new InputMismatchException("The field '" + filedName + "' was not provided correctly. "
26 | + "Kindly ensure that the environment variable '" + envName + "' has been correctly inserted.");
27 | }
28 |
29 | static String DownloadFilesOrContinue(String paths, FilePath agentWorkspace, Launcher launcher)
30 | throws IOException, InterruptedException {
31 | ArgumentListBuilder args;
32 | FilePath userFilesPath;
33 | StringBuilder pathsToFilesOnAgent = new StringBuilder();
34 | String[] splitPathFiles = paths.split(",");
35 |
36 | for (String singlePath : splitPathFiles) {
37 | if (!isHttpUrl(singlePath)) {
38 | pathsToFilesOnAgent.append(singlePath).append(',');
39 | } else {
40 | args = new ArgumentListBuilder("mkdir", "user_files");
41 | launcher.launch().cmds(args).pwd(agentWorkspace).quiet(true).join();
42 | userFilesPath = agentWorkspace.child("user_files");
43 | pathsToFilesOnAgent
44 | .append(DownloadFiles(userFilesPath, launcher, singlePath))
45 | .append(',');
46 | }
47 | }
48 | return pathsToFilesOnAgent
49 | .substring(0, pathsToFilesOnAgent.length() - 1)
50 | .trim();
51 | }
52 |
53 | private static String DownloadFiles(FilePath userFilesPath, Launcher launcher, String url)
54 | throws IOException, InterruptedException {
55 | ArgumentListBuilder args = new ArgumentListBuilder("curl", "-LO", url);
56 | String fileName = url.substring(url.lastIndexOf('/') + 1);
57 | String outputPath = userFilesPath.getRemote() + File.separator + fileName;
58 | launcher.launch().cmds(args).pwd(userFilesPath).quiet(true).join();
59 | return outputPath;
60 | }
61 |
62 | /**
63 | * This method deletes the contents and the workspace directory of an Appdome workspace.
64 | *
65 | * @param listener listener object to log messages
66 | * @param appdomeWorkspace the path to the Appdome workspace to delete
67 | * @throws IOException : if there is an error accessing the file system
68 | * @throws InterruptedException if the current thread is interrupted by another thread while
69 | * it is waiting for the workspace deletion to complete.
70 | */
71 | static void deleteAppdomeWorkspacce(TaskListener listener, FilePath appdomeWorkspace)
72 | throws IOException, InterruptedException {
73 | listener.getLogger().print("Deleting temporary files." + System.lineSeparator());
74 | appdomeWorkspace.deleteSuffixesRecursive();
75 | appdomeWorkspace.deleteContents();
76 | appdomeWorkspace.deleteRecursive();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appdome/validate/to/secure/AppdomeValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appdome.validate.to.secure;
2 |
3 | import static io.jenkins.plugins.appdome.validate.to.secure.AppdomeValidateConstants.*;
4 | import static io.jenkins.plugins.appdome.validate.to.secure.Utils.*;
5 |
6 | import edu.umd.cs.findbugs.annotations.NonNull;
7 | import hudson.*;
8 | import hudson.model.*;
9 | import hudson.tasks.BuildStepDescriptor;
10 | import hudson.tasks.Builder;
11 | import hudson.util.ArgumentListBuilder;
12 | import hudson.util.FormValidation;
13 | import hudson.util.Secret;
14 | import java.io.*;
15 | import java.util.List;
16 | import java.util.stream.Collectors;
17 | import java.util.stream.Stream;
18 | import javax.swing.*;
19 | import jenkins.model.Jenkins;
20 | import jenkins.tasks.SimpleBuildStep;
21 | import org.jenkinsci.Symbol;
22 | import org.kohsuke.stapler.DataBoundConstructor;
23 | import org.kohsuke.stapler.DataBoundSetter;
24 | import org.kohsuke.stapler.QueryParameter;
25 | import org.kohsuke.stapler.verb.POST;
26 |
27 | public class AppdomeValidator extends Builder implements SimpleBuildStep {
28 | private final Secret token;
29 | private String appPath;
30 | private String outputLocation;
31 |
32 | @DataBoundConstructor
33 | public AppdomeValidator(Secret token, String appPath) {
34 | this.token = token;
35 | this.appPath = appPath;
36 | }
37 |
38 | /**
39 | * Clones the Appdome API repository.
40 | * https://github.com/Appdome/appdome-api-bash.git
41 | *
42 | * @param listener the TaskListener to use for logging
43 | * @param appdomeWorkspace the working directory of the build
44 | * @param launcher used to launch commands.
45 | * @return the exit code of the process
46 | * @throws IOException if an I/O error occurs
47 | * @throws InterruptedException if the process is interrupted
48 | */
49 | private int CloneAppdomeApi(TaskListener listener, FilePath appdomeWorkspace, Launcher launcher)
50 | throws IOException, InterruptedException {
51 | listener.getLogger().println("Updating Appdome Engine...");
52 |
53 | ArgumentListBuilder gitCloneCommand =
54 | new ArgumentListBuilder("git", "clone", "https://github.com/Appdome/appdome-api-bash.git");
55 | return launcher.launch()
56 | .cmds(gitCloneCommand)
57 | .pwd(appdomeWorkspace)
58 | .quiet(true)
59 | .join();
60 | }
61 |
62 | @Override
63 | public void perform(
64 | @NonNull Run, ?> run,
65 | @NonNull FilePath workspace,
66 | @NonNull EnvVars env,
67 | @NonNull Launcher launcher,
68 | @NonNull TaskListener listener)
69 | throws InterruptedException, IOException {
70 | int exitCode;
71 | FilePath appdomeWorkspace = workspace.createTempDir("AppdomeValidation", "Validate");
72 | exitCode = CloneAppdomeApi(listener, appdomeWorkspace, launcher);
73 | if (exitCode == 0) {
74 | listener.getLogger().println("Appdome engine updated successfully");
75 | try {
76 | ExecuteAppdomeApi(run, listener, appdomeWorkspace, workspace, env, launcher);
77 | } catch (Exception e) {
78 | listener.error("Couldn't run Appdome Verification, read logs for more information. error:" + e);
79 | run.setResult(Result.FAILURE);
80 | }
81 | } else {
82 | listener.error("Couldn't Update Appdome engine, read logs for more information.");
83 | run.setResult(Result.FAILURE);
84 | }
85 | deleteAppdomeWorkspacce(listener, appdomeWorkspace);
86 | }
87 |
88 | private void ExecuteAppdomeApi(
89 | Run, ?> run,
90 | TaskListener listener,
91 | FilePath appdomeWorkspace,
92 | FilePath agentWorkspace,
93 | EnvVars env,
94 | Launcher launcher)
95 | throws IOException, InterruptedException {
96 | FilePath scriptPath = appdomeWorkspace.child("appdome-api-bash");
97 | String command = ComposeAppdomeValidateCommand(appdomeWorkspace, agentWorkspace, env, launcher, listener);
98 | if (command.equals("")) {
99 | run.setResult(Result.FAILURE);
100 | return;
101 | }
102 | List filteredCommandList =
103 | Stream.of(command.split(" ")).filter(s -> !s.isEmpty()).collect(Collectors.toList());
104 | env.put(APPDOME_HEADER_ENV_NAME, APPDOME_BUILDE2SECURE_VERSION);
105 | listener.getLogger().println("Launching Appdome Validator");
106 | ByteArrayOutputStream stdoutStream = new ByteArrayOutputStream();
107 |
108 | int exitCode = launcher.launch()
109 | .cmds(filteredCommandList)
110 | .pwd(scriptPath)
111 | .envs(env)
112 | .stdout(stdoutStream)
113 | .stdout(listener.getLogger())
114 | .stderr(listener.getLogger())
115 | .quiet(true)
116 | .start()
117 | .join();
118 |
119 | if (exitCode == 0) {
120 | Boolean isSignedCorrectly = false;
121 | // Initialize the result to FAILURE
122 | Result result = Result.FAILURE;
123 |
124 | // Iterate through the log entries once
125 | for (String logEntry : run.getLog(Integer.MAX_VALUE)) {
126 | if (logEntry.contains("This app is not built by Appdome")) {
127 | // If "This app is not built by Appdome" is found, set the result to UNSTABLE
128 | result = Result.UNSTABLE;
129 | break; // Exit the loop since we found an unstable condition
130 | } else if (logEntry.contains("This app is signed correctly")) {
131 | // If "This app is signed correctly" is found, continue the loop
132 | isSignedCorrectly = true;
133 | continue;
134 | }
135 | }
136 | if (isSignedCorrectly && result != Result.UNSTABLE) {
137 | result = Result.SUCCESS;
138 | }
139 | // Set the result based on the conditions
140 | run.setResult(result);
141 | } else {
142 | listener.error("Couldn't run Appdome Verification, exitcode " + exitCode
143 | + ".\nCouldn't run Appdome Verification, read logs for more information.");
144 | run.setResult(Result.FAILURE);
145 | }
146 | }
147 |
148 | private String ComposeAppdomeValidateCommand(
149 | FilePath appdomeWorkspace, FilePath agentWorkspace, EnvVars env, Launcher launcher, TaskListener listener)
150 | throws IOException, InterruptedException {
151 | StringBuilder command = new StringBuilder("./appdome_api_bash/validate.sh");
152 |
153 | if (!(Util.fixEmptyAndTrim(this.getToken()) == null)) {
154 | command.append(KEY_FLAG).append(this.getToken());
155 | } else {
156 | listener.fatalError("Appdome-provided API token was not provided.");
157 | return "";
158 | }
159 |
160 | String appPath = "";
161 | // concatenate the app path if it is not empty:
162 | if (!(Util.fixEmptyAndTrim(this.appPath) == null)) {
163 | appPath = DownloadFilesOrContinue(this.appPath, appdomeWorkspace, launcher);
164 | } else {
165 | appPath = DownloadFilesOrContinue(
166 | UseEnvironmentVariable(
167 | env, APP_PATH, appPath, APP_FLAG.trim().substring(2)),
168 | agentWorkspace,
169 | launcher);
170 | }
171 |
172 | if (appPath.isEmpty()) {
173 | throw new RuntimeException("App path was not provided.");
174 | } else {
175 | FilePath file = new FilePath(agentWorkspace, appPath);
176 | if (file.exists()) {
177 | command.append(APP_FLAG).append(appPath);
178 | listener.getLogger().println("Validating app " + new File(appPath).getName());
179 |
180 | } else {
181 | listener.fatalError("App file " + appPath + " does not exist");
182 | return "";
183 | }
184 | }
185 |
186 | if (!(Util.fixEmptyAndTrim(this.outputLocation) == null)) {
187 |
188 | if (this.outputLocation.toLowerCase().endsWith(".json")) {
189 | command.append(OUTPUT_FLAG).append(this.outputLocation);
190 | } else if (this.outputLocation.endsWith("/")) {
191 | ArgumentListBuilder args = new ArgumentListBuilder("mkdir", this.outputLocation);
192 | launcher.launch().cmds(args).pwd(agentWorkspace).quiet(true).join();
193 | command.append(OUTPUT_FLAG)
194 | .append(this.outputLocation)
195 | .append(File.separator)
196 | .append(VALIDATION_RESULTS_NAME);
197 | } else {
198 | listener.error("Output location is not valid. Result won't be save to a JSON file.");
199 | }
200 | } else {
201 | String outputLocationMissing = "";
202 | if (!isHttpUrl(this.appPath)) {
203 | outputLocationMissing =
204 | (appPath.substring(0, appPath.lastIndexOf("/") + 1)).concat(VALIDATION_RESULTS_NAME);
205 | command.append(OUTPUT_FLAG).append(outputLocationMissing);
206 | listener.getLogger()
207 | .println("WARNING: The output location for the JSON result was not provided. "
208 | + "The JSON data will be saved to " + outputLocationMissing);
209 |
210 | } else {
211 | listener.getLogger().println("ERROR: Result won't be save to a JSON file.");
212 | }
213 | }
214 |
215 | return command.toString();
216 | }
217 |
218 | public String getToken() {
219 | return token.getPlainText();
220 | }
221 |
222 | public String getAppPath() {
223 | return appPath;
224 | }
225 |
226 | public String getOutputLocation() {
227 | return outputLocation;
228 | }
229 |
230 | @DataBoundSetter
231 | public void setOutputLocation(String outputLocation) {
232 | this.outputLocation = outputLocation;
233 | }
234 |
235 | @Symbol("AppdomeValidator")
236 | @Extension
237 | public static final class DescriptorImpl extends BuildStepDescriptor {
238 |
239 | @POST
240 | public FormValidation doCheckToken(@QueryParameter Secret token) {
241 | Jenkins.get().checkPermission(Jenkins.READ);
242 | if (token != null && Util.fixEmptyAndTrim(token.getPlainText()) == null) {
243 | return FormValidation.error("Token is required");
244 | } else if (token != null && token.getPlainText().contains(" ")) {
245 | return FormValidation.error("White spaces are not allowed in Token.");
246 | }
247 | // Perform any additional validation here
248 | return FormValidation.ok();
249 | }
250 |
251 | @POST
252 | public FormValidation doCheckAppPath(@QueryParameter String appPath) {
253 | Jenkins.get().checkPermission(Jenkins.READ);
254 | if (appPath != null && Util.fixEmptyAndTrim(appPath) == null) {
255 | return FormValidation.warning("Application path was not provided.\n "
256 | + "Or please ensure that a valid path is provided for application in the environment variable"
257 | + " named "
258 | + APP_PATH + ".");
259 | } else if (appPath != null && appPath.contains(" ")) {
260 | return FormValidation.error("White spaces are not allowed in the path.");
261 | } else if (appPath != null
262 | && !(appPath.endsWith(".aab") || appPath.endsWith(".apk") || (appPath.endsWith(".ipa")))) {
263 | return FormValidation.error(
264 | "Application - File extension is not allowed,"
265 | + " allowed extensions are: '.apk' or '.aab'. Please rename your file or upload a different file.");
266 | }
267 | // Perform any additional validation here
268 | return FormValidation.ok();
269 | }
270 |
271 | @POST
272 | public FormValidation doCheckOutputLocation(
273 | @QueryParameter String outputLocation, @QueryParameter String appPath) {
274 | Jenkins.get().checkPermission(Jenkins.READ);
275 | if (outputLocation != null && Util.fixEmptyAndTrim(outputLocation) == null) {
276 | if ((appPath != null && Util.fixEmptyAndTrim(appPath) != null) && !isHttpUrl(appPath)) {
277 | String outputLocationMissing =
278 | (appPath.substring(0, appPath.lastIndexOf("/") + 1)).concat(VALIDATION_RESULTS_NAME);
279 | return FormValidation.warning("Output path for JSON file was not provided. and it will be saved to "
280 | + outputLocationMissing);
281 | } else {
282 | return FormValidation.warning("Output path for JSON file was not provided.");
283 | }
284 | } else if (outputLocation != null && outputLocation.contains(" ")) {
285 | return FormValidation.error("White spaces are not allowed in the path.");
286 | } else if (outputLocation != null && outputLocation.endsWith("/")) {
287 | return FormValidation.ok(
288 | "Output JSON result file will be saved to " + outputLocation + VALIDATION_RESULTS_NAME);
289 | } else if (outputLocation != null
290 | && Util.fixEmptyAndTrim(outputLocation) != null
291 | && outputLocation.endsWith(".json")) {
292 | return FormValidation.ok("JSON result file will be saved to " + outputLocation);
293 | } else if (outputLocation != null && Util.fixEmptyAndTrim(outputLocation) != null) {
294 | return FormValidation.error("Please provide a valid path to JSON file results.");
295 | }
296 |
297 | return FormValidation.ok();
298 | }
299 |
300 | @Override
301 | public boolean isApplicable(Class extends AbstractProject> aClass) {
302 | return true;
303 | }
304 |
305 | @Override
306 | public String getDisplayName() {
307 | return "Appdome Validate-2secure";
308 | }
309 | }
310 | }
311 |
--------------------------------------------------------------------------------