Skip to content
This repository has been archived by the owner on Nov 11, 2024. It is now read-only.

Commit

Permalink
refactor: Separate validation from resource processors
Browse files Browse the repository at this point in the history
Signed-off-by: Eamonn Mansour <[email protected]>
  • Loading branch information
eamansour committed Oct 29, 2024
1 parent be4e991 commit 3447130
Show file tree
Hide file tree
Showing 17 changed files with 338 additions and 208 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import dev.galasa.framework.api.common.IBeanValidator;
import dev.galasa.framework.api.common.InternalServletException;
import dev.galasa.framework.api.common.ServletError;
import dev.galasa.framework.api.common.resources.BaseResourceValidator;
import dev.galasa.framework.api.common.resources.AbstractValidator;

import static dev.galasa.framework.api.common.ServletErrorMessage.*;

import javax.servlet.http.HttpServletResponse;

public class TokenPayloadValidator extends BaseResourceValidator implements IBeanValidator<TokenPayload> {
public class TokenPayloadValidator extends AbstractValidator implements IBeanValidator<TokenPayload> {

@Override
public void validate(TokenPayload tokenPayload) throws InternalServletException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public enum ServletErrorMessage {
GAL5082_NO_LOGINID_PARAM_PROVIDED (5082, "E: A request to get the user details failed. The request did not supply a ‘loginId’ filter. A ‘loginId’ query parameter with a value of : ‘me’ was expected. This problem is caused by the client program sending a bad request. Please report this problem to the owner of your client program."),

// Secrets APIs...
GAL5092_INVALID_SECRET_NAME_PROVIDED (5092, "E: Invalid secret name provided. The name of a Galasa secret cannot be empty or contain only spaces or tabs, and must only contain characters in the Latin-1 character set. Check your request payload and try again."),
GAL5092_INVALID_SECRET_NAME_PROVIDED (5092, "E: Invalid secret name provided. The name of a Galasa secret cannot be empty, contain only spaces or tabs, or contain dots ('.'), and must only contain characters in the Latin-1 character set. Check your request payload and try again."),
GAL5093_ERROR_SECRET_NOT_FOUND (5093, "E: Unable to retrieve a secret with the given name. No such secret exists. Check your request query parameters and try again."),
GAL5094_FAILED_TO_GET_SECRET_FROM_CREDS (5094, "E: Failed to retrieve a secret with the given name from the credentials store. The credentials store might be badly configured or could be experiencing a temporary issue. Report the problem to your Galasa Ecosystem owner."),
GAL5095_ERROR_PASSWORD_AND_TOKEN_PROVIDED (5095, "E: Invalid secret payload provided. The ''password'' and ''token'' fields are mutually exclusive and cannot be provided in the same secret. Check your request payload and try again."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/**
* A base validator class that contains commonly-used validation methods
*/
public class BaseResourceValidator {
public abstract class AbstractValidator {

/**
* Checks whether a given string is in valid Latin-1 format (e.g. characters in the range 0 - 255)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package dev.galasa.framework.api.common.resources;

import static dev.galasa.framework.api.common.ServletErrorMessage.*;
import static dev.galasa.framework.api.common.resources.ResourceAction.*;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import com.google.gson.JsonObject;

import dev.galasa.framework.api.common.IBeanValidator;
import dev.galasa.framework.api.common.InternalServletException;
import dev.galasa.framework.api.common.ServletError;

/**
* An abstract class containing the base methods used to validate Galasa resources.
*/
public abstract class GalasaResourceValidator<T> extends AbstractValidator implements IBeanValidator<T> {

public static final String DEFAULT_API_VERSION = "galasa-dev/v1alpha1";

protected List<String> validationErrors = new ArrayList<>();
protected ResourceAction action;

public GalasaResourceValidator() {}

public GalasaResourceValidator(ResourceAction action) {
this.action = action;
}

public List<String> getValidationErrors() {
return validationErrors;
}

private List<String> getRequiredResourceFields() {
List<String> requiredFields = new ArrayList<>();
requiredFields.add("apiVersion");
requiredFields.add("metadata");
if (action != DELETE) {
requiredFields.add("data");
}
return requiredFields;
}

protected List<String> getMissingResourceFields(JsonObject resourceJson, List<String> requiredFields) {
List<String> missingFields = new ArrayList<>();
for (String field : requiredFields) {
if (!resourceJson.has(field)) {
missingFields.add(field);
}
}
return missingFields;
}

protected void checkResourceHasRequiredFields(
JsonObject resourceJson,
String expectedApiVersion
) throws InternalServletException {
List<String> requiredFields = getRequiredResourceFields();
List<String> missingFields = getMissingResourceFields(resourceJson, requiredFields);
if (!missingFields.isEmpty()) {
ServletError error = new ServletError(GAL5069_MISSING_REQUIRED_FIELDS, String.join(", ", missingFields));
throw new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST);
}

String apiVersion = resourceJson.get("apiVersion").getAsString();
if (!apiVersion.equals(expectedApiVersion)) {
ServletError error = new ServletError(GAL5027_UNSUPPORTED_API_VERSION, expectedApiVersion);
throw new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@
*/
package dev.galasa.framework.api.common.resources;

import java.util.List;

public enum GalasaSecretType {
USERNAME_PASSWORD("UsernamePassword", "username", "password"),
USERNAME_TOKEN("UsernameToken", "username", "token"),
USERNAME("Username", "username"),
TOKEN("Token", "token");

public static final String DEFAULT_API_VERSION = "galasa-dev/v1alpha1";
public static final List<String> SUPPORTED_ENCODING_SCHEMES = List.of("base64");

private String name;
private String[] requiredDataFields;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package dev.galasa.framework.api.common.resources;

import static dev.galasa.framework.api.common.ServletErrorMessage.*;

import java.util.List;

import javax.servlet.http.HttpServletResponse;

import dev.galasa.framework.api.common.InternalServletException;
import dev.galasa.framework.api.common.ServletError;

public abstract class SecretValidator<T> extends GalasaResourceValidator<T> {

public static final List<String> SUPPORTED_ENCODING_SCHEMES = List.of("base64");

public SecretValidator() {}

public SecretValidator(ResourceAction action) {
super(action);
}

protected void validateSecretName(String secretName) throws InternalServletException {
if (secretName == null || secretName.isBlank() || secretName.contains(".") || !isLatin1(secretName)) {
ServletError error = new ServletError(GAL5092_INVALID_SECRET_NAME_PROVIDED);
throw new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST);
}
}

protected void validateDescription(String description) throws InternalServletException {
if (description != null && (description.isBlank() || !isLatin1(description))) {
ServletError error = new ServletError(GAL5102_INVALID_SECRET_DESCRIPTION_PROVIDED);
throw new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,26 @@
*/
package dev.galasa.framework.api.resources.processors;

import static dev.galasa.framework.api.common.ServletErrorMessage.*;
import static dev.galasa.framework.api.common.resources.ResourceAction.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

import com.google.gson.JsonObject;

import dev.galasa.framework.api.common.InternalServletException;
import dev.galasa.framework.api.common.ServletError;
import dev.galasa.framework.api.common.resources.GalasaResourceValidator;
import dev.galasa.framework.api.common.resources.ResourceAction;
import dev.galasa.framework.spi.utils.GalasaGson;

public abstract class AbstractGalasaResourceProcessor {
protected static final Set<ResourceAction> updateActions = Set.of(APPLY, UPDATE);
protected static final GalasaGson gson = new GalasaGson();

protected void checkResourceHasRequiredFields(
JsonObject resourceJson,
String expectedApiVersion,
ResourceAction action
) throws InternalServletException {
List<String> requiredFields = getRequiredResourceFields(action);
List<String> missingFields = getMissingResourceFields(resourceJson, requiredFields);
if (!missingFields.isEmpty()) {
ServletError error = new ServletError(GAL5069_MISSING_REQUIRED_FIELDS, String.join(", ", missingFields));
throw new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST);
}

String apiVersion = resourceJson.get("apiVersion").getAsString();
if (!apiVersion.equals(expectedApiVersion)) {
ServletError error = new ServletError(GAL5027_UNSUPPORTED_API_VERSION, expectedApiVersion);
throw new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST);
}
}

protected List<String> getMissingResourceFields(JsonObject resourceJson, List<String> requiredFields) {
List<String> missingFields = new ArrayList<>();
for (String field : requiredFields) {
if (!resourceJson.has(field)) {
missingFields.add(field);
}
}
return missingFields;
}
protected List<String> checkGalasaResourceJsonStructure(GalasaResourceValidator<JsonObject> validator, JsonObject propertyJson) throws InternalServletException {
validator.validate(propertyJson);

private List<String> getRequiredResourceFields(ResourceAction action) {
List<String> requiredFields = new ArrayList<>();
requiredFields.add("apiVersion");
requiredFields.add("metadata");
if (action != DELETE) {
requiredFields.add("data");
}
return requiredFields;
List<String> validationErrors = validator.getValidationErrors();
return validationErrors;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@
import static dev.galasa.framework.api.common.ServletErrorMessage.*;
import static dev.galasa.framework.api.common.resources.ResourceAction.*;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import dev.galasa.framework.api.beans.GalasaProperty;
Expand All @@ -23,13 +21,12 @@
import dev.galasa.framework.api.common.resources.CPSNamespace;
import dev.galasa.framework.api.common.resources.CPSProperty;
import dev.galasa.framework.api.common.resources.ResourceAction;
import dev.galasa.framework.api.common.resources.ResourceNameValidator;
import dev.galasa.framework.api.resources.validators.GalasaPropertyValidator;
import dev.galasa.framework.spi.ConfigurationPropertyStoreException;

public class GalasaPropertyProcessor extends AbstractGalasaResourceProcessor implements IGalasaResourceProcessor {

private CPSFacade cps;
static final ResourceNameValidator nameValidator = new ResourceNameValidator();

public GalasaPropertyProcessor(CPSFacade cps) {
this.cps = cps;
Expand Down Expand Up @@ -75,61 +72,7 @@ public List<String> processResource(JsonObject resource, ResourceAction action,
}

private List<String> checkGalasaPropertyJsonStructure(JsonObject propertyJson, ResourceAction action) throws InternalServletException {
checkResourceHasRequiredFields(propertyJson, GalasaProperty.DEFAULTAPIVERSION, action);

List<String> validationErrors = new ArrayList<String>();
validatePropertyMetadata(propertyJson, validationErrors);

// Delete operations shouldn't require a 'data' section, just the metadata to identify
// the property to delete
if (action != DELETE) {
validatePropertyData(propertyJson, validationErrors);
}
return validationErrors;
}

private void validatePropertyMetadata(JsonObject propertyJson, List<String> validationErrors) {
//Check metadata is not null and contains name and namespace fields in the correct format
JsonObject metadata = propertyJson.get("metadata").getAsJsonObject();
if (metadata.has("name") && metadata.has("namespace")) {
JsonElement name = metadata.get("name");
JsonElement namespace = metadata.get("namespace");

// Use the ResourceNameValidator to check that the name is correctly formatted and not null
try {
nameValidator.assertPropertyNameCharPatternIsValid(name.getAsString());
} catch (InternalServletException e) {
// All ResourceNameValidator error should be added to the list of reasons why the property action has failed
validationErrors.add(e.getMessage());
}

// Use the ResourceNameValidator to check that the namespace is correctly formatted and not null
try {
nameValidator.assertNamespaceCharPatternIsValid(namespace.getAsString());
} catch (InternalServletException e) {
validationErrors.add(e.getMessage());
}
} else {
String message = "The 'metadata' field cannot be empty. The fields 'name' and 'namespace' are mandatory for the type GalasaProperty.";
ServletError error = new ServletError(GAL5024_INVALID_GALASAPROPERTY, message);
validationErrors.add(new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST).getMessage());
}
}

private void validatePropertyData(JsonObject propertyJson, List<String> validationErrors) {
//Check that data is not null and contains the value field
JsonObject data = propertyJson.get("data").getAsJsonObject();
if (data.size() > 0 && data.has("value")) {
String value = data.get("value").getAsString();
if (value == null || value.isBlank()) {
String message = "The 'value' field cannot be empty. The field 'value' is mandatory for the type GalasaProperty.";
ServletError error = new ServletError(GAL5024_INVALID_GALASAPROPERTY, message);
validationErrors.add(new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST).getMessage());
}
} else {
String message = "The 'data' field cannot be empty. The field 'value' is mandatory for the type GalasaProperty.";
ServletError error = new ServletError(GAL5024_INVALID_GALASAPROPERTY, message);
validationErrors.add(new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST).getMessage());
}
GalasaPropertyValidator validator = new GalasaPropertyValidator(action);
return checkGalasaResourceJsonStructure(validator, propertyJson);
}
}
Loading

0 comments on commit 3447130

Please sign in to comment.