/**
* 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 ddf.security.pdp.realm.xacml.processor;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Set;
import org.apache.commons.io.monitor.FileAlterationListener;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.connexta.arbitro.finder.impl.FileBasedPolicyFinderModule;
import ddf.security.common.audit.SecurityLogger;
/**
* This class Polls Directories for policies. It is used with the PDP to poll directories
* for changes to polices or the policy set in the directories.
*/
public class PollingPolicyFinderModule extends FileBasedPolicyFinderModule
implements FileAlterationListener {
private static final Logger LOGGER = LoggerFactory.getLogger(PollingPolicyFinderModule.class);
/*
* Milliseconds
*/
private static final int MULTIPLIER = 1000;
private FileAlterationMonitor monitor;
private Set<String> xacmlPolicyDirectories;
/**
* @param xacmlPolicyDirectories - to search for policies
* @param pollingInterval - in seconds
*/
public PollingPolicyFinderModule(Set<String> xacmlPolicyDirectories, long pollingInterval) {
super(xacmlPolicyDirectories);
this.xacmlPolicyDirectories = xacmlPolicyDirectories;
initialize(pollingInterval);
}
private void initialize(long pollingInterval) {
LOGGER.debug("initializing polling: {}, every {}", xacmlPolicyDirectories, pollingInterval);
monitor = new FileAlterationMonitor(pollingInterval * MULTIPLIER);
for (String xacmlPolicyDirectory : xacmlPolicyDirectories) {
File directoryToMonitor = new File(xacmlPolicyDirectory);
FileAlterationObserver observer = new FileAlterationObserver(directoryToMonitor,
getXmlFileFilter());
observer.addListener(this);
monitor.addObserver(observer);
LOGGER.debug("Monitoring directory: {}", directoryToMonitor);
}
}
public void start() {
try {
monitor.start();
} catch (Exception e) {
LOGGER.info(e.getMessage(), e);
}
}
public void onDirectoryChange(File changedDir) {
try {
SecurityLogger.audit("Directory {} changed.", changedDir.getCanonicalPath());
} catch (IOException e) {
LOGGER.info(e.getMessage(), e);
}
reloadPolicies();
}
public void onDirectoryCreate(File createdDir) {
try {
SecurityLogger.audit("Directory {} was created.", createdDir.getCanonicalPath());
} catch (IOException e) {
LOGGER.info(e.getMessage(), e);
}
}
public void onDirectoryDelete(File deletedDir) {
try {
SecurityLogger.audit("Directory {} was deleted.", deletedDir.getCanonicalPath());
} catch (IOException e) {
LOGGER.info(e.getMessage(), e);
}
}
public void onFileChange(File changedFile) {
try {
SecurityLogger.audit("File {} changed to:\n{}",
changedFile.getCanonicalPath(),
new String(Files.readAllBytes(Paths.get(changedFile.getCanonicalPath())),
StandardCharsets.UTF_8));
} catch (IOException e) {
LOGGER.info(e.getMessage(), e);
}
reloadPolicies();
}
public void onFileCreate(File createdFile) {
try {
SecurityLogger.audit("File {} was created with content:\n{}",
createdFile.getCanonicalPath(),
new String(Files.readAllBytes(Paths.get(createdFile.getCanonicalPath())),
StandardCharsets.UTF_8));
} catch (IOException e) {
LOGGER.info(e.getMessage(), e);
}
reloadPolicies();
}
public void onFileDelete(File deleteFile) {
try {
SecurityLogger.audit("File {} was deleted.", deleteFile.getCanonicalPath());
} catch (IOException e) {
LOGGER.info(e.getMessage(), e);
}
reloadPolicies();
}
public void onStart(FileAlterationObserver observer) {
try {
String directoryPath = observer.getDirectory()
.getCanonicalPath();
LOGGER.trace("starting to check directory for xacml policy update(s) {}", directoryPath);
if (!xacmlPolicyDirectories.isEmpty() && isXacmlPoliciesDirectoryEmpty(
xacmlPolicyDirectories.iterator()
.next())) {
LOGGER.warn("No XACML Policies found in: {}", directoryPath);
}
} catch (IOException e) {
LOGGER.info(e.getMessage(), e);
}
}
public void onStop(FileAlterationObserver observer) {
try {
LOGGER.trace("Done checking directory {}", observer.getDirectory().getCanonicalPath());
} catch (IOException e) {
LOGGER.info(e.getMessage(), e);
}
}
/**
* Checks if the XACML policy directory is empty.
*
* @param xacmlPoliciesDirectory The directory containing the XACML policy.
* @return true if the directory is empty and false otherwise.
*/
private boolean isXacmlPoliciesDirectoryEmpty(File xacmlPoliciesDirectory) {
boolean empty = false;
if (null != xacmlPoliciesDirectory && xacmlPoliciesDirectory.isDirectory()) {
File[] files = xacmlPoliciesDirectory.listFiles();
empty = files == null || files.length == 0;
}
return empty;
}
/**
* Checks if the XACML policy directory is empty.
*
* @param xacmlPoliciesDirectory The directory containing the XACML policy.
* @return true if the directory is empty and false otherwise.
*/
private boolean isXacmlPoliciesDirectoryEmpty(String xacmlPoliciesDirectory) {
return isXacmlPoliciesDirectoryEmpty(new File(xacmlPoliciesDirectory));
}
private FileFilter getXmlFileFilter() {
return pathName -> pathName.getName()
.toLowerCase()
.endsWith(".xml");
}
public void reloadPolicies() {
LOGGER.debug("Reloading XACML policies");
this.loadPolicies();
}
}