Merge "b/163430475 Add missing traversal test classes"
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtendedValidator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtendedValidator.java
index 18400b8..f7b7551 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtendedValidator.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtendedValidator.java
@@ -1,5 +1,7 @@
 package com.apigee.security.oas.extendedvalidator;
 
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.valueOfExtensionName;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
@@ -10,19 +12,44 @@
 
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
   private final TraversalHelperFactory traversalHelperFactory;
+  private final ExtensionSchemaValidator schemaValidator;
+  private final ExtensionScopeValidator scopeValidator;
 
   @Inject
-  BaseExtendedValidator(TraversalHelperFactory traversalHelperFactory) {
+  BaseExtendedValidator(
+      TraversalHelperFactory traversalHelperFactory,
+      ExtensionSchemaValidator schemaValidator,
+      ExtensionScopeValidator scopeValidator) {
     this.traversalHelperFactory = traversalHelperFactory;
+    this.schemaValidator = schemaValidator;
+    this.scopeValidator = scopeValidator;
   }
 
   @Override
-  public void validate(OpenApi3 openApiSpec) {
+  public ImmutableSet<ExtensionValidationMessage> validate(OpenApi3 openApiSpec) {
+    ImmutableSet<Extension> extensions = collectExtensions(openApiSpec);
+
+    logger.atInfo().log("Found %s extensions", extensions.size());
+
+    return validateSchemaAndScope(extensions);
+  }
+
+  private ImmutableSet<Extension> collectExtensions(OpenApi3 openApiSpec) {
     TraversalHelper traversalHelper = traversalHelperFactory.create(ImmutableList.of());
     traversalHelper.sendOpenApiTraversal(openApiSpec);
-    ImmutableSet<Extension> extensions = traversalHelper.traverse();
+    return traversalHelper.traverse();
+  }
 
-    // TODO(b/161441872) : Add extension validation logic
-    logger.atInfo().log("%s", extensions);
+  private ImmutableSet<ExtensionValidationMessage> validateSchemaAndScope(
+      ImmutableSet<Extension> extensions) {
+
+    return extensions.stream()
+        .filter(extension -> valueOfExtensionName(extension.getExtensionName()).isPresent())
+        .flatMap(
+            extension ->
+                ImmutableSet.<ExtensionValidationMessage>builder()
+                    .addAll(extension.validate(schemaValidator))
+                    .addAll(extension.validate(scopeValidator)).build().stream())
+        .collect(ImmutableSet.toImmutableSet());
   }
 }
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
index 8ff013a..ac576b2 100644
--- 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
@@ -1,9 +1,13 @@
 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_FRESH_SCOPE_ERROR;
+import static com.apigee.security.oas.extendedvalidator.ErrorMessage.X_SECURITY_RULES_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_FRESH;
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_RULES;
 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;
@@ -17,6 +21,7 @@
 import com.google.common.flogger.FluentLogger;
 import java.util.Optional;
 import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.Info;
 import org.openapi4j.parser.model.v3.OpenApi3;
 import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Parameter;
@@ -30,7 +35,9 @@
           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));
+              X_SECURITY_TYPE_DEFINITIONS, X_SECURITY_TYPE_DEFINITIONS_SCOPE_ERROR,
+              X_SECURITY_FRESH, X_SECURITY_FRESH_SCOPE_ERROR,
+              X_SECURITY_RULES, X_SECURITY_RULES_SCOPE_ERROR));
 
   private static final ImmutableMap<ExtensionName, ImmutableSet<Class<? extends OpenApiSchema>>>
       allowedParentsMap =
@@ -38,7 +45,9 @@
               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)));
+                  X_SECURITY_TYPE_DEFINITIONS, ImmutableSet.of(OpenApi3.class),
+                  X_SECURITY_FRESH, ImmutableSet.of(OpenApi3.class, Info.class),
+                  X_SECURITY_RULES, ImmutableSet.of(OpenApi3.class)));
 
   /**
    * Validates the scope of an supported {@link Extension} by contrasting it against its appropriate
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
index 40b6c53..fe13127 100644
--- 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
@@ -10,6 +10,9 @@
       "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."),
+  X_SECURITY_FRESH_SCOPE_ERROR(
+      "INVALID_SCOPE", "extension should be on top-level or sub-level scope."),
+  X_SECURITY_RULES_SCOPE_ERROR("INVALID_SCOPE", "extension should be on top-level scope."),
   INVALID_SCHEMA("INVALID_SCHEMA", "extension has an invalid schema.");
 
   private final String errorType;
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtendedValidator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtendedValidator.java
index dae3f6b..44cc68c 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtendedValidator.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtendedValidator.java
@@ -1,5 +1,6 @@
 package com.apigee.security.oas.extendedvalidator;
 
+import com.google.common.collect.ImmutableSet;
 import org.openapi4j.parser.model.v3.OpenApi3;
 
 /**
@@ -9,5 +10,5 @@
 public interface ExtendedValidator {
 
   /** Validates ApiSecurityTool's extensions in passed {@link OpenApi3} object. */
-  void validate(OpenApi3 openApi3);
+  ImmutableSet<ExtensionValidationMessage> validate(OpenApi3 openApi3);
 }
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtendedValidatorTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtendedValidatorTest.java
index 46871da..86be8d9 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtendedValidatorTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtendedValidatorTest.java
@@ -1,10 +1,15 @@
 package com.apigee.security.oas.extendedvalidator;
 
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_TYPE;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -23,7 +28,13 @@
   @Rule public final MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
 
   @Mock private OpenApi3 openApiSpec;
+  @Mock private Extension supportedExtension;
+  @Mock private Extension unsupportedExtension;
+  @Mock private ExtensionValidationMessage schemaMessage;
+  @Mock private ExtensionValidationMessage scopeMessage;
 
+  @Mock private ExtensionSchemaValidator schemaValidator;
+  @Mock private ExtensionScopeValidator scopeValidator;
   @Mock private TraversalHelperFactory traversalHelperFactory;
   @Mock private TraversalHelper traversalHelper;
   @InjectMocks private BaseExtendedValidator baseExtendedValidator;
@@ -32,12 +43,45 @@
   @Before
   public void setup() {
     when(traversalHelperFactory.create(any(ImmutableList.class))).thenReturn(traversalHelper);
+    when(traversalHelper.traverse()).thenReturn(ImmutableSet.of(unsupportedExtension));
   }
 
   @Test
   public void validate_openApiSpec_callsTraversalCoordinatorTraverse() {
+    when(unsupportedExtension.getExtensionName()).thenReturn("x-custom");
+
     baseExtendedValidator.validate(openApiSpec);
 
     verify(traversalHelper).sendOpenApiTraversal(openApiSpec);
   }
+
+  @Test
+  public void validate_supportedExtension_callsExtensionValidate() {
+    when(supportedExtension.getExtensionName()).thenReturn(X_SECURITY_TYPE.getExtensionName());
+    when(supportedExtension.validate(any(ExtensionValidator.class))).thenReturn(ImmutableSet.of());
+    when(traversalHelper.traverse()).thenReturn(ImmutableSet.of(supportedExtension));
+
+    baseExtendedValidator.validate(openApiSpec);
+
+    verify(supportedExtension, atLeastOnce()).validate(any(ExtensionValidator.class));
+  }
+
+  @Test
+  public void validate_unsupportedExtension_shouldNotCallExtensionValidate() {
+    when(unsupportedExtension.getExtensionName()).thenReturn("x-custom");
+
+    baseExtendedValidator.validate(openApiSpec);
+
+    verify(unsupportedExtension, never()).validate(any(ExtensionValidator.class));
+  }
+
+  @Test
+  public void validate_withExtensions_returnsExactExtensionValidationMessages() {
+    when(supportedExtension.getExtensionName()).thenReturn(X_SECURITY_TYPE.getExtensionName());
+    when(traversalHelper.traverse()).thenReturn(ImmutableSet.of(supportedExtension));
+    when(supportedExtension.validate(schemaValidator)).thenReturn(ImmutableSet.of(schemaMessage));
+    when(supportedExtension.validate(scopeValidator)).thenReturn(ImmutableSet.of(scopeMessage));
+
+    assertThat(baseExtendedValidator.validate(openApiSpec)).hasSize(2);
+  }
 }
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
index aab22ec..dfbf7fd 100644
--- 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
@@ -1,6 +1,8 @@
 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_FRESH;
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_RULES;
 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;
@@ -151,4 +153,52 @@
         .extracting(ExtensionValidationMessage::type)
         .isEqualTo("INVALID_SCOPE");
   }
+
+  @Test
+  public void validate_validScopeSecurityFreshExtension_returnsEmptyErrorSet() {
+    ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+        createPathList(new Class[] {OpenApi3.class});
+    when(extension.getExtensionName()).thenReturn(X_SECURITY_FRESH.getExtensionName());
+    when(extension.getExtensionPath()).thenReturn(path);
+
+    assertThat(validator.validate(extension)).isEmpty();
+  }
+
+  @Test
+  public void validate_invalidScopeSecurityFreshExtension_returnsInvalidScopeErrorType() {
+    ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+        createPathList(new Class[] {OpenApi3.class, Path.class});
+    when(extension.getExtensionName()).thenReturn(X_SECURITY_FRESH.getExtensionName());
+    when(extension.getExtensionPath()).thenReturn(path);
+
+    assertThat(validator.validate(extension))
+        .hasSize(1)
+        .first()
+        .extracting(ExtensionValidationMessage::type)
+        .isEqualTo("INVALID_SCOPE");
+  }
+
+  @Test
+  public void validate_validScopeSecurityRulesExtension_returnsEmptyErrorSet() {
+    ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+        createPathList(new Class[] {OpenApi3.class});
+    when(extension.getExtensionName()).thenReturn(X_SECURITY_RULES.getExtensionName());
+    when(extension.getExtensionPath()).thenReturn(path);
+
+    assertThat(validator.validate(extension)).isEmpty();
+  }
+
+  @Test
+  public void validate_invalidScopeSecurityRulesExtension_returnsInvalidScopeErrorType() {
+    ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path =
+        createPathList(new Class[] {OpenApi3.class, Path.class});
+    when(extension.getExtensionName()).thenReturn(X_SECURITY_RULES.getExtensionName());
+    when(extension.getExtensionPath()).thenReturn(path);
+
+    assertThat(validator.validate(extension))
+        .hasSize(1)
+        .first()
+        .extracting(ExtensionValidationMessage::type)
+        .isEqualTo("INVALID_SCOPE");
+  }
 }