/*
*
* 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.apacheds.impl;
import org.apache.axiom.om.util.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.server.core.CoreSession;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.core.factory.JdbmPartitionFactory;
import org.apache.directory.server.core.factory.PartitionFactory;
import org.apache.directory.server.core.interceptor.Interceptor;
import org.apache.directory.server.core.partition.Partition;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
import org.apache.directory.server.kerberos.shared.store.KerberosAttribute;
import org.apache.directory.server.xdbm.Index;
import org.apache.directory.shared.ldap.entry.ServerEntry;
import org.apache.directory.shared.ldap.exception.LdapException;
import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
import org.apache.directory.shared.ldap.name.DN;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.apacheds.AdminGroupInfo;
import org.wso2.carbon.apacheds.AdminInfo;
import org.wso2.carbon.apacheds.PartitionInfo;
import org.wso2.carbon.apacheds.PartitionManager;
import org.wso2.carbon.apacheds.PasswordAlgorithm;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.ldap.server.exception.DirectoryServerException;
import javax.naming.NamingException;
import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* ApacheDS implementation of LDAP server. Contains methods to manipulate LDAP partitions.
*/
class ApacheDirectoryPartitionManager implements PartitionManager {
/*Partition cache size is expressed as number of entries*/
private static final int PARTITION_CACHE_SIZE = 500;
private static final Logger logger = LoggerFactory.getLogger(
ApacheDirectoryPartitionManager.class);
private DirectoryService directoryService = null;
private String workingDirectory;
private PartitionFactory partitionFactory = null;
public ApacheDirectoryPartitionManager(DirectoryService directoryService, String wd) {
this.directoryService = directoryService;
this.workingDirectory = wd;
this.partitionFactory = new JdbmPartitionFactory();
}
private static void throwDirectoryServerException(String message, Throwable e)
throws DirectoryServerException {
logger.error(message, e);
throw new DirectoryServerException(message, e);
}
private static void addObjectClasses(ServerEntry serverEntry, List<String> objectClasses)
throws DirectoryServerException {
for (String objectClass : objectClasses) {
try {
serverEntry.add("objectClass", objectClass);
} catch (LdapException e) {
throwDirectoryServerException("Could not add class to partition " +
serverEntry.getDn().getName(), e);
}
}
}
/**
* @inheritDoc
*/
@Override
public void addPartition(PartitionInfo partitionInformation)
throws DirectoryServerException {
try {
JdbmPartition partition = createNewPartition(partitionInformation.getPartitionId(),
partitionInformation.getRootDN());
this.directoryService.addPartition(partition);
CoreSession adminSession = this.directoryService.getAdminSession();
if (!adminSession.exists(partition.getSuffixDn())) {
addPartitionAttributes(partitionInformation.getRootDN(), partitionInformation.
getObjectClasses(), partitionInformation.getRealm(),
partitionInformation.getPreferredDomainComponent());
// Create user ou
addUserStoreToPartition(partition.getSuffix());
// Create group ou
addGroupStoreToPartition(partition.getSuffix());
//Creates the shared groups ou
addSharedGroupToPartition(partition.getSuffix());
/*do not create admin user and admin group because it is anyway checked and created
*in user core.*/
// create tenant administrator entry at the time of tenant-partition created.
addAdmin(partitionInformation.getPartitionAdministrator(), partition.getSuffix(),
partitionInformation.getRealm(), partitionInformation.isKdcEnabled());
addAdminGroup(partitionInformation.getPartitionAdministrator(), partition.getSuffix());
addAdminACLEntry(partitionInformation.getPartitionAdministrator().getAdminUserName(),
partition.getSuffix());
this.directoryService.sync();
}
} catch (Exception e) {
String errorMessage = "Could not add the partition";
logger.error(errorMessage, e);
throw new DirectoryServerException(errorMessage, e);
}
}
/**
* @inheritDoc
*/
@Override
public boolean partitionDirectoryExists(String partitionID) throws DirectoryServerException {
boolean partitionDirectoryExists = false;
String partitionDirectoryName = this.workingDirectory + File.separator + partitionID;
File partitionDirectory = new File(partitionDirectoryName);
//if a partition directory exists,it should be initialized without creating a new partition.
if (partitionDirectory.exists()) {
if (logger.isDebugEnabled()) {
logger.debug("Partition directory - " + partitionDirectoryName + " already exists.");
}
partitionDirectoryExists = true;
}
return partitionDirectoryExists;
}
/**
* @inheritDoc
*/
@Override
public boolean partitionInitialized(String partitionId) {
Set<? extends Partition> partitions = this.directoryService.getPartitions();
for (Partition partition : partitions) {
if (partition.getId().equals(partitionId)) {
return true;
}
}
return false;
}
/**
* @inheritDoc
*/
@Override
public int getNumberOfPartitions() {
int numOfPartitions = 0; //if no partition is created
Set<? extends Partition> partitions = this.directoryService.getPartitions();
numOfPartitions = partitions.size();
return numOfPartitions;
}
/**
* This method initializes a partition from existing partition directory.
*/
@Override
public void initializeExistingPartition(PartitionInfo partitionInfo) throws
DirectoryServerException {
Partition existingPartition = null;
try {
existingPartition = partitionFactory.createPartition(
partitionInfo.getPartitionId(), partitionInfo.getRootDN(), PARTITION_CACHE_SIZE,
new File(this.workingDirectory, partitionInfo.getPartitionId()));
existingPartition.setSchemaManager(directoryService.getSchemaManager());
if (logger.isDebugEnabled()) {
logger.debug("Partition" + partitionInfo.getPartitionId() +
" created from existing partition directory.");
}
} catch (Exception e) {
logger.error("Error in creating partition from existing partition directory.", e);
throw new DirectoryServerException("Error in creating partition from existing partition directory.", e);
}
/**
*Initialize the existing partition in the directory service.
*/
try {
this.directoryService.addPartition(existingPartition);
this.directoryService.sync();
if (logger.isDebugEnabled()) {
logger.debug("Partition" + partitionInfo.getPartitionId() +
" added to directory service.");
}
} catch (Exception e) {
logger.error("Error in initializing partition in directory service", e);
throw new DirectoryServerException("Error in initializing partition in directory service", e);
}
}
/**
* @inheritDoc
*/
@Override
public void removePartition(String partitionSuffix)
throws DirectoryServerException {
Partition partition = getPartition(partitionSuffix);
if (partition == null) {
String msg = "Error deleting partition. Could not find a partition with suffix " +
partitionSuffix;
logger.error(msg);
throw new DirectoryServerException(msg);
}
try {
this.directoryService.removePartition(partition);
} catch (Exception e) {
String msg = "Unable to delete partition with suffix " + partitionSuffix;
logger.error(msg, e);
throw new DirectoryServerException("Unable to delete partition with suffix " +
partitionSuffix, e);
}
}
@Override
public void removeAllPartitions() throws DirectoryServerException {
Set<? extends Partition> partitions = this.directoryService.getPartitions();
for (Partition partition : partitions) {
if (!"schema".equalsIgnoreCase(partition.getId())) {
try {
if (logger.isDebugEnabled()) {
logger.debug("Removing partition with id - " + partition.getId() + " suffix - " +
partition.getSuffix());
}
this.directoryService.removePartition(partition);
} catch (Exception e) {
String msg = "Unable to remove partition with id " + partition.getId() +
" with suffix " + partition.getSuffix();
logger.error(msg, e);
throw new DirectoryServerException(msg, e);
}
}
}
}
/**
* @inheritDoc
*/
@Override
public void synchronizePartitions()
throws DirectoryServerException {
try {
this.directoryService.sync();
List<Interceptor> interceptors = this.directoryService.getInterceptors();
for (Interceptor interceptor : interceptors) {
interceptor.init(this.directoryService);
}
} catch (Exception e) {
throw new DirectoryServerException("Unable to sync partitions. ", e);
}
}
private void addAccessControlAttributes(ServerEntry serverEntry)
throws LdapException {
serverEntry.add("administrativeRole", "accessControlSpecificArea");
}
private void addPartitionAttributes(String partitionDN, List<String> objectClasses,
String realm, String dc)
throws DirectoryServerException {
try {
DN adminDN = new DN(partitionDN);
ServerEntry serverEntry = this.directoryService.newEntry(adminDN);
addObjectClasses(serverEntry, objectClasses);
serverEntry.add("o", realm);
if (dc == null) {
logger.warn("Domain component not found for partition with DN - " + partitionDN +
". Not setting domain component.");
} else {
serverEntry.add("dc", dc);
}
addAccessControlAttributes(serverEntry);
this.directoryService.getAdminSession().add(serverEntry);
} catch (Exception e) {
String msg = "Could not add partition attributes for partition - " + partitionDN;
throwDirectoryServerException(msg, e);
}
}
private void addUserStoreToPartition(String partitionSuffixDn)
throws DirectoryServerException {
try {
DN usersDN = new DN("ou=Users," + partitionSuffixDn);
ServerEntry usersEntry = this.directoryService.newEntry(usersDN);
usersEntry.add("objectClass", "organizationalUnit", "top");
usersEntry.add("ou", "Users");
this.directoryService.getAdminSession().add(usersEntry);
} catch (LdapInvalidDnException e) {
String msg = "Could not add user store to partition - " + partitionSuffixDn +
". Cause - partition domain name is not valid.";
throwDirectoryServerException(msg, e);
} catch (LdapException e) {
String msg = "Could not add user store to partition - " + partitionSuffixDn;
throwDirectoryServerException(msg, e);
} catch (NamingException e) {
String msg = "Could not add user store to partition - " + partitionSuffixDn +
". Cause - partition domain name is not valid.";
throwDirectoryServerException(msg, e);
} catch (Exception e) {
String msg = "Could not add user store to partition admin session. - " +
partitionSuffixDn;
throwDirectoryServerException(msg, e);
}
}
private void addGroupStoreToPartition(String partitionSuffixDn)
throws DirectoryServerException {
ServerEntry groupsEntry;
try {
DN groupsDN = new DN("ou=Groups," + partitionSuffixDn);
groupsEntry = this.directoryService.newEntry(groupsDN);
groupsEntry.add("objectClass", "organizationalUnit", "top");
groupsEntry.add("ou", "Groups");
this.directoryService.getAdminSession().add(groupsEntry);
} catch (NamingException e) {
String msg = "Could not add group store to partition - " + partitionSuffixDn +
". Cause - partition domain name is not valid.";
throwDirectoryServerException(msg, e);
} catch (LdapException e) {
String msg = "Could not add group store to partition - " + partitionSuffixDn;
throwDirectoryServerException(msg, e);
} catch (Exception e) {
String msg = "Could not add group store to partition admin session. - " +
partitionSuffixDn;
throwDirectoryServerException(msg, e);
}
}
private void addSharedGroupToPartition(String partitionSuffixDn) throws DirectoryServerException {
ServerEntry groupsEntry;
try {
DN groupsDN = new DN("ou=SharedGroups," + partitionSuffixDn);
groupsEntry = this.directoryService.newEntry(groupsDN);
groupsEntry.add("objectClass", "organizationalUnit", "top");
groupsEntry.add("ou", "SharedGroups");
this.directoryService.getAdminSession().add(groupsEntry);
} catch (NamingException e) {
String msg = "Could not add shared group store to partition - " + partitionSuffixDn +
". Cause - partition domain name is not valid.";
throwDirectoryServerException(msg, e);
} catch (LdapException e) {
String msg = "Could not add shared group store to partition - " + partitionSuffixDn;
throwDirectoryServerException(msg, e);
} catch (Exception e) {
String msg = "Could not add shared group store to partition admin session. - " +
partitionSuffixDn;
throwDirectoryServerException(msg, e);
}
}
private Partition getPartition(String partitionSuffix) {
Set availablePartitions = this.directoryService.getPartitions();
Partition partition;
for (Object object : availablePartitions) {
partition = (Partition) object;
if (partition.getSuffix().equals(partitionSuffix)) {
return partition;
}
}
return null;
}
private JdbmPartition createNewPartition(String partitionId, String partitionSuffix)
throws DirectoryServerException {
try {
JdbmPartition partition = new JdbmPartition();
String partitionDirectoryName = this.workingDirectory + File.separator + partitionId;
File partitionDirectory = new File(partitionDirectoryName);
partition.setId(partitionId);
partition.setSuffix(partitionSuffix);
partition.setPartitionDir(partitionDirectory);
Set<Index<?, ServerEntry, Long>> indexedAttrs =
new HashSet<Index<?, ServerEntry, Long>>();
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("1.3.6.1.4.1.18060.0.4.1.2.1"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("1.3.6.1.4.1.18060.0.4.1.2.2"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("1.3.6.1.4.1.18060.0.4.1.2.3"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("1.3.6.1.4.1.18060.0.4.1.2.4"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("1.3.6.1.4.1.18060.0.4.1.2.5"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("1.3.6.1.4.1.18060.0.4.1.2.6"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("1.3.6.1.4.1.18060.0.4.1.2.7"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("ou"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("dc"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("objectClass"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("cn"));
indexedAttrs.add(new JdbmIndex<String, ServerEntry>("uid"));
partition.setIndexedAttributes(indexedAttrs);
String message = MessageFormat.format(
"Partition created with following attributes, partition id - {0}, Partition " +
"domain - {1}, Partition working directory {2}", partitionId,
partitionSuffix, partitionDirectoryName);
if (logger.isDebugEnabled()) {
logger.debug(message);
}
return partition;
} catch (LdapInvalidDnException e) {
String msg = "Could not add a new partition with partition id " + partitionId +
" and suffix " + partitionSuffix;
logger.error(msg, e);
throw new DirectoryServerException(msg, e);
}
}
private void addAdminACLEntry(String adminUid, String tenantSuffix)
throws DirectoryServerException {
try {
//add the permission entry
DN adminACLEntrydn = new DN("cn=adminACLEntry," + tenantSuffix);
ServerEntry adminACLEntry = directoryService.newEntry(adminACLEntrydn);
adminACLEntry.add("objectClass", "accessControlSubentry", "subentry", "top");
adminACLEntry.add("cn", "adminACLEntry");
String aclScript = "{ " +
"identificationTag \"adminACLEntryTag\", " +
"precedence 1, " +
"authenticationLevel simple, " +
"itemOrUserFirst userFirst: " +
"{ " +
"userClasses " +
"{ " +
"name { " +
"\"uid=" + adminUid + ",ou=Users," + tenantSuffix + "\" " +
"} " +
"}, " +
"userPermissions " +
"{ " +
"{ " +
"protectedItems { entry, allUserAttributeTypesAndValues }, " +
"grantsAndDenials { " +
"grantBrowse, " +
"grantFilterMatch, " +
"grantModify, " +
"grantAdd, " +
"grantCompare, " +
"grantRename, " +
"grantRead, " +
"grantReturnDN, " +
"grantImport, " +
"grantInvoke, " +
"grantRemove, " +
"grantExport, " +
"grantDiscloseOnError " +
"} " +
"} " +
"} " +
"} " +
"}";
adminACLEntry.add("prescriptiveACI", aclScript);
adminACLEntry.add("subtreeSpecification", "{ }");
directoryService.getAdminSession().add(adminACLEntry);
} catch (LdapInvalidDnException e) {
throwDirectoryServerException("Domain name invalid - cn=adminACLEntry," +
tenantSuffix, e);
} catch (LdapException e) {
throwDirectoryServerException("Unable to create ACL entry for user " + adminUid, e);
} catch (NamingException e) {
throwDirectoryServerException("Invalid domain name entry - cn=adminACLEntry," +
tenantSuffix, e);
} catch (Exception e) {
throwDirectoryServerException(
"Unable to add ACL entry for user - " + adminUid +
" with DN - cn=adminACLEntry," + tenantSuffix, e);
}
}
private void addAdminPassword(ServerEntry adminEntry, String password,
PasswordAlgorithm algorithm,
final boolean kdcEnabled)
throws DirectoryServerException {
try {
String passwordToStore = "{" + algorithm.getAlgorithmName() + "}";
if (algorithm != PasswordAlgorithm.PLAIN_TEXT && !kdcEnabled) {
MessageDigest md = MessageDigest.getInstance(algorithm.getAlgorithmName());
md.update(password.getBytes());
byte[] bytes = md.digest();
String hash = Base64.encode(bytes);
passwordToStore = passwordToStore + hash;
} else {
if (kdcEnabled) {
logger.warn(
"KDC enabled. Enforcing passwords to be plain text. Cause - KDC " +
"cannot operate with hashed passwords.");
}
passwordToStore = password;
}
adminEntry.put("userPassword", passwordToStore.getBytes());
} catch (NoSuchAlgorithmException e) {
throwDirectoryServerException("Could not find matching hash algorithm - " +
algorithm.getAlgorithmName(), e);
}
}
private void addAdminGroup(AdminInfo adminInfo, String partitionSuffix)
throws DirectoryServerException {
AdminGroupInfo groupInfo = adminInfo.getGroupInformation();
if (groupInfo != null && StringUtils.contains(groupInfo.getAdminRoleName(),"/")) {
String adminRole = groupInfo.getAdminRoleName();
adminRole = adminRole.substring(adminRole.indexOf("/") + 1);
groupInfo.setAdminRoleName(adminRole);
}
String domainName = "";
try {
if (groupInfo != null) {
domainName = groupInfo.getGroupNameAttribute() + "=" +
groupInfo.getAdminRoleName() + "," + "ou=Groups," + partitionSuffix;
DN adminGroup = new DN(domainName);
ServerEntry adminGroupEntry = directoryService.newEntry(adminGroup);
addObjectClasses(adminGroupEntry, groupInfo.getObjectClasses());
adminGroupEntry.add(groupInfo.getGroupNameAttribute(),
groupInfo.getAdminRoleName());
adminGroupEntry.add(groupInfo.getMemberNameAttribute(),
adminInfo.getUsernameAttribute() + "=" + adminInfo.getAdminUserName() + "," + "ou=Users," +
partitionSuffix);
directoryService.getAdminSession().add(adminGroupEntry);
}
} catch (LdapInvalidDnException e) {
String msg = "Domain name invalid " + domainName;
throwDirectoryServerException(msg, e);
} catch (LdapException e) {
throwDirectoryServerException("Could not add group entry - " + domainName, e);
} catch (NamingException e) {
throwDirectoryServerException("Domain name invalid - " + domainName, e);
} catch (Exception e) {
throwDirectoryServerException("Could not add group entry to admin session. DN - " +
domainName, e);
}
}
private void addAdmin(AdminInfo adminInfo, String partitionSuffix, final String realm,
final boolean kdcEnabled) throws DirectoryServerException {
if (adminInfo.getAdminUserName().contains("/")) {
String admin = adminInfo.getAdminUserName();
admin = admin.substring(admin.indexOf("/") + 1);
adminInfo.setAdminUserName(admin);
}
String domainName = adminInfo.getUsernameAttribute() + "=" + adminInfo.getAdminUserName() + "," + "ou=Users," + partitionSuffix;
try {
DN adminDn = new DN(domainName);
ServerEntry adminEntry = directoryService.newEntry(adminDn);
List<String> objectClasses = adminInfo.getObjectClasses();
// Add Kerberose specific object classes
objectClasses = new ArrayList<String>(adminInfo.getObjectClasses());
objectClasses.add("krb5principal");
objectClasses.add("krb5kdcentry");
addObjectClasses(adminEntry, objectClasses);
adminEntry.add(adminInfo.getUsernameAttribute(),
adminInfo.getAdminUserName());
adminEntry.add("sn", adminInfo.getAdminLastName());
adminEntry.add("givenName", adminInfo.getAdminCommonName());
// setting admin full name as uid since 'cn' is a compulsory
// attribute when constructing a
// user entry.
adminEntry.add("cn", adminInfo.getAdminUserName());
if (!"mail".equals(adminInfo.getUsernameAttribute())) {
adminEntry.add("mail", adminInfo.getAdminEmail());
}
String principal = adminInfo.getAdminUserName() + "/" + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME + "@" + realm;
adminEntry.put(KerberosAttribute.KRB5_PRINCIPAL_NAME_AT, principal);
adminEntry.put(KerberosAttribute.KRB5_KEY_VERSION_NUMBER_AT, "0");
addAdminPassword(adminEntry, adminInfo.getAdminPassword(),
adminInfo.getPasswordAlgorithm(), kdcEnabled);
directoryService.getAdminSession().add(adminEntry);
} catch (LdapInvalidDnException e) {
throwDirectoryServerException("Domain name invalid " + domainName, e);
} catch (LdapException e) {
throwDirectoryServerException("Could not add entry to partition. DN - " +
domainName, e);
} catch (NamingException e) {
throwDirectoryServerException("Domain name invalid - " + domainName, e);
} catch (Exception e) {
throwDirectoryServerException("Could not add group entry to admin session. DN - " +
domainName, e);
}
}
}