/**
* Copyright (c) Codice Foundation
* <p/>
* 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 3 of the
* License, or any later version.
* <p/>
* This program 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. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package org.codice.ddf.catalog.security.policy.xml;
import java.io.Serializable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.codehaus.stax2.XMLInputFactory2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ddf.catalog.data.Metacard;
import ddf.catalog.data.Result;
import ddf.catalog.operation.Query;
import ddf.catalog.operation.ResourceRequest;
import ddf.catalog.operation.ResourceResponse;
import ddf.catalog.plugin.PolicyPlugin;
import ddf.catalog.plugin.PolicyResponse;
import ddf.catalog.plugin.StopProcessingException;
import ddf.catalog.plugin.impl.PolicyResponseImpl;
/**
* Plugin that parses XML metadata for elements that contain attributes with security policy information
*/
public class XmlAttributeSecurityPolicyPlugin implements PolicyPlugin {
/**
* Logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(
XmlAttributeSecurityPolicyPlugin.class);
/**
* Input factory
*/
private static volatile XMLInputFactory xmlInputFactory = null;
static {
XMLInputFactory xmlInputFactoryTmp = XMLInputFactory2.newInstance();
xmlInputFactoryTmp.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES,
Boolean.FALSE);
xmlInputFactoryTmp.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,
Boolean.FALSE);
xmlInputFactoryTmp.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); // This disables DTDs entirely for that factory
xmlInputFactoryTmp.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE);
xmlInputFactory = xmlInputFactoryTmp;
}
/**
* Default XML elements to be parsed.
* Overridden with the metatype.
*/
private List<String> xmlElements = new ArrayList<>();
/**
* Default match all attribute.
* Overridden with the metatype.
*/
private List<String> securityAttributeUnions = new ArrayList<>();
/**
* Default match all attribute.
* Overridden with the metatype.
*/
private List<String> securityAttributeIntersections = new ArrayList<>();
/**
* Parse XML metadata using StAX to find the security element
*
* @param metacard XML metadata to parse
*/
public Map<String, Set<String>> parseSecurityMetadata(Metacard metacard) {
Map<String, Set<String>> securityMap = new HashMap<>();
String xmlMetadata = metacard.getMetadata();
if (xmlMetadata == null) {
return securityMap;
}
XMLStreamReader xmlStreamReader = null;
try {
xmlStreamReader = xmlInputFactory.createXMLStreamReader(new StringReader(xmlMetadata));
Map<String, Set<Set<String>>> intersectionMap = new HashMap<>();
while (xmlStreamReader.hasNext()) {
int event = xmlStreamReader.next();
if (event == XMLStreamConstants.START_ELEMENT) {
String localName = xmlStreamReader.getLocalName();
if (xmlElements.contains(localName)) {
LOGGER.debug("Parsing security attribute.");
parseSecurityBlock(securityMap, intersectionMap, xmlStreamReader);
}
}
}
} catch (XMLStreamException e) {
//if this happens and message redacting is enabled, the message will be excluded from results
LOGGER.info("Unable to parse security from XML metadata.", e);
} finally {
if (xmlStreamReader != null) {
try {
xmlStreamReader.close();
} catch (XMLStreamException e) {
//ignore
}
}
}
return securityMap;
}
/**
* Parses the security element in the metadata
*
* @param xmlStreamReader xml stream
*/
private void parseSecurityBlock(Map<String, Set<String>> securityMap,
Map<String, Set<Set<String>>> intersectionMap, XMLStreamReader xmlStreamReader) {
LOGGER.debug("Parsing metacard security block");
int numAttrs = xmlStreamReader.getAttributeCount();
for (int i = 0; i < numAttrs; i++) {
String name = xmlStreamReader.getAttributeLocalName(i);
if (getSecurityAttributeUnions().contains(name)) {
LOGGER.debug("Found {} in metacard", name);
if (!securityMap.containsKey(name)) {
securityMap.put(name, new HashSet<>());
}
buildSecurityAttribute(securityMap.get(name), xmlStreamReader.getAttributeValue(i));
} else if (getSecurityAttributeIntersections().contains(name)) {
if (!intersectionMap.containsKey(name)) {
intersectionMap.put(name, new HashSet<>());
}
Set<String> valueSet = new HashSet<>();
buildSecurityAttribute(valueSet, xmlStreamReader.getAttributeValue(i));
intersectionMap.get(name)
.add(valueSet);
}
}
buildIntersectionAttributes(securityMap, intersectionMap);
}
private void buildSecurityAttribute(Set<String> builderSet, String attributeValue) {
StringTokenizer tokenizer = new StringTokenizer(attributeValue, " ", false);
while (tokenizer.hasMoreElements()) {
String value = tokenizer.nextToken();
LOGGER.debug("Adding {} to set", value);
builderSet.add(value);
}
}
private void buildIntersectionAttributes(Map<String, Set<String>> securityMap,
Map<String, Set<Set<String>>> intersectionMap) {
if (!intersectionMap.isEmpty()) {
for (Map.Entry<String, Set<Set<String>>> entry : intersectionMap.entrySet()) {
Set<Set<String>> setsOfValues = entry.getValue();
if (!setsOfValues.isEmpty()) {
Iterator<Set<String>> iterator = setsOfValues.iterator();
Set<String> endSet = iterator.next();
while (iterator.hasNext()) {
endSet.retainAll(iterator.next());
}
securityMap.put(entry.getKey(), endSet);
}
}
}
}
public List<String> getXmlElements() {
if (xmlElements == null) {
return new ArrayList<>();
} else {
return xmlElements;
}
}
public void setXmlElements(List<String> xmlElements) {
this.xmlElements = xmlElements;
}
public List<String> getSecurityAttributeUnions() {
return securityAttributeUnions;
}
public void setSecurityAttributeUnions(List<String> securityAttributeUnions) {
this.securityAttributeUnions = securityAttributeUnions;
}
public List<String> getSecurityAttributeIntersections() {
return securityAttributeIntersections;
}
public void setSecurityAttributeIntersections(List<String> securityAttributeIntersections) {
this.securityAttributeIntersections = securityAttributeIntersections;
}
@Override
public PolicyResponse processPreCreate(Metacard metacard, Map<String, Serializable> map)
throws StopProcessingException {
if (metacard != null) {
return new PolicyResponseImpl(null, parseSecurityMetadata(metacard));
}
return new PolicyResponseImpl();
}
@Override
public PolicyResponse processPreUpdate(Metacard metacard, Map<String, Serializable> map)
throws StopProcessingException {
if (metacard != null) {
return new PolicyResponseImpl(null, parseSecurityMetadata(metacard));
}
return new PolicyResponseImpl();
}
@Override
public PolicyResponse processPreDelete(List<Metacard> list, Map<String, Serializable> map)
throws StopProcessingException {
Map<String, Set<String>> response = new HashMap<>();
for (Metacard metacard : list) {
Map<String, Set<String>> parseSecurityMetadata = parseSecurityMetadata(metacard);
for (Map.Entry<String, Set<String>> entry : parseSecurityMetadata.entrySet()) {
if (response.containsKey(entry.getKey())) {
response.get(entry.getKey())
.addAll(entry.getValue());
} else {
response.put(entry.getKey(), entry.getValue());
}
}
}
return new PolicyResponseImpl(response, null);
}
@Override
public PolicyResponse processPostDelete(Metacard metacard, Map<String, Serializable> map)
throws StopProcessingException {
return new PolicyResponseImpl(null, parseSecurityMetadata(metacard));
}
@Override
public PolicyResponse processPreQuery(Query query, Map<String, Serializable> map)
throws StopProcessingException {
return new PolicyResponseImpl();
}
@Override
public PolicyResponse processPostQuery(Result result, Map<String, Serializable> map)
throws StopProcessingException {
return new PolicyResponseImpl(null, parseSecurityMetadata(result.getMetacard()));
}
@Override
public PolicyResponse processPreResource(ResourceRequest resourceRequest)
throws StopProcessingException {
return new PolicyResponseImpl();
}
@Override
public PolicyResponse processPostResource(ResourceResponse resourceResponse, Metacard metacard)
throws StopProcessingException {
return new PolicyResponseImpl(null, parseSecurityMetadata(metacard));
}
}