/*
*
* Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.ldap.server.configuration;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.log4j.Logger;
import org.wso2.carbon.apacheds.AdminGroupInfo;
import org.wso2.carbon.apacheds.AdminInfo;
import org.wso2.carbon.apacheds.KdcConfiguration;
import org.wso2.carbon.apacheds.LDAPConfiguration;
import org.wso2.carbon.apacheds.PartitionInfo;
import org.wso2.carbon.apacheds.PasswordAlgorithm;
import org.wso2.carbon.ldap.server.exception.DirectoryServerException;
import org.wso2.carbon.ldap.server.util.EmbeddingLDAPException;
import org.wso2.carbon.ldap.server.util.IdentityIOStreamUtils;
import org.wso2.carbon.user.api.RealmConfiguration;
import org.wso2.carbon.user.core.UserCoreConstants;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.config.RealmConfigXMLProcessor;
import org.wso2.carbon.utils.CarbonUtils;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* This class is responsible for building LDAP and KDC configurations. Given a file
* name this will read configuration values and populate them into configuration classes.
* Related configuration file is embedded-ldap.xml.
*/
public class LDAPConfigurationBuilder {
private static String CARBON_KDC_PORT_CONFIG_SECTION = "Ports.EmbeddedLDAP.KDCServerPort";
private static int DEFAULT_KDC_SERVER_PORT = 8000;
private Logger logger = Logger.getLogger(LDAPConfigurationBuilder.class);
private String userMgtXMLFilePath = null;
private InputStream configurationFileStream;
/*Password to connect with the embedded-ldap server*/
private String connectionPassword;
/*contains embedded-ldap server configurations*/
private LDAPConfiguration ldapConfiguration;
/*contains default partition's configurations*/
private PartitionInfo partitionConfigurations;
/*contains KDC server configurations*/
private KdcConfiguration kdcConfigurations;
private boolean kdcEnabled = false;
/**
* Constructor with the configuration file as input that reads the file into an InputStream.
*
* @param file that includes embedded-ldap server configurations.
* @throws FileNotFoundException
*/
public LDAPConfigurationBuilder(File file) throws FileNotFoundException {
if (!file.exists()) {
String msg = "File not found. - " + file.getAbsolutePath();
logger.error(msg);
throw new FileNotFoundException(msg);
}
try {
configurationFileStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
String msg = "Could not open file - " + file.getAbsolutePath();
logger.error(msg, e);
throw new FileNotFoundException(msg);
}
}
/**
* Build separate sections of the configuration file and the configuration file as a whole, as
* OMElements.
*
* @throws org.wso2.carbon.ldap.server.util.EmbeddingLDAPException Following is a sample configuration file:
* <EmbeddedLDAPConfig>
* <!-- LDAP server configurations -->
* <EmbeddedLDAP>
* <Property name="enable">true</Property>
* <Property name="instanceId">default</Property>
* <Property name="connectionPassword">admin</Property>
* <Property name="workingDirectory">.</Property>
* <Property name="allowAnonymousAccess">false</Property>
* <Property name="accessControlEnabled">true</Property>
* <Property name="denormalizeOpAttrsEnabled">false</Property>
* <Property name="maxPDUSize">2000000</Property>
* <Property name="saslHostName">localhost</Property>
* <Property name="saslPrincipalName">ldap/localhost@EXAMPLE.COM</Property>
* </EmbeddedLDAP>
* <p/>
* <!-- Default partition configurations -->
* <DefaultPartition>
* <Property name="id">root</Property>
* <Property name="realm">wso2.com</Property>
* <Property name="kdcPassword">secret</Property>
* <Property name="ldapServerPrinciplePassword">randall</Property>
* </DefaultPartition>
* <p/>
* <!-- Default partition admin configurations -->
* <PartitionAdmin>
* <Property name="uid">admin</Property>
* <Property name="commonName">admin</Property>
* <Property name="lastName">admin</Property>
* <Property name="email">admin</Property>
* <Property name="password">admin</Property>
* <Property name="passwordType">SHA</Property>
* </PartitionAdmin>
* <p/>
* <!-- Default partition admin's group configuration -->
* <PartitionAdminGroup>
* <Property name="adminRoleName">admin</Property>
* <Property name="groupNameAttribute">cn</Property>
* <Property name="memberNameAttribute">member</Property>
* </PartitionAdminGroup>
* <p/>
* <!-- KDC configurations -->
* <KDCServer>
* <Property name="name">defaultKDC</Property>
* <Property name="enabled">false</Property>
* <Property name="protocol">UDP</Property>
* <Property name="host">localhost</Property>
* <Property name="maximumTicketLifeTime">8640000</Property>
* <Property name="maximumRenewableLifeTime">604800000</Property>
* <Property name="preAuthenticationTimeStampEnabled">true</Property>
* </KDCServer>
* <p/>
* </EmbeddedLDAPConfig>
*/
public void buildConfigurations() throws EmbeddingLDAPException {
StAXOMBuilder builder;
try {
builder = new StAXOMBuilder(configurationFileStream);
} catch (XMLStreamException e) {
logger.error("Unable to build LDAP configurations.", e);
throw new EmbeddingLDAPException("Unable to build LDAP configurations", e);
}
/*Read the whole config file as an OMElement*/
OMElement documentElement = builder.getDocumentElement();
/*Extract the part that contains embedded-ldap specific configurations*/
OMElement embeddedLdap = documentElement.getFirstChildWithName(new QName("EmbeddedLDAP"));
/*Set properties in ldapConfiguration object from those read from the config element.*/
buildLDAPConfigurations(embeddedLdap);
if (ldapConfiguration.isEnable()) {
/*Set properties in partitionConfigurations object from those read from the config file.*/
buildPartitionConfigurations(documentElement);
/*Extract the part that contains kdc-server specific configurations*/
OMElement kdcConfigElement = documentElement.getFirstChildWithName(new QName("KDCServer"));
/*Set properties in kdcConfiguration object from those read from the config element.*/
buildKDCConfigurations(kdcConfigElement);
/*Says root partition that KDC is enabled. Root partition admin should have KDC object
attributes in LDAP*/
this.partitionConfigurations.setKdcEnabled(this.kdcEnabled);
// Do some cross checking
if (this.kdcEnabled) {
this.kdcConfigurations.setSystemAdminPassword(this.getConnectionPassword());
// Set admin partition for KDC
this.kdcConfigurations.setPartitionInfo(this.getPartitionConfigurations());
}
}
}
public String getConnectionPassword() throws EmbeddingLDAPException {
if (connectionPassword == null) {
buildConfigurations();
}
return connectionPassword;
}
public LDAPConfiguration getLdapConfiguration() throws EmbeddingLDAPException {
if (ldapConfiguration == null) {
buildConfigurations();
}
return ldapConfiguration;
}
/**
* Read and set the connection password from the property map.
*
* @param propertyMap : containing properties of EmbeddedLDAP Element.
* @throws org.wso2.carbon.ldap.server.util.EmbeddingLDAPException
*/
private void buildConnectionPassword(Map<String, String> propertyMap)
throws EmbeddingLDAPException {
connectionPassword = propertyMap.get("connectionPassword");
if (connectionPassword == null) {
throw new EmbeddingLDAPException("Connection password not specified in the " +
"configuration file.");
}
}
/**
* Read properties from EmbeddedLDAP element in configuration and set them in the
* ldapConfiguration object.
*
* @param embeddedLDAP: part of the XML config file named: EmbeddedLDAP
* @throws org.wso2.carbon.ldap.server.util.EmbeddingLDAPException
*/
private void buildLDAPConfigurations(OMElement embeddedLDAP) throws EmbeddingLDAPException {
/*Read the properties of EmbeddedLDAP XML element.*/
Map<String, String> propertyMap = getChildPropertyElements(embeddedLDAP);
ldapConfiguration = new LDAPConfiguration();
/*set connectionPassword*/
buildConnectionPassword(propertyMap);
String booleanString;
if ((booleanString = propertyMap.get("accessControlEnabled")) != null) {
ldapConfiguration.setAccessControlOn(Boolean.parseBoolean(booleanString));
}
if ((booleanString = propertyMap.get("allowAnonymousAccess")) != null) {
ldapConfiguration.setAllowAnonymousAccess(Boolean.parseBoolean(booleanString));
}
if ((booleanString = propertyMap.get("changedLogEnabled")) != null) {
ldapConfiguration.setChangeLogEnabled(Boolean.parseBoolean(booleanString));
}
if ((booleanString = propertyMap.get("denormalizeOpAttrsEnabled")) != null) {
ldapConfiguration.setDeNormalizedAttributesEnabled(Boolean.parseBoolean(booleanString));
}
//check and set whether embedded ldap is enabled.
String enableInfo = propertyMap.get("enable");
if (("true").equals(enableInfo)) {
ldapConfiguration.setEnable(true);
} else {
ldapConfiguration.setEnable(false);
}
ldapConfiguration.setInstanceId(propertyMap.get("instanceId"));
//Read LDAP server port from carbon.xml and set it
ldapConfiguration.setLdapPort(getPort(propertyMap.get("port")));
ldapConfiguration.setWorkingDirectory(propertyMap.get("workingDirectory"));
ldapConfiguration.setAdminEntryObjectClass(propertyMap.get("AdminEntryObjectClass"));
ldapConfiguration.setMaxPDUSize(getIntegerValue(propertyMap.get("maxPDUSize")));
ldapConfiguration.setSaslHostName(propertyMap.get("saslHostName"));
ldapConfiguration.setSaslPrincipalName(propertyMap.get("saslPrincipalName"));
}
/**
* Set LDAP server port. Port is read from embedded-ldap.conf, and if it refers to port in
* carbon.xml (Ports/EmbeddedLDAP configuration section), then it is read from carbon.xml
*
* @param portParamValue
* @return The port either read from embedded-ldap.xml or carbon.xml
*/
private int getPort(String portParamValue) {
int port = -1;
if (portParamValue != null) {
if (portParamValue.startsWith("${")) { // should port be taken from carbon.xml?
port = CarbonUtils.getPortFromServerConfig(portParamValue);
} else { //else read from embedded-ldap.xml
port = Integer.parseInt(portParamValue);
}
}
return port;
}
private int getIntegerValue(String value) {
if (value != null) {
return Integer.parseInt(value);
}
return -1;
}
/**
* Reads the properties mentioned under the XML Element in the config file, which is passed
* as an OMElement to the method.
*
* @param omElement : main XML element whose properties should be read
* @return : the map containing property names and the values.
*/
private Map<String, String> getChildPropertyElements(OMElement omElement) {
Map<String, String> map = new HashMap<String, String>();
Iterator<?> ite = omElement.getChildrenWithName(new QName(
UserCoreConstants.RealmConfig.LOCAL_NAME_PROPERTY));
while (ite.hasNext()) {
OMElement propElem = (OMElement) ite.next();
String propName = propElem.getAttributeValue(new QName(
UserCoreConstants.RealmConfig.ATTR_NAME_PROP_NAME));
String propValue = propElem.getText();
map.put(propName, propValue);
}
return map;
}
/**
* Read properties related to default partition and set them in partitionConfigurations object.
*
* @param documentElement: whole config file read as an OMElement.
* Following parts are read from the config file:
* <!-- Default partition configurations -->
* <DefaultPartition>
* <Property name="id">root</Property>
* <Property name="realm">wso2.com</Property>
* <Property name="kdcPassword">secret</Property>
* <Property name="ldapServerPrinciplePassword">randall</Property>
* </DefaultPartition>
* <p/>
* <!-- Default partition admin configurations -->
* <PartitionAdmin>
* <Property name="uid">admin</Property>
* <Property name="commonName">admin</Property>
* <Property name="lastName">admin</Property>
* <Property name="email">admin</Property>
* <Property name="password">admin</Property>
* <Property name="passwordType">SHA</Property>
* </PartitionAdmin>
* <p/>
* <!-- Default partition admin's group configuration -->
* <PartitionAdminGroup>
* <Property name="adminRoleName">admin</Property>
* <Property name="groupNameAttribute">cn</Property>
* <Property name="memberNameAttribute">member</Property>
* </PartitionAdminGroup>
*/
private void buildPartitionConfigurations(OMElement documentElement) {
this.partitionConfigurations = new PartitionInfo();
OMElement defaultPartition = documentElement.getFirstChildWithName(new QName("DefaultPartition"));
Map<String, String> propertyMap = getChildPropertyElements(defaultPartition);
this.partitionConfigurations.setPartitionId(propertyMap.get("id"));
this.partitionConfigurations.setRealm(propertyMap.get("realm"));
this.partitionConfigurations.setPartitionKdcPassword(propertyMap.get("kdcPassword"));
this.partitionConfigurations.setLdapServerPrinciplePassword(propertyMap.get("ldapServerPrinciplePassword"));
this.partitionConfigurations.setRootDN(getDomainNameForRealm(propertyMap.get("realm")));
// Admin user config
OMElement partitionAdmin = documentElement.getFirstChildWithName(new QName("PartitionAdmin"));
propertyMap = getChildPropertyElements(partitionAdmin);
AdminInfo defaultPartitionAdmin = buildPartitionAdminConfigurations(propertyMap);
// Admin role config
OMElement partitionAdminRole = documentElement.getFirstChildWithName(new QName("PartitionAdminGroup"));
propertyMap = getChildPropertyElements(partitionAdminRole);
AdminGroupInfo adminGroupInfo = buildPartitionAdminGroupConfigurations(propertyMap);
defaultPartitionAdmin.setGroupInformation(adminGroupInfo);
this.partitionConfigurations.setPartitionAdministrator(defaultPartitionAdmin);
}
private AdminInfo buildPartitionAdminConfigurations(Map<String, String> propertyMap) {
AdminInfo adminInfo = new AdminInfo();
adminInfo.setAdminUserName(propertyMap.get("uid"));
adminInfo.setAdminCommonName(propertyMap.get("firstName"));
adminInfo.setAdminLastName(propertyMap.get("lastName"));
adminInfo.setAdminEmail(propertyMap.get("email"));
adminInfo.setAdminPassword(propertyMap.get("password"));
adminInfo.setPasswordAlgorithm(PasswordAlgorithm.valueOf(propertyMap.get("passwordType")));
adminInfo.addObjectClass(ldapConfiguration.getAdminEntryObjectClass());
adminInfo.setUsernameAttribute("uid");
return adminInfo;
}
private AdminGroupInfo buildPartitionAdminGroupConfigurations(Map<String, String> propertyMap) {
AdminGroupInfo adminGroupInfo = new AdminGroupInfo();
adminGroupInfo.setAdminRoleName(propertyMap.get("adminRoleName"));
adminGroupInfo.setGroupNameAttribute(propertyMap.get("groupNameAttribute"));
adminGroupInfo.setMemberNameAttribute(propertyMap.get("memberNameAttribute"));
return adminGroupInfo;
}
private String getDomainNameForRealm(String realm) {
if (realm == null) {
return null;
}
String[] components = realm.split("\\.");
if (components.length == 0) {
return "dc=" + realm;
}
StringBuilder domainName = new StringBuilder();
for (int i = 0; i < components.length; ++i) {
domainName.append("dc=");
domainName.append(components[i]);
if (i != (components.length - 1)) {
domainName.append(",");
}
}
return domainName.toString();
}
public PartitionInfo getPartitionConfigurations() throws EmbeddingLDAPException {
if (partitionConfigurations == null) {
buildConfigurations();
}
return partitionConfigurations;
}
public KdcConfiguration getKdcConfigurations() throws EmbeddingLDAPException {
if (kdcConfigurations == null) {
buildConfigurations();
}
return kdcConfigurations;
}
public boolean isKdcEnabled() {
return kdcEnabled;
}
/**
* Read properties from KDCConfiguration element in configuration and set them in the
* kdcConfigurations object.
*
* @param kdcConfigElement
* @throws org.wso2.carbon.ldap.server.util.EmbeddingLDAPException <!-- KDC configurations -->
* <KDCServer>
* <Property name="name">defaultKDC</Property>
* <Property name="enabled">false</Property>
* <Property name="protocol">UDP</Property>
* <Property name="host">localhost</Property>
* <Property name="maximumTicketLifeTime">8640000</Property>
* <Property name="maximumRenewableLifeTime">604800000</Property>
* <Property name="preAuthenticationTimeStampEnabled">true</Property>
* </KDCServer>
*/
private void buildKDCConfigurations(OMElement kdcConfigElement)
throws EmbeddingLDAPException {
Map<String, String> propertyMap = getChildPropertyElements(kdcConfigElement);
String booleanString;
if ((booleanString = propertyMap.get("enabled")) != null) {
this.kdcEnabled = Boolean.parseBoolean(booleanString);
if (!this.kdcEnabled) {
logger.info("KDC server is disabled.");
return;
}
} else {
logger.info("KDC server is disabled.");
return;
}
this.kdcConfigurations = new KdcConfiguration();
this.kdcConfigurations.setKdcName(propertyMap.get("name"));
try {
this.kdcConfigurations.setKdcCommunicationProtocol(propertyMap.get("protocol"));
} catch (DirectoryServerException e) {
String errorMessage = "Can not read/set protocol parameter in KDCConfig.";
logger.error(errorMessage, e);
throw new EmbeddingLDAPException(errorMessage, e);
}
this.kdcConfigurations.setKdcHostAddress(propertyMap.get("host"));
//Read KDC port from carbon.xml and set it
int port = getPort(propertyMap.get("port"));
if (port == -1) {
logger.warn("KDC port defined in carbon.xml's " + CARBON_KDC_PORT_CONFIG_SECTION +
" config section or embedded-ldap.xml is invalid. " +
"Setting KDC server port to default - " + DEFAULT_KDC_SERVER_PORT);
port = DEFAULT_KDC_SERVER_PORT;
}
this.kdcConfigurations.setKdcCommunicationPort(port);
this.kdcConfigurations.setMaxTicketLifeTime(getIntegerValue(propertyMap.get(
"maximumTicketLifeTime")));
this.kdcConfigurations.setMaxRenewableLifeTime(getIntegerValue(propertyMap.get(
"maximumRenewableLifeTime")));
if ((booleanString = propertyMap.get("preAuthenticationTimeStampEnabled")) != null) {
boolean preAuthenticationTSEnabled = Boolean.parseBoolean(booleanString);
this.kdcConfigurations.setPreAuthenticateTimeStampRequired(preAuthenticationTSEnabled);
}
}
public boolean isEmbeddedLDAPEnabled() {
return ldapConfiguration.isEnable();
}
protected RealmConfiguration getUserManagementXMLElement() {
String REALM_CONFIG_FILE = "user-mgt.xml";
StAXOMBuilder builder = null;
InputStream inStream = null;
OMElement realmElement = null;
RealmConfiguration config = null;
if (userMgtXMLFilePath == null) {
String carbonHome = CarbonUtils.getCarbonHome();
if (carbonHome != null) {
userMgtXMLFilePath = CarbonUtils.getCarbonConfigDirPath();
}
}
try {
File userMgtXMLFile = new File(userMgtXMLFilePath,
REALM_CONFIG_FILE);
if (userMgtXMLFile.exists()) {
inStream = new FileInputStream(userMgtXMLFile);
}
builder = new StAXOMBuilder(inStream);
OMElement documentElement = builder.getDocumentElement();
realmElement = documentElement.getFirstChildWithName(new QName(
UserCoreConstants.RealmConfig.LOCAL_NAME_REALM));
RealmConfigXMLProcessor rmProcessor = new RealmConfigXMLProcessor();
config = rmProcessor.buildRealmConfiguration(realmElement);
} catch (XMLStreamException | FileNotFoundException e) {
String errorMsg = "User-mgt.xml is not found. " +
"Hence admin properties will be read from embedded-ldap.xml";
if (logger.isDebugEnabled()) {
logger.debug(errorMsg, e);
}
} catch (UserStoreException e) {
logger.error("Error occured while reading user-mgt.xml", e);
} finally {
IdentityIOStreamUtils.closeInputStream(inStream);
}
return config;
}
public void setUserMgtXMLFilePath(String filePath) {
this.userMgtXMLFilePath = filePath;
}
}