/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
/ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.wildfly.extension.security.manager.deployment;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.module.ModuleSpecification;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoader;
import org.jboss.modules.security.PermissionFactory;
import org.jboss.vfs.VirtualFile;
/**
* This class implements a {@link DeploymentUnitProcessor} that parses security permission files that might be
* included in application components.
* <p/>
* The EE7 specification (section EE6.2.2.6) allows application components to specify required security permissions:
* <p/>
* "<i>Permission declarations must be stored in META-INF/permissions.xml file within an EJB, web, application client, or
* resource adapter archive in order for them to be located and processed.
* <p/>
* The permissions for a packaged library are the same as the permissions for the module. Thus, if a library is packaged
* in a .war file, it gets the permissions of the .war file.
* <p/>
* For applications packaged in an .ear file, the declaration of permissions must be at .ear file level. This permission
* set is applied to all modules and libraries packaged within the .ear file or within its contained modules. Any
* permissions.xml files within such packaged modules are ignored, regardless of whether a permissions.xml file has been
* supplied for the .ear file itself.</i>"
* <p/>
* As can be noted, the EE spec doesn't allow sub-deployments to override permissions set at the .ear level. We find it
* a bit too restrictive, so we introduced the META-INF/jboss-permissions.xml descriptor. It uses the same schema as the
* standard permissions.xml file but, unlike the latter, is always processed and the permissions contained in it override
* any permissions set by a parent deployment. If a deployment contains both permissions files, jboss-permissions.xml
* takes precedence over the standard permissions.xml.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class PermissionsParserProcessor implements DeploymentUnitProcessor {
private static final String PERMISSIONS_XML = "META-INF/permissions.xml";
private static final String JBOSS_PERMISSIONS_XML = "META-INF/jboss-permissions.xml";
// minimum set of permissions that are to be granted to all deployments.
private final List<PermissionFactory> minPermissions;
/**
* Creates an instance of {@link PermissionsParserProcessor} with the specified minimum and maximum set of permissions.
*
* @param minPermissions a {@link List} containing the permissions that are to be granted to all deployments.
*/
public PermissionsParserProcessor(List<PermissionFactory> minPermissions) {
this.minPermissions = minPermissions;
}
@Override
public void deploy(final DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
final ResourceRoot deploymentRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
final ModuleLoader moduleLoader = deploymentUnit.getAttachment(Attachments.SERVICE_MODULE_LOADER);
final ModuleIdentifier moduleIdentifier = deploymentUnit.getAttachment(Attachments.MODULE_IDENTIFIER);
// non-spec behavior: always process permissions declared in META-INF/jboss-permissions.xml.
VirtualFile jbossPermissionsXML = deploymentRoot.getRoot().getChild(JBOSS_PERMISSIONS_XML);
if (jbossPermissionsXML.exists() && jbossPermissionsXML.isFile()) {
List<PermissionFactory> factories = this.parsePermissions(jbossPermissionsXML, moduleLoader, moduleIdentifier);
for (PermissionFactory factory : factories) {
moduleSpecification.addPermissionFactory(factory);
}
// add the permissions specified in the minimum set.
for (PermissionFactory factory : this.minPermissions) {
moduleSpecification.addPermissionFactory(factory);
}
}
// spec compliant behavior: only top-level deployments are processed (sub-deployments inherit permissions
// defined at the .ear level, if any).
else {
if (deploymentUnit.getParent() == null) {
VirtualFile permissionsXML = deploymentRoot.getRoot().getChild(PERMISSIONS_XML);
if (permissionsXML.exists() && permissionsXML.isFile()) {
// parse the permissions and attach them in the deployment unit.
List<PermissionFactory> factories = this.parsePermissions(permissionsXML, moduleLoader, moduleIdentifier);
for (PermissionFactory factory : factories) {
moduleSpecification.addPermissionFactory(factory);
}
}
// add the minimum set of permissions to top-level deployments - sub-deployments will inherit them automatically.
for (PermissionFactory factory : this.minPermissions) {
moduleSpecification.addPermissionFactory(factory);
}
} else {
ModuleSpecification parentSpecification = deploymentUnit.getParent().getAttachment(Attachments.MODULE_SPECIFICATION);
List<PermissionFactory> factories = parentSpecification.getPermissionFactories();
if (factories != null && factories.size() > 0) {
// parent deployment contains permissions: subdeployments inherit those permissions.
for (PermissionFactory factory : factories) {
moduleSpecification.addPermissionFactory(factory);
}
}
}
}
}
@Override
public void undeploy(final DeploymentUnit context) {
}
/**
* <p>
* Parses the permissions declared in the specified file. The permissions are wrapped in factory objects so they can
* be lazily instantiated after the deployment unit module has been created.
* </p>
*
* @param file the {@link VirtualFile} that contains the permissions declarations.
* @param loader the {@link ModuleLoader} that is to be used by the factory to instantiate the permission.
* @param identifier the {@link ModuleIdentifier} that is to be used by the factory to instantiate the permission.
* @return a list of {@link PermissionFactory} objects representing the parsed permissions.
* @throws DeploymentUnitProcessingException if an error occurs while parsing the permissions.
*/
private List<PermissionFactory> parsePermissions(final VirtualFile file, final ModuleLoader loader, final ModuleIdentifier identifier)
throws DeploymentUnitProcessingException {
InputStream inputStream = null;
try {
inputStream = file.openStream();
final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
XMLStreamReader xmlReader = inputFactory.createXMLStreamReader(inputStream);
return PermissionsParser.parse(xmlReader, loader, identifier);
} catch (Exception e) {
throw new DeploymentUnitProcessingException(e.getMessage(), e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
}
}
}
}