/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package fedora.server.security;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import fedora.common.Constants;
/**
* Security configuration for backend services.
*
* @author Chris Wilper
*/
public class BESecurityConfig
implements Constants {
private static final String _CONFIG = "serviceSecurityDescription";
private static final String _INTERNAL_PREFIX = "fedoraInternalCall-";
private final static String _ROLE = "role";
private final static String _CALLSSL = "callSSL";
private final static String _CALLBASICAUTH = "callBasicAuth";
private final static String _CALLUSERNAME = "callUsername";
private final static String _CALLPASSWORD = "callPassword";
private final static String _CALLBACKSSL = "callbackSSL";
private final static String _CALLBACKBASICAUTH = "callbackBasicAuth";
private final static String _IPLIST = "iplist";
/**
* The default role configuration, specifying the values to be assumed for
* any internal call or SDep configuration value which is null.
*/
private DefaultRoleConfig m_defaultConfig;
/**
* Whether Fedora-to-self calls should use SSL.
*/
private Boolean m_internalSSL;
/**
* Whether Fedora-to-self calls should use basic auth.
*/
private Boolean m_internalBasicAuth;
/**
* The username to be used for basic-authenticaed Fedora-to-self calls. This
* value, along with the username, should also be configured in
* tomcat-users.xml or whatever other authentication database is in effect.
*/
private String m_internalUsername;
/**
* The password to be used for basic-authenticaed Fedora-to-self calls. This
* value, along with the password, should also be configured in
* tomcat-users.xml or whatever other authentication database is in effect.
*/
private String m_internalPassword;
/**
* The list of IP addresses that are allowed for Fedora-to-self calls. This
* should normally contain 127.0.0.1 and the external IP address of the
* running server, if known.
*/
private String[] m_internalIPList;
/**
* A sorted, PID-keyed map of <code>ServiceDeploymentRoleConfig</code>s.
*/
private final SortedMap<String, ServiceDeploymentRoleConfig> m_sDepConfigs;
/**
* Create an empty BESecurityConfig with an empty map of
* <code>ServiceDeploymentRoleConfig</code>s and <code>null</code> values for
* everything else.
*/
public BESecurityConfig() {
m_sDepConfigs = new TreeMap<String, ServiceDeploymentRoleConfig>();
}
/**
* Get the default role configuration.
*/
public DefaultRoleConfig getDefaultConfig() {
return m_defaultConfig;
}
/**
* Set the default role configuration.
*/
public void setDefaultConfig(DefaultRoleConfig config) {
m_defaultConfig = config;
}
///////////////////////////////////////////////////////////////////////////
/**
* Get whether SSL should be used for Fedora-to-self calls. This should be
* true if API-A is only available via SSL.
*/
public Boolean getInternalSSL() {
return m_internalSSL;
}
/**
* Get whether SSL is effectively used for Fedora-to-self calls. This will
* be the internalSSL value, if set, or the inherited call value from the
* default role, if set, or Boolean.FALSE.
*/
public Boolean getEffectiveInternalSSL() {
if (m_internalSSL != null) {
return m_internalSSL;
} else if (m_defaultConfig != null) {
return m_defaultConfig.getEffectiveCallSSL();
} else {
return Boolean.FALSE;
}
}
/**
* Set whether SSL is used for Fedora-to-self calls.
*/
public void setInternalSSL(Boolean value) {
m_internalSSL = value;
}
///////////////////////////////////////////////////////////////////////////
/**
* Get whether basic auth should be used for Fedora-to-self calls. This
* should be true if API-A requires basic auth.
*/
public Boolean getInternalBasicAuth() {
return m_internalBasicAuth;
}
/**
* Get whether basic auth is effectively used for Fedora-to-self calls. This
* will be the internalBasicAuth value, if set, or the inherited call value
* from the default role, if set, or Boolean.FALSE.
*/
public Boolean getEffectiveInternalBasicAuth() {
if (m_internalBasicAuth != null) {
return m_internalBasicAuth;
} else if (m_defaultConfig != null) {
return m_defaultConfig.getEffectiveCallBasicAuth();
} else {
return Boolean.FALSE;
}
}
/**
* Set whether basic auth is used for Fedora-to-self calls.
*/
public void setInternalBasicAuth(Boolean value) {
m_internalBasicAuth = value;
}
///////////////////////////////////////////////////////////////////////////
/**
* Get the internal username.
*/
public String getInternalUsername() {
return m_internalUsername;
}
/**
* Get the effective internal username for basic auth Fedora-to-self calls.
* This will be the internal username, if set, or the inherited call value
* from the default role, if set, or null.
*/
public String getEffectiveInternalUsername() {
if (m_internalUsername != null) {
return m_internalUsername;
} else if (m_defaultConfig != null) {
return m_defaultConfig.getEffectiveCallUsername();
} else {
return null;
}
}
/**
* Set the internal username.
*/
public void setInternalUsername(String username) {
m_internalUsername = username;
}
///////////////////////////////////////////////////////////////////////////
/**
* Get the internal password.
*/
public String getInternalPassword() {
return m_internalPassword;
}
/**
* Get the effective internal password for basic auth Fedora-to-self calls.
* This will be the internal password, if set, or the inherited call value
* from the default role, if set, or null.
*/
public String getEffectiveInternalPassword() {
if (m_internalPassword != null) {
return m_internalPassword;
} else if (m_defaultConfig != null) {
return m_defaultConfig.getEffectiveCallPassword();
} else {
return null;
}
}
/**
* Set the internal password.
*/
public void setInternalPassword(String password) {
m_internalPassword = password;
}
///////////////////////////////////////////////////////////////////////////
/**
* Get the list of internal IP addresses.
*/
public String[] getInternalIPList() {
return m_internalIPList;
}
/**
* Get the effective list of internal IP addresses. This will be the
* internalIPList value, if set, or the inherited value from the default
* role, if set, or null.
*/
public String[] getEffectiveInternalIPList() {
if (m_internalIPList != null) {
return m_internalIPList;
} else if (m_defaultConfig != null) {
return m_defaultConfig.getEffectiveIPList();
} else {
return null;
}
}
/**
* Set the list of internal IP addresses.
*/
public void setInternalIPList(String[] ips) {
m_internalIPList = ips;
}
///////////////////////////////////////////////////////////////////////////
/**
* Get the mutable, sorted, PID-keyed map of <code>ServiceDeploymentRoleConfig</code>s.
*/
public SortedMap<String, ServiceDeploymentRoleConfig> getServiceDeploymentConfigs() {
return m_sDepConfigs;
}
///////////////////////////////////////////////////////////////////////////
/**
* Add empty sDep and method configurations given by the map if they are
* not already already defined.
*/
public void addEmptyConfigs(Map pidToMethodList) {
Iterator pIter = pidToMethodList.keySet().iterator();
while (pIter.hasNext()) {
String sDepPID = (String) pIter.next();
// add the sDep indicated by the key if it doesn't exist
ServiceDeploymentRoleConfig sDepRoleConfig = m_sDepConfigs.get(sDepPID);
if (sDepRoleConfig == null) {
sDepRoleConfig =
new ServiceDeploymentRoleConfig(m_defaultConfig, sDepPID);
m_sDepConfigs.put(sDepPID, sDepRoleConfig);
}
// add each method indicated by the List which doesn't already exist
Iterator mIter = ((List) pidToMethodList.get(sDepPID)).iterator();
while (mIter.hasNext()) {
String methodName = (String) mIter.next();
MethodRoleConfig methodRoleConfig =
sDepRoleConfig.getMethodConfigs().get(methodName);
if (methodRoleConfig == null) {
methodRoleConfig =
new MethodRoleConfig(sDepRoleConfig, methodName);
sDepRoleConfig.getMethodConfigs().put(methodName,
methodRoleConfig);
}
}
}
}
//
// Deserialization/serialization to/from XML streams.
//
/**
* Instantiate a <code>BESecurityConfig</code> from an XML stream.
*/
public static BESecurityConfig fromStream(InputStream in) throws Exception {
BESecurityConfig config = new BESecurityConfig();
// instantiate DOM
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
factory.setValidating(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(in);
Element root = doc.getDocumentElement();
// set default role configuration
DefaultRoleConfig defaultRoleConfig = new DefaultRoleConfig();
setValuesFromElement(defaultRoleConfig, root);
config.setDefaultConfig(defaultRoleConfig);
// get all child config nodes for repeated use
NodeList nodes = root.getElementsByTagName(_CONFIG);
// parse and add all explicitly configured sdef configurations
// while also parsing fedoraInternalCall-1 and setting appropriate vals
for (int i = 0; i < nodes.getLength(); i++) {
Element e = (Element) nodes.item(i);
String role = e.getAttribute(_ROLE);
if (role.indexOf(":") != -1 && role.indexOf("/") == -1) {
ServiceDeploymentRoleConfig sDepRoleConfig =
new ServiceDeploymentRoleConfig(defaultRoleConfig, role);
setValuesFromElement(sDepRoleConfig, e);
config.getServiceDeploymentConfigs().put(role, sDepRoleConfig);
} else if (role.equals(_INTERNAL_PREFIX + "1")) {
config.setInternalSSL(getBoolean(e, _CALLSSL));
config.setInternalBasicAuth(getBoolean(e, _CALLBASICAUTH));
config.setInternalUsername(getString(e, _CALLUSERNAME));
config.setInternalPassword(getString(e, _CALLPASSWORD));
config.setInternalIPList(getStringArray(e, _IPLIST));
}
}
// finally, parse and add all configured methods, first adding
// a blank sdef role configuration if needed
for (int i = 0; i < nodes.getLength(); i++) {
Element e = (Element) nodes.item(i);
String[] parts = e.getAttribute(_ROLE).split("/");
if (parts.length == 2) {
String sDepPID = parts[0];
String methodName = parts[1];
ServiceDeploymentRoleConfig sDepRoleConfig =
config.getServiceDeploymentConfigs().get(sDepPID);
if (sDepRoleConfig == null) {
sDepRoleConfig =
new ServiceDeploymentRoleConfig(defaultRoleConfig, sDepPID);
config.getServiceDeploymentConfigs().put(sDepPID, sDepRoleConfig);
}
MethodRoleConfig methodRoleConfig =
new MethodRoleConfig(sDepRoleConfig, methodName);
setValuesFromElement(methodRoleConfig, e);
sDepRoleConfig.getMethodConfigs().put(methodName,
methodRoleConfig);
}
}
return config;
}
private static void setValuesFromElement(BERoleConfig roleConfig, Element e)
throws Exception {
roleConfig.setCallSSL(getBoolean(e, _CALLSSL));
roleConfig.setCallBasicAuth(getBoolean(e, _CALLBASICAUTH));
roleConfig.setCallUsername(getString(e, _CALLUSERNAME));
roleConfig.setCallPassword(getString(e, _CALLPASSWORD));
roleConfig.setCallbackSSL(getBoolean(e, _CALLBACKSSL));
roleConfig.setCallbackBasicAuth(getBoolean(e, _CALLBACKBASICAUTH));
roleConfig.setIPList(getStringArray(e, _IPLIST));
}
private static String getString(Element e, String name) {
Attr a = e.getAttributeNode(name);
if (a != null) {
return a.getValue();
} else {
return null;
}
}
private static Boolean getBoolean(Element e, String name) {
String s = getString(e, name);
if (s != null) {
return new Boolean(s);
} else {
return null;
}
}
private static String[] getStringArray(Element e, String name) {
String s = getString(e, name);
if (s != null) {
String[] array = s.split(" +");
if (array.length == 1 && array[0].length() == 0) {
return null;
}
return array;
} else {
return null;
}
}
/**
* Serialize to the given stream, closing it when finished. If
* skipNonOverrides is true, any configuration whose values are all null
* will not be written.
*/
public void toStream(boolean skipNonOverrides, OutputStream out)
throws Exception {
PrintWriter writer = null;
try {
writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
write(skipNonOverrides, true, writer);
} finally {
try {
writer.close();
} catch (Throwable th) {
}
try {
out.close();
} catch (Throwable th) {
}
}
}
/**
* Serialize to the given writer, keeping it open when finished. If
* skipNonOverrides is true, any configuration whose values are all null
* will not be written.
*/
public void write(boolean skipNonOverrides,
boolean withXMLDeclaration,
PrintWriter writer) {
final String indent = " ";
// header
if (withXMLDeclaration) {
writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
}
writer.println("<" + _CONFIG + " xmlns=\"" + BE_SECURITY.uri + "\"");
writer.println(indent + " xmlns:xsi=\"" + XSI.uri + "\"");
writer.println(indent + " xsi:schemaLocation=\"" + BE_SECURITY.uri
+ " " + BE_SECURITY1_0.xsdLocation + "\"");
// default values
writer.print(indent);
write(m_defaultConfig, false, skipNonOverrides, writer);
writer.println(">");
// fedoraInternalCall-1 and -2
writeInternalConfig(1,
m_internalSSL,
m_internalBasicAuth,
m_internalUsername,
m_internalPassword,
m_internalIPList,
writer);
writeInternalConfig(2,
Boolean.FALSE,
Boolean.FALSE,
null,
null,
m_internalIPList,
writer);
// sDep roles
Iterator bIter = m_sDepConfigs.keySet().iterator();
while (bIter.hasNext()) {
String role = (String) bIter.next();
ServiceDeploymentRoleConfig bConfig = m_sDepConfigs.get(role);
write(bConfig, true, skipNonOverrides, writer);
// per-method roles
Iterator mIter = bConfig.getMethodConfigs().keySet().iterator();
while (mIter.hasNext()) {
String methodName = (String) mIter.next();
MethodRoleConfig mConfig =
bConfig.getMethodConfigs().get(methodName);
write(mConfig, true, skipNonOverrides, writer);
}
}
// closing element for entire doc
writer.println("</" + _CONFIG + ">");
}
private static void writeInternalConfig(int n,
Boolean ssl,
Boolean basicAuth,
String username,
String password,
String[] ipList,
PrintWriter writer) {
writer.print(" <" + _CONFIG);
writeAttribute(_ROLE, _INTERNAL_PREFIX + n, writer);
writeAttribute(_CALLSSL, ssl, writer);
writeAttribute(_CALLBASICAUTH, basicAuth, writer);
writeAttribute(_CALLUSERNAME, username, writer);
writeAttribute(_CALLPASSWORD, password, writer);
writeAttribute(_CALLBACKSSL, ssl, writer);
writeAttribute(_CALLBACKBASICAUTH, basicAuth, writer);
writeAttribute(_IPLIST, ipList, writer);
writer.println("/>");
}
/**
* Write all the defined attributes of the given <code>BERoleConfig</code>,
* surrounding them with the appropriate element start/end text if
* <code>wholeElement</code> is true. Skip the entire element if
* skipIfAllNull is true.
*/
private static void write(BERoleConfig config,
boolean wholeElement,
boolean skipIfAllNull,
PrintWriter writer) {
if (wholeElement) {
if (skipIfAllNull && config.getCallSSL() == null
&& config.getCallBasicAuth() == null
&& config.getCallUsername() == null
&& config.getCallPassword() == null
&& config.getCallbackSSL() == null
&& config.getCallbackBasicAuth() == null
&& config.getIPList() == null) {
return;
}
writer.print(" <" + _CONFIG);
}
writeAttribute(_ROLE, config.getRole(), writer);
writeAttribute(_CALLSSL, config.getCallSSL(), writer);
writeAttribute(_CALLBASICAUTH, config.getCallBasicAuth(), writer);
writeAttribute(_CALLUSERNAME, config.getCallUsername(), writer);
writeAttribute(_CALLPASSWORD, config.getCallPassword(), writer);
writeAttribute(_CALLBACKSSL, config.getCallbackSSL(), writer);
writeAttribute(_CALLBACKBASICAUTH,
config.getCallbackBasicAuth(),
writer);
writeAttribute(_IPLIST, config.getIPList(), writer);
if (wholeElement) {
writer.println("/>");
}
}
/**
* Write (space)name="value" to the given PrintWriter if value is defined.
*/
private static void writeAttribute(String name,
Object value,
PrintWriter writer) {
if (value != null) {
String s;
if (value instanceof String || value instanceof Boolean) {
// for String/Boolean we can just use toString()
s = value.toString();
} else {
// otherwise its a String[], so space-delimit the values
String[] tokens = (String[]) value;
StringBuffer buf = new StringBuffer();
for (int i = 0; i < tokens.length; i++) {
if (i > 0) {
buf.append(' ');
}
buf.append(tokens[i]);
}
s = buf.toString();
}
writer.print(" " + name + "=\"" + s + "\"");
}
}
}