├── .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 aClass) { 302 | return true; 303 | } 304 | 305 | @Override 306 | public String getDisplayName() { 307 | return "Appdome Validate-2secure"; 308 | } 309 | } 310 | } 311 | --------------------------------------------------------------------------------