Merge "b/163430475 Refactor traverse methods to return extensions"
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidator.java
index edfa743..2372979 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidator.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidator.java
@@ -1,5 +1,6 @@
package com.apigee.security.oas.extendedvalidator;
+import static com.apigee.security.oas.extendedvalidator.ErrorMessage.INVALID_SCHEMA;
import static com.apigee.security.oas.extendedvalidator.ExtensionName.valueOfExtensionName;
import static com.apigee.security.oas.extendedvalidator.ExtensionValidators.defaultErrors;
import static com.apigee.security.oas.extendedvalidator.ExtensionValidators.prepareNamedPath;
@@ -73,7 +74,8 @@
JsonSchema schema = getJsonSchemaFromUrl(schemaUrl);
Set<ValidationMessage> errors = schema.validate(extension.getExtensionContent());
- return toExtensionValidationMessages(errors, "INVALID_SCHEMA", prepareNamedPath(extension));
+ return toExtensionValidationMessages(
+ errors, INVALID_SCHEMA.getErrorType(), prepareNamedPath(extension));
} catch (URISyntaxException e) {
String errorMessage = "Failed to validate extension schema";
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionScopeValidator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionScopeValidator.java
new file mode 100644
index 0000000..8ff013a
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionScopeValidator.java
@@ -0,0 +1,94 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import static com.apigee.security.oas.extendedvalidator.ErrorMessage.X_SECURITY_ALLOW_SCOPE_ERROR;
+import static com.apigee.security.oas.extendedvalidator.ErrorMessage.X_SECURITY_TYPE_DEFINITIONS_SCOPE_ERROR;
+import static com.apigee.security.oas.extendedvalidator.ErrorMessage.X_SECURITY_TYPE_SCOPE_ERROR;
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_ALLOW;
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_TYPE;
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_TYPE_DEFINITIONS;
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.valueOfExtensionName;
+import static com.apigee.security.oas.extendedvalidator.ExtensionValidators.defaultErrors;
+import static com.apigee.security.oas.extendedvalidator.ExtensionValidators.fetchExtensionParent;
+import static com.apigee.security.oas.extendedvalidator.ExtensionValidators.prepareNamedPath;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.flogger.FluentLogger;
+import java.util.Optional;
+import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.OpenApi3;
+import org.openapi4j.parser.model.v3.Operation;
+import org.openapi4j.parser.model.v3.Parameter;
+import org.openapi4j.parser.model.v3.Schema;
+
+final class BaseExtensionScopeValidator implements ExtensionScopeValidator {
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final ImmutableMap<ExtensionName, ErrorMessage> errorMessages =
+ Maps.immutableEnumMap(
+ ImmutableMap.of(
+ X_SECURITY_TYPE, X_SECURITY_TYPE_SCOPE_ERROR,
+ X_SECURITY_ALLOW, X_SECURITY_ALLOW_SCOPE_ERROR,
+ X_SECURITY_TYPE_DEFINITIONS, X_SECURITY_TYPE_DEFINITIONS_SCOPE_ERROR));
+
+ private static final ImmutableMap<ExtensionName, ImmutableSet<Class<? extends OpenApiSchema>>>
+ allowedParentsMap =
+ Maps.immutableEnumMap(
+ ImmutableMap.of(
+ X_SECURITY_TYPE, ImmutableSet.of(Schema.class, Parameter.class),
+ X_SECURITY_ALLOW, ImmutableSet.of(Operation.class),
+ X_SECURITY_TYPE_DEFINITIONS, ImmutableSet.of(OpenApi3.class)));
+
+ /**
+ * Validates the scope of an supported {@link Extension} by contrasting it against its appropriate
+ * scope.
+ *
+ * <p>An invalid scoped Extension will return {@code INVALID_SCOPE} {@link
+ * ExtensionValidationMessage}.
+ */
+ @Override
+ public ImmutableSet<ExtensionValidationMessage> validate(Extension extension) {
+ Optional<ExtensionName> extensionNameOptional =
+ valueOfExtensionName(extension.getExtensionName());
+ ImmutableSet.Builder<ExtensionValidationMessage> errors = ImmutableSet.builder();
+
+ extensionNameOptional.ifPresentOrElse(
+ extensionNameEnum -> errors.addAll(validateExtensionScope(extension, extensionNameEnum)),
+ () -> errors.addAll(defaultErrors(extension)));
+
+ logger.atInfo().log("Scope Errors(%s) : %s", prepareNamedPath(extension), errors);
+
+ return errors.build();
+ }
+
+ /**
+ * Checks whether {@link Extension} exists under an allowed parent class.
+ *
+ * <p>A parent class is allowed if {@link Extension} has an entry in {@link
+ * BaseExtensionScopeValidator#allowedParentsMap} with it.
+ */
+ private static ImmutableSet<ExtensionValidationMessage> validateExtensionScope(
+ Extension extension, ExtensionName extensionNameEnum) {
+
+ Optional<Class<? extends OpenApiSchema>> extensionParent = fetchExtensionParent(extension);
+
+ if (extensionParent.isPresent()
+ && allowedParentsMap.get(extensionNameEnum).contains(extensionParent.get())) {
+ return ImmutableSet.of();
+ }
+
+ return ImmutableSet.of(prepareInvalidScopeValidationMessage(extension, extensionNameEnum));
+ }
+
+ private static ExtensionValidationMessage prepareInvalidScopeValidationMessage(
+ Extension extension, ExtensionName extensionNameEnum) {
+
+ ErrorMessage errorMessageEnum = errorMessages.get(extensionNameEnum);
+ return ExtensionValidationMessage.builder()
+ .setType(errorMessageEnum.getErrorType())
+ .setMessage(errorMessageEnum.getErrorMessage())
+ .setPath(prepareNamedPath(extension))
+ .build();
+ }
+}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ErrorMessage.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ErrorMessage.java
new file mode 100644
index 0000000..40b6c53
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ErrorMessage.java
@@ -0,0 +1,30 @@
+package com.apigee.security.oas.extendedvalidator;
+
+/** Stores information of {@link Extension} validation errors for ease of use. */
+enum ErrorMessage {
+ UNSUPPORTED_EXTENSION("UNSUPPORTED_EXTENSION", "extension is not binded to a validator."),
+ X_SECURITY_TYPE_SCOPE_ERROR(
+ "INVALID_SCOPE", "extension should be under a schema or parameter object."),
+ X_SECURITY_ALLOW_SCOPE_ERROR(
+ "INVALID_SCOPE",
+ "extension should be under a paths operation object like get, post, patch, etc."),
+ X_SECURITY_TYPE_DEFINITIONS_SCOPE_ERROR(
+ "INVALID_SCOPE", "extension should be on the top-level scope."),
+ INVALID_SCHEMA("INVALID_SCHEMA", "extension has an invalid schema.");
+
+ private final String errorType;
+ private final String errorMessage;
+
+ ErrorMessage(String errorType, String errorMessage) {
+ this.errorType = errorType;
+ this.errorMessage = errorMessage;
+ }
+
+ String getErrorType() {
+ return errorType;
+ }
+
+ String getErrorMessage() {
+ return errorMessage;
+ }
+}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionModule.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionModule.java
index 70c3f7a..bd083e2 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionModule.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionModule.java
@@ -19,6 +19,7 @@
.build(ExtensionFactory.class));
bind(ExtensionSchemaValidator.class).to(BaseExtensionSchemaValidator.class);
+ bind(ExtensionScopeValidator.class).to(BaseExtensionScopeValidator.class);
}
@Provides
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionScopeValidator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionScopeValidator.java
new file mode 100644
index 0000000..bb0e888
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionScopeValidator.java
@@ -0,0 +1,9 @@
+package com.apigee.security.oas.extendedvalidator;
+
+/**
+ * Validates that {@link Extension} exists in a valid scope.
+ *
+ * <p>A valid scope of {@link Extension} is a defined {@link Class}<? extends {@link
+ * org.openapi4j.parser.model.OpenApiSchema}> object that it is allowed to exist under.
+ */
+interface ExtensionScopeValidator extends ExtensionValidator {}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidators.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidators.java
index d380ca7..1d85f31 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidators.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidators.java
@@ -1,5 +1,7 @@
package com.apigee.security.oas.extendedvalidator;
+import static com.apigee.security.oas.extendedvalidator.ErrorMessage.UNSUPPORTED_EXTENSION;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.networknt.schema.ValidationMessage;
@@ -72,11 +74,15 @@
return ImmutableSet.copyOf(updatedErrors);
}
- // TODO(b/162938113) : Create validation error type and message constants.
/** Prepares {@link ExtensionValidationMessage} for extensions without validators. */
static ImmutableSet<ExtensionValidationMessage> defaultErrors(Extension extension) {
- String errorType = "UNSUPPORTED_EXTENSION";
- String errorMessage = extension.getExtensionName() + " is not binded to the validator.";
+ String errorType = UNSUPPORTED_EXTENSION.getErrorType();
+ String errorMessage =
+ new StringBuilder()
+ .append(extension.getExtensionName())
+ .append(" ")
+ .append(UNSUPPORTED_EXTENSION.getErrorMessage())
+ .toString();
String path = prepareNamedPath(extension);
return ImmutableSet.of(
ExtensionValidationMessage.builder()
@@ -86,5 +92,16 @@
.build());
}
+ static Optional<Class<? extends OpenApiSchema>> fetchExtensionParent(Extension extension) {
+ ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> extensionPath =
+ extension.getExtensionPath();
+
+ if (extensionPath.isEmpty()) {
+ return Optional.empty();
+ }
+
+ return Optional.of(extensionPath.get(extensionPath.size() - 1).getKey());
+ }
+
private ExtensionValidators() {}
}
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionScopeValidatorTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionScopeValidatorTest.java
new file mode 100644
index 0000000..aab22ec
--- /dev/null
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionScopeValidatorTest.java
@@ -0,0 +1,154 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_ALLOW;
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_TYPE;
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_TYPE_DEFINITIONS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.OpenApi3;
+import org.openapi4j.parser.model.v3.Operation;
+import org.openapi4j.parser.model.v3.Parameter;
+import org.openapi4j.parser.model.v3.Path;
+
+@RunWith(JUnit4.class)
+public class BaseExtensionScopeValidatorTest {
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+
+ @Mock private Extension extension;
+
+ private ExtensionValidator validator;
+
+ /** Setting up injector and scope validator instances. */
+ @Before
+ public void setup() {
+ Injector injector = Guice.createInjector(new ExtendedValidatorMainModule());
+ validator = injector.getInstance(ExtensionScopeValidator.class);
+ }
+
+ private static ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>>
+ createPathList(Class<? extends OpenApiSchema>[] pathClasses) {
+ ArrayList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> list = new ArrayList<>();
+ for (Class<? extends OpenApiSchema> pathClass : pathClasses) {
+ list.add(new SimpleImmutableEntry<>(pathClass, Optional.empty()));
+ }
+
+ return ImmutableList.copyOf(list);
+ }
+
+ @Test
+ public void validate_unsupportedExtension_returnsUnsupportedExtensionErrorType() {
+ ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+ ImmutableList.of();
+ when(extension.getExtensionName()).thenReturn("x-unsupported");
+ when(extension.getExtensionPath()).thenReturn(path);
+
+ assertThat(validator.validate(extension))
+ .hasSize(1)
+ .first()
+ .extracting(ExtensionValidationMessage::type)
+ .isEqualTo("UNSUPPORTED_EXTENSION");
+ }
+
+ @Test
+ public void validate_emptyPathSecurityTypeExtension_returnsInvalidScopeErrorType() {
+ when(extension.getExtensionName()).thenReturn(X_SECURITY_TYPE.getExtensionName());
+ when(extension.getExtensionPath()).thenReturn(ImmutableList.of());
+
+ assertThat(validator.validate(extension))
+ .hasSize(1)
+ .first()
+ .extracting(ExtensionValidationMessage::type)
+ .isEqualTo("INVALID_SCOPE");
+ }
+
+ @Test
+ public void validate_validScopeSecurityTypeExtension_returnsEmptyErrorSet() {
+ ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+ createPathList(new Class[] {Path.class, Operation.class, Parameter.class});
+ when(extension.getExtensionName()).thenReturn(X_SECURITY_TYPE.getExtensionName());
+ when(extension.getExtensionPath()).thenReturn(path);
+
+ assertThat(validator.validate(extension)).isEmpty();
+ }
+
+ @Test
+ public void validate_invalidScopeSecurityTypeExtension_returnsInvalidScopeErrorType() {
+ ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+ createPathList(new Class[] {Path.class});
+ when(extension.getExtensionName()).thenReturn(X_SECURITY_TYPE.getExtensionName());
+ when(extension.getExtensionPath()).thenReturn(path);
+
+ assertThat(validator.validate(extension))
+ .hasSize(1)
+ .first()
+ .extracting(ExtensionValidationMessage::type)
+ .isEqualTo("INVALID_SCOPE");
+ }
+
+ @Test
+ public void validate_validScopeSecurityAllowExtension_returnsEmptyErrorSet() {
+ ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+ createPathList(new Class[] {Path.class, Operation.class});
+ when(extension.getExtensionName()).thenReturn(X_SECURITY_ALLOW.getExtensionName());
+ when(extension.getExtensionPath()).thenReturn(path);
+
+ assertThat(validator.validate(extension)).isEmpty();
+ }
+
+ @Test
+ public void validate_invalidScopeSecurityAllowExtension_returnsInvalidScopeErrorTypes() {
+ ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+ createPathList(new Class[] {Path.class});
+ when(extension.getExtensionName()).thenReturn(X_SECURITY_ALLOW.getExtensionName());
+ when(extension.getExtensionPath()).thenReturn(path);
+
+ assertThat(validator.validate(extension))
+ .hasSize(1)
+ .first()
+ .extracting(ExtensionValidationMessage::type)
+ .isEqualTo("INVALID_SCOPE");
+ }
+
+ @Test
+ public void validate_validScopeSecurityTypeDefinitionsExtension_returnsEmptyErrorSet() {
+ ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+ createPathList(new Class[] {OpenApi3.class});
+ when(extension.getExtensionName()).thenReturn(X_SECURITY_TYPE_DEFINITIONS.getExtensionName());
+ when(extension.getExtensionPath()).thenReturn(path);
+
+ assertThat(validator.validate(extension)).isEmpty();
+ }
+
+ @Test
+ public void validate_invalidScopeSecurityTypeDefinitionsExtension_returnsInvalidScopeErrorType() {
+ ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+ createPathList(new Class[] {OpenApi3.class, Path.class});
+ when(extension.getExtensionName()).thenReturn(X_SECURITY_TYPE_DEFINITIONS.getExtensionName());
+ when(extension.getExtensionPath()).thenReturn(path);
+
+ assertThat(validator.validate(extension))
+ .hasSize(1)
+ .first()
+ .extracting(ExtensionValidationMessage::type)
+ .isEqualTo("INVALID_SCOPE");
+ }
+}