
com.atlassian.bamboo.specs.api.validators.common.ValidationUtils Maven / Gradle / Ivy
package com.atlassian.bamboo.specs.api.validators.common;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static com.atlassian.bamboo.specs.api.validators.common.DbConstraintUtils.checkLength;
/**
* Various utility methods for validating data of Bamboo Specs.
*
* Validation methods from this class prefixed with {@code validate} return validation problems and do not
* throw any exception. For helper methods which throw exceptions on validation failures, use
* {@link ImporterUtils}.
*/
public final class ValidationUtils {
private ValidationUtils() {
}
//doesn't contain system variables
public static boolean containsBambooVariable(@Nullable String value) {
return value != null && value.contains("${bamboo");
}
public static boolean repositoryUriContainsPassword(final URI repositoryUri) {
return StringUtils.contains(repositoryUri.getRawUserInfo(), ":");
}
public static Optional validateRequired(@NotNull ValidationContext validationContext,
@Nullable Object value) {
if (value == null) {
return Optional.of(new ValidationProblem(validationContext, "must be defined"));
} else {
return Optional.empty();
}
}
public static Optional validateRequiredString(@NotNull ValidationContext validationContext,
@Nullable String value) {
return validateRequiredString(validationContext, value, false);
}
public static Optional validateRequiredString(@NotNull ValidationContext validationContext,
@Nullable String value,
boolean requireNonBlank) {
if (value == null) {
return Optional.of(new ValidationProblem(validationContext, "must be defined"));
} else if (requireNonBlank ? StringUtils.isBlank(value) : StringUtils.isEmpty(value)) {
return Optional.of(new ValidationProblem(validationContext, "can not be empty"));
} else {
return Optional.empty();
}
}
public static Optional validateBoolean(@NotNull ValidationContext validationContext,
@Nullable String value) {
if (value != null && BooleanUtils.toBooleanObject(value) == null) {
return Optional.of(new ValidationProblem(validationContext, "must be boolean value but is '%s'", value));
} else {
return Optional.empty();
}
}
public static Optional validateRequiredBoolean(@NotNull ValidationContext validationContext,
@Nullable String value) {
if (value == null) {
return Optional.of(new ValidationProblem(validationContext, "must be defined"));
}
return validateBoolean(validationContext, value);
}
public static Optional validateLong(@NotNull ValidationContext validationContext,
@Nullable String value) {
if (value == null) {
return Optional.empty();
}
if (!NumberUtils.isNumber(value)) {
return Optional.of(new ValidationProblem(validationContext,
"must be numerical long value but is '%s'",
value));
}
try {
Long.parseLong(value);
} catch (NumberFormatException e) {
return Optional.of(new ValidationProblem(validationContext,
"must be numerical long value but is '%s'",
value));
}
return Optional.empty();
}
public static Optional validateRequiredLong(@NotNull ValidationContext validationContext,
@Nullable String value) {
if (value == null) {
return Optional.of(new ValidationProblem(validationContext, "must be defined"));
}
return validateLong(validationContext, value);
}
public static > Optional validateEnum(@NotNull ValidationContext validationContext,
@Nullable String value,
@NotNull Class enumClass) {
if (value == null) {
return Optional.empty();
}
if (!EnumUtils.isValidEnum(enumClass, value)) {
return Optional.of(new ValidationProblem(validationContext,
"must be enum value of %s but is '%s'",
ArrayUtils.toString(enumClass.getEnumConstants(), "{}"), value));
} else {
return Optional.empty();
}
}
public static > Optional validateRequiredEnum(@NotNull ValidationContext validationContext,
@Nullable String value,
@NotNull Class enumClass) {
if (value == null) {
return Optional.of(new ValidationProblem(validationContext, "must be defined"));
}
return validateEnum(validationContext, value, enumClass);
}
public static Optional validatePositive(@NotNull ValidationContext validationContext,
long value) {
if (value < 0) {
return Optional.of(new ValidationProblem(validationContext, "must be >= 0 but is %d", value));
} else {
return Optional.empty();
}
}
public static Optional validatePositive(@NotNull ValidationContext validationContext,
int value) {
if (value < 0) {
return Optional.of(new ValidationProblem(validationContext, "must be >= 0 but is %d", value));
} else {
return Optional.empty();
}
}
public static Optional validateUrl(@NotNull ValidationContext validationContext,
@Nullable String urlString,
@NotNull Set supportedSchemes) {
if (urlString == null) {
return Optional.empty();
}
try {
final URI uri = new URI(urlString);
final String scheme = uri.getScheme();
if (!supportedSchemes.contains(uri.getScheme())) {
return Optional.of(new ValidationProblem(validationContext,
"scheme '%s' is not supported - supported schemes are: %s",
scheme, String.join(", ", supportedSchemes)));
}
} catch (URISyntaxException e) {
//windows network share or possibly scp format
//scp format is not usual uri (for example [email protected]:atlassian/bamboo-docker-plugin.git)
//we do best effort accepting everything that doesn't contain "://"
if (urlString.startsWith("\\\\") || !urlString.contains("://")) {
return Optional.empty();
}
return Optional.of(new ValidationProblem(validationContext, String.format("Malformed URL: %s", urlString)));
}
return Optional.empty();
}
public static Optional validateNotContainsXssRelatedCharacters(@NotNull ValidationContext validationContext,
@Nullable String value) {
if (BambooStringUtils.containsXssRelatedCharacters(value)) {
return Optional.of(new ValidationProblem(validationContext,
"can not contain any of those characters: %s but it is '%s'",
Arrays.toString(BambooStringUtils.XSS_RELATED_CHARACTERS), value));
}
return Optional.empty();
}
public static Optional validateNotContainsRelaxedXssRelatedCharacters(@NotNull ValidationContext validationContext,
@Nullable String value) {
if (BambooStringUtils.containsRelaxedXssRelatedCharacters(value)) {
return Optional.of(new ValidationProblem(validationContext,
"can not contain any of those characters: %s but it is '%s'",
Arrays.toString(BambooStringUtils.RELAXED_XSS_RELATED_CHARACTERS), value));
}
return Optional.empty();
}
public static Optional validateNotContainsShellInjectionRelatedCharacters(@NotNull ValidationContext validationContext,
@Nullable String value) {
if (BambooStringUtils.containsShellInjectionRelatedCharacters(value)) {
return Optional.of(
new ValidationProblem(validationContext,
"can not contain any of those characters: %s, nor this substring: '%s' but it is '%s'",
Arrays.toString(BambooStringUtils.SHELL_INJECTION_RELATED_CHARACTERS),
BambooStringUtils.SHELL_INJECTION_DOLLAR_PARENTHESIS,
value));
}
return Optional.empty();
}
@NotNull
public static List validateName(@NotNull ValidationContext validationContext,
@Nullable String name) {
return validateName(validationContext, name, true);
}
@NotNull
public static List validateName(@NotNull ValidationContext validationContext,
@Nullable String name,
boolean required) {
final ValidationContext validationContextWithName = validationContext.with("Name");
final List errors = new ArrayList<>();
if (required) {
validateRequiredString(validationContextWithName, name, true)
.ifPresent(errors::add);
}
checkLength(validationContextWithName, name,
DbConstraintUtils.DATABASE_STRING_LIMIT,
"it can not be longer than " + DbConstraintUtils.DATABASE_STRING_LIMIT
+ " characters but has " + StringUtils.length(name))
.ifPresent(errors::add);
validateNotContainsXssRelatedCharacters(validationContextWithName, name)
.ifPresent(errors::add);
return errors;
}
@NotNull
public static List validateDescription(@NotNull ValidationContext validationContext,
@Nullable String description) {
final ValidationContext validationContextWithDescription = validationContext.with("Description");
final List errors = new ArrayList<>();
validateNotContainsXssRelatedCharacters(validationContextWithDescription, description)
.ifPresent(errors::add);
return errors;
}
}