org.wso2.carbon.user.core.tenant.CommonHybridLDAPTenantManager.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.user.core.tenant.CommonHybridLDAPTenantManager.java

Source

/*
*  Copyright (c) 2005-2010, 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.user.core.tenant;

import org.apache.axiom.om.OMElement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.user.api.RealmConfiguration;
import org.wso2.carbon.user.api.TenantMgtConfiguration;
import org.wso2.carbon.user.core.UserCoreConstants;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.ldap.LDAPConnectionContext;
import org.wso2.carbon.user.core.ldap.LDAPConstants;
import org.wso2.carbon.user.core.util.JNDIUtil;
import org.wso2.carbon.user.core.util.UserCoreUtil;

import javax.naming.Name;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.sql.DataSource;
import java.util.Locale;
import java.util.Map;

/**
 * This class is the tenant manager for any external LDAP and based on the "ou" partitioning
 * per tenant under one DIT.
 */
public class CommonHybridLDAPTenantManager extends JDBCTenantManager {
    //constants
    private static final String USER_PASSWORD_ATTRIBUTE_NAME = "userPassword";
    private static final String EMAIL_ATTRIBUTE_NAME = "mail";
    //TODO move the following configurations and constants to relevant files.
    private static final String SN_ATTRIBUTE_NAME = "sn";
    private static final String CN_ATTRIBUTE_NAME = "cn";
    private static Log logger = LogFactory.getLog(CommonHybridLDAPTenantManager.class);
    private LDAPConnectionContext ldapConnectionSource;
    private TenantMgtConfiguration tenantMgtConfig = null;
    private RealmConfiguration realmConfig = null;

    public CommonHybridLDAPTenantManager(OMElement omElement, Map<String, Object> properties) throws Exception {
        super(omElement, properties);

        tenantMgtConfig = (TenantMgtConfiguration) properties.get(UserCoreConstants.TENANT_MGT_CONFIGURATION);

        realmConfig = (RealmConfiguration) properties.get(UserCoreConstants.REALM_CONFIGURATION);
        if (realmConfig == null) {
            throw new UserStoreException("Tenant Manager can not function without a bootstrap realm config");
        }

        if (ldapConnectionSource == null) {
            ldapConnectionSource = new LDAPConnectionContext(realmConfig);
        }

    }

    public CommonHybridLDAPTenantManager(DataSource dataSource, String superTenantDomain) {
        super(dataSource, superTenantDomain);
    }

    /**
     * Do necessary things in LDAP when adding a tenant.
     *
     * @param tenant Tenant to be added.
     * @return The tenant id.
     * @throws UserStoreException If an error occurred while creating the tenant.
     */
    @Override
    public int addTenant(org.wso2.carbon.user.api.Tenant tenant) throws UserStoreException {
        int tenantID = super.addTenant(tenant);
        tenant.setId(tenantID);

        DirContext initialDirContext = null;
        try {
            initialDirContext = this.ldapConnectionSource.getContext();
            //create per tenant context and its user store and group store with admin related entries.
            if (!isOrganizationalUnitCreated(tenant.getDomain(), initialDirContext)) {
                createOrganizationalUnit(tenant.getDomain(), (Tenant) tenant, initialDirContext);
                addSharedGroupForTenant((Tenant) tenant, initialDirContext);
            } else {
                logger.warn("Organizational unit for tenant domain:" + tenant.getDomain() + " is already created.");
            }
        } finally {
            closeContext(initialDirContext);
        }

        return tenantID;
    }

    /**
     * Check if organizational unit is created in tenant.
     *
     * @param orgName           Organization name.
     * @param initialDirContext The directory connection.
     * @throws UserStoreException If an error occurred while searching.
     */
    protected boolean isOrganizationalUnitCreated(String orgName, DirContext initialDirContext)
            throws UserStoreException {

        //construct search filter,eg. (&(objectClass=organizationalUnit)(ou=wso2.com))
        String partitionDN = tenantMgtConfig.getTenantStoreProperties()
                .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ROOT_PARTITION);
        String organizationalObjectClass = tenantMgtConfig.getTenantStoreProperties()
                .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ORGANIZATIONAL_OBJECT_CLASS);
        String organizationalAttribute = tenantMgtConfig.getTenantStoreProperties()
                .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ORGANIZATIONAL_ATTRIBUTE);
        String searchFilter = "(&(objectClass=" + organizationalObjectClass + ")(" + organizationalAttribute + "="
                + orgName + "))";

        SearchControls userSearchControl = new SearchControls();
        userSearchControl.setSearchScope(SearchControls.ONELEVEL_SCOPE);
        NamingEnumeration<SearchResult> userSearchResults = null;

        try {
            userSearchResults = initialDirContext.search(partitionDN, searchFilter, userSearchControl);
            return userSearchResults.hasMore();
        } catch (NamingException e) {
            String errorMessage = "Error occurred while searching in root partition for organization : " + orgName;
            if (logger.isDebugEnabled()) {
                logger.debug(errorMessage, e);
            }
            throw new UserStoreException(errorMessage, e);
        }
    }

    /**
     * Create a space for tenant in LDAP.
     *
     * @param orgName           Organization name.
     * @param tenant            The tenant
     * @param initialDirContext The directory connection.
     * @throws UserStoreException If an error occurred while creating.
     */
    protected void createOrganizationalUnit(String orgName, Tenant tenant, DirContext initialDirContext)
            throws UserStoreException {
        //e.g: ou=wso2.com
        String partitionDN = tenantMgtConfig.getTenantStoreProperties()
                .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ROOT_PARTITION);
        createOrganizationalContext(partitionDN, orgName, initialDirContext);

        //create user store
        String organizationNameAttribute = tenantMgtConfig.getTenantStoreProperties()
                .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ORGANIZATIONAL_ATTRIBUTE);
        //eg:o=cse.org,dc=wso2,dc=com
        String dnOfOrganizationalContext = organizationNameAttribute + "=" + orgName + "," + partitionDN;
        String orgSubContextValue = tenantMgtConfig.getTenantStoreProperties()
                .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ORG_SUB_CONTEXT_USER_CONTEXT_VALUE);
        if (orgSubContextValue == null) {
            //if property value is not set use default value
            orgSubContextValue = LDAPConstants.USER_CONTEXT_NAME;
        }
        createOrganizationalSubContext(dnOfOrganizationalContext, orgSubContextValue, initialDirContext);

        String orgGroupContextValue = tenantMgtConfig.getTenantStoreProperties()
                .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ORG_SUB_CONTEXT_GROUP_CONTEXT_VALUE);
        //create group store
        if (orgGroupContextValue == null) {
            //if property value is not set use default value
            orgGroupContextValue = LDAPConstants.GROUP_CONTEXT_NAME;
        }

        createOrganizationalSubContext(dnOfOrganizationalContext, orgGroupContextValue, initialDirContext);
    }

    /**
     * Create main context corresponding to tenant.
     *
     * @param rootDN            Root domain name.
     * @param orgName           Organization name
     * @param initialDirContext The directory connection.
     * @throws UserStoreException If an error occurred while creating context.
     */
    protected void createOrganizationalContext(String rootDN, String orgName, DirContext initialDirContext)
            throws UserStoreException {

        DirContext subContext = null;
        DirContext organizationalContext = null;
        try {

            //get the connection context for rootDN
            subContext = (DirContext) initialDirContext.lookup(rootDN);

            Attributes contextAttributes = new BasicAttributes(true);
            //create organizational object class attribute
            Attribute objectClass = new BasicAttribute(LDAPConstants.OBJECT_CLASS_NAME);
            objectClass.add(tenantMgtConfig.getTenantStoreProperties()
                    .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ORGANIZATIONAL_OBJECT_CLASS));
            contextAttributes.put(objectClass);
            //create organizational name attribute
            String organizationalNameAttribute = tenantMgtConfig.getTenantStoreProperties()
                    .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ORGANIZATIONAL_ATTRIBUTE);
            Attribute organization = new BasicAttribute(organizationalNameAttribute);
            organization.add(orgName);
            contextAttributes.put(organization);
            //construct organization rdn.
            String rdnOfOrganizationalContext = organizationalNameAttribute + "=" + orgName;
            if (logger.isDebugEnabled()) {
                logger.debug("Adding sub context: " + rdnOfOrganizationalContext + " under " + rootDN + " ...");
            }
            //create organization sub context
            organizationalContext = subContext.createSubcontext(rdnOfOrganizationalContext, contextAttributes);
            if (logger.isDebugEnabled()) {
                logger.debug("Sub context: " + rdnOfOrganizationalContext + " was added under " + rootDN
                        + " successfully.");
            }

        } catch (NamingException e) {
            String errorMsg = "Error occurred while adding the organizational unit " + "sub context.";
            if (logger.isDebugEnabled()) {
                logger.debug(errorMsg, e);
            }
            throw new UserStoreException(errorMsg, e);
        } finally {
            closeContext(organizationalContext);
            closeContext(subContext);
        }
    }

    protected void closeContext(DirContext ldapContext) {
        if (ldapContext != null) {
            try {
                ldapContext.close();
            } catch (NamingException e) {
                logger.error("Error closing sub context.", e);
            }
        }
    }

    /**
     * Create sub contexts under the tenant's main context.
     *
     * @param dnOfParentContext    domain name of the parent context.
     * @param nameOfCurrentContext name of the current context.
     * @param initialDirContext    The directory connection.
     * @throws UserStoreException if an error occurs while creating context.
     */
    protected void createOrganizationalSubContext(String dnOfParentContext, String nameOfCurrentContext,
            DirContext initialDirContext) throws UserStoreException {

        DirContext subContext = null;
        DirContext organizationalContext = null;

        try {
            //get the connection for tenant's main context
            subContext = (DirContext) initialDirContext.lookup(dnOfParentContext);

            Attributes contextAttributes = new BasicAttributes(true);
            //create sub unit object class attribute
            Attribute objectClass = new BasicAttribute(LDAPConstants.OBJECT_CLASS_NAME);
            objectClass.add(tenantMgtConfig.getTenantStoreProperties()
                    .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ORG_SUB_CONTEXT_OBJ_CLASS));
            contextAttributes.put(objectClass);

            //create org sub unit name attribute
            String orgSubUnitAttributeName = tenantMgtConfig.getTenantStoreProperties()
                    .get(UserCoreConstants.TenantMgtConfig.PROPERTY_ORG_SUB_CONTEXT_ATTRIBUTE);
            Attribute organizationSubUnit = new BasicAttribute(orgSubUnitAttributeName);
            organizationSubUnit.add(nameOfCurrentContext);
            contextAttributes.put(organizationSubUnit);

            //construct the rdn of org sub context
            String rdnOfOrganizationalContext = orgSubUnitAttributeName + "=" + nameOfCurrentContext;
            if (logger.isDebugEnabled()) {
                logger.debug("Adding sub context: " + rdnOfOrganizationalContext + " under " + dnOfParentContext
                        + " ...");
            }
            //create sub context
            organizationalContext = subContext.createSubcontext(rdnOfOrganizationalContext, contextAttributes);
            if (logger.isDebugEnabled()) {
                logger.debug("Sub context: " + rdnOfOrganizationalContext + " was added under " + dnOfParentContext
                        + " successfully.");
            }

        } catch (NamingException e) {
            String errorMsg = "Error occurred while adding the organizational unit " + "sub context.";
            if (logger.isDebugEnabled()) {
                logger.debug(errorMsg, e);
            }
            throw new UserStoreException(errorMsg, e);
        } finally {
            closeContext(organizationalContext);
            closeContext(subContext);
        }
    }

    @Deprecated
    protected String createAdminEntry(String dnOfUserContext, Tenant tenant, DirContext initialDirContext)
            throws UserStoreException {
        String userDN = null;
        DirContext organizationalUsersContext = null;
        try {
            //get connection to tenant's user context
            organizationalUsersContext = (DirContext) initialDirContext.lookup(dnOfUserContext);
            Attributes userAttributes = new BasicAttributes(true);

            //create person object class attribute
            Attribute objClass = new BasicAttribute(LDAPConstants.OBJECT_CLASS_NAME);
            objClass.add(realmConfig.getUserStoreProperty(LDAPConstants.USER_ENTRY_OBJECT_CLASS));
            if (UserCoreUtil.isKdcEnabled(realmConfig)) {
                // Add Kerberos specific object classes
                objClass.add("krb5principal");
                objClass.add("krb5kdcentry");
                objClass.add("subschema");

                String principal = tenant.getAdminName() + UserCoreConstants.PRINCIPAL_USERNAME_SEPARATOR
                        + tenant.getDomain() + UserCoreConstants.TENANT_DOMAIN_COMBINER + getRealmName();
                Attribute kerberosPrincipalName = new BasicAttribute("krb5PrincipalName");
                kerberosPrincipalName.add(principal);

                Attribute keyVersionNumber = new BasicAttribute("krb5KeyVersionNumber");
                keyVersionNumber.add("0");

                userAttributes.put(kerberosPrincipalName);
                userAttributes.put(keyVersionNumber);
            }
            userAttributes.put(objClass);

            //create user password attribute
            Attribute password = new BasicAttribute(USER_PASSWORD_ATTRIBUTE_NAME);
            String passwordHashMethod = realmConfig.getUserStoreProperty(LDAPConstants.PASSWORD_HASH_METHOD);
            String passwordToStore = UserCoreUtil.getPasswordToStore(tenant.getAdminPassword(), passwordHashMethod,
                    isKDCEnabled());
            password.add(passwordToStore);
            userAttributes.put(password);

            //create mail attribute
            Attribute adminEmail = new BasicAttribute(EMAIL_ATTRIBUTE_NAME);
            adminEmail.add(tenant.getEmail());
            userAttributes.put(adminEmail);

            //create compulsory attribute: sn-last name
            Attribute lastName = new BasicAttribute(SN_ATTRIBUTE_NAME);
            lastName.add(tenant.getAdminLastName());
            userAttributes.put(lastName);

            //read user name attribute in user-mgt.xml
            String userNameAttribute = realmConfig.getUserStoreProperty(LDAPConstants.USER_NAME_ATTRIBUTE);

            //if user name attribute is not cn, add it to attribute list
            if (!(CN_ATTRIBUTE_NAME.equals(userNameAttribute))) {
                Attribute firstName = new BasicAttribute(CN_ATTRIBUTE_NAME);
                firstName.add(tenant.getAdminFirstName());
                userAttributes.put(firstName);
            }
            String userRDN = userNameAttribute + "=" + tenant.getAdminName();
            organizationalUsersContext.bind(userRDN, null, userAttributes);
            userDN = userRDN + "," + dnOfUserContext;
            //return (userRDN + dnOfUserContext);
        } catch (NamingException e) {
            String errorMsg = "Error occurred while creating Admin entry";
            if (logger.isDebugEnabled()) {
                logger.debug(errorMsg, e);
            }
            throw new UserStoreException(errorMsg, e);
        } finally {
            closeContext(organizationalUsersContext);
        }

        return userDN;
    }

    @Deprecated
    protected void createAdminGroup(String dnOfGroupContext, String adminUserDN, DirContext initialDirContext)
            throws UserStoreException {
        //create set of attributes required to create admin group
        Attributes adminGroupAttributes = new BasicAttributes(true);
        //admin entry object class
        Attribute objectClassAttribute = new BasicAttribute(LDAPConstants.OBJECT_CLASS_NAME);
        objectClassAttribute.add(realmConfig.getUserStoreProperty(LDAPConstants.GROUP_ENTRY_OBJECT_CLASS));
        adminGroupAttributes.put(objectClassAttribute);

        //group name attribute
        String groupNameAttributeName = realmConfig.getUserStoreProperty(LDAPConstants.GROUP_NAME_ATTRIBUTE);
        Attribute groupNameAttribute = new BasicAttribute(groupNameAttributeName);
        String adminRoleName = realmConfig.getAdminRoleName();
        groupNameAttribute.add(UserCoreUtil.removeDomainFromName(adminRoleName));
        adminGroupAttributes.put(groupNameAttribute);

        //membership attribute
        Attribute membershipAttribute = new BasicAttribute(
                realmConfig.getUserStoreProperty(LDAPConstants.MEMBERSHIP_ATTRIBUTE));
        membershipAttribute.add(adminUserDN);
        adminGroupAttributes.put(membershipAttribute);

        DirContext groupContext = null;
        try {
            groupContext = (DirContext) initialDirContext.lookup(dnOfGroupContext);
            String rdnOfAdminGroup = groupNameAttributeName + "="
                    + UserCoreUtil.removeDomainFromName(adminRoleName);
            groupContext.bind(rdnOfAdminGroup, null, adminGroupAttributes);

        } catch (NamingException e) {
            String errorMessage = "Error occurred while creating the admin group.";
            if (logger.isDebugEnabled()) {
                logger.debug(errorMessage, e);
            }
            throw new UserStoreException(errorMessage, e);
        } finally {
            closeContext(groupContext);
        }
    }

    private boolean isKDCEnabled() {
        return UserCoreUtil.isKdcEnabled(realmConfig);
    }

    public boolean isSharedGroupEnabled() {
        String value = realmConfig.getUserStoreProperty(UserCoreConstants.RealmConfig.SHARED_GROUPS_ENABLED);
        return realmConfig.isPrimary() && "true".equalsIgnoreCase(value);
    }

    public void addSharedGroupForTenant(Tenant tenant, DirContext mainDirContext) throws UserStoreException {

        if (!isSharedGroupEnabled()) {
            return;
        }
        Attributes groupAttributes = new BasicAttributes(true);

        String domainName = tenant.getDomain();
        // create ou attribute
        String groupNameAttributeName = realmConfig
                .getUserStoreProperty(LDAPConstants.SHARED_TENANT_NAME_ATTRIBUTE);

        // create group entry's object class attribute
        Attribute objectClassAttribute = new BasicAttribute(LDAPConstants.OBJECT_CLASS_NAME);
        objectClassAttribute.add(realmConfig.getUserStoreProperty(LDAPConstants.SHARED_TENANT_OBJECT_CLASS));
        groupAttributes.put(objectClassAttribute);

        DirContext groupContext = null;

        String searchBase = realmConfig.getUserStoreProperties().get(LDAPConstants.SHARED_GROUP_SEARCH_BASE);

        try {
            groupContext = (DirContext) mainDirContext.lookup(searchBase);
            NameParser ldapParser = groupContext.getNameParser("");
            Name compoundGroupName = ldapParser.parse(groupNameAttributeName + "=" + domainName);
            groupContext.bind(compoundGroupName, null, groupAttributes);

        } catch (Exception e) {
            String errorMsg = "Shared tenant: " + domainName + "could not be added.";
            if (logger.isDebugEnabled()) {
                logger.debug(errorMsg, e);
            }
            throw new UserStoreException(errorMsg, e);
        } finally {
            JNDIUtil.closeContext(groupContext);
        }

    }

    /**
     * @return
     */
    protected String getRealmName() {

        // First check whether realm name is defined in the configuration
        String defaultRealmName = this.realmConfig
                .getUserStoreProperty(UserCoreConstants.RealmConfig.DEFAULT_REALM_NAME);

        if (defaultRealmName != null) {
            return defaultRealmName;
        }

        // If not build the realm name from the search base.
        // Here the realm name will be a concatenation of dc components in the
        // search base.
        String searchBase = this.realmConfig.getUserStoreProperty(LDAPConstants.USER_SEARCH_BASE);

        String[] domainComponents = searchBase.split("dc=");

        StringBuilder builder = new StringBuilder();

        for (String dc : domainComponents) {
            if (!dc.contains("=")) {
                String trimmedDc = dc.trim();
                if (trimmedDc.endsWith(",")) {
                    builder.append(trimmedDc.replace(',', '.'));
                } else {
                    builder.append(trimmedDc);
                }
            }
        }

        return builder.toString().toUpperCase(Locale.ENGLISH);
    }

}