Skip to content

Commit

Permalink
Merge pull request #18 from ls1intum/chore/fix-failing-tests-remove-a…
Browse files Browse the repository at this point in the history
…spectconfig

`Ares`: ArchUnit Network access, JVM termination and reflection tests for post-compile mode
  • Loading branch information
MarkusPaulsen authored Aug 26, 2024
2 parents 10a756c + 616c5c2 commit 846e704
Show file tree
Hide file tree
Showing 15 changed files with 852 additions and 365 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.tum.cit.ase.ares.api.architecturetest;

import com.tngtech.archunit.core.domain.JavaClasses;

/**
* Interface for the architecture test cases in any programming language and abstract product of the abstract factory design pattern.
*
Expand All @@ -19,5 +21,5 @@ public interface ArchitectureTestCase {
/**
* Runs the architecture test case in any programming language.
*/
void runArchitectureTestCase();
void runArchitectureTestCase(JavaClasses classes);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
*/
public class FileHandlerConstants {

public static final Path JAVA_FILESYSTEM_INTERACTION_METHODS = Path.of("src" + File.separator + "main" + File.separator + "resources" + File.separator + "archunit" + File.separator + "files" + File.separator + "java" + File.separator + "methods" + File.separator + "file-system-access-methods.txt");
public static final Path JAVA_FILESYSTEM_INTERACTION_CONTENT = Path.of("src" + File.separator + "main" + File.separator + "resources" + File.separator + "archunit" + File.separator + "files" + File.separator + "java" + File.separator + "rules" + File.separator + "file-system-arch-rule.txt");
private static final String JAVA_METHODS_DIRECTORY = "src" + File.separator + "main" + File.separator + "resources" + File.separator + "archunit" + File.separator + "files" + File.separator + "java" + File.separator + "methods" + File.separator;
public static final Path JAVA_FILESYSTEM_INTERACTION_METHODS = Path.of(JAVA_METHODS_DIRECTORY + "file-system-access-methods.txt");
public static final Path JAVA_NETWORK_ACCESS_METHODS = Path.of(JAVA_METHODS_DIRECTORY + "network-access-methods.txt");
public static final Path JAVA_JVM_TERMINATION_METHODS = Path.of(JAVA_METHODS_DIRECTORY + "jvm-termination-methods.txt");
public static final Path JAVA_REFLECTION_METHODS = Path.of(JAVA_METHODS_DIRECTORY + "reflection-methods.txt");

private FileHandlerConstants() {
throw new IllegalArgumentException("FileHandlerConstants is a utility class and should not be instantiated");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package de.tum.cit.ase.ares.api.architecturetest.java;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import de.tum.cit.ase.ares.api.architecturetest.ArchitectureTestCase;
import de.tum.cit.ase.ares.api.architecturetest.java.postcompile.JavaArchitectureTestCaseCollection;
import de.tum.cit.ase.ares.api.util.ProjectSourcesFinder;
import de.tum.cit.ase.ares.api.policy.PackageImport;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static de.tum.cit.ase.ares.api.architecturetest.java.postcompile.JavaArchitectureTestCaseCollection.getArchitectureRuleFileContent;

Expand All @@ -25,43 +29,58 @@ public class JavaArchitectureTestCase implements ArchitectureTestCase {
* Selects the supported architecture test case in the Java programming language.
*/
private final JavaSupportedArchitectureTestCase javaSupportedArchitectureTestCase;
private final Path withinPath;

/**
* List of allowed packages to be imported.
*/
private final Set<String> allowedPackages;

/**
* Constructor for JavaArchitectureTestCase.
*
* @param javaSupportedArchitectureTestCase Selects the supported architecture test case in the Java programming language
*/
public JavaArchitectureTestCase(JavaSupportedArchitectureTestCase javaSupportedArchitectureTestCase, Path withinPath) {
public JavaArchitectureTestCase(JavaSupportedArchitectureTestCase javaSupportedArchitectureTestCase) {
super();
this.allowedPackages = new HashSet<>();
this.javaSupportedArchitectureTestCase = javaSupportedArchitectureTestCase;
this.withinPath = withinPath;
}

public JavaArchitectureTestCase(JavaSupportedArchitectureTestCase javaSupportedArchitectureTestCase, Set<PackageImport> packages) {
super();
this.javaSupportedArchitectureTestCase = javaSupportedArchitectureTestCase;
this.allowedPackages = packages.stream().map(PackageImport::iAllowTheStudentsToImportTheFollowingPackage).collect(Collectors.toSet());
}

/**
* Returns the content of the architecture test case file in the Java programming language.
*/
@Override
public String createArchitectureTestCaseFileContent() {
return getArchitectureRuleFileContent(this.javaSupportedArchitectureTestCase.name());
try {
return getArchitectureRuleFileContent(this.javaSupportedArchitectureTestCase.name());
} catch (AssertionError e) {
throw new SecurityException("Ares Security Error (Stage: Execution): Illegal Statement found: " + e.getMessage());
} catch (IOException e) {
throw new IllegalStateException("Could not load the architecture rule file content", e);
}
}

/**
* Runs the architecture test case in the Java programming language.
*/
@Override
public void runArchitectureTestCase() {
JavaClasses classes = new ClassFileImporter().importPath((ProjectSourcesFinder.isGradleProject() ? "build" : "target") + File.separator + withinPath.toString());
public void runArchitectureTestCase(JavaClasses classes) {
try {
switch (this.javaSupportedArchitectureTestCase) {
case FILESYSTEM_INTERACTION ->
JavaArchitectureTestCaseCollection.NO_CLASS_SHOULD_ACCESS_FILE_SYSTEM.check(classes);
case PACKAGE_IMPORT -> throw new UnsupportedOperationException("Package import not implemented yet");
case PACKAGE_IMPORT -> JavaArchitectureTestCaseCollection.noClassesShouldImportForbiddenPackages(allowedPackages).check(classes);
case THREAD_CREATION -> throw new UnsupportedOperationException("Thread creation not implemented yet");
case COMMAND_EXECUTION ->
throw new UnsupportedOperationException("Command execution not implemented yet");
case NETWORK_CONNECTION ->
throw new UnsupportedOperationException("Network connection not implemented yet");
JavaArchitectureTestCaseCollection.NO_CLASSES_SHOULD_ACCESS_NETWORK.check(classes);
default -> throw new UnsupportedOperationException("Not implemented yet");
}
} catch (AssertionError e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public CustomClassResolver() {
// TODO: We definitely need to improve this. We should not import all classes as it is not memory efficient.
// https://www.javadoc.io/doc/com.tngtech.archunit/archunit/0.10.2/com/tngtech/archunit/core/importer/ClassFileImporter.html
allClasses = new ClassFileImporter()
.withImportOption(location -> !location.contains("jrt"))
.importClasspath();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
import com.google.common.collect.ImmutableMap;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaAccess;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import de.tum.cit.ase.ares.api.architecturetest.java.FileHandlerConstants;
import de.tum.cit.ase.ares.api.architecturetest.java.JavaSupportedArchitectureTestCase;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static de.tum.cit.ase.ares.api.architecturetest.java.JavaSupportedArchitectureTestCase.FILESYSTEM_INTERACTION;
import static de.tum.cit.ase.ares.api.architecturetest.java.JavaSupportedArchitectureTestCase.NETWORK_CONNECTION;

/**
* This class runs the security rules on the architecture for the post-compile mode.
Expand All @@ -25,29 +28,12 @@ private JavaArchitectureTestCaseCollection() {
throw new IllegalArgumentException("This class should not be instantiated");
}

public static final String LOAD_FORBIDDEN_METHODS_FROM_FILE_FAILED = "Could not load the architecture rule file content";
/**
* Map to store the forbidden methods for the supported architectural test cases
*/
private static final ImmutableMap.Builder<String, Set<String>> FORBIDDEN_METHODS_FOR_SUPPORTED_ARCHITECTURAL_TEST_CASE = ImmutableMap.builder();

/**
* Map to store the content of the architecture test case files
*/
private static final ImmutableMap.Builder<String, String> ARCHITECTURAL_RULES_CONTENT_MAP = ImmutableMap.builder();

/**
* The packages that should not be accessed by the student submission.
*/
private static final List<String> BANNED_FILESYSTEM_ACCESS_PACKAGES = List.of(
"java.nio.file",
"java.util.prefs",
"sun.print",
"java.util.jar",
"java.util.zip",
"sun.awt.X11",
"javax.imageio",
"javax.sound.midi",
"javax.swing.filechooser");

/**
* Load pre file contents
Expand All @@ -57,14 +43,6 @@ public static void loadForbiddenMethodsFromFile(Path filePath, String key) throw
FORBIDDEN_METHODS_FOR_SUPPORTED_ARCHITECTURAL_TEST_CASE.put(key, content);
}

/**
* Load the content of the architecture test case files
*/
public static void loadArchitectureRuleFileContent(Path filePath, String key) throws IOException {
String content = Files.readString(filePath);
ARCHITECTURAL_RULES_CONTENT_MAP.put(key, content);
}

/**
* Get the content of a file from the architectural rules storage
*/
Expand All @@ -75,23 +53,99 @@ public static Set<String> getForbiddenMethods(String key) {
/**
* Get the content of a file from the architectural rules storage
*/
public static String getArchitectureRuleFileContent(String key) {
return ARCHITECTURAL_RULES_CONTENT_MAP.build().get(key);
public static String getArchitectureRuleFileContent(String key) throws IOException {
return Files.readString(Paths.get("src", "main", "resources", "archunit", "files", "java", "rules", "%s.txt".formatted(key)));
}

/**
* This method checks if any class in the given package accesses the file system.
*/
public static final ArchRule NO_CLASS_SHOULD_ACCESS_FILE_SYSTEM = ArchRuleDefinition.noClasses()
.should(new TransitivelyAccessesMethodsCondition(new DescribedPredicate<>("accesses file system") {
private Set<String> forbiddenMethods;

@Override
public boolean test(JavaAccess<?> javaAccess) {
if (forbiddenMethods == null) {
try {
loadForbiddenMethodsFromFile(FileHandlerConstants.JAVA_FILESYSTEM_INTERACTION_METHODS, JavaSupportedArchitectureTestCase.FILESYSTEM_INTERACTION.name());
} catch (IOException e) {
throw new IllegalStateException(LOAD_FORBIDDEN_METHODS_FROM_FILE_FAILED, e);
}
forbiddenMethods = getForbiddenMethods(FILESYSTEM_INTERACTION.name());
}

return forbiddenMethods.stream().anyMatch(method -> javaAccess.getTarget().getFullName().startsWith(method));
}
}));

/**
* This method checks if any class in the given package accesses the network.
*/
public static final ArchRule NO_CLASSES_SHOULD_ACCESS_NETWORK = ArchRuleDefinition.noClasses()
.should(new TransitivelyAccessesMethodsCondition(new DescribedPredicate<>("accesses network") {
private Set<String> forbiddenMethods;

@Override
public boolean test(JavaAccess<?> javaAccess) {
if (forbiddenMethods == null) {
try {
loadForbiddenMethodsFromFile(FileHandlerConstants.JAVA_NETWORK_ACCESS_METHODS, JavaSupportedArchitectureTestCase.NETWORK_CONNECTION.name());
} catch (IOException e) {
throw new IllegalStateException(LOAD_FORBIDDEN_METHODS_FROM_FILE_FAILED, e);
}
forbiddenMethods = getForbiddenMethods(NETWORK_CONNECTION.name());
}

return forbiddenMethods.stream().anyMatch(method -> javaAccess.getTarget().getFullName().startsWith(method));
}
}));

/**
* This method checks if any class in the given package imports forbidden packages.
*/
public static ArchRule noClassesShouldImportForbiddenPackages(Set<String> allowedPackages) {
return ArchRuleDefinition.noClasses()
.should()
.transitivelyDependOnClassesThat(new DescribedPredicate<>("imports package") {
@Override
public boolean test(JavaClass javaClass) {
return !allowedPackages.contains(javaClass.getPackageName());
}
});
}

/**
* This method checks if any class in the given package uses reflection.
*/
public static final ArchRule NO_CLASSES_SHOULD_USE_REFLECTION = ArchRuleDefinition.noClasses()
.should(new TransitivelyAccessesMethodsCondition(new DescribedPredicate<>("uses reflection") {
@Override
public boolean test(JavaAccess<?> javaAccess) {
return javaAccess.getTarget().getFullName().startsWith("java.lang.reflect")
|| javaAccess.getTarget().getFullName().startsWith("sun.reflect.misc");
}
}));

/**
* This method checks if any class in the given package uses the command line.
*/
public static final ArchRule NO_CLASSES_SHOULD_TERMINATE_JVM = ArchRuleDefinition.noClasses()
.should(new TransitivelyAccessesMethodsCondition((new DescribedPredicate<>("terminates JVM") {
private Set<String> forbiddenMethods;

@Override
public boolean test(JavaAccess<?> javaAccess) {
if (BANNED_FILESYSTEM_ACCESS_PACKAGES.stream().anyMatch(p -> javaAccess.getTarget().getFullName().startsWith(p))) {
return true;
if (forbiddenMethods == null) {
try {
loadForbiddenMethodsFromFile(FileHandlerConstants.JAVA_JVM_TERMINATION_METHODS, "JVM_TERMINATION");
} catch (IOException e) {
throw new IllegalStateException(LOAD_FORBIDDEN_METHODS_FROM_FILE_FAILED, e);
}
forbiddenMethods = getForbiddenMethods("JVM_TERMINATION");
}

Optional<Set<String>> bannedMethods = Optional.ofNullable(JavaArchitectureTestCaseCollection.getForbiddenMethods(FILESYSTEM_INTERACTION.name()));
return bannedMethods.map(strings -> strings.contains(javaAccess.getTarget().getName())).orElse(false);
return forbiddenMethods.stream().anyMatch(method -> javaAccess.getTarget().getFullName().startsWith(method));
}
}, FILESYSTEM_INTERACTION));
})));
}
Loading

0 comments on commit 846e704

Please sign in to comment.