/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security.impl;
import static org.geoserver.security.impl.DataAccessRule.ANY;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resource.Type;
import org.geoserver.security.PropertyFileWatcher;
import org.geoserver.util.IOUtils;
import org.geotools.util.logging.Logging;
/**
* Abstract class for security dao's whose configuration is stored in a property file.
* <p>
* Subclasses must implement {@link #loadRules(Properties)} and {@link #toProperties()} to provide
* the mapping back and forth to the underlying properly file.
* </p>
* @author Justin Deoliveira, OpenGeo
*
* @param <R> The access rule class.
*/
public abstract class AbstractAccessRuleDAO<R extends Comparable<?>> {
private final static Logger LOGGER = Logging.getLogger(AbstractAccessRuleDAO.class);
/**
* Parsed rules
*/
protected Set<R> rules;
/**
* Used to check the file for modifications
*/
PropertyFileWatcher watcher;
/**
* Stores the time of the last rule list loading
*/
protected long lastModified;
/**
* The security dir
*/
Resource securityDir;
/**
* The property file name that stores the raw rule names.
*/
String propertyFileName;
/**
* Data directory accessor
*/
GeoServerDataDirectory dd;
protected AbstractAccessRuleDAO(GeoServerDataDirectory dd, String propertyFileName) throws IOException {
this.dd = dd;
this.securityDir = dd.getSecurity();
this.propertyFileName = propertyFileName;
}
protected AbstractAccessRuleDAO(Resource securityDirectory, String propertyFileName) {
this.securityDir = securityDirectory;
this.propertyFileName = propertyFileName;
this.dd = GeoServerExtensions.bean(GeoServerDataDirectory.class);
//this.dd = org.vfny.geoserver.global.GeoserverDataDirectory.accessor();
}
/**
* Returns the list of rules contained in the property file. The returned rules are
* sorted against the <code>R</code> natural order
*/
public List<R> getRules() {
checkPropertyFile(false);
return new ArrayList<R>(rules);
}
/**
* Adds/overwrites a rule in the rule set
*
* @param rule
* @return true if the set did not contain the rule already, false otherwise
*/
public boolean addRule(R rule) {
lastModified = System.currentTimeMillis();
return rules.add(rule);
}
/**
* Forces a reload of the rules
*/
public void reload() {
checkPropertyFile(true);
}
/**
* Cleans up the contents of the rule set
*/
public void clear() {
rules.clear();
lastModified = System.currentTimeMillis();
}
/**
* Removes the rule from rule set
* @param rule
*
*/
public boolean removeRule(R rule) {
lastModified = System.currentTimeMillis();
return rules.remove(rule);
}
/**
* Returns the last modification date of the rules in this DAO (last time the rules were
* reloaded from the property file)
*
*
*/
public long getLastModified() {
return lastModified;
}
public boolean isModified() {
return watcher != null && watcher.isStale();
}
/**
* Writes the rules back to file system
* @throws IOException
*/
public void storeRules() throws IOException {
OutputStream os = null;
try {
// turn back the users into a users map
Properties p = toProperties();
// write out to the data dir
Resource propFile = securityDir.get(propertyFileName);
os = propFile.out();
p.store(os, null);
lastModified = System.currentTimeMillis();
} catch (Exception e) {
if (e instanceof IOException)
throw (IOException) e;
else
throw (IOException) new IOException(
"Could not write rules to " + propertyFileName).initCause(e);
} finally {
if (os != null)
os.close();
}
}
/**
* Checks the property file is up to date, eventually rebuilds the tree
*/
protected void checkPropertyFile(boolean force) {
try {
if (rules == null || force) {
// no security folder, let's work against an empty properties then
if (securityDir == null || securityDir.getType() == Type.UNDEFINED) {
this.rules = new TreeSet<R>();
} else {
// no security config, let's work against an empty properties then
Resource layers = securityDir.get(propertyFileName);
if (layers.getType() == Type.UNDEFINED) {
//try to load a template and copy it over
InputStream in = getClass().getResourceAsStream(propertyFileName+".template");
if (in != null) {
IOUtils.copy(in, layers.out());
}
}
if (layers.getType() == Type.UNDEFINED) {
this.rules = new TreeSet<R>();
} else {
// ok, something is there, let's load it
watcher = new PropertyFileWatcher(layers);
loadRules(watcher.getProperties());
}
}
lastModified = System.currentTimeMillis();
} else if (isModified()) {
loadRules(watcher.getProperties());
lastModified = System.currentTimeMillis();
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE,
"Failed to reload data access rules from layers.properties, keeping old rules",
e);
}
}
/**
* Parses the rules contained in the property file
*
* @param props The parsed property file.
*/
protected abstract void loadRules(Properties props);
/**
* Turns the rules list into a property bag
*/
protected abstract Properties toProperties();
/**
* Parses a comma separated list of roles into a set of strings, with special handling for the
* {@link DataAccessRule#ANY} role
*
* @param roleCsv Comma separated list of roles.
*/
protected Set<String> parseRoles(String roleCsv) {
// regexp: treat extra spaces as separators, ignore extra commas
// "a,,b, ,, c" --> ["a","b","c"]
String[] rolesArray = roleCsv.split("[\\s,]+");
Set<String> roles = new HashSet<String>(rolesArray.length);
roles.addAll(Arrays.asList(rolesArray));
// if any of the roles is * we just remove all of the others
for (String role : roles) {
if (ANY.equals(role))
return Collections.singleton("*");
}
return roles;
}
}