Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WFARQ-192] Add an appendPermissions to the DeploymentDescriptors hel… #499

Merged
merged 1 commit into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,38 @@
import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.asset.ByteArrayAsset;
import org.jboss.shrinkwrap.api.container.WebContainer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.wildfly.testing.tools.xml.CloseableXMLStreamWriter;
import org.xml.sax.SAXException;

/**
* A utility to generate various deployment descriptors.
Expand Down Expand Up @@ -235,25 +247,80 @@ public static byte[] createPermissionsXml(Permission... permissions) {
*/
public static byte[] createPermissionsXml(final Iterable<? extends Permission> permissions,
final Permission... additionalPermissions) {
try (
ByteArrayOutputStream out = new ByteArrayOutputStream();
CloseableXMLStreamWriter writer = CloseableXMLStreamWriter.of(out);) {
final Set<PermissionDescription> allPermissions = new LinkedHashSet<>();
permissions.forEach(permission -> allPermissions.add(PermissionDescription.of(permission)));
allPermissions.addAll(Stream.of(additionalPermissions)
.map(PermissionDescription::of)
.collect(Collectors.toSet()));
return createPermissionsXml(allPermissions);
}

writer.writeStartDocument("utf-8", "1.0");
writer.writeStartElement("permissions");
writer.writeNamespace(null, "https://jakarta.ee/xml/ns/jakartaee");
writer.writeAttribute("version", "10");
addPermissionXml(writer, permissions);
if (additionalPermissions != null && additionalPermissions.length > 0) {
addPermissionXml(writer, List.of(additionalPermissions));
/**
* Creates a new asset with the new permissions appended to the current permissions. Note that duplicates will not
* be added. A duplicates is considered a {@link Permission} with the same {@linkplain Class#getName() class name},
* same {@linkplain Permission#getName() name} and same {@linkplain Permission#getActions() actions}.
*
* @param currentPermissions the current permissions, must be valid XML content
* @param permissions the permissions to add
*
* @return a new asset to replace the current {@code permissions.xml} file
*/
public static Asset appendPermissions(final Asset currentPermissions, final Permission... permissions) {
final Set<Permission> orderedPermissions = new LinkedHashSet<>();
Collections.addAll(orderedPermissions, permissions);
return appendPermissions(currentPermissions, orderedPermissions);
}

/**
* Creates a new asset with the new permissions appended to the current permissions. Note that duplicates will not
* be added. A duplicates is considered a {@link Permission} with the same {@linkplain Class#getName() class name},
* same {@linkplain Permission#getName() name} and same {@linkplain Permission#getActions() actions}.
*
* @param currentPermissions the current permissions, must be valid XML content
* @param permissions the permissions to add
*
* @return a new asset to replace the current {@code permissions.xml} file
*/
public static Asset appendPermissions(final Asset currentPermissions, final Iterable<? extends Permission> permissions) {
final Set<PermissionDescription> allPermissions = new LinkedHashSet<>();
try {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilder builder = factory.newDocumentBuilder();
try (InputStream in = currentPermissions.openStream()) {
final Document doc = builder.parse(in);

final Element root = doc.getDocumentElement();
final NodeList xmlPermissions = root.getElementsByTagName("permission");
for (int i = 0; i < xmlPermissions.getLength(); i++) {
final Node permission = xmlPermissions.item(i);
String className = null;
String name = null;
String actions = null;
if (permission.hasChildNodes()) {
final NodeList children = permission.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
final Node child = children.item(j);
if (child.getNodeType() == Node.ELEMENT_NODE) {
if (child.getNodeName().equals("class-name")) {
className = child.getTextContent();
} else if (child.getNodeName().equals("name")) {
name = child.getTextContent();
} else if (child.getNodeName().equals("actions")) {
actions = child.getTextContent();
}
}
}
}
if (className != null) {
allPermissions.add(new PermissionDescription(className, name, actions));
}
}
}
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
return out.toByteArray();
} catch (IOException | XMLStreamException e) {
throw new RuntimeException("Failed to create the permissions.xml file.", e);
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new RuntimeException("Failed to append permissions.xml file.", e);
}
permissions.forEach(p -> allPermissions.add(PermissionDescription.of(p)));
return new ByteArrayAsset(createPermissionsXml(allPermissions));
}

/**
Expand Down Expand Up @@ -310,20 +377,21 @@ public static Collection<FilePermission> createTempDirPermission(final String ac
return List.of(new FilePermission(tempDir, "read"), new FilePermission(tempDir + "-", actions));
}

private static void addPermissionXml(final XMLStreamWriter writer, final Iterable<? extends Permission> permissions)
private static void addPermissionXml(final XMLStreamWriter writer,
final Iterable<? extends PermissionDescription> permissions)
throws XMLStreamException {
for (Permission permission : permissions) {
for (PermissionDescription permission : permissions) {
writer.writeStartElement("permission");

writer.writeStartElement("class-name");
writer.writeCharacters(permission.getClass().getName());
writer.writeCharacters(permission.className);
writer.writeEndElement();

writer.writeStartElement("name");
writer.writeCharacters(permission.getName());
writer.writeCharacters(permission.name);
writer.writeEndElement();

final String actions = permission.getActions();
final String actions = permission.actions;
if (actions != null && !actions.isEmpty()) {
writer.writeStartElement("actions");
writer.writeCharacters(actions);
Expand All @@ -332,4 +400,64 @@ private static void addPermissionXml(final XMLStreamWriter writer, final Iterabl
writer.writeEndElement();
}
}

private static byte[] createPermissionsXml(final Set<PermissionDescription> permissions) {
try (
ByteArrayOutputStream out = new ByteArrayOutputStream();
CloseableXMLStreamWriter writer = CloseableXMLStreamWriter.of(out);) {

writer.writeStartDocument("utf-8", "1.0");
writer.writeStartElement("permissions");
writer.writeNamespace(null, "https://jakarta.ee/xml/ns/jakartaee");
writer.writeAttribute("version", "10");
addPermissionXml(writer, permissions);
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
return out.toByteArray();
} catch (IOException | XMLStreamException e) {
throw new RuntimeException("Failed to create the permissions.xml file.", e);
}
}

private static class PermissionDescription {
private final String className;
private final String name;
private final String actions;

private PermissionDescription(final String className, final String name, final String actions) {
this.className = className;
this.name = name;
this.actions = actions;
}

static PermissionDescription of(final Permission permission) {
return new PermissionDescription(permission.getClass()
.getName(), permission.getName(), permission.getActions());
}

@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PermissionDescription)) {
return false;
}
final PermissionDescription other = (PermissionDescription) obj;
return Objects.equals(className, other.className)
&& Objects.equals(name, other.name)
&& Objects.equals(actions, other.actions);
}

@Override
public int hashCode() {
return Objects.hash(className, name, actions);
}

@Override
public String toString() {
return "PermissionDescription[className=" + className + ", name=" + name + ", actions=" + actions + "]";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@

package org.wildly.testing.tools.deployments;

import java.io.IOException;
import java.io.InputStream;
import java.net.SocketPermission;
import java.security.Permission;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.PropertyPermission;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jboss.shrinkwrap.api.asset.Asset;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Parser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -68,6 +77,50 @@ public void permissionsXml(final Set<Permission> permissions) {
new String(DeploymentDescriptors.createPermissionsXml(permissions)));
}

@Test
public void appendPermissionsXml() throws IOException {
final Set<Permission> permissions = new LinkedHashSet<>();
permissions.add(new RuntimePermission("test.permissions", "action1"));
permissions.add(new SocketPermission("localhost", "connect,resolve"));
permissions.add(new PropertyPermission("java.io.tmpdir", "read"));
permissions.add(new PropertyPermission("test.property", "read,write"));

final Set<Permission> allPermissions = new LinkedHashSet<>(permissions);
final Set<Permission> additionalPermissions = new LinkedHashSet<>();
additionalPermissions.add(new RuntimePermission("getClassLoader"));
additionalPermissions.add(new PropertyPermission("java.io.tmpdir", "read,write"));
additionalPermissions.add(new PropertyPermission("other.property", "read"));
additionalPermissions.add(new PropertyPermission("test.property", "read,write"));

allPermissions.addAll(additionalPermissions);

final String expected = generatePermissionsXml(allPermissions);
final Asset permissionsXml = DeploymentDescriptors.createPermissionsXmlAsset(permissions);
try (
InputStream in = DeploymentDescriptors.appendPermissions(permissionsXml, additionalPermissions)
.openStream()) {
final String assetValue = new String(in.readAllBytes());
Assertions.assertEquals(expected, assetValue);
// Ensure the java.io.tmpdir is inserted twice, but test.property only once
final Document document = Jsoup.parse(assetValue, Parser.xmlParser());
List<Element> elements = document.select("permission")
.stream()
.filter(e -> e.select("class-name").text().equals(PropertyPermission.class.getName())
&& e.select("name").text().equals("java.io.tmpdir"))
.collect(Collectors.toList());
Assertions.assertEquals(2, elements.size(),
() -> String.format("Expected two java.io.tmpdir properties in %n%s", assetValue));

elements = document.select("permission")
.stream()
.filter(e -> e.select("class-name").text().equals(PropertyPermission.class.getName())
&& e.select("name").text().equals("test.property"))
.collect(Collectors.toList());
Assertions.assertEquals(1, elements.size(),
() -> String.format("Expected one test.property properties in %n%s", assetValue));
}
}

static Stream<Arguments> moduleArguments() {
return Stream.of(
Arguments.of(Named.of("addedModules", Set.of("org.wildfly.arquillian", "org.wildfly.arquillian.test")),
Expand Down