com.vmware.identity.idm.server.IdentityManager.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.identity.idm.server.IdentityManager.java

Source

/*
 *
 *  Copyright (c) 2012-2015 VMware, Inc.  All Rights Reserved.
 *
 *  Licensed 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 com.vmware.identity.idm.server;

import java.io.IOException;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import javax.security.auth.login.LoginException;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.Validate;

import sun.security.krb5.KrbException;

import com.vmware.af.VmAfClientNativeException;
import com.vmware.af.interop.VmAfAccessDeniedException;
import com.vmware.af.interop.VmAfAlreadyJoinedException;
import com.vmware.af.interop.VmAfBadPacketException;
import com.vmware.af.interop.VmAfInvalidComputerNameException;
import com.vmware.af.interop.VmAfInvalidParameterException;
import com.vmware.af.interop.VmAfLdapNoSuchObjectException;
import com.vmware.af.interop.VmAfNoSuchDomainException;
import com.vmware.af.interop.VmAfNoSuchLogonSessionException;
import com.vmware.af.interop.VmAfNotSupportedException;
import com.vmware.af.interop.VmAfUnknownServerException;
import com.vmware.af.interop.VmAfWrongPasswordException;
import com.vmware.identity.auth.passcode.spi.AuthenticationSecret;
import com.vmware.identity.auth.passcode.spi.AuthenticationSession;
import com.vmware.identity.auth.passcode.spi.AuthenticationSessionFactory;
import com.vmware.identity.auth.passcode.spi.AuthenticationResult;
import com.vmware.identity.auth.passcode.spi.SessionFactoryProvider;
import com.vmware.identity.diagnostics.DiagnosticsContextFactory;
import com.vmware.identity.diagnostics.DiagnosticsLoggerFactory;
import com.vmware.identity.diagnostics.IDiagnosticsContextScope;
import com.vmware.identity.diagnostics.IDiagnosticsLogger;
import com.vmware.identity.diagnostics.VmEvent;
import com.vmware.identity.idm.ADIDSAlreadyExistException;
import com.vmware.identity.idm.ActiveDirectoryJoinInfo;
import com.vmware.identity.idm.Attribute;
import com.vmware.identity.idm.AttributeValuePair;
import com.vmware.identity.idm.AuthenticationType;
import com.vmware.identity.idm.AuthnPolicy;
import com.vmware.identity.idm.CertificateRevocationCheckException;
import com.vmware.identity.idm.CertificateType;
import com.vmware.identity.idm.ClientCertPolicy;
import com.vmware.identity.idm.CommonUtil;
import com.vmware.identity.idm.DomainManagerException;
import com.vmware.identity.idm.DomainTrustsInfo;
import com.vmware.identity.idm.DomainType;
import com.vmware.identity.idm.DuplicateProviderException;
import com.vmware.identity.idm.DuplicateTenantException;
import com.vmware.identity.idm.GSSResult;
import com.vmware.identity.idm.Group;
import com.vmware.identity.idm.GroupDetail;
import com.vmware.identity.idm.HostNotJoinedRequiredDomainException;
import com.vmware.identity.idm.IDMException;
import com.vmware.identity.idm.IDMLoginException;
import com.vmware.identity.idm.IDMSecureIDNewPinException;
import com.vmware.identity.idm.IDPConfig;
import com.vmware.identity.idm.IIdentityManager;
import com.vmware.identity.idm.IIdentityStoreData;
import com.vmware.identity.idm.IIdentityStoreDataEx;
import com.vmware.identity.idm.IIdmServiceContext;
import com.vmware.identity.idm.IdentityStoreData;
import com.vmware.identity.idm.IdentityStoreType;
import com.vmware.identity.idm.IdmADDomainException;
import com.vmware.identity.idm.IdmADDomainJoinStatusException;
import com.vmware.identity.idm.IdmCertificateRevokedException;
import com.vmware.identity.idm.InvalidArgumentException;
import com.vmware.identity.idm.InvalidPasswordPolicyException;
import com.vmware.identity.idm.InvalidPrincipalException;
import com.vmware.identity.idm.InvalidProviderException;
import com.vmware.identity.idm.LocalISRegistrationException;
import com.vmware.identity.idm.LockoutPolicy;
import com.vmware.identity.idm.MemberAlreadyExistException;
import com.vmware.identity.idm.NoSuchExternalIdpConfigException;
import com.vmware.identity.idm.NoSuchIdpException;
import com.vmware.identity.idm.NoSuchTenantException;
import com.vmware.identity.idm.OIDCClient;
import com.vmware.identity.idm.PasswordExpiration;
import com.vmware.identity.idm.PasswordExpiredException;
import com.vmware.identity.idm.PasswordPolicy;
import com.vmware.identity.idm.PasswordPolicyViolationException;
import com.vmware.identity.idm.PersonDetail;
import com.vmware.identity.idm.PersonUser;
import com.vmware.identity.idm.Principal;
import com.vmware.identity.idm.PrincipalId;
import com.vmware.identity.idm.RSAAMInstanceInfo;
import com.vmware.identity.idm.RSAAMResult;
import com.vmware.identity.idm.RSAAgentConfig;
import com.vmware.identity.idm.RelyingParty;
import com.vmware.identity.idm.ResourceServer;
import com.vmware.identity.idm.STSSpnValidator;
import com.vmware.identity.idm.SearchCriteria;
import com.vmware.identity.idm.SearchResult;
import com.vmware.identity.idm.SecurityDomain;
import com.vmware.identity.idm.SolutionDetail;
import com.vmware.identity.idm.SolutionUser;
import com.vmware.identity.idm.SsoHealthStatsData;
import com.vmware.identity.idm.Tenant;
import com.vmware.identity.idm.TokenClaimAttribute;
import com.vmware.identity.idm.UserAccountLockedException;
import com.vmware.identity.idm.ValidateUtil;
import com.vmware.identity.idm.VmHostData;
import com.vmware.identity.idm.server.clientcert.IdmClientCertificateValidator;
import com.vmware.identity.idm.server.clientcert.IdmCrlCache;
import com.vmware.identity.idm.server.clientcert.TenantCrlCache;
import com.vmware.identity.idm.server.config.IConfigStore;
import com.vmware.identity.idm.server.config.IConfigStoreFactory;
import com.vmware.identity.idm.server.config.IdmServerConfig;
import com.vmware.identity.idm.server.config.ServerIdentityStoreData;
import com.vmware.identity.idm.server.config.directory.DirectoryConfigStore;
import com.vmware.identity.idm.server.config.directory.TenantAttributes;
import com.vmware.identity.idm.server.config.directory.TokenPolicy;
import com.vmware.identity.idm.server.performance.IIdmAuthStatRecorder;
import com.vmware.identity.idm.server.performance.IdmAuthStatCache;
import com.vmware.identity.idm.server.performance.IdmAuthStatRecorder;
import com.vmware.identity.idm.server.performance.NoopIdmAuthStatRecorder;
import com.vmware.identity.idm.server.performance.PerformanceMonitor;
import com.vmware.identity.idm.server.performance.PerformanceMonitorFactory;
import com.vmware.identity.idm.server.provider.BaseLdapProvider;
import com.vmware.identity.idm.server.provider.GSSAuthProvider;
import com.vmware.identity.idm.server.provider.GSSAuthResult;
import com.vmware.identity.idm.server.provider.IGssAuthIdentityProvider;
import com.vmware.identity.idm.server.provider.IIdentityProvider;
import com.vmware.identity.idm.server.provider.IProviderFactory;
import com.vmware.identity.idm.server.provider.ISystemDomainIdentityProvider;
import com.vmware.identity.idm.server.provider.NoSuchUserException;
import com.vmware.identity.idm.server.provider.PrincipalGroupLookupInfo;
import com.vmware.identity.idm.server.provider.activedirectory.ActiveDirectoryProvider;
import com.vmware.identity.idm.server.provider.activedirectory.ServerKrbUtils;
import com.vmware.identity.idm.server.provider.localos.LocalOsIdentityProvider;
import com.vmware.identity.idm.server.vmaf.VmafClientUtil;
import com.vmware.identity.interop.IdmUtils;
import com.vmware.identity.interop.accountmanager.AccountLockedOutException;
import com.vmware.identity.interop.accountmanager.AccountPasswordExpiredException;
import com.vmware.identity.interop.directory.Directory;
import com.vmware.identity.interop.domainmanager.DomainControllerInfo;
import com.vmware.identity.interop.domainmanager.DomainTrustInfo;
import com.vmware.identity.interop.idm.IIdmClientLibrary;
import com.vmware.identity.interop.idm.IdmClientLibraryFactory;
import com.vmware.identity.interop.ldap.AlreadyExistsLdapException;
import com.vmware.identity.interop.ldap.ConstraintViolationLdapException;
import com.vmware.identity.interop.ldap.DirectoryStoreProtocol;
import com.vmware.identity.interop.ldap.ILdapConnectionEx;
import com.vmware.identity.interop.ldap.ServerDownLdapException;
import com.vmware.identity.interop.registry.IRegistryAdapter;
import com.vmware.identity.interop.registry.IRegistryKey;
import com.vmware.identity.interop.registry.RegKeyAccess;
import com.vmware.identity.interop.registry.RegistryAdapterFactory;
import com.vmware.identity.performanceSupport.IIdmAuthStat;
import com.vmware.identity.performanceSupport.IIdmAuthStat.ActivityKind;
import com.vmware.identity.performanceSupport.IIdmAuthStat.EventLevel;
import com.vmware.identity.performanceSupport.IIdmAuthStatus;
import com.vmware.identity.performanceSupport.IdmAuthStatus;
import com.vmware.identity.performanceSupport.PerfBucketKey;
import com.vmware.identity.performanceSupport.PerfMeasurementPoint;

/**
 * User: snambakam
 * Date: 12/23/11
 * Time: 1:56 PM
 */
public class IdentityManager extends UnicastRemoteObject implements IIdentityManager {
    class IdmCachePeriodicChecker extends Thread {
        @Override
        public void run() {
            try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", "bba76607-42b4-4a15-a3c2-7542f427d12c",
                    "IdmCachePeriodicChecker")) {
                while (true) {
                    try {
                        long startTime = System.nanoTime();

                        IdentityManager.this.refreshTenantCache();

                        if (IdmServer.getPerfDataSinkInstance() != null) {
                            IdmServer.getPerfDataSinkInstance().addMeasurement(
                                    new PerfBucketKey(PerfMeasurementPoint.IDMPeriodicRefreshTenantCertificates),
                                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
                        }
                    } catch (Throwable t) {
                        logger.error(String.format("IdmCachePeriodicChecker refreshTenantCredential failed : %s",
                                t.getMessage()), t);
                        t.printStackTrace();
                    }

                    try {
                        // refresh tenant certificates every 15 seconds
                        Thread.sleep(15000);
                    } catch (InterruptedException e) {
                        logger.error("IdmCachePeriodicChecker Thread is interrupted!");
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * CRL checker thread refreshing at interval of 1 hour (3600000 milliseconds)
     *
     */
    class IdmCrlCachePeriodicChecker extends Thread {
        private final Integer CheckInterval = 3600000;

        @Override
        public void run() {
            try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", "IdmCrlCachePeriodicChecker",
                    "IdmCrlCachePeriodicChecker")) {
                while (_crlCacheChecker == Thread.currentThread()) {
                    try {
                        IdentityManager.this.refreshTenantCrlCache();
                    } catch (Throwable t) {
                        logger.error(String.format("IdmCrlCachePeriodicChecker refreshTenantCrl failed : %s",
                                t.getMessage()), t);
                        t.printStackTrace();
                    }

                    try {
                        Thread.sleep(CheckInterval);
                    } catch (InterruptedException e) {
                        logger.error("IdmCrlCachePeriodicChecker Thread is interrupted!");
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static final String THIRD_PARTY_IDP_USER_DEFAULT_GROUP_NAME = "Users";

    // this will include both user's own sid as well as the sids of groups user is a member of
    public static final String INTERNAL_ATTR_GROUP_OBJECTIDS = "userMemberOfGroupIds";

    private IProviderFactory providerFactory;

    private IConfigStoreFactory configStoreFactory;

    private AuthSessionFactoryCache _rsaSessionFactoryCache = new AuthSessionFactoryCache();

    private static final long serialVersionUID = -7719567998332472234L;
    /* The following chars cannot be part of:
     * - userName
     * - firstName, lastName or userDetails.description
     * when user is created (locally on tenant's system IDP)
     */
    final static char[] INVALID_CHARS_FOR_USER_DETAIL = "^<>&%`".toCharArray();

    // we support either 'user@domain', 'domain\\user', or default domain formats
    final static char UPN_SEPARATOR = '@';
    final static char NETBIOS_SEPARATOR = '\\';
    final static char[] VALID_ACCOUNT_NAME_SEPARATORS = { UPN_SEPARATOR, NETBIOS_SEPARATOR };

    final static char[] INVALID_CHARS_FOR_USER_ID = (String.valueOf(INVALID_CHARS_FOR_USER_DETAIL) + UPN_SEPARATOR
            + NETBIOS_SEPARATOR).toCharArray();

    private final static String LOCAL_OS_STATIC_ALIAS = "localos";
    private final static RsaAuthSessionCache _rsaSessionCache = new RsaAuthSessionCache();

    private IConfigStore _configStore;
    private Collection<Attribute> _defaultAttributes;
    private TenantCache _tenantCache;
    private TenantCrlCache _tenantCrlCache;
    private volatile Thread _crlCacheChecker = null;

    public static final String WELLKNOWN_SOLUTIONUSERS_GROUP_NAME = "SolutionUsers";
    public static final String WELLKNOWN_SOLUTIONUSERS_GROUP_DESCRIPTION = "Well-known solution users' group, which contains all solution users as members.";

    public static final String WELLKNOWN_CONFIGURATIONUSERS_GROUP_NAME = "SystemConfiguration.Administrators";
    public static final String WELLKNOWN_CONFIGURATIONUSERS_GROUP_DESCRIPTION = "Well-known configuration users' group which contains all configuration users as members.";

    public static final String WELLKNOWN_EXTERNALIDP_USERS_GROUP_NAME = "ExternalIDPUsers";
    public static final String WELLKNOWN_EXTERNALIDP_USERS_GROUP_DESCRIPTION = "Well-known external IDP users' group, which registers external IDP users as guests.";

    private static final PersonDetail EXTERNAL_USER_SAMPLE_PERSON_DETAIL = new PersonDetail.Builder().build();

    private static final IDiagnosticsLogger logger = DiagnosticsLoggerFactory.getLogger(IdentityManager.class);

    private static final String DEFAULT_HTTPS_PORT = "443";
    private static final String HOSTNAME_MACRO = "<HOSTNAME>"; // to be susbstituted with getHostIPAddress()
    private static final String PORT_MACRO = "<PORT>"; // to be substituted with StsTomcat ipaddress

    private static final String PROVIDER_TYPE_RSA_SECURID = "RsaSecureID";
    private static final String PASSCODE_PROVIDER_TYPE = "RSA";

    private static SsoHealthStatistics ssoHealthStatistics = new SsoHealthStatistics();

    private class ProvidersInfo {
        Collection<IIdentityProvider> _providers;
        Collection<IIdentityStoreData> _idsStores; // internal provider data store information
        IIdentityProvider _adProvider;
        Collection<String> _defaultProviders;

        private ProvidersInfo(Collection<IIdentityProvider> providers, Collection<IIdentityStoreData> idsStores,
                IIdentityProvider adProvider, Collection<String> defaultProviders) {
            this._providers = providers;
            this._idsStores = idsStores;
            this._adProvider = adProvider;
            this._defaultProviders = defaultProviders;
        }
    }

    public IdentityManager(IConfigStoreFactory configFactory, IProviderFactory pdFactory)
            throws RemoteException, IDMException {
        super();

        try {
            ssoHealthStatistics.setUpTimeIDMService();
            IdmDomainState.getInstance();

            this.configStoreFactory = configFactory;
            this.providerFactory = pdFactory;

            _tenantCache = new TenantCache();
            _tenantCrlCache = new TenantCrlCache();

            IdmServerConfig settings = IdmServerConfig.getInstance();
            _defaultAttributes = settings.getDefaultAttributesList();

            if (_defaultAttributes == null) {
                _defaultAttributes = Collections.emptyList();
            }

            _configStore = this.configStoreFactory.getConfigStore();
            if (_configStore == null) {
                throw new RemoteException("Failed to initialize config store");
            }

            String systemTenant = registerServiceProviderAsTenant();

            // prepare tenant cache
            initializeTenantCache();

            ensureValidTenant(systemTenant);

            // Create 'SystemConfiguration.Administrators' well-known group specific to the system tenant
            ensureWellKnownConfigurationUsersGroupExist(systemTenant);

            // start the cache checking thread
            Thread t = new IdmCachePeriodicChecker();
            t.start();

            // start the crl cache checking thread
            ManageCrlCacheChecker();

            logger.info("Identity Manager initialized successfully");
        } catch (Exception ex) {
            logger.error("Identity Manager failed to initialize", ex);

            throw ServerUtils.getRemoteException(ex);
        }
    }

    /**
     * Check if any tenant enabled smartcard authentication
     * @return true if at least one tenant enables smart card authentication.
     * @throws Exception if unable to check tenants.
     */
    private boolean smartCardAuthnEnabled() throws Exception {
        Collection<String> allTenantNames = this.getAllTenants();
        assert (allTenantNames != null && allTenantNames.size() > 0);

        boolean smartCardAuthnEnabled = false;
        for (String tenantName : allTenantNames) {
            try {
                AuthnPolicy policy = getTenantInfo(tenantName).getAuthnPolicy();

                if (policy.IsTLSClientCertAuthnEnabled()) {
                    smartCardAuthnEnabled = true;
                    break;
                }
            } catch (Exception ex) {
                logger.error(String.format("Failed to retrieve Authentication policy for tenant %s. ", tenantName));
                throw ex;
            }
        }
        return smartCardAuthnEnabled;
    }

    private void ensureValidTenant(String tenantName) throws Exception {
        // create 'SolutionUsers' well-known group
        // (each solution user is automatically memberof such group upon creation)
        EnsureWellKnownSolutionUsersGroupExist(tenantName);

        //create registration group for external IDP users
        ensureWellKnownExternalIDPUsersGroupExist(tenantName);

        // Make sure we create ServicePrincipal containers to place solution users
        _configStore.ensureSPContainerExist(tenantName);
    }

    private void addTenant(Tenant tenant, String adminAccountName, char[] adminPwd) throws Exception {
        try {
            ValidateUtil.validateNotNull(tenant, "Tenant");
            ValidateUtil.validateNotEmpty(adminAccountName, "adminAccountName");
            ValidateUtil.validateNotEmpty(adminPwd, "adminPwd");

            registerTenant(tenant, adminAccountName, adminPwd);

            Directory.createInstance(tenant.getName(), adminAccountName, String.valueOf(adminPwd));

            //Add Tenant admin to System Admins group
            Collection<IIdentityStoreData> stores = getProviders(tenant.getName(),
                    EnumSet.of(DomainType.SYSTEM_DOMAIN), true/*internal*/);
            if (stores.size() != 1) {
                throw new IllegalStateException("Failed to find tenant's system domain");
            }
            stores.iterator().next().getExtendedIdentityStoreData();

            ensureValidTenant(tenant.getName());

            // load tenant information to cache when adding tenant
            TenantInformation tenantInfo = loadTenant(tenant.getName());
            assert (tenantInfo != null);

            logger.info(String.format("Tenant [%s] added successfully", tenant.getName()));
        } catch (Exception ex) {
            logger.error(String.format("Failed to add tenant [%s]", tenant.getName()));

            if (ex instanceof AlreadyExistsLdapException) {
                throw new DuplicateTenantException(ex.getMessage());
            } else {
                throw ex;
            }
        }
    }

    private void deleteTenant(String name) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(name, "Tenant name");

            TenantInformation tenantInfo = findTenant(name);
            ServerUtils.validateNotNullTenant(tenantInfo, name);

            // verify that we are not deleting the system tenant
            if (name.equalsIgnoreCase(getSystemTenant())) {
                String errMsg = String.format("Failed to delete tenant [%s], this is system tenant", name);
                logger.error(errMsg);

                throw new IllegalArgumentException(errMsg);
            }

            unregisterTenant(name);

            deleteTenantDomain(name);

            _tenantCache.deleteTenant(name);
            ssoHealthStatistics.removeTenantStats(name);

            PerformanceMonitor.getInstance().deleteCache(name);

            logger.info(String.format("Tenant [%s] deleted successfully", name));
        } catch (Exception ex) {
            logger.error(String.format("Failed to delete tenant [%s]", name));
            throw ex;
        }
    }

    private Tenant getTenant(String name) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(name, "Tenant name");

            TenantInformation tenantInfo = findTenant(name);
            ServerUtils.validateNotNullTenant(tenantInfo, name);

            return tenantInfo.getTenant();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get tenant [%s]", name));

            throw ex;
        }
    }

    private void setTenant(Tenant tenant) throws Exception {
        try {
            ValidateUtil.validateNotNull(tenant, "Tenant");

            TenantInformation tenantInfo = findTenant(tenant.getName());
            ServerUtils.validateNotNullTenant(tenantInfo, tenant.getName());

            _configStore.setTenant(tenant);

            _tenantCache.deleteTenant(tenant.getName());

            logger.info(String.format("Tenant [%s] set successfully", tenant.getName()));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set tenant [%s]", tenant.getName()));

            throw ex;
        }
    }

    private List<Certificate> getTenantCertificate(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getTenantCertificate();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get certificate for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private Collection<List<Certificate>> getTenantCertificates(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getTenantCertificates();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get certificate for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Retrieves the currently configured signature algorithm for the tenant.
     *
     * @param tenantName
     * @return algorithm name.
     * @throws IDMException
     * @throws NoSuchTenantException - if no such tenant exist
     * @throws InvalidArgumentException    -- if the tenant name is
     *                                  null or empty
     */
    private String getTenantSignatureAlgorithm(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getSignatureAlgorithm();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get signature algorithm for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
    * Sets the signature algorithm used for signing tokens from STS for that tenant.
    *
    * @param tenantName
    * @return
    * @throws IDMException
    * @throws NoSuchTenantException - if no such tenant exist
    * @throws InvalidArgumentException    -- if the tenant name or
    *                                 signatureAlgorithm is null or empty
    */
    private void setTenantSignatureAlgorithm(String tenantName, String signatureAlgorithm) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            logger.debug(String.format("Signature algorithm [%s] will be set for tenant [%s]", signatureAlgorithm,
                    tenantName));

            _configStore.setSignatureAlgorithm(tenantName, signatureAlgorithm);

            logger.info(String.format("Signature algorithm successfully set for tenant [%s]", tenantName));

            _tenantCache.deleteTenant(tenantName);
            logger.debug(String.format("tenant cache removed for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set signature algorithm for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Sets the optional brand name for the tenant. This string is used on login page.
     * If the attribute is not set or set to null/empty string, it will use vCenter login page with vCenter logo
     * If a non-empty value is set, the brand name string is displayed on the login page.
     *
     * @param tenantName
     * @param brandname
     * @return
     * @throws IDMException
     * @throws NoSuchTenantException - if no such tenant exist
     * @throws InvalidArgumentException    -- if the tenant name or
     *                                 signatureAlgorithm is null or empty
     */
    private void setBrandName(String tenantName, String brandName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            logger.debug(String.format("Band name [%s] will be set for tenant [%s]", brandName, tenantName));

            _configStore.setBrandName(tenantName, brandName);

            logger.info(String.format("Brand name successfully set for tenant [%s]", tenantName));

            _tenantCache.deleteTenant(tenantName);
            logger.debug(String.format("tenant cache removed for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set brand name for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Retrieves configured brand name for the tenant.
     *
     * @param tenantName
     * @return brand name.
     * @throws IDMException
     * @throws NoSuchTenantException - if no such tenant exist
     * @throws InvalidArgumentException    -- if the tenant name is
     *                                  null or empty
     */
    private String getBrandName(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getBrandName();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get brand name for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setLogonBannerContent(String tenantName, String logonBannerContent) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            logger.debug(String.format("Logon banner will be set for tenant [%s]", tenantName));

            _configStore.setLogonBannerContent(tenantName, logonBannerContent);
            logger.info(String.format("Logon banner successfully set for tenant [%s]", tenantName));

            _tenantCache.deleteTenant(tenantName);
            logger.debug(String.format("tenant cache removed for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set logon banner for tenant [%s]", tenantName));
            throw ex;
        }
    }

    /**
     * Set authentication policies per identity source. This also takes care of synchronizing authentication types for tenant
     */
    @Override
    public void setAuthnPolicyForProvider(String tenantName, String providerName, AuthnPolicy policy,
            IIdmServiceContext serviceContext) throws Exception {

        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setAuthnPolicyForProvider")) {

            // Validate if provider exists
            ValidateUtil.validateNotEmpty(providerName, "Identity provider name");
            IIdentityStoreData provider = getProviderWithInternalInfo(tenantName, providerName);
            if (provider == null) {
                String errMessage = String.format("Failed to find provider:'%s' on tenant :'%s'", providerName,
                        tenantName);
                logger.error(errMessage);
                throw new NoSuchIdpException(errMessage);
            }

            if (policy.IsRsaSecureIDAuthnEnabled()) {
                checkPasscodeAuthProviderConfigured();
            }

            // Set authentication policy for identity source
            _configStore.setAuthnTypesForProvider(tenantName, providerName, policy.IsPasswordAuthEnabled(),
                    policy.IsWindowsAuthEnabled(), policy.IsTLSClientCertAuthnEnabled(),
                    policy.IsRsaSecureIDAuthnEnabled());

            // Delete tenant data from cache such that it will load fresh data on next request
            _tenantCache.deleteTenant(tenantName);

            // Synchronize tenant authentication policy
            synchronizeTenantAuthenticationPolicy(tenantName);

        } catch (Exception ex) {
            logger.error("Failed to set authentication policy for provider : " + providerName, ex);
            throw ServerUtils.getRemoteException(ex);
        }
    }

    /**
     *
     * This ensure the authentication policy set on tenant is always a super set of authentication policies of each identity source
     * attached to the same tenant.
     *
     * @param tenantName name of tenant
     */
    private void synchronizeTenantAuthenticationPolicy(String tenantName) throws Exception {

        boolean passwordAuthEnabled = false;
        boolean windowsAuthEnabled = false;
        boolean certAuthEnabled = false;
        boolean rsaSecureIDAuthnEnabled = false;

        Collection<IIdentityStoreData> providers = getProviders(tenantName);
        Set<Integer> tenantPermissibleAuthnTypes = new HashSet<Integer>();
        for (IIdentityStoreData provider : providers) {
            int[] authnTypes = provider.getExtendedIdentityStoreData().getAuthnTypes();
            for (int authnType : authnTypes) {
                tenantPermissibleAuthnTypes.add(Integer.valueOf(authnType));
            }
        }

        // Sync password authentication
        if (tenantPermissibleAuthnTypes.contains(DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_PASSWORD)) {
            passwordAuthEnabled = true;
        }

        // Sync windows based authentication
        if (tenantPermissibleAuthnTypes.contains(DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_WINDOWS)) {
            windowsAuthEnabled = true;
        }

        // Sync CAC based authentication
        if (tenantPermissibleAuthnTypes.contains(DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_TLS_CERTIFICATE)) {
            certAuthEnabled = true;
        }

        // Sync RSA securID authentication
        if (tenantPermissibleAuthnTypes.contains(DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_RSA_SECUREID)) {
            rsaSecureIDAuthnEnabled = true;
        }
        _configStore.setAuthnTypes(tenantName, passwordAuthEnabled, windowsAuthEnabled, certAuthEnabled,
                rsaSecureIDAuthnEnabled);
    }

    private String getLogonBannerContent(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getLogonBannerContent();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get logon banner for tenant [%s]", tenantName));
            throw ex;
        }
    }

    private String getLogonBannerTitle(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getLogonBannerTitle();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get logon banner title for tenant [%s]", tenantName));
            throw ex;
        }
    }

    private boolean getLogonBannerCheckboxFlag(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getLogonBannerCheckboxFlag();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get logon banner title for tenant [%s]", tenantName));
            throw ex;
        }
    }

    private void setLogonBannerTitle(String tenantName, String logonBannerTitle) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            logger.debug(String.format("Logon banner title [%s] will be set for tenant [%s]", logonBannerTitle,
                    tenantName));

            _configStore.setLogonBannerTitle(tenantName, logonBannerTitle);
            logger.info(String.format("Logon banner successfully set for tenant [%s]", tenantName));

            _tenantCache.deleteTenant(tenantName);
            logger.debug(String.format("tenant cache removed for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set logon banner for tenant [%s]", tenantName));
            throw ex;
        }
    }

    private void setLogonBannerCheckbox(String tenantName, boolean enableLogonBannerCheckbox) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            logger.debug(String.format("Logon banner checkbox [%s] will be set for tenant [%s]",
                    enableLogonBannerCheckbox, tenantName));

            _configStore.setLogonBannerCheckboxFlag(tenantName, enableLogonBannerCheckbox);
            logger.info(String.format("Logon banner successfully set for tenant [%s]", tenantName));

            _tenantCache.deleteTenant(tenantName);
            logger.debug(String.format("tenant cache removed for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set logon banner for tenant [%s]", tenantName));
            throw ex;
        }
    }

    private List<Certificate> validateCertificateChain(String tenantName, /* optional */
            Collection<Certificate> tenantCertificates, PrivateKey tenantPrivateKey) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantCertificates, "Certificate chain");

            if (tenantCertificates.size() <= 1) {
                throw new IllegalArgumentException("Invalid certificate chain");
            }

            // MEMO: IDM also can simply throw exception
            // telling client certChain is invalid with duplicate certificates instead

            // Make sure certificate chain does not have duplicate certs
            // OR ldap operation will fail due to dup values for one attribute
            List<Certificate> certChainWithNoDup = new ArrayList<Certificate>();
            for (Certificate cert : tenantCertificates) {
                if (!certChainWithNoDup.contains(cert)) {
                    certChainWithNoDup.add(cert);
                }
            }

            // Validate the certChain with no duplicate certificates while preserving the original order

            // (1) length validation
            if (certChainWithNoDup.size() <= 1) {
                throw new IllegalArgumentException("Invalid certificate chain");
            }

            if (tenantPrivateKey != null) {
                // (2) The certChain must be ordered and
                // contain a Certificate at index 0 corresponding to the private key
                Certificate cert = certChainWithNoDup.get(0);
                new PrivateKeyWithCertificateSignatureValidator().validate(tenantPrivateKey, cert);
            }

            return certChainWithNoDup;
        } catch (Exception ex) {
            logger.error(String.format("Failed to validate tenant credentials for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setTenantCredentials(String tenantName, Collection<Certificate> tenantCertificates,
            PrivateKey tenantPrivateKey) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            // check whether tenant exists, if not, throw NoSucnTenantException.
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            // Validate 'tenantCertificates' and "tenantPrivateKey"
            ValidateUtil.validateNotNull(tenantPrivateKey, "Tenant privatekey");

            List<Certificate> certChainWithNoDup = validateCertificateChain(tenantName, tenantCertificates,
                    tenantPrivateKey);

            _configStore.setTenantCredentials(tenantName, certChainWithNoDup, tenantPrivateKey);

            _tenantCache.deleteTenant(tenantName);

            logger.info(String.format("Credentials successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set credentials for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setTenantTrustedCertificateChain(String tenantName, Collection<Certificate> tenantCertificates)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            // check whether tenant exists, if not, throw NoSucnTenantException.
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            List<Certificate> certChainWithNoDup = validateCertificateChain(tenantName, tenantCertificates, null);

            _configStore.setTenantTrustedCertificateChain(tenantName, certChainWithNoDup);

            _tenantCache.deleteTenant(tenantName);

            logger.info(String.format("Trusted certificate chain successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set credentials for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private PrivateKey getTenantPrivateKey(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getPrivateKey();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get private key for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private boolean isTenantIDPSelectionEnabled(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.isIDPSelectionEnabled();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get IDP selection flag for tenant [%s]", tenantName));
            throw ex;
        }
    }

    private void setTenantIDPSelectionEnabled(String tenantName, boolean enableIDPSelection) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            logger.debug(String.format("IDP selection flag [%s] will be set for tenant [%s]", enableIDPSelection,
                    tenantName));

            _configStore.setTenantIDPSelectionEnabled(tenantName, enableIDPSelection);
            logger.info(String.format("IDP selection flag successfully set for tenant [%s]", tenantName));

            _tenantCache.deleteTenant(tenantName);
            logger.debug(String.format("tenant cache removed for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set IDP selection flag for tenant [%s]", tenantName));
            throw ex;
        }
    }

    private long getClockTolerance(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getClockTolerance();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get clock tolerance for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setClockTolerance(String tenantName, long milliseconds) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            _configStore.setClockTolerance(tenantName, milliseconds);

            _tenantCache.deleteTenant(tenantName);

            logger.info(String.format("Clock tolerance successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set clock tolerance for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Retrieves the configuration setting in the tenant pertaining to the
     * maximum number of times a SAML token may be delegated.
     *
     * @param tenantName Name of tenant
     * @throws IDMException
     * @throws RemoteException
     * @throws NoSuchTenantException      - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     */
    private int getDelegationCount(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getDelegationCount();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get delegation count for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Sets the maximum number of times a SAML token generated by the tenant
     * may be delegated.
     *
     * @param tenantName      Name of tenant, required, non-null, non-empty.
     * @param delegationCount A positive integer, required, non-negative
     * @throws IDMException
     * @throws NoSuchTenantException      - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     */
    private void setDelegationCount(String tenantName, int delegationCount) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            _configStore.setDelegationCount(tenantName, delegationCount);

            _tenantCache.deleteTenant(tenantName);

            logger.info(String.format("Delegation count successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set delegation count for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private int getRenewCount(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getRenewCount();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get renew count for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Sets the maximum number of times a SAML token generated by the tenant may
     * be renewed.
     *
     * @param tenantName Name of tenant, required, non-null, non-empty
     * @param renewCount Maximum number of time. Positive integer
     * @throws IDMException
     * @throws Exception
     * @throws NoSuchTenantException      - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     */
    private void setRenewCount(String tenantName, int renewCount) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            _configStore.setRenewCount(tenantName, renewCount);

            _tenantCache.deleteTenant(tenantName);

            logger.info(String.format("Renew count successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set renew count for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private SsoHealthStatsData getSsoStatistics(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            return ssoHealthStatistics.getSsoStatistics(tenantName);

        } catch (Exception ex) {
            logger.error(String.format("Failed to get Sso Health Statistics for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void incrementGeneratedTokens(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            ssoHealthStatistics.incrementGeneratedTokens(tenantName);

        } catch (Exception ex) {
            logger.error(String.format("Failed to increment generated tokens for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void incrementRenewedTokens(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            ssoHealthStatistics.incrementRenewedTokens(tenantName);

        } catch (Exception ex) {
            logger.error(String.format("Failed to increment renewed tokens for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private long getMaximumBearerTokenLifetime(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getMaxBearerTokenLifetime();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get maximum bearer lifetime for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setMaximumBearerTokenLifetime(String tenantName, long maxLifetime) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            _configStore.setMaximumBearerTokenLifetime(tenantName, maxLifetime);

            _tenantCache.deleteTenant(tenantName);

            logger.info(
                    String.format("Maximum Bearer token lifetime successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set maximum bearer lifetime for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private long getMaximumHoKTokenLifetime(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getMaxHOKLifetime();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get maximum holder of key lifetime for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setMaximumHoKTokenLifetime(String tenantName, long maxLifetime) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            _configStore.setMaximumHoKTokenLifetime(tenantName, maxLifetime);

            _tenantCache.deleteTenant(tenantName);

            logger.info(String.format("Maximum HoK token lifetime successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set maximum holder of key lifetime for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private long getMaximumBearerRefreshTokenLifetime(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getMaxBearerRefreshTokenLifetime();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get maximum bearer refresh token lifetime for tenant [%s]",
                    tenantName));

            throw ex;
        }
    }

    private void setMaximumBearerRefreshTokenLifetime(String tenantName, long maxLifetime) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            this._configStore.setMaximumBearerRefreshTokenLifetime(tenantName, maxLifetime);

            this._tenantCache.deleteTenant(tenantName);

            logger.info(String.format("Maximum bearer refresh token lifetime successfully set for tenant [%s]",
                    tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set maximum bearer refresh token lifetime for tenant [%s]",
                    tenantName));

            throw ex;
        }
    }

    private long getMaximumHoKRefreshTokenLifetime(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getMaxHoKRefreshTokenLifetime();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get maximum holder of key refresh token lifetime for tenant [%s]",
                    tenantName));

            throw ex;
        }
    }

    private void setMaximumHoKRefreshTokenLifetime(String tenantName, long maxLifetime) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            this._configStore.setMaximumHoKRefreshTokenLifetime(tenantName, maxLifetime);

            this._tenantCache.deleteTenant(tenantName);

            logger.info(String.format("Maximum HoK refresh token lifetime successfully set for tenant [%s]",
                    tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set maximum holder of key refresh token lifetime for tenant [%s]",
                    tenantName));

            throw ex;
        }
    }

    private PasswordExpiration getPasswordExpirationConfiguration(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            return _configStore.getPasswordExpirationConfiguration(tenantName);
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to get password expiration configuration for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void updatePasswordExpirationConfiguration(String tenantName, PasswordExpiration config)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(config, "Password expiration configuration");

            _configStore.updatePasswordExpirationConfiguration(tenantName, config);
        } catch (Exception ex) {
            logger.error(String.format("Failed to update password expiration configuration for tenant [%s]",
                    tenantName));

            throw ex;
        }
    }

    private void addCertificate(String tenantName, Certificate idmCert, CertificateType certificateType)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(idmCert, "Certificate");
            ValidateUtil.validateCertType(certificateType, "Certificate type");

            if (ServerUtils.isEquals(tenantName, this.getSystemTenant())) {
                _configStore.addCertificateForSystemTenant(tenantName, idmCert, certificateType);
            } else {
                _configStore.addCertificateForNonSystemTenant(tenantName, idmCert, certificateType);
            }

            _tenantCache.deleteTenant(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add certificate for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private Collection<Certificate> getAllCertificates(String tenantName, CertificateType certificateType)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateCertType(certificateType, "Certificate type");

            if (ServerUtils.isEquals(tenantName, this.getSystemTenant())) {
                return _configStore.getAllCertificatesForSystemTenant(tenantName, certificateType);
            } else {
                return _configStore.getAllCertificatesForNonSystemTenant(tenantName, certificateType);
            }
        } catch (Exception ex) {
            logger.error(String.format("Failed to get all certificates for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private Collection<Certificate> getTrustedCertificates(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            return _configStore.getTrustedCertificatesForTenant(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get all certificates for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private Collection<Certificate> getStsIssuersCertificates(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            return _configStore.getStsIssuersCertificatesForTenant(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get all certificates for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void deleteCertificate(String tenantName, String fingerprint, CertificateType certificateType)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(fingerprint, "Fingerprint");
            ValidateUtil.validateCertType(certificateType, "Certificate type");

            if (ServerUtils.isEquals(tenantName, this.getSystemTenant())) {
                _configStore.deleteCertificateForSystemTenant(tenantName, fingerprint.toLowerCase(),
                        certificateType);
            } else {
                _configStore.deleteCertificateForNonSystemTenant(tenantName, fingerprint.toLowerCase(),
                        certificateType);
            }
            _tenantCache.deleteTenant(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to delete certificates for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void addRelyingParty(String tenantName, RelyingParty rp) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(rp, "Relying party");

            _configStore.addRelyingParty(tenantName, rp);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add relying party for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void deleteRelyingParty(String tenantName, String rpName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(rpName, "Replying party name");

            _configStore.deleteRelyingParty(tenantName, rpName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to delete relying party [%s] for tenant [%s]", rpName, tenantName));

            throw ex;
        }
    }

    private RelyingParty getRelyingParty(String tenantName, String rpName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(rpName, "Replying party name");

            return _configStore.getRelyingParty(tenantName, rpName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get relying party [%s] for tenant [%s]", rpName, tenantName));

            throw ex;
        }
    }

    private RelyingParty getRelyingPartyByUrl(String tenantName, String url) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(url, "Replying party URL");

            return _configStore.getRelyingPartyByUrl(tenantName, url);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get relying party by url [%s] for tenant [%s]", url, tenantName));

            throw ex;
        }
    }

    private void setRelyingParty(String tenantName, RelyingParty rp) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(rp, "Replying party");

            _configStore.setRelyingParty(tenantName, rp);

            logger.info(String.format("Relying party successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set relying party for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private Collection<RelyingParty> getRelyingParties(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            return _configStore.getRelyingParties(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get relying parties for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void addOIDCClient(String tenantName, OIDCClient oidcClient) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(oidcClient, "oidcClient");

            _configStore.addOIDCClient(tenantName, oidcClient);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add OIDC client for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void deleteOIDCClient(String tenantName, String clientID) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(clientID, "clientID");

            _configStore.deleteOIDCClient(tenantName, clientID);
        } catch (Exception ex) {
            logger.error(String.format("Failed to delete OIDC client [%s] for tenant [%s]", clientID, tenantName));

            throw ex;
        }
    }

    private OIDCClient getOIDCClient(String tenantName, String clientID) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(clientID, "clientID");

            return _configStore.getOIDCClient(tenantName, clientID);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get OIDC client [%s] for tenant [%s]", clientID, tenantName));

            throw ex;
        }
    }

    private void setOIDCClient(String tenantName, OIDCClient oidcClient) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(oidcClient, "oidcClient");

            _configStore.setOIDCClient(tenantName, oidcClient);

            logger.info(String.format("OIDC client successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set OIDC client for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private Collection<OIDCClient> getOIDCClients(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");

            return _configStore.getOIDCClients(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get OIDC clients for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void addResourceServer(String tenantName, ResourceServer resourceServer) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(resourceServer, "resourceServer");
            _configStore.addResourceServer(tenantName, resourceServer);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add resource server for tenant [%s]", tenantName), ex);
            throw ex;
        }
    }

    private void deleteResourceServer(String tenantName, String resourceServerName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(resourceServerName, "resourceServerName");
            _configStore.deleteResourceServer(tenantName, resourceServerName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to delete resource server [%s] for tenant [%s]", resourceServerName,
                    tenantName), ex);
            throw ex;
        }
    }

    private ResourceServer getResourceServer(String tenantName, String resourceServerName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(resourceServerName, "resourceServerName");
            return _configStore.getResourceServer(tenantName, resourceServerName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get resource server [%s] for tenant [%s]", resourceServerName,
                    tenantName), ex);
            throw ex;
        }
    }

    private void setResourceServer(String tenantName, ResourceServer resourceServer) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(resourceServer, "resourceServer");
            _configStore.setResourceServer(tenantName, resourceServer);
        } catch (Exception ex) {
            logger.error(String.format("Failed to set resource server for tenant [%s]", tenantName), ex);
            throw ex;
        }
    }

    private Collection<ResourceServer> getResourceServers(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            return _configStore.getResourceServers(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get resource servers for tenant [%s]", tenantName), ex);
            throw ex;
        }
    }

    /**
     * Go through the IDPs of the tenant and check whether IDP with specified type already exists.
     * Return name of the IDP or null if not found
     */
    private String findIdpTypeRegistered(String tenantName, IdentityStoreType type) throws Exception {
        Collection<IIdentityStoreData> extIDPs = _configStore.getProviders(tenantName,
                EnumSet.of(DomainType.EXTERNAL_DOMAIN), true);
        for (IIdentityStoreData idp : extIDPs) {
            if (idp.getExtendedIdentityStoreData().getProviderType() == type) {
                return idp.getName();
            }
        }
        return null;
    }

    /**
     * Go through the IDPs of the tenant and check whether IDP with specified type and specified name already exists.
     * Return name of the IDP or null if not found
     */
    private String findIdpTypeRegisteredWithName(String tenantName, IdentityStoreType type, String providerName)
            throws Exception {
        ValidateUtil.validateNotEmpty(providerName, "Provider name");
        Collection<IIdentityStoreData> extIDPs = _configStore.getProviders(tenantName,
                EnumSet.of(DomainType.EXTERNAL_DOMAIN), true);
        for (IIdentityStoreData idp : extIDPs) {
            if (idp.getExtendedIdentityStoreData().getProviderType() == type
                    && idp.getName().equalsIgnoreCase(providerName)) {
                return idp.getName();
            }
        }
        return null;
    }

    // idsData.getExtendedIdentityStoreData() is non-null
    private IIdentityStoreData checkAndNormalizeAdIdStore(IIdentityStoreData idsData) throws IDMException {
        String adDomainName = idsData.getName();
        ActiveDirectoryJoinInfo adJoinInfo = null;
        boolean useMachineAccount = idsData.getExtendedIdentityStoreData().useMachineAccount();

        try {
            adJoinInfo = IdmDomainState.getInstance().getDomainJoinInfo();
            if (adJoinInfo == null || !adJoinInfo.getName().equalsIgnoreCase(adDomainName)) {
                throw new HostNotJoinedRequiredDomainException(adDomainName,
                        adJoinInfo == null ? "" : adJoinInfo.getName());
            }
        } catch (com.vmware.identity.interop.domainmanager.HostNotJoinedException e) {
            throw new HostNotJoinedRequiredDomainException(adDomainName, null);
        } catch (com.vmware.identity.interop.domainmanager.DomainManagerNativeException e) {
            throw new DomainManagerException(adDomainName, e.getErrCode());
        } catch (com.vmware.identity.interop.domainmanager.DomainManagerException e) {
            throw new DomainManagerException(adDomainName);
        }

        // if use machine account, by pass spn check
        if (!useMachineAccount) {
            // make sure SPN name is prefixed with 'STS/'
            String spn = idsData.getExtendedIdentityStoreData().getServicePrincipalName();
            STSSpnValidator.validate(spn);
            ActiveDirectoryProvider.probeAdConnectivity(idsData);
        }

        // For native AD, we only need persist username, spn, password, bUserMachineAccount and flags
        return getADIdsToStore(idsData);
    }

    /**
     * Adds an identity provider to the tenant's configuration
     *
     * @param tenantName Name of tenant. Required, non-null, non-empty, case insensitive.
     * @param idp        Identity Provider information, required.
     * @throws Exception
     *              overall -- when finally addProvider() fails;
     * @throws InvalidArgumentException     -- when the data store has improper config;
     * @throws IDMLoginException            -- failed generic probe connectivity test;
     * @throws DuplicateProviderException   -- when we have duplicate tenant name;
     * @throws InvalidProviderException     -- when the userBaseDN or groupBaseDN is invalid for
     *                                         adding non-nativeAD IDS.
     * @throws ADIDSAlreadyExistException   -- nativeAD or ADLdap already exist when adding nativeAD IDS;
     *                                         Or, nativeAD already exist when adding ADLdap.
     * @throws LocalISRegistrationException -- when trying to add localOS provider is already exist
     * @throws RemoteException        - if unable to connect the IDM server
     */
    private synchronized void addProvider(String tenantName, IIdentityStoreData idsData) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(idsData, "Identity store configuration");

            if (idsData.getDomainType() == DomainType.EXTERNAL_DOMAIN
                    && idsData.getExtendedIdentityStoreData() != null) {
                if (idsData.getExtendedIdentityStoreData().getAuthenticationType() == AuthenticationType.SRP) {
                    throw new InvalidArgumentException(
                            "AuthenticationType.SRP is not allowed for EXTERNAL_DOMAIN.");
                }

                // Check whether we have Native AD configured
                String adIDP = findIdpTypeRegistered(tenantName,
                        IdentityStoreType.IDENTITY_STORE_TYPE_ACTIVE_DIRECTORY);
                ActiveDirectoryJoinInfo machineJoinInfo = IdmDomainState.getInstance().getDomainJoinInfo();
                if (adIDP != null && machineJoinInfo == null) {
                    throw new IllegalStateException(
                            "A native active directory is configued with SSO, however, machine is not currently joined.");
                }

                // Native AD, make sure host is joined to this AD domain
                if (idsData.getExtendedIdentityStoreData()
                        .getProviderType() == IdentityStoreType.IDENTITY_STORE_TYPE_ACTIVE_DIRECTORY
                        && idsData.getExtendedIdentityStoreData()
                                .getAuthenticationType() == AuthenticationType.USE_KERBEROS) {
                    if (adIDP != null && machineJoinInfo != null) {
                        logger.error("There is already one nativeAD [%s] registered", adIDP);
                        throw new ADIDSAlreadyExistException(machineJoinInfo.getName());
                    }
                    String adLdapIDPName = findIdpTypeRegisteredWithName(tenantName,
                            IdentityStoreType.IDENTITY_STORE_TYPE_LDAP_WITH_AD_MAPPING, machineJoinInfo.getName());
                    if (adLdapIDPName != null) {
                        logger.error(
                                String.format("There is already one AD-Over-LDAP [%s] registered", adLdapIDPName));
                        throw new ADIDSAlreadyExistException(adLdapIDPName);
                    }
                    idsData = checkAndNormalizeAdIdStore(idsData);
                }
                // Non-native AD
                else {
                    ValidateUtil.validateIdsDomainNameNAlias(idsData);
                    // Do not add AD Over Ldap provider if the name is the same as the currently joined domain (if native AD is already configured)
                    if (adIDP != null && machineJoinInfo != null
                            && idsData.getName().equalsIgnoreCase(machineJoinInfo.getName())) {
                        logger.error("There is already one nativeAD [%s] registered", adIDP);
                        throw new ADIDSAlreadyExistException(machineJoinInfo.getName());
                    }

                    ValidateUtil.validateIdsUserNameAndBaseDN(idsData);

                    ValidateUtil.validateNotNull(idsData.getExtendedIdentityStoreData(),
                            "idsData.getExtendedIdentityStoreData()");
                    ValidateUtil.validateNotNull(idsData.getExtendedIdentityStoreData().getConnectionStrings(),
                            "idsData.getExtendedIdentityStoreData().getConnectionStrings()");

                    if (idsData.getExtendedIdentityStoreData().getConnectionStrings().size() < 1) {
                        throw new InvalidArgumentException("There must be at least 1 connection string provided.");
                    }

                    // use simple bind
                    try {
                        probeProviderConnectivity(tenantName, idsData);
                    } catch (Exception ex) {
                        logger.error(String.format("Failed to pass connection test for [%s]", tenantName));

                        throw ex;
                    }
                }
            }

            _configStore.addProvider(tenantName, idsData);

            _tenantCache.deleteTenant(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add identity provider for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setNativeADProvider(String tenantName, IIdentityStoreData idsData) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(idsData, "idsData");
            ValidateUtil.validateNotNull(idsData.getExtendedIdentityStoreData(), "extenededStoreData");
            assert idsData.getExtendedIdentityStoreData()
                    .getProviderType() == IdentityStoreType.IDENTITY_STORE_TYPE_ACTIVE_DIRECTORY;
            assert idsData.getExtendedIdentityStoreData()
                    .getAuthenticationType() == AuthenticationType.USE_KERBEROS;

            //make sure host is joined to this AD domain
            idsData = checkAndNormalizeAdIdStore(idsData);

            ValidateUtil.validateIdsDomainNameNAlias(idsData);
            ValidateUtil.validateIdsUserNameAndBaseDN(idsData);
            ValidateUtil.validateNotEmpty(idsData.getExtendedIdentityStoreData().getConnectionStrings(),
                    "connectionStrings");

            if (!idsData.getExtendedIdentityStoreData().useMachineAccount()) {
                ActiveDirectoryProvider.probeAdConnectivity(idsData);
            }

            _configStore.setProvider(tenantName, idsData);
            _tenantCache.deleteTenant(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to set native AD provider for tenant [%s]", tenantName));
            throw ex;
        }
    }

    private void deleteProvider(String tenantName, String providerName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(providerName, "Identity provider name");

            IIdentityStoreData provider = this.getProvider(tenantName, providerName);
            if (provider != null) {
                if (provider.getDomainType() == DomainType.SYSTEM_DOMAIN) {
                    throw new InvalidArgumentException(
                            String.format("Cannot delete a system domain provider [%s]", providerName));
                }

                _configStore.deleteProvider(tenantName, provider.getName());
                _tenantCache.deleteTenant(tenantName);
            }
        } catch (Exception ex) {
            logger.error(String.format("Failed to delete identity provider [%s] for tenant [%s]", providerName,
                    tenantName));

            throw ex;
        }
    }

    /**
     * Retrieves an identity provider from the tenant's configuration
     *
     * @param tenantName   Name of tenant. Required, non-null, non-empty, case insensitive.
     * @param ProviderName Name of Identity Provider. Required.
     * @return             Identity Provider information, null if not found.
     * @throws IDMException
     *  @ NoSuchTenantException
     * @throws InvalidArgumentException - one or more input argument is invalid.
     */
    private IIdentityStoreData getProvider(String tenantName, String providerName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(providerName, "Identity provider name");

            return _configStore.getProvider(tenantName, providerName, false /* exclude internal info */);
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to get identity provider [%s] for tenant [%s]", providerName, tenantName),
                    ex);

            throw ex;
        }
    }

    /**
     * Retrieves an identity provider from the tenant's configuration along with its internal info
     *
     * @param tenantName   Name of tenant. Required, non-null, non-empty, case insensitive.
     * @param ProviderName Name of Identity Provider. Required.
     * @return             Identity Provider information, null if not found.
     * @throws IDMException On IDM server errors
     * @throws NoSuchTenantException If tenant doesn't exist
     * @throws InvalidArgumentException - one or more input argument is invalid.
     */
    private IIdentityStoreData getProviderWithInternalInfo(String tenantName, String providerName)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(providerName, "Identity provider name");
            return _configStore.getProvider(tenantName, providerName, true);
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to get identity provider [%s] for tenant [%s]", providerName, tenantName),
                    ex);
            throw ex;
        }
    }

    private void setProvider(String tenantName, IIdentityStoreData idsData) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(idsData, "Identity store configuration");

            IIdentityStoreData provider = this.getProviderWithInternalInfo(tenantName, idsData.getName());
            if (provider != null && provider.getDomainType() == DomainType.SYSTEM_DOMAIN) {
                throw new InvalidArgumentException(
                        String.format("Cannot update a system domain provider [%s] ", idsData.getName()));
            }

            AuthenticationType authType = idsData.getExtendedIdentityStoreData().getAuthenticationType();
            if (authType == AuthenticationType.SRP) {
                throw new InvalidArgumentException("AuthenticationType.SRP is not allowed for EXTERNAL_DOMAIN.");
            }
            // if we are not native ad
            if (idsData.getExtendedIdentityStoreData()
                    .getProviderType() != IdentityStoreType.IDENTITY_STORE_TYPE_ACTIVE_DIRECTORY
                    || authType != AuthenticationType.USE_KERBEROS) {
                probeProviderConnectivity(tenantName, idsData);
            }
            // native AD
            else {
                String adIDP = findIdpTypeRegistered(tenantName,
                        IdentityStoreType.IDENTITY_STORE_TYPE_ACTIVE_DIRECTORY);
                ActiveDirectoryJoinInfo machineJoinInfo = IdmDomainState.getInstance().getDomainJoinInfo();
                if (adIDP != null && machineJoinInfo == null) {
                    throw new IllegalStateException(
                            "A native active directory is configued with SSO, however, machine is not currently joined.");
                }
                idsData = checkAndNormalizeAdIdStore(idsData);
            }

            _configStore.setProvider(tenantName, idsData);

            _tenantCache.deleteTenant(tenantName);

            logger.info(
                    String.format("Provider [%s] successfully set for tenant [%s]", idsData.getName(), tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set Ldap provider for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void checkDn(IIdentityStoreData idsData, URI serverUri) throws Exception {
        ILdapConnectionEx connection = null;
        try {
            IIdentityStoreDataEx idsDataEx = idsData.getExtendedIdentityStoreData();

            connection = ServerUtils.getLdapConnectionByURIs(Collections.singleton(serverUri),
                    idsDataEx.getUserName(), idsDataEx.getPassword(), AuthenticationType.PASSWORD, false,
                    new LdapCertificateValidationSettings(
                            idsData.getExtendedIdentityStoreData().getCertificates()));

            String userBaseDn = idsDataEx.getUserBaseDn();
            String groupBaseDn = idsDataEx.getGroupBaseDn();
            if (!ServerUtils.isValidDN(connection, userBaseDn)) {
                String msg = String.format("DN is invalid: [%s]", userBaseDn);
                logger.error(msg);
                throw new InvalidProviderException(msg, "userBaseDN", userBaseDn);
            }
            if (!ServerUtils.isValidDN(connection, groupBaseDn)) {
                String msg = String.format("DN is invalid: [%s]", groupBaseDn);
                logger.error(msg);
                throw new InvalidProviderException(msg, "groupBaseDN", groupBaseDn);
            }
        } finally {
            if (null != connection) {
                connection.close();
            }
        }
    }

    /**
     * Retrieves all providers configured for a tenant
     *
     * @param tenantName Name of tenant, non-null, non-empty, required.
     * @return           Collection of identity providers, Empty collect if no provider found
     * @throws IDMException
     * @throws InvalidArgumentException
     * @ NoSuchTenantException
     */
    private Collection<IIdentityStoreData> getProviders(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            return getProviders(tenantName,
                    EnumSet.of(DomainType.EXTERNAL_DOMAIN, DomainType.LOCAL_OS_DOMAIN, DomainType.SYSTEM_DOMAIN));
        } catch (Exception ex) {
            logger.error(String.format("Failed to get all identity providers for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Retrieves all the providers matching the set of specified domain types
     * in a tenant's configuration
     *
     * @param tenantName  Name of tenant, non-null non-empty, required
     * @param domains    Set of domain types. Required.
     * @return           Collection of identity providers, Empty collect if no provider found
     * @throws Exception
     * @throws InvalidArgumentException
     *  @ NoSuchTenantException
     * @throws RemoteException        - if unable to connect the IDM server
     * @see    DomainType
     */
    private Collection<IIdentityStoreData> getProviders(String tenantName, EnumSet<DomainType> domainTypes)
            throws Exception {
        try {
            return this.getProviders(tenantName, domainTypes, false);
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to get identity providers by domain types for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Retrieves the security domains supported by a provider
     *
     * @param tenantName    Name of tenant, non-null non-empty, required
     * @param providerName  Name of identity provider
     * @return Collection of domains, Empty collection if no provider found
     * @throws Exception
     */
    private Collection<SecurityDomain> getSecurityDomains(String tenantName, String providerName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(providerName, "Provider name");

            TenantInformation tenantInfo = findTenant(tenantName);

            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            // The provider name is the fully qualified domain name
            IIdentityProvider provider = tenantInfo.findProviderByName(providerName);
            if (provider != null) {
                return Collections.unmodifiableCollection(provider.getDomains());
            }
        } catch (Exception ex) {
            logger.error(String.format("Failed to get security domains for provider [%s] in tenant [%s]",
                    providerName, tenantName));

            throw ex;
        }

        return Collections.emptySet();
    }

    /**
     * Checks the connectivity to an identity provider
     *
     * @param tenantName  Name of tenant, non-null non-empty, required
     * @param providerUri Location of identity provider. non-null non-empty, required
     * @param authType    Type of authentication. required.
     *                 currently supports AuthenticationType.PASSWORD only.
     * @param userName    Login identifier. non-null, required
     * @param pwd         Password    non-null non-empty, required
     * @param certs       Trusted certificates used for SSL connection
     * @throws InvalidPrincipalException    user name is invalid (empty)
     * @throws IDMLoginException. If one or more of the input argument is illegal.
     *                                 Or URI syntax is incorrect.
     * @throws IDMException
     * @see AuthenticationType
     */
    private void probeProviderConnectivity(String tenantName, String providerUri, AuthenticationType authType,
            String userName, String pwd, LdapCertificateValidationSettings certValidationSettings)
            throws Exception {

        URI uri = null;
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(providerUri, "providerUri");

            // providerUri for now has to start with ldap||ldaps
            try {
                uri = new URI(providerUri);
            } catch (URISyntaxException e) {
                throw new IllegalArgumentException(e.getMessage());
            }

            if ((!uri.getScheme().toLowerCase().equals("ldap"))
                    && (!uri.getScheme().toLowerCase().equals("ldaps"))) {
                throw new IllegalArgumentException(String.format("Unsupported providerUri='%s'.", providerUri));
            }

            if (AuthenticationType.PASSWORD != authType) {
                throw new IllegalArgumentException(
                        String.format("AuthenticationType='%s' is not supported.", authType.toString()));
            }
            ServerUtils.validateNotEmptyUsername(userName);
            ValidateUtil.validateNotNull(pwd, "pwd");
        } catch (Exception ex) {
            logger.error(ex.getMessage());
            throw ex;
        } //Validation done.

        try {
            Collection<URI> uris = Collections.unmodifiableList(Arrays.asList(new URI(providerUri)));
            // connection must be closed if succeeded ....
            try (ILdapConnectionEx connection = ServerUtils.getLdapConnectionByURIs(uris, userName, pwd,
                    AuthenticationType.PASSWORD, false, certValidationSettings)) {
            }
            return;
        } catch (Exception ex) {
            String msg = String.format(
                    "Failed to probe provider connectivity [URI: %s]; tenantName [%s], userName [%s]", providerUri,
                    tenantName, userName);

            logger.warn(msg);

            throw new IDMLoginException(msg, uri, ex);
        }
    }

    private void probeProviderConnectivity(String tenantName, IIdentityStoreData idsData) throws Exception {

        ValidateUtil.validateNotNull(idsData.getExtendedIdentityStoreData(), "idsData details");
        if (AuthenticationType.PASSWORD != idsData.getExtendedIdentityStoreData().getAuthenticationType()) {
            throw new IllegalArgumentException(String.format("AuthenticationType='%s' is not supported.",
                    idsData.getExtendedIdentityStoreData().getAuthenticationType().toString()));
        }
        ServerUtils.validateNotEmptyUsername(idsData.getExtendedIdentityStoreData().getUserName());
        ValidateUtil.validateNotNull(idsData.getExtendedIdentityStoreData().getPassword(), "pwd");

        IIdentityProvider provider = providerFactory.buildProvider(tenantName, idsData,
                idsData.getExtendedIdentityStoreData().getCertificates());
        if (!(provider instanceof BaseLdapProvider))
            throw new IllegalArgumentException(
                    String.format("Supported provider type is %s, %s provider is not of supported type.",
                            BaseLdapProvider.class.toString(), provider.getClass().toString()));

        StringBuilder connections = new StringBuilder();
        for (String connectionStr : idsData.getExtendedIdentityStoreData().getConnectionStrings()) {
            ValidateUtil.validateNotEmpty(connectionStr, "connectionString");

            try {
                URI connectionUri = new URI(connectionStr);
                DirectoryStoreProtocol protocol = DirectoryStoreProtocol
                        .getValue(connectionUri.getScheme().toString());
                if (protocol == null) {
                    throw new IllegalArgumentException(
                            String.format("Unsupported providerUri='%s'.", connectionUri));
                } else if (protocol == DirectoryStoreProtocol.LDAPS) {
                    ValidateUtil.validateNotEmpty(idsData.getExtendedIdentityStoreData().getCertificates(),
                            "IdentityStore certificates");
                    for (X509Certificate cert : idsData.getExtendedIdentityStoreData().getCertificates()) {
                        ValidateUtil.validateCertificate(cert);
                    }
                }
                connections.append(connectionStr + " ");
            } catch (URISyntaxException e) {
                throw new IllegalArgumentException(e.getMessage(), e);
            }
        }

        try {
            //probe connectivity and DN check
            ((BaseLdapProvider) provider).probeConnectionSettings();
        } catch (Exception ex) {
            String msg = String.format(
                    "Failed to probe provider connectivity [URI: %s]; tenantName [%s], userName [%s]",
                    connections.toString(), tenantName, idsData.getExtendedIdentityStoreData().getUserName());

            logger.warn(msg);

            throw new IDMLoginException(msg, null, ex);
        }
    }

    private Collection<String> getDefaultProviders(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getDefaultProviders();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get default providers for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setDefaultProviders(String tenantName, Collection<String> defaultProviders) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            // per admin interface setting null is allowed meaning erase the default provider settings.

            _configStore.setDefaultProviders(tenantName, defaultProviders);

            _tenantCache.deleteTenant(tenantName);

            logger.info(String.format("Default Providers successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set default providers for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Authenticates a principal using the password specified
     *
     * The principal is expected to be managed by one of the Identity providers
     * configured in the tenant.
     *
     * @param tenantName Name of tenant. non-null non-empty, required
     * @param principal  User principal to be authenticated. non-null non-empty, required
     * @param password   Password.  non-null non-empty, required
     * @return Normalized principal if successfully authenticated.
     * @throws IDMLoginException when authentication failed.
     * @throws PasswordExpiredException when authentication failed due to
     *         expired password.
     * @throws UserAccountLockedException when authentication failed due to
     *         locked user account.
     * @throws Exception
     */
    private PrincipalId authenticate(String tenantName, String principal, String password) throws Exception {
        long startTime = System.nanoTime();
        boolean authFailed = false;

        PrincipalId userPrincipal = null;
        IIdentityProvider provider = null;

        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ServerUtils.validateNotEmptyUsername(principal);
            ValidateUtil.validateNotNull(password, "Password");

            TenantInformation tenantInfo = findTenant(tenantName);

            if (tenantInfo == null) {
                throw new IDMLoginException("Access denied");
            }

            userPrincipal = getUserPrincipal(tenantName, principal);
            if (userPrincipal == null) {
                throw new IDMLoginException(String.format("Invalid user principal '%s'.", principal));
            }

            provider = tenantInfo.findProviderADAsFallBack(userPrincipal.getDomain());

            if (provider == null) {
                throw new IDMLoginException("Access denied");
            }

            String identityProviderName = provider.getName();

            validateProviderAllowedAuthnTypes(DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_PASSWORD,
                    identityProviderName, tenantInfo);

            return provider.authenticate(userPrincipal, password);
        } catch (InvalidPrincipalException e) {
            authFailed = true;
            logger.error(VmEvent.USER_NAME_PWD_AUTH_FAILED,
                    "Failed to authenticate principal [{}]. Invalid principal.",
                    (principal != null ? principal : "null"));
            throw e;
        } catch (Exception ex) {
            authFailed = true;

            logger.error(String.format("Failed to authenticate principal [%s] for tenant [%s]",
                    principal != null ? principal : "null", tenantName != null ? tenantName : "null"), ex);

            if (provider != null && userPrincipal != null) {
                try {
                    provider.checkUserAccountFlags(userPrincipal);
                } catch (UserAccountLockedException ex1) {
                    logger.error(VmEvent.USER_NAME_PWD_AUTH_FAILED,
                            "Failed to authenticate principal [{}]. User account locked.",
                            (principal != null ? principal : "null"));

                    throw ex1;
                } catch (PasswordExpiredException ex1) {
                    logger.error(VmEvent.USER_NAME_PWD_AUTH_FAILED,
                            "Failed to authenticate principal [{}]. User password expired.",
                            (principal != null ? principal : "null"));
                    throw ex1;
                } catch (Exception ex2) {
                    logger.error(String.format("Failed to checkUserAccountFlags principal [%s] for tenant [%s]",
                            principal != null ? principal : "null", tenantName != null ? tenantName : "null"));
                    // we are ignoring this exception here, because we want to propagate the original
                    // authenticate failure, and not this failure
                }
            }

            if (ex instanceof LoginException) { //Kerberos exception needs to be handled here
                Throwable t = ex.getCause();
                if (t instanceof KrbException) { // get the Kerberos return code only when the cause if KrbException
                    int returnCode = ((KrbException) t).returnCode();
                    switch (returnCode) {
                    case ServerKrbUtils.KDC_ERR_CLIENT_REVOKED: {
                        logger.error(VmEvent.USER_NAME_PWD_AUTH_FAILED,
                                "Failed to authenticate principal [{}]. User account locked.",
                                (principal != null ? principal : "null"));

                        throw new UserAccountLockedException(ex.getMessage());
                    }
                    case ServerKrbUtils.KDC_ERR_KEY_EXPIRED: {
                        logger.error(VmEvent.USER_NAME_PWD_AUTH_FAILED,
                                "Failed to authenticate principal [{}]. User password expired.",
                                (principal != null ? principal : "null"));
                        throw new PasswordExpiredException(ex.getMessage());
                    }
                    default://no op for others
                        break;
                    }
                }
            }

            logger.error(VmEvent.USER_NAME_PWD_AUTH_FAILED,
                    String.format("Failed to authenticate principal [%s]. %s",
                            principal != null ? principal : "null", ex.getMessage()),
                    ex);

            if (ex instanceof AccountLockedOutException) {
                throw new UserAccountLockedException(ex.getMessage());
            } else if (ex instanceof AccountPasswordExpiredException) {
                throw new PasswordExpiredException(ex.getMessage());
            }

            throw new IDMLoginException(ex.getMessage());
        } finally {
            long delta = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);

            if (logger.isInfoEnabled()) {
                if (provider == null) {
                    logger.info(String.format(
                            "Authentication %s for user [%s] in tenant [%s] in [%d] milliseconds because the provider is not registered",
                            authFailed ? "failed" : "succeeded", principal, tenantName, delta));
                } else {
                    logger.info(String.format(
                            "Authentication %s for user [%s] in tenant [%s] in [%d] milliseconds with provider [%s] of type [%s]",
                            authFailed ? "failed" : "succeeded", principal, tenantName, delta, provider.getName(),
                            provider.getClass().getName()));
                }
            }

            IdmServer.getPerfDataSinkInstance()
                    .addMeasurement(new PerfBucketKey(PerfMeasurementPoint.IDMAuthenticate, principal), delta);
        }
    }

    /**
     * A helper method that will check if requested authentication type is supported based on a provider (user belonging to)
     * @param requestedAuthnType authentication type to check if it is associated with provider
     * @param requestedProvider Name of identity source
     * @param tenantInfo tenant information
     */
    private void validateProviderAllowedAuthnTypes(int requestedAuthnType, String requestedProvider,
            TenantInformation tenantInfo) throws IDMLoginException {
        boolean authenticationAllowed = false;

        Collection<IIdentityStoreData> idsStores = tenantInfo.getIdsStores(); // All identity sources on tenant
        AuthnPolicy tenantAuthnPolicy = tenantInfo.getAuthnPolicy(); // Authentication policy on tenant
        IIdentityStoreData identitySource = null;

        // Retrieve information of requested identity source
        for (IIdentityStoreData identityStore : idsStores) {
            if (identityStore.getName().equalsIgnoreCase(requestedProvider)) {
                identitySource = identityStore;
                if (identitySource != null) {
                    IIdentityStoreDataEx extendedData = identitySource.getExtendedIdentityStoreData();
                    if (extendedData.getAuthnTypes() != null) {
                        int[] providerAuthnTypes = extendedData.getAuthnTypes();
                        if (ArrayUtils.contains(providerAuthnTypes, requestedAuthnType)) {
                            authenticationAllowed = true;
                        }

                        if (!authenticationAllowed) {
                            String errMessage = String.format(
                                    "Authentication type : '%s' is not allowed for requested identity provider : '%s'",
                                    requestedAuthnType, requestedProvider);
                            throw new IDMLoginException(errMessage);
                        }
                    }
                }
                break;
            }
        }
    }

    private GSSResult authenticate(String tenantName, String contextId, byte[] gssTicket) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(gssTicket, "GSSAPI Token");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IGssAuthIdentityProvider provider = GSSAuthProvider.getInstance();

            if (provider == null) {
                logger.error(VmEvent.GSS_AUTH_FAILED,
                        "Failed to authenticate gss token. Unable to find an ActiveDirectoryProvider.");
                String message = String.format("Unable to find an GSS Provider for tenant [%s].",
                        (tenantName != null ? tenantName : "null"));
                logger.error(message);

                throw new IDMLoginException(message);
            }

            GSSAuthResult result = provider.authenticate(contextId, gssTicket);

            if (result.getUserInfo() != null) {
                IIdentityProvider adIdentityProvider = tenantInfo
                        .findProviderADAsFallBack(result.getUserInfo().getDomain());
                if (adIdentityProvider == null) {
                    throw new NoSuchIdpException("Native AD Provider does not exist.");
                }
                //save PAC info to ActiveDirectoryProvider if the user domain is from AD provider
                if (adIdentityProvider instanceof ActiveDirectoryProvider) {
                    ActiveDirectoryProvider adProvider = ((ActiveDirectoryProvider) adIdentityProvider);
                    adProvider.saveUserInfo(result.getUserInfo());
                }

                return new GSSResult(contextId,
                        new PrincipalId(result.getUserInfo().getName(), result.getUserInfo().getDomain()));
            } else {
                return new GSSResult(contextId, result.getServerLeg());
            }
        } catch (Exception ex) {
            logger.error(VmEvent.GSS_AUTH_FAILED, "Failed to authenticate gss token", ex);
            logger.error(String.format("Failed to authenticate gss token for tenant [%s]",
                    (tenantName != null ? tenantName : "null")));

            throw ex;
        }
    }

    /**
     * TLS Client Certificate (or smartcard) authentication.
     *
     * This function does the following Certificate path validation, revocation
     * check Subject validation OID filtering
     *
     * @param tenantName
     * @param tlsCertChain
     *            certificate chain may or may not provide full client cert
     *            chain.
     * @return principal matched
     * @throws IDMLoginException
     *             certificate not provided or can not find a matching user in
     *             directory
     * @throws IdmCertificateRevokedException
     *             certificate revoked
     * @throws InvalidArgumentException
     *             parameter was incorrectly set
     * @throws CertificateRevocationCheckException
     *             revocation check fails to determine the certificate status
     * @throws IDMException
     *             any other exceptions
     */
    private PrincipalId authenticate(String tenantName, X509Certificate[] tlsCertChain)
            throws IDMLoginException, CertificateRevocationCheckException, InvalidArgumentException,
            IdmCertificateRevokedException, IDMException {

        TenantInformation info;

        try {
            info = findTenant(tenantName);
        } catch (Exception e) {
            throw new IDMLoginException("Error in retrieve tenantInfo");
        }

        if (tlsCertChain == null || tlsCertChain.length < 1) {
            logger.error("Certificate chain is empty or null");
            throw new IDMLoginException("Certificate chain is empty or null");
        }

        if (logger.isDebugEnabled()) {
            for (int i = 0; i < tlsCertChain.length; i++) {
                logger.debug("Client Certificate [" + i + "] = " + tlsCertChain[i].toString());
            }
        }

        String subjectDn = tlsCertChain[0].getSubjectDN() != null ? tlsCertChain[0].getSubjectDN().toString() : "";

        IIdmAuthStatRecorder recorder = PerformanceMonitorFactory.createIdmAuthStatRecorderInstance(tenantName,
                "CertificateAuthentication", "IDM", 0, IIdmAuthStat.ActivityKind.AUTHENTICATE,
                IIdmAuthStat.EventLevel.INFO, subjectDn);
        recorder.start();

        AuthnPolicy aPolicy = info.getAuthnPolicy();

        Validate.notNull(aPolicy, "AuthnPolicy can not be null.");
        Validate.isTrue(aPolicy.IsTLSClientCertAuthnEnabled(), "TLSClient authn is not enabled.");

        ClientCertPolicy certPolicy = aPolicy.getClientCertPolicy();
        Validate.notNull(certPolicy, "Client Certificate Policy can not be null.");

        ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

        IdmClientCertificateValidator certValidator = new IdmClientCertificateValidator(certPolicy, tenantName);

        Map<String, String> authStatsExtension = new HashMap<String, String>();
        recorder.add(authStatsExtension);
        String clusterID;

        try {
            clusterID = this.getClusterId();
        } catch (Exception e1) {
            throw new IDMException("Failed to retrieve PSC cluster ID.");
        }
        certValidator.validateCertificatePath(tlsCertChain[0], clusterID, authStatsExtension);

        long startTime = System.nanoTime();
        String upn = certValidator.extractUPN(tlsCertChain[0]);

        //Validate allowed authentication type on provider
        IIdentityProvider provider = null;
        try {
            PrincipalId userPrincipal = getUserPrincipal(tenantName, upn);
            provider = info.findProviderADAsFallBack(userPrincipal.getDomain());
        } catch (Exception e) {
            throw new IDMException("Failed to retrieve details of identity provider with domain :" + subjectDn);
        }
        if (provider != null) {
            validateProviderAllowedAuthnTypes(DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_TLS_CERTIFICATE,
                    provider.getName(), info);
        }

        String[] parts = upn.split("@");
        PrincipalId principalID;
        if (parts.length == 2) {
            principalID = new PrincipalId(parts[0], parts[1]);

            try {
                if (!this.IsActive(tenantName, principalID)) {
                    logger.error("The user is not found or inactive:" + principalID.getUPN());
                    throw new IDMLoginException(
                            "The user owning this certificate is not found or inactive. User UPN: "
                                    + principalID.getUPN());
                }

                logger.info("Successfully validated subject of the client certificate : " + principalID.getUPN());
            } catch (Exception e) {
                logger.error(
                        "Failed to determine the status of principal with candicate UPN:" + principalID.getUPN());
                throw new IDMLoginException("Unable to find user with UPN: " + principalID.getUPN());
            }
        } else {
            logger.error(upn + " is in illegal UPN format");
            throw new IDMLoginException("Illegal UPN format: " + upn);
        }

        authStatsExtension.put("SearchUserByCertificateUpn",
                String.format("%d Ms", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)));
        recorder.end();
        // authentication is successful
        return principalID;
    }

    /**
     * authenticate with secure ID
     *
     * @param tenantName
     * @param principal
     * @param passcode
     * @return
     * @throw IDMLoginExceptin  if credential is denied
     * @throw IDMRsaSecurIDNewPinException  if user need to setup new pin.
     * @throws IDMException all other errors.
     */
    private RSAAMResult authenticateRsaSecurId(String tenantName, String sessionId, String userName,
            String passcode) throws IDMException

    {
        long startTime = System.nanoTime();
        boolean authFailed = false;
        RSAAMResult authResult = null;

        logger.debug("Authenticating with RSA securID ..");

        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(userName, "User Principal");

            TenantInformation info = findTenant(tenantName);
            AuthnPolicy aPolicy = info.getAuthnPolicy();
            Validate.notNull(aPolicy, "AuthnPolicy can not be null.");
            Validate.isTrue(aPolicy.IsRsaSecureIDAuthnEnabled(),
                    "SecureID authentication is not turned on for this tenant.");

            RSAAgentConfig rsaConfig = aPolicy.get_rsaAgentConfig();
            Validate.notNull(rsaConfig, "RSAAgentConfig is not defined");

            HashMap<String, String> userIDAttrMap = rsaConfig.get_idsUserIDAttributeMap();

            // we should not need to create api all the time; but different tenant should have different api
            AuthenticationSessionFactory api = null;

            String[] userInfo = separateUserIDAndDomain(userName);
            IIdentityProvider provider = info.findProviderADAsFallBack(userInfo[1]);

            if (null == provider) {
                throw new IDMLoginException(
                        String.format("Identity source was not defined for user: %s.", userName));
            }
            validateProviderAllowedAuthnTypes(DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_RSA_SECUREID,
                    provider.getName(), info);

            api = this._rsaSessionFactoryCache.getAuthnFactory(info);

            String userID = extractRsaUserID(info, userName, userIDAttrMap);
            AuthenticationSession session = null;
            String cachedSessionId = sessionId;
            PrincipalId pId = getPrincipalIDFromUserName(info, userName, userIDAttrMap);
            try {
                // Retrieve session if this it is provided
                if (cachedSessionId != null) {
                    session = IdentityManager._rsaSessionCache.getSession(tenantName, cachedSessionId);
                }
                // generate a rsa session if not found in rsa session cache
                if (session == null) {
                    logger.debug("Using new AuthSession ...");
                    session = api.createSession();
                    String newSessionId = createSessionId();

                    int status = session.authenticate(userID, new AuthenticationSecret(passcode)).getStatusCode();

                    if (status == AuthenticationResult.NEXT_CODE_REQUIRED) {
                        IdentityManager._rsaSessionCache.addSession(tenantName, newSessionId, session);
                    }

                    authResult = afterProcessRSAStatus(status, newSessionId, userName, pId);
                } else {
                    logger.debug("Using cached AuthSession, in second leg of NEXT_CODE_REQUIRED mode ...");

                    //It must be in nextcode mode if the session is found
                    int prevStatus = session.getAuthenticationStatus().getStatusCode();
                    if (prevStatus != AuthenticationResult.NEXT_CODE_REQUIRED) {
                        throw new IDMLoginException(
                                String.format("Unexpected status in a cached rsa session: %s.", prevStatus));
                    }

                    int status = session.nextAuthenticationStep(new AuthenticationSecret(passcode)).getStatusCode();
                    authResult = afterProcessRSAStatus(status, cachedSessionId, userName, pId);

                    if (status != AuthenticationResult.NEXT_CODE_REQUIRED) {
                        IdentityManager._rsaSessionCache.removeSession(tenantName, cachedSessionId);
                    }
                }
            } finally {
                if (session.getAuthenticationStatus().getStatusCode() != AuthenticationResult.NEXT_CODE_REQUIRED) {
                    session.closeSession();
                }
            }
        } catch (IDMLoginException ex) {
            authFailed = true;
            throw ex;
        } catch (IDMException ex) {
            // don't wrap it.
            authFailed = true;
            throw ex;
        } catch (Exception ex) {
            authFailed = true;
            logger.error(String.format("Failed to authenticate principal [%s] by passcode",
                    userName != null ? userName : "null"), ex);

            throw new IDMException(ex.getMessage());
        } finally {
            long delta = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);

            if (logger.isInfoEnabled()) {
                logger.info(String.format(
                        "Authentication %s for user [%s] in tenant [%s] in [%d] milliseconds with rsa secureID",
                        authFailed ? "failed" : "succeeded or entered \"NEXT_CODE_REQUIRED\" mode", userName,
                        tenantName, delta));

            }

            IdmServer.getPerfDataSinkInstance()
                    .addMeasurement(new PerfBucketKey(PerfMeasurementPoint.IDMAuthenticate, userName), delta);
        }
        return authResult;
    }

    private String createSessionId() {
        SecureRandom randomGenerator = new SecureRandom();
        int sessionId = randomGenerator.nextInt(1000000);
        return String.valueOf(sessionId);
    }

    /**
     *
     * Handle rsa session status.
     *
     * Throw at NewPin mode. User need to setup new pin via rsa tool rather than our login page.
     *
     * For each status do the following 1. log the event 2. update rsa session
     * cache. 3. throw or return RSAAMResult.
     *
     * @param status
     *            Normal return conditions:
     *            ACCESS_OK NEW_PIN_REQUIRED NEXT_CODE_REQUIRED,PIN_ACCEPTED
     *            Throwing conditions:
     *            ACCESS_DENIED, NEXT_CODE_BAD,PIN_REJECTED,
     * @param cachedSessionId
     * @param userName
     *            userName used at login
     *            -need in status of succeed.
     * @return RSAAMResult
     * @throws IDMLoginException
     * @throws IDMSecureIDNewPinException if the status is AuthSession.NEW_PIN_REQUIRED.
     */
    private RSAAMResult afterProcessRSAStatus(int status, String cachedSessionId, String userName, PrincipalId pId)
            throws IDMLoginException, IDMSecureIDNewPinException {
        Validate.notEmpty(userName, "Empty userName");
        RSAAMResult result;

        switch (status) {
        case AuthenticationResult.ACCESS_OK:
            logger.info(String.format("Successfully authenticating principal [%s] by passcode.", userName));
            result = new RSAAMResult(pId);
            break;
        case AuthenticationResult.ACCESS_DENIED:
            logger.error(String.format("Failed authenticating principal [%s] by passcode.", userName));
            throw new IDMLoginException(String.format("RSA status: ACCESS_DENIED."));
        case AuthenticationResult.NEXT_CODE_BAD:
            //Next passcode failed to pass authentication.
            logger.error(String.format("Failed authenticating principal [%s] by passcode.", userName));
            throw new IDMLoginException(String.format("RSA status: NEXT_CODE_BAD."));
        case AuthenticationResult.NEXT_CODE_REQUIRED:
            logger.info(
                    String.format("Next code required authenticating principal [%s] by passcode.RSA SessionID [%s]",
                            userName, cachedSessionId));
            result = new RSAAMResult(cachedSessionId);
            break;
        case AuthenticationResult.NEW_PIN_REQUIRED:
            logger.info(String.format("New pin required authenticate principal [%s] by passcode.", userName));
            throw new IDMSecureIDNewPinException(String.format("RSA status: PIN_REJECTED."));
        default:
            throw new IDMLoginException(String.format("Unexpected RSA AM status:  %d", status));
        }

        return result;
    }

    private String extractRsaUserID(TenantInformation info, String userName, HashMap<String, String> userIDAttrMap)
            throws Exception {

        Validate.notNull(info, "info");

        String[] userInfo = separateUserIDAndDomain(userName);
        if (userInfo == null) {
            throw new IDMLoginException(String.format(
                    "User name %s does not contain the domain - expected in format of name@domain", userName));
        }

        String userID;
        if (userIDAttrMap == null || userIDAttrMap.isEmpty()) {
            userID = userName;
        } else {
            IIdentityProvider provider = info.findProviderADAsFallBack(userInfo[1]);
            String ldapAttr = userIDAttrMap.get(provider.getName());

            if (ldapAttr == null || ldapAttr.equals("userPrincipalName")) {
                userID = userName;
            } else {
                userID = userInfo[0];
            }
        }
        return userID;
    }

    /**
     * Split user name and domain at '@'.
     * @param userName in the form of name@domain.
     * @return name and domain in a string array.  null if there is no domain part
     */
    private String[] separateUserIDAndDomain(String userName) {
        Validate.notEmpty(userName, "userName");
        int i = userName.lastIndexOf("@");

        if (i == -1) {
            logger.error("User name does not have domain part.");
            return null;
        }
        String name = userName.substring(0, i);
        Validate.notEmpty(name, "Empty name string before the last \'@\' in user name string.");

        String domainName = userName.substring(i + 1);
        Validate.notEmpty(domainName, "expect domain name after the last \'@\' in user name.");
        String[] userInfo = { name, domainName };

        return userInfo;
    }

    /**
     * UserName is could be UPN or userID+domain. UserID here is the ldap attribute value used to identity the user.
     * @param userName
     *            user name for securID login
     * @return
     * @throws Exception
     */
    private PrincipalId getPrincipalIDFromUserName(TenantInformation info, String userName,
            HashMap<String, String> userIDAttrMap) throws Exception {

        String[] userInfo = separateUserIDAndDomain(userName);
        if (userInfo == null) {
            throw new IDMLoginException(String.format(
                    "User name %s does not contain the domain - expected in format of name@domain", userName));
        }

        if (userIDAttrMap == null || userIDAttrMap.isEmpty()) {
            return new PrincipalId(userInfo[0], userInfo[1]);
        }

        IIdentityProvider provider = info.findProviderADAsFallBack(userInfo[1]);
        String ldapAttrName = userIDAttrMap.get(provider.getName());

        if (ldapAttrName == null || ldapAttrName.equals("userPrincipalName")) {
            return new PrincipalId(userInfo[0], userInfo[1]);
        } else {
            // find the user with the ldap attribute
            PrincipalId pID = provider.findActiveUser(ldapAttrName, userInfo[0]);
            return pID;
        }
    }

    private boolean IsActive(String tenantName, PrincipalId principal) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(principal, "User Principal");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(principal.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, principal.getDomain());

            return provider.IsActive(principal);
        } catch (Exception ex) {
            logger.error(String.format("Failed to check if principal [%s@%s] is active for tenant [%s]",
                    principal != null ? principal.getName() : "null",
                    principal != null ? principal.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    private Collection<Attribute> getAttributeDefinitions(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getAttributeDefinitions();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get attribute definitions in tenant [%s].", tenantName));

            throw ex;
        }
    }

    private Collection<AttributeValuePair> getAttributeValues(String tenantName, PrincipalId principal,
            Collection<Attribute> attributes) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(principal, "User Principal");
            ValidateUtil.validateNotNull(attributes, "Attributes");
            ValidateUtil.validatePositiveNumber(attributes.size(), "Attribute count");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(principal.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, principal.getDomain());

            Collection<AttributeValuePair> attributeValues = new HashSet<AttributeValuePair>();

            long startTime = System.nanoTime();

            String samlGroupAttrName = tenantInfo.findSystemProvider().getMappingSamlAttributeForGroupMembership();
            ISystemDomainIdentityProvider systemProvider = tenantInfo.findSystemProvider();

            Collection<AttributeValuePair> retAttrs = provider.getAttributes(principal, attributes);

            AttributeValuePair sids = null;
            AttributeValuePair groupNames = null;

            for (AttributeValuePair attr : retAttrs) {
                if (attr.getAttrDefinition().getName().equalsIgnoreCase(INTERNAL_ATTR_GROUP_OBJECTIDS)) {
                    sids = attr;
                } else if (attr.getAttrDefinition().getName().equalsIgnoreCase(samlGroupAttrName)) {
                    groupNames = attr;
                } else {
                    attributeValues.add(attr);
                }
            }

            if (!(provider instanceof ISystemDomainIdentityProvider)) {
                List<String> groupsInSp = new ArrayList<String>();
                if ((sids != null) && (sids.getValues() != null) && (!sids.getValues().isEmpty())) {
                    try {
                        groupsInSp = systemProvider.findGroupsForFsps(sids.getValues());
                    } catch (Exception e) {
                        logger.trace(String.format(
                                "Failed to determine FSP for principal [%s] or any of its groups in tenant [%s]",
                                principal != null ? principal.getUPN() : "null", tenantName));
                    }

                    if (groupNames != null && groupsInSp != null && groupsInSp.size() > 0) {
                        for (String groupName : groupsInSp) {
                            groupNames.getValues().add(groupName);
                        }
                    }
                }
            }
            if (groupNames != null) {
                attributeValues.add(groupNames);
            }

            addEveryoneGroupTo(attributeValues, samlGroupAttrName, systemProvider);

            //only measure time of successful results
            IdmServer.getPerfDataSinkInstance().addMeasurement(
                    new PerfBucketKey(PerfMeasurementPoint.IDMGetAttributeValues, principal.getDomain()),
                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));

            return attributeValues;
        } catch (Exception ex) {
            logger.error(String.format("Failed to get attributes for principal [%s@%s] in tenant [%s]",
                    principal != null ? principal.getName() : "null",
                    principal != null ? principal.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    /**
     * Add Everyone@<systemProviderDomain> to attribute values without duplicates
     *
     * @param attributeValues
     *           collection of {@code AttributeValuePair}. The Everyone
     *           group is going to be added if it does not exist.
     * @param systemProviderDomain
     *           domain name of the system provider
     */
    private void addEveryoneGroupTo(Collection<AttributeValuePair> attributeValues, String samlGroupAttr,
            ISystemDomainIdentityProvider systemProvider) throws Exception {
        //If system provider does not provide groupMembership info ==> no-op
        if (samlGroupAttr != null) {
            for (AttributeValuePair currentPair : attributeValues) {
                if (currentPair.getAttrDefinition().getName().equalsIgnoreCase(samlGroupAttr)) {
                    Group g = systemProvider.getEveryoneGroup();
                    String everyoneGroupNetbios = g.getNetbios();
                    String everyoneAliasNetbios = ServerUtils.getGroupAliasNetbios(g);
                    if (currentPair.getValues() == null) {
                        throw new IllegalStateException(
                                "SAML attribute definition Group is found but associated value list is null");
                    }
                    if (!currentPair.getValues().contains(everyoneGroupNetbios)) {//don't add duplicates
                        currentPair.getValues().add(everyoneGroupNetbios);
                    }
                    if ((ServerUtils.isNullOrEmpty(everyoneAliasNetbios) == false)
                            && (!currentPair.getValues().contains(everyoneAliasNetbios))) {
                        currentPair.getValues().add(everyoneAliasNetbios);
                    }
                    break;
                }
            }
        }
    }

    private PrincipalId addSolutionUser(String tenantName, String userName, SolutionDetail detail)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(userName, "User name");
            ValidateUtil.validateNotNull(detail, "Solution user detail");
            // Validate solution user detail: make sure its certificate is valid
            ValidateUtil.validateSolutionDetail(detail, "Solution user detail", this.getClockTolerance(tenantName));

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.addServicePrincipal(userName, detail);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add solution user [%s] in tenant [%s]", userName, tenantName));

            throw ex;
        }
    }

    private SolutionUser findSolutionUser(String tenantName, String userAccount) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ServerUtils.validateNotEmptyUsername(userAccount);

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            if (ServerUtils.isEquals(tenantName, this.getSystemTenant())) {
                return provider.findServicePrincipal(userAccount);
            } else {
                return provider.findServicePrincipalInExternalTenant(userAccount);
            }
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to find solution user [%s] in tenant [%s]", userAccount, tenantName));

            throw ex;
        }
    }

    private SolutionUser findSolutionUserByCertDn(String tenantName, String subjectDN) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(subjectDN, "Subject DN");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            if (ServerUtils.isEquals(tenantName, this.getSystemTenant())) {
                return provider.findServicePrincipalByCertDn(subjectDN);
            } else {
                return provider.findServicePrincipalByCertDnInExternalTenant(subjectDN);
            }
        } catch (NoSuchUserException ex) {
            logger.info(String.format("Failed to find solution user by subject DN [%s] in tenant [%s]", subjectDN,
                    tenantName));

            return null;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find solution user by subject DN [%s] in tenant [%s]", subjectDN,
                    tenantName));

            throw ex;
        }
    }

    /**
     * Retrieve user hashed password blob
     *
     * @param tenantName Name of tenant
     * @param Principal id of the user
     * @return User's hashed password blob
     * @throws IDMException
     * @throws NoSuchTenantException - if no such tenant exist
     * @throws NoSuchIdpException   - system tenant is not set up.
     * @throws InvalidArgumentException    -- if the tenant name is null or empty
     * @throws InValidPrincipleException    - if user does not exist or multiple ones are found
     */
    private byte[] getUserHashedPassword(String tenantName, PrincipalId principal) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(principal, "User principal");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(principal.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, principal.getDomain());

            ISystemDomainIdentityProvider systemProvider = null;
            if (provider instanceof ISystemDomainIdentityProvider) {
                systemProvider = (ISystemDomainIdentityProvider) provider;
            }

            if (systemProvider != null) {
                return systemProvider.getUserHashedPassword(principal);
            } else {
                PersonUser user = provider.findUser(principal);

                if (user == null) {
                    throw new InvalidPrincipalException(String.format("User %s is invalid in domain %s",
                            principal.getName(), principal.getDomain()), principal.getUPN());
                }

                // return null password
                return null;
            }
        } catch (Exception ex) {
            logger.error(String.format("Failed to get hashsed password for person user [%s@%s] in tenant [%s]",
                    principal != null ? principal.getName() : "null",
                    principal != null ? principal.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    private PersonUser findPersonUser(String tenantName, PrincipalId id) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(id, "User principal");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(id.getDomain());

            if (null == provider) {
                String msg = String.format("PrincipalId [%s] does not match any providers on primary system.",
                        id.toString());
                logger.info(msg);
                return null; // caller will try to resolve it as external IDP registered user.
            } else {
                return provider.findUser(id); //return null if not found
            }
        } catch (Exception ex) {
            logger.error(String.format("Failed to find person user [%s@%s] in tenant [%s]",
                    id != null ? id.getName() : "null", id != null ? id.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    private PersonUser findPersonUserByObjectId(String tenantName, String userObjectId) throws Exception {
        PersonUser user = null;

        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(userObjectId, "User objectId");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            // Look in Tenant's SP first
            ISystemDomainIdentityProvider systemProvider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(systemProvider, tenantName);

            try {
                user = systemProvider.findUserByObjectId(userObjectId);
            } catch (Exception ex) {// not necessary an error, just log info
                logger.info(String.format("cannot find person user with objectId [%s] in Service Provider [%s]",
                        userObjectId != null ? userObjectId : "null", systemProvider.getDomain()));
            }

            if (user != null)
                return user;

            // Look in Tenant's ID store iteratively until found
            Collection<IIdentityProvider> providers = tenantInfo.getProviders();

            for (IIdentityProvider provider : providers) {
                try {
                    assert (provider != null);
                    user = provider.findUserByObjectId(userObjectId);
                } catch (Exception ex) {// not necessary an error, just log info
                    logger.info(String.format("cannot find person user with objectId [%s] in provider [%s]",
                            userObjectId != null ? userObjectId : "null", provider.getDomain()));
                    continue;
                }

                if (user != null)
                    break;
            }

            return user;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find person user with objectId [%s] in tenant [%s]",
                    userObjectId != null ? userObjectId : "null", tenantName));

            throw ex;
        }
    }

    private Set<SolutionUser> findSolutionUsers(String tenantName, String searchString, int limit)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(searchString, "Search string");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            if (ServerUtils.isEquals(tenantName, this.getSystemTenant())) {
                return provider.findServicePrincipals(searchString);
            } else {
                return provider.findServicePrincipalsInExternalTenant(searchString);
            }
        } catch (Exception ex) {
            logger.error(String.format("Failed to find solution users [criteria : %s] in tenant [%s]", searchString,
                    tenantName));

            throw ex;
        }
    }

    /**
     * Finds a security group defined in one of the tenant's identity providers
     *
     * @param tenantName Name of tenant
     * @param id         Group to be found
     * @return Principal id of the security group. null if no such group
     * @throws IDMException
     * @throws RemoteException
     * @throws NoSuchTenantException      - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     * @throws NoSuchIdpException         - can't find provider for this group.
     *         IDMEception              - any other exceptions could be returned.
     */
    private Group findGroup(String tenantName, PrincipalId groupId) throws Exception {
        Group candidate = null;

        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(groupId, "Group name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(groupId.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, groupId.getDomain());

            candidate = provider.findGroup(groupId);
            // Provider fulfills the contract return null in case group does not exist
            if (candidate == null) {
                logger.info(String.format("Failed to find group [%s@%s] in tenant [%s]", groupId.getName(),
                        groupId.getDomain(), tenantName));

                throw new InvalidPrincipalException(String.format("Group [%s] could not be found for tenant [%s]",
                        groupId.getName(), tenantName), ServerUtils.getUpn(groupId));
            }

            return candidate;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find group [%s@%s] in tenant [%s]",
                    groupId != null ? groupId.getName() : "null", groupId != null ? groupId.getDomain() : "null",
                    tenantName));

            throw ex;
        }
    }

    /**
     * Finds a security group defined in one of the tenant's identity providers
     *
     * @param tenantName Name of tenant
     * @param id         Group to be found
     * @return Group object of the security group. null if no such group
     * @throws Exception
     * @throws RemoteException
     * @throws NoSuchTenantException      - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     * @throws NoSuchIdpException         - can't find provider for this group.
     *         IDMEception              - any other exceptions could be returned.
     */
    private Group findGroupByObjectId(String tenantName, String groupObjectId) throws Exception {
        Group group = null;

        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(groupObjectId, "Group objectId");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            // Look in Tenant's SP first
            ISystemDomainIdentityProvider systemProvider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(systemProvider, tenantName);

            try {
                group = systemProvider.findGroupByObjectId(groupObjectId);
            } catch (Exception ex) {
                logger.error(String.format("Failed to find group with objectId [%s] in Service Provider [%s]",
                        groupObjectId != null ? groupObjectId : "null", systemProvider.getDomain()));
            }

            if (group != null)
                return group;

            // Look in Tenant's ID store iteratively until found
            Collection<IIdentityProvider> providers = tenantInfo.getProviders();

            for (IIdentityProvider provider : providers) {
                try {
                    assert (provider != null);
                    group = provider.findGroupByObjectId(groupObjectId);
                } catch (Exception ex) {
                    logger.error(String.format("Failed to find group with objectId [%s] in provider [%s]",
                            groupObjectId != null ? groupObjectId : "null", provider.getDomain()));
                    continue;
                }

                if (group != null)
                    break;
            }

            return group;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find group with objectId [%s] in tenant [%s]",
                    groupObjectId != null ? groupObjectId : "null", tenantName));

            throw ex;
        }
    }

    /**
     * Finds regular users in one of the identity providers configured for the
     * tenant.
     *
     * The search criteria defines the identity domain to be searched.
     *
     * A user account is chosen for the search results if the search string is
     * part of either the account name, first name, last name, or description
     * of the account.
     *
     * Regular expressions are not allowed in the search string at this time.
     *
     * @param tenantName Name of the tenant
     * @param criteria   Search criteria, non negative
     * @param limit  a positive integer for the maximum number of items to return
     * @return Set of regular users found. Empty set when no user found.
     * @throws IDMException
     * @throws RemoteException
     * @throws NoSuchTenantException      - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     * @throws NoSuchIdpException         - can't find provider for this group.
     * @throws IDMException              - any other exceptions could be returned.
     */
    private Set<PersonUser> findPersonUsers(String tenantName, SearchCriteria criteria, int limit)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(criteria, "Search criteria");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(criteria.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, criteria.getDomain());

            return provider.findUsers(criteria.getSearchString(), criteria.getDomain(), limit);
        } catch (Exception ex) {
            logger.error(String.format("Failed to find person users [Criteria : %s] in tenant [%s]", criteria,
                    tenantName));

            throw ex;
        }
    }

    /**
     * Finds regular users in one of the identity providers configured for the
     * tenant.
     *
     * The search criteria defines the identity domain to be searched.
     *
     * A user account is chosen for the search results if the search string is
     * part of the account name, i.e. in AD, samAccountName is used
     *
     * Regular expressions are not allowed in the search string at this time.
     *
     * @param tenantName Name of the tenant
     * @param criteria   Search criteria, non negative, this search only targets searching on accountName
     *        for users (i.e. in AD it uses samAccountName)
     * @param limit  a positive integer for the maximum number of items to return
     * @return Set of regular users found. Empty set when no user found.
     * @throws IDMException
     * @throws RemoteException
     * @throws NoSuchTenantException      - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     * @throws NoSuchIdpException         - can't find provider for this group.
     * @throws IDMException              - any other exceptions could be returned.
     */
    private Set<PersonUser> findPersonUsersByName(String tenantName, SearchCriteria criteria, int limit)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(criteria, "Search criteria");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(criteria.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, criteria.getDomain());

            return provider.findUsersByName(criteria.getSearchString(), criteria.getDomain(), limit);
        } catch (Exception ex) {
            logger.error(String.format("Failed to find person users [Criteria : %s] in tenant [%s]", criteria,
                    tenantName));

            throw ex;
        }
    }

    private Set<Group> findGroups(String tenantName, SearchCriteria criteria, int limit) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(criteria, "Search criteria");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(criteria.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, criteria.getDomain());

            return provider.findGroups(criteria.getSearchString(), criteria.getDomain(), limit);
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to find groups [Criteria : %s] in tenant [%s]", criteria, tenantName));

            throw ex;
        }
    }

    // the search is limited to search only on accountNames, i.e. for AD, samAccountName is searched
    private Set<Group> findGroupsByName(String tenantName, SearchCriteria criteria, int limit) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(criteria, "Search criteria");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(criteria.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, criteria.getDomain());

            return provider.findGroupsByName(criteria.getSearchString(), criteria.getDomain(), limit);
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to find groups [Criteria : %s] in tenant [%s]", criteria, tenantName));

            throw ex;
        }
    }

    private Set<PersonUser> findPersonUsersInGroup(String tenantName, PrincipalId groupId, String searchString,
            int limit) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(groupId, "Group");
            ValidateUtil.validateNotNull(searchString, "Search string");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(groupId.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, groupId.getDomain());
            ISystemDomainIdentityProvider systemProvider = null;
            if (provider instanceof ISystemDomainIdentityProvider) {
                systemProvider = (ISystemDomainIdentityProvider) provider;
            }

            Set<PersonUser> members = provider.findUsersInGroup(groupId, searchString, limit);
            Set<PersonUser> modifiedMembers = new HashSet<PersonUser>();

            for (PersonUser user : members) {
                String userId = user.getId().getName();

                if ((systemProvider != null) && (systemProvider.isObjectIdCandidate(userId))) {
                    String userObjectId = systemProvider.getObjectId(userId);

                    PersonUser resolvedUser = findPersonUserByObjectId(tenantName, userObjectId);

                    if (resolvedUser != null) {
                        modifiedMembers.add(resolvedUser);
                    }
                } else {
                    modifiedMembers.add(user);
                }
            }

            return modifiedMembers;
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to find person users [Criteria : %s] in group [%s@%s] in tenant [%s]",
                            searchString, groupId != null ? groupId.getName() : "null",
                            groupId != null ? groupId.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    private Set<PersonUser> findPersonUsersByNameInGroup(String tenantName, PrincipalId groupId,
            String searchString, int limit) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(groupId, "Group");
            ValidateUtil.validateNotNull(searchString, "Search string");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(groupId.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, groupId.getDomain());
            ISystemDomainIdentityProvider systemProvider = null;

            if (provider instanceof ISystemDomainIdentityProvider) {
                systemProvider = (ISystemDomainIdentityProvider) provider;
            }

            Set<PersonUser> members = provider.findUsersByNameInGroup(groupId, searchString, limit);
            Set<PersonUser> modifiedMembers = new HashSet<PersonUser>();

            for (PersonUser user : members) {
                String userId = user.getId().getName();

                if ((systemProvider != null) && (systemProvider.isObjectIdCandidate(userId))) {
                    String userObjectId = systemProvider.getObjectId(userId);

                    PersonUser resolvedUser = findPersonUserByObjectId(tenantName, userObjectId);

                    if (resolvedUser != null) {
                        modifiedMembers.add(resolvedUser);
                    }
                } else {
                    modifiedMembers.add(user);
                }
            }

            return modifiedMembers;
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to find person users [Criteria : %s] in group [%s@%s] in tenant [%s]",
                            searchString, groupId != null ? groupId.getName() : "null",
                            groupId != null ? groupId.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    private Set<SolutionUser> findSolutionUsersInGroup(String tenantName, String groupName, String searchString,
            int limit) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(groupName, "Group name");
            ValidateUtil.validateNotNull(searchString, "Search string");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.findServicePrincipalsInGroup(groupName, searchString);
        } catch (Exception ex) {
            logger.error(String.format("Failed to find solution users [Criteria : %s] in group [%s] in tenant [%s]",
                    searchString, groupName, tenantName));

            throw ex;
        }
    }

    private Set<Group> findGroupsInGroup(String tenantName, PrincipalId groupId, String searchString, int limit)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(groupId, "Group");
            ValidateUtil.validateNotNull(searchString, "Search string");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(groupId.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, groupId.getDomain());

            ISystemDomainIdentityProvider systemProvider = null;
            if (provider instanceof ISystemDomainIdentityProvider) {
                systemProvider = (ISystemDomainIdentityProvider) provider;
            }

            Set<Group> members = provider.findGroupsInGroup(groupId, searchString, limit);
            Set<Group> modifiedMembers = new HashSet<Group>();

            for (Group group : members) {
                String subGroupName = group.getId().getName();

                if ((systemProvider != null) && (systemProvider.isObjectIdCandidate(subGroupName))) {
                    String subgroupObjectId = systemProvider.getObjectId(subGroupName);

                    Group resolvedGroup = findGroupByObjectId(tenantName, subgroupObjectId);

                    if (resolvedGroup != null) {
                        modifiedMembers.add(resolvedGroup);
                    }
                } else {
                    modifiedMembers.add(group);
                }
            }

            return modifiedMembers;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find groups [Criteria : %s] in group [%s@%s] in tenant [%s]",
                    searchString, groupId != null ? groupId.getName() : "null",
                    groupId != null ? groupId.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    private Set<Group> findGroupsByNameInGroup(String tenantName, PrincipalId groupId, String searchString,
            int limit) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(groupId, "Group");
            ValidateUtil.validateNotNull(searchString, "Search string");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(groupId.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, groupId.getDomain());

            ISystemDomainIdentityProvider systemProvider = null;
            if (provider instanceof ISystemDomainIdentityProvider) {
                systemProvider = (ISystemDomainIdentityProvider) provider;
            }

            Set<Group> members = provider.findGroupsByNameInGroup(groupId, searchString, limit);
            Set<Group> modifiedMembers = new HashSet<Group>();

            for (Group group : members) {
                String subGroupName = group.getId().getName();

                if ((systemProvider != null) && (systemProvider.isObjectIdCandidate(subGroupName))) {
                    String subgroupObjectId = systemProvider.getObjectId(subGroupName);

                    Group resolvedGroup = findGroupByObjectId(tenantName, subgroupObjectId);

                    if (resolvedGroup != null) {
                        modifiedMembers.add(resolvedGroup);
                    }
                } else {
                    modifiedMembers.add(group);
                }
            }

            return modifiedMembers;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find groups [Criteria : %s] in group [%s@%s] in tenant [%s]",
                    searchString, groupId != null ? groupId.getName() : "null",
                    groupId != null ? groupId.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    private boolean isMemberOfSystemGroup(String tenantName, PrincipalId principalId, String groupName)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(principalId, "User principal");
            ValidateUtil.validateNotEmpty(groupName, "groupName");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            ISystemDomainIdentityProvider systemProvider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(systemProvider, tenantName);

            // we can optimize this by introducing the groups check api in system domain provider
            // at the moment keeping existing behavior to minimize changes ...
            boolean isMember = false;
            Set<Group> directParentGroups = this.findDirectParentGroups(tenantName, principalId);

            if ((directParentGroups != null) && (directParentGroups.isEmpty() == false)) {
                for (Group group : directParentGroups) {
                    if ((group != null) && (group.getDomain().equalsIgnoreCase(systemProvider.getDomain()))
                            && (group.getName().equalsIgnoreCase(groupName))) {
                        isMember = true;
                        break;
                    }
                }
            }

            return isMember;
        } catch (Exception ex) {
            logger.error(String.format(
                    "Failed to check System Group Membership: tenant name=[%s], principalId=[%s], groupName=[%s]",
                    (tenantName != null) ? tenantName : "(NULL)",
                    (principalId != null) ? principalId.getUPN() : "(NULL)",
                    (groupName != null) ? groupName : "(NULL)"), ex);
            throw ex;
        }
    }

    /**
     * Finds the set of groups that contain the specified security principal in
     * the tenant.
     *
     * The principal whose immediate parents are desired, may be a user or group
     *
     * @param tenantName  Name of tenant, required.non-null non-empty
        
     * @param principalId Security principal id, required, non-null.
     * @return Set of immediate parent groups found.
     * @throws IDMException
     * @throws NoSuchTenantException - if no such tenant exist
     * @throws NoSuchIdpException   - system tenant is not set up.
     * @throws InvalidArgumentException    -- if the tenant name is null or empty
     * @throws InValidPrincipleException    - principal ID is invalid.
     * @throws IDMException         - wrapping exception for any other exceptions
     *                              from down the stack.
     */
    private Set<Group> findDirectParentGroups(String tenantName, PrincipalId principalId) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(principalId, "User principal");

            long startedTime = System.nanoTime();

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            // (1) Find from system domain
            ISystemDomainIdentityProvider systemProvider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(systemProvider, tenantName);

            Set<Group> groups = new HashSet<Group>();
            PrincipalGroupLookupInfo idpGroups = null;
            List<Group> sysGroups = null;
            String fspId = null;
            // direct parent groups is a union of:
            // - set of direct parent groups from specific identity source provider
            // - set of direct parent groups from system provider for specified principal's object id
            // [if prinicipal's identity source != system provider]

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(principalId.getDomain());

            // TODO: external registered Idp support needs to be re-considered for more straightforward handling...
            if (provider == null) // this could be external idp registered user
            {
                fspId = getRegisteredExternalIDPUserObjectId(tenantName, principalId);
            } else {
                try {
                    idpGroups = provider.findDirectParentGroups(principalId);
                    if ((idpGroups != null)
                            && (provider.getName().equalsIgnoreCase(systemProvider.getName()) == false))

                    {
                        fspId = idpGroups.getPrincipalObjectId();

                    }
                } catch (InvalidPrincipalException ex)

                {
                    // this could be external idp registered user
                    fspId = getRegisteredExternalIDPUserObjectId(tenantName, principalId);
                }
            }

            if (ServerUtils.isNullOrEmpty(fspId) == false)

            {

                try {
                    sysGroups = systemProvider.findGroupObjectsForFsps(Collections.<String>singletonList(fspId));
                } catch (InvalidPrincipalException ex)

                {
                    logger.trace(String.format("Failed to find principal [%s@%s] as FSP principal in tenant [%s]",
                            principalId != null ? principalId.getName() : "null",
                            principalId != null ? principalId.getDomain() : "null", tenantName));
                    sysGroups = null;
                }
            }

            if ((idpGroups != null) && (idpGroups.getGroups() != null)
                    && (idpGroups.getGroups().isEmpty() == false))

            {
                groups.addAll(idpGroups.getGroups());
            }

            if (sysGroups != null && !sysGroups.isEmpty()) {
                groups.addAll(sysGroups);
            }

            // TODO: ideally everyone group should be added within system domain provider
            groups.add(systemProvider.getEveryoneGroup());
            IdmServer.getPerfDataSinkInstance().addMeasurement(
                    new PerfBucketKey(PerfMeasurementPoint.IDMFindDirectParentGroups, principalId.getDomain()),
                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startedTime));
            return groups;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find direct parent groups of principal [%s@%s] in tenant [%s]",
                    principalId != null ? principalId.getName() : "null",
                    principalId != null ? principalId.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    private Set<Group> findNestedParentGroups(String tenantName, PrincipalId principalId) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(principalId, "Principal Id");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider systemProvider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(systemProvider, tenantName);

            Set<Group> groups = new HashSet<Group>();
            PrincipalGroupLookupInfo idpGroups = null;
            List<Group> sysGroups = null;
            ArrayList<String> fspIds = new ArrayList<String>();

            // nested parent groups is a union of:
            // - set of nested parent groups from specific identity source provider
            // - set of fsp parent groups from system provider for { specified principal's object id + all of the nested groups ids}.
            //   [if principal's identity source != system provider]
            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(principalId.getDomain());

            // TODO: external registered Idp support needs to be re-considered for more straightforward handling...
            if (provider == null) // this could be external idp registered user
            {
                String externalUserId = getRegisteredExternalIDPUserObjectId(tenantName, principalId);
                if (ServerUtils.isNullOrEmpty(externalUserId) == false) {
                    fspIds.add(externalUserId);
                }
            } else {
                try {
                    idpGroups = provider.findNestedParentGroups(principalId);
                    if ((idpGroups != null)
                            && (provider.getName().equalsIgnoreCase(systemProvider.getName()) == false)) {
                        if ((ServerUtils.isNullOrEmpty(idpGroups.getPrincipalObjectId()) == false)) {
                            fspIds.add(idpGroups.getPrincipalObjectId());
                        }

                        if ((idpGroups.getGroups() != null) && (idpGroups.getGroups().isEmpty() == false)) {
                            for (Group g : idpGroups.getGroups())

                            {
                                if ((g != null) && (ServerUtils.isNullOrEmpty(g.getObjectId()) == false)) {
                                    fspIds.add(g.getObjectId());

                                }
                            }
                        }
                    }
                } catch (InvalidPrincipalException ex) {
                    // this could be external idp registered user
                    String externalUserId = getRegisteredExternalIDPUserObjectId(tenantName, principalId);
                    if (ServerUtils.isNullOrEmpty(externalUserId) == false) {
                        fspIds.add(externalUserId);
                    }
                }
            }

            if ((fspIds != null) && (fspIds.isEmpty() == false))

            {
                try {
                    sysGroups = systemProvider.findGroupObjectsForFsps(fspIds);
                } catch (InvalidPrincipalException ex) {
                    logger.trace(String.format("Failed to find principal [%s@%s] as FSP principal in tenant [%s]",
                            principalId != null ? principalId.getName() : "null",
                            principalId != null ? principalId.getDomain() : "null", tenantName));
                    sysGroups = null;
                }
            }

            if (idpGroups != null && idpGroups.getGroups() != null && !idpGroups.getGroups().isEmpty())

            {
                groups.addAll(idpGroups.getGroups());
            }

            if (sysGroups != null && !sysGroups.isEmpty()) {
                groups.addAll(sysGroups);
            }

            groups.add(systemProvider.getEveryoneGroup());
            return groups;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find nested parent groups of principal [%s@%s] in tenant [%s]",
                    principalId != null ? principalId.getName() : "null",
                    principalId != null ? principalId.getDomain() : "null", tenantName));

            throw ex;
        }
    }

    private String getRegisteredExternalIDPUserObjectId(String tenantName, PrincipalId principalId)
            throws Exception {
        String objectId = null;
        PersonUser user = findRegisteredExternalIDPUser(tenantName, principalId);
        if (null != user) {
            objectId = user.getObjectId();
        } else {
            throw new InvalidPrincipalException("Principal cannot be found.", principalId.getUPN());
        }

        return objectId;
    }

    private SearchResult find(String tenantName, SearchCriteria criteria, int limit) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(criteria, "Search criteria");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(criteria.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, criteria.getDomain());

            return provider.find(criteria.getSearchString(), criteria.getDomain(), limit);

        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to find objects [Criteria : %s] in tenant [%s]", criteria, tenantName));

            throw ex;
        }
    }

    private SearchResult findByName(String tenantName, SearchCriteria criteria, int limit) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(criteria, "Search criteria");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            IIdentityProvider provider = tenantInfo.findProviderADAsFallBack(criteria.getDomain());
            ServerUtils.validateNotNullIdp(provider, tenantName, criteria.getDomain());

            return provider.findByName(criteria.getSearchString(), criteria.getDomain(), limit);
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to find objects [Criteria : %s] in tenant [%s]", criteria, tenantName));

            throw ex;
        }
    }

    private Set<PersonUser> findLockedUsers(String tenantName, String searchString, int limit) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(searchString, "Search string");
            Set<PersonUser> lockedUsers = new HashSet<PersonUser>();

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            Set<PersonUser> lockedUsers_in_sp = provider.findLockedUsers(searchString, limit);

            if (lockedUsers_in_sp != null && lockedUsers_in_sp.size() > 0) {
                lockedUsers.addAll(lockedUsers_in_sp);
            }

            Collection<IIdentityProvider> providers = tenantInfo.getProviders();

            if (providers != null && providers.size() > 0) {
                for (IIdentityProvider idp : providers) {
                    Set<PersonUser> lockedUsers_in_idp = idp.findLockedUsers(searchString, limit);

                    if (lockedUsers_in_idp != null && lockedUsers_in_idp.size() > 0) {
                        lockedUsers.addAll(lockedUsers_in_idp);
                    }
                }
            }

            return lockedUsers;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find locked users [Criteria : %s] in tenant [%s]", searchString,
                    tenantName));

            throw ex;
        }
    }

    private Set<PersonUser> findDisabledPersonUsers(String tenantName, String searchString, int limit)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(searchString, "Search string");
            Set<PersonUser> disabledUsers = new HashSet<PersonUser>();

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            Set<PersonUser> disabledUsers_in_sp = provider.findDisabledUsers(searchString, limit);

            if (disabledUsers_in_sp != null && disabledUsers_in_sp.size() > 0) {
                disabledUsers.addAll(disabledUsers_in_sp);
            }

            Collection<IIdentityProvider> providers = tenantInfo.getProviders();

            if (providers != null && providers.size() > 0) {
                for (IIdentityProvider idp : providers) {
                    Set<PersonUser> disabledUsers_in_idp = idp.findDisabledUsers(searchString, limit);

                    if (disabledUsers_in_idp != null && disabledUsers_in_idp.size() > 0) {
                        disabledUsers.addAll(disabledUsers_in_idp);
                    }
                }
            }

            return disabledUsers;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find disabled users [Criteria : %s] in tenant [%s]", searchString,
                    tenantName));

            throw ex;
        }
    }

    private Set<SolutionUser> findDisabledSolutionUsers(String tenantName, String searchString) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(searchString, "Search string");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            if (ServerUtils.isEquals(tenantName, this.getSystemTenant())) {
                return provider.findDisabledServicePrincipals(searchString);
            } else {
                return provider.findDisabledServicePrincipalsInExternalTenant(searchString);
            }
        } catch (Exception ex) {
            logger.error(String.format("Failed to find disabled solution users [search string : %s] in tenant [%s]",
                    searchString, tenantName));

            throw ex;
        }
    }

    /**
     * Adds a regular user to the tenant's system domain
     *
     * @param tenantName Name of tenant. required, non-null, non-empty
     * @param userName   Name of regular user. required, non-null,
     * @param detail     Detailed information about the user. required, non-null,
     * @param hashedPassword   User's hashed password. required, non-null,
     * @param hashingAlgorithm  The algorithm used to generate the password hash, this is mandatory,
     *        The valid values are defined in enum 'HashingAlgorithmType', currently vmware-directory
     *        only supports 'SSO-v1-1'. required, non-null,
     * @return Principal id of the regular user after it has been created.
     * @throws IDMException
     * @throws RemoteException
     * @throws NoSuchTenantException    - when tenant does not exist
     * @throws InvalidArguementException  - invalid input
     * @throws InValidPrincipleException    - user already exist, or username is empty,
     *                                        or invalid username format
     */
    private PrincipalId addUser(String tenantName, String userName, PersonDetail detail, byte[] hasedPassword,
            String hashingAlgorithm) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");

            ValidateUtil.validateNotNull(userName, "user name");
            checkInvalidCharForUserData(userName, INVALID_CHARS_FOR_USER_ID);

            ValidateUtil.validateNotNull(detail, "user detail");
            checkInvalidCharForUserData(detail.getFirstName(), INVALID_CHARS_FOR_USER_DETAIL);
            checkInvalidCharForUserData(detail.getLastName(), INVALID_CHARS_FOR_USER_DETAIL);
            checkInvalidCharForUserData(detail.getDescription(), INVALID_CHARS_FOR_USER_DETAIL);

            ValidateUtil.validateNotNull(hasedPassword, "Hashed Password");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.addUser(userName, detail, hasedPassword, hashingAlgorithm);
        } catch (ConstraintViolationLdapException e) {
            logger.warn(String.format(
                    "provided password for user [%s] violates password policy constraint for tenant [%s]", userName,
                    tenantName), e);
            throw new PasswordPolicyViolationException(e.getMessage(), e);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add user [%s] in tenant [%s]", userName, tenantName));

            throw ex;
        }
    }

    /**
     * Adds a regular/jit user to the tenant's system domain.
     * If extIdpEntityId is non-null, this user is a jit user.
     * extUserId must be provided for jit user provisioning.
     *
     * @param tenantName Name of tenant. required, non-null, non-empty
     * @param userName   Name of regular/jit user. required, non-null.
     * @param detail     Detailed information about the user. required, non-null.
     * @param extIdpEntityId ExternalIDP entity ID. If it is non-null, add jit user.
     * @param extUserId      External User's ID. Required attribute for jit user.
     * @param password   User's password
     * @return Principal id of the regular user after it has been created.
     * @throws IDMException
     * @throws RemoteException
     * @throws NoSuchTenantException    - when tenant does not exist
     * @throws InvalidArguementException  - invalid input
     * @throws InValidPrincipleException    - user already exist, or invalid user name format
     */
    private PrincipalId addUser(String tenantName, String userName, PersonDetail detail, String extIdpEntityId,
            String extUserId, char[] password) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");

            ValidateUtil.validateNotNull(userName, "user name");
            checkInvalidCharForUserData(userName, INVALID_CHARS_FOR_USER_ID);

            ValidateUtil.validateNotNull(detail, "user detail");
            checkInvalidCharForUserData(detail.getFirstName(), INVALID_CHARS_FOR_USER_DETAIL);
            checkInvalidCharForUserData(detail.getLastName(), INVALID_CHARS_FOR_USER_DETAIL);
            checkInvalidCharForUserData(detail.getDescription(), INVALID_CHARS_FOR_USER_DETAIL);

            ValidateUtil.validateNotNull(detail, "user detail");

            if (!ValidateUtil.isEmpty(extIdpEntityId)) {
                ValidateUtil.validateNotEmpty(extUserId, "external user id.");
            }

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.addUser(userName, detail, password, extIdpEntityId, extUserId);
        } catch (ConstraintViolationLdapException e) {
            logger.warn(String.format(
                    "provided password for user [%s] violates password policy constraint for tenant [%s]", userName,
                    tenantName), e);
            throw new PasswordPolicyViolationException(e.getMessage(), e);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add user [%s] in tenant [%s]", userName, tenantName));

            throw ex;
        }
    }

    private boolean enableUserAccount(String tenantName, PrincipalId userId) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(userId, "user id");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.enableUserAccount(userId);
        } catch (Exception ex) {
            logger.error(String.format("Failed to enable user [%s@%s] in tenant [%s]",
                    userId != null ? userId.getName() : "null", userId != null ? userId.getDomain() : "null",
                    tenantName));

            throw ex;
        }
    }

    private boolean disableUserAccount(String tenantName, PrincipalId userId) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(userId, "user id");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.disableUserAccount(userId);
        } catch (Exception ex) {
            logger.error(String.format("Failed to disable user [%s@%s] in tenant [%s]",
                    userId != null ? userId.getName() : "null", userId != null ? userId.getDomain() : "null",
                    tenantName));

            throw ex;
        }
    }

    private boolean unlockUserAccount(String tenantName, PrincipalId userId) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(userId, "user id");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.unlockUserAccount(userId);
        } catch (Exception ex) {
            logger.error(String.format("Failed to unlock user [%s@%s] in tenant [%s]",
                    userId != null ? userId.getName() : "null", userId != null ? userId.getDomain() : "null",
                    tenantName));

            throw ex;
        }
    }

    /**
     * Adds a security group to the tenant's system domain
     *
     * @param tenantName  Name of tenant, required.non-null non-empty
     * @param groupName   Name of security group, required non-null, non-empty
     * @param groupDetail Detailed information about the group. required, non-null.
     * @return Principal id of the group after it has been created.
     * @throws IDMException
     * @throws Exception
     * @throws InvalidArgumentException    - illegal input valid was passed.
     * @throws NoSuchTenantException
     * @throws NoSuchIdpException        when system provider is missing
     */
    private PrincipalId addGroup(String tenantName, String groupName, GroupDetail detail) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(groupName, "group name");
            ValidateUtil.validateNotNull(detail, "group detail");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.addGroup(groupName, detail);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add group [%s] in tenant [%s]", groupName, tenantName));

            throw ex;
        }
    }

    private boolean lookupPrincipalIdServicePrincipal(String tenantName, ISystemDomainIdentityProvider provider,
            PrincipalId principalId) throws Exception {
        boolean bIsSystemTenant = ServerUtils.isEquals(tenantName, this.getSystemTenant());

        if ((!principalId.getDomain().equalsIgnoreCase(provider.getDomain()))
                && (!principalId.getDomain().equalsIgnoreCase(provider.getAlias()))) {//the subsequent processing is only relying on the name of the principalId.
                                                                                                                                                            //check here first and return if domain mismatch.
            return true; //not found
        }
        return ((bIsSystemTenant && provider.findServicePrincipal(principalId.getName()) == null)
                || (!bIsSystemTenant
                        && provider.findServicePrincipalInExternalTenant(principalId.getName()) == null));
    }

    /**
     * Adds a regular user to a security group in the tenant
     *
     * @param tenantName Name of tenant,  required non-null, non-empty
     * @param userId     Name of regular user to be assigned group
     *                     membership.  required non-null.
     * @param groupName  Name of security group.  required non-null, non-empty
     * @return true if the user's group membership was successfully assigned,
     *         false otherwise.
     * @throws IDMException
     * @throws NoSuchTenantException    - when tenant does not exist
     * @throws InvalidArguementException  - invalid input
     * @throws InValidPrincipleException  - unable to find the group
     * @throws MemberAlreadyExistException    - user already exist
     *
     */
    private boolean addUserToGroup(String tenantName, PrincipalId userId, String groupName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(userId, "userId");
            ValidateUtil.validateNotNull(groupName, "groupName");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            if (provider.findUser(userId) == null
                    && lookupPrincipalIdServicePrincipal(tenantName, provider, userId)) {
                PersonUser user = findPersonUser(tenantName, userId);

                if (user != null) {
                    validateObjectIdNotNull(user);
                    PrincipalId newUserId = getFspIdForSystemDomain(provider, user);
                    return provider.addUserToGroup(newUserId, groupName);
                } else {
                    user = findRegisteredExternalIDPUser(tenantName, userId);
                    if (user != null) {
                        PrincipalId newFspId = getFspIdForSystemDomain(provider, user);
                        return provider.addUserToGroup(newFspId, groupName);
                    }
                }
            }

            return provider.addUserToGroup(userId, groupName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add user [%s@%s] to group [%s] in tenant [%s]",
                    userId != null ? userId.getName() : "null", userId != null ? userId.getDomain() : "null",
                    groupName, tenantName));

            throw ex;
        }
    }

    private boolean removeFromLocalGroup(String tenantName, PrincipalId principalId, String groupName)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(principalId, "Principal ID");
            ValidateUtil.validateNotEmpty(groupName, "Group name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            if (provider.findUser(principalId) == null && provider.findGroup(principalId) == null
                    && lookupPrincipalIdServicePrincipal(tenantName, provider, principalId)) {
                // lookup principal Id as user
                try {
                    PersonUser user = findPersonUser(tenantName, principalId);

                    if (user != null) {
                        if (user.getObjectId() != null) {
                            PrincipalId newUserId = getFspIdForSystemDomain(provider, user);

                            return provider.removeFromGroup(newUserId, groupName);
                        }
                    } else {
                        user = findRegisteredExternalIDPUser(tenantName, principalId);
                        if (user != null) {
                            PrincipalId newFspId = getFspIdForSystemDomain(provider, user);
                            return provider.removeFromGroup(newFspId, groupName);
                        }
                    }
                } catch (Exception ex) {
                    logger.warn(String.format("Failed to find principal [%s@%s] as user in tenant [%s]",
                            principalId != null ? principalId.getName() : "null",
                            principalId != null ? principalId.getDomain() : "null", tenantName));
                }

                // lookup principal Id as group
                try {
                    Group group = findGroup(tenantName, principalId);
                    if (group != null && group.getObjectId() != null) {
                        PrincipalId newGroupId = getFspIdForSystemDomain(provider, group);
                        return provider.removeFromGroup(newGroupId, groupName);
                    }
                    //NB: We don't need to support externalIDP group,
                    //So no registration, no removal of register group.
                } catch (Exception ex) {
                    logger.warn(String.format("Failed to find principal [%s@%s] as group in tenant [%s]",
                            principalId != null ? principalId.getName() : "null",
                            principalId != null ? principalId.getDomain() : "null", tenantName));
                }
            }

            return provider.removeFromGroup(principalId, groupName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to remove principal [%s@%s] from local group [%s] in tenant [%s]",
                    principalId != null ? principalId.getName() : "null",
                    principalId != null ? principalId.getDomain() : "null", groupName, tenantName));

            throw ex;
        }
    }

    /**
     * @param principal
     * @throws IllegalArgumentException
     *             when the objectId is null
     */
    private void validateObjectIdNotNull(Principal principal) {
        ValidateUtil.validateNotNull(principal, "principal");
        if (principal.getObjectId() == null) {
            //Throw exception if objectId is not set
            //when such attribute is optional in the IDS's schema
            String msg = String.format("Not allowed: %s's objectId is null", principal.getId().getUPN());
            logger.error(msg);
            throw new IllegalArgumentException(msg);
        }
    }

    /**
     * Adds a security group to another in the tenant's system domain.
     *     Applies to system provider only. For external provider, we have read privilege only.
     *  If the group is already exist, nothing is changed.
     *
     * @param tenantName Name of tenant. required.
     * @param groupId    Name of security group to be added. required, non-null
     * @param groupName  Name of destination security group to add to. required, non-null, non-empty.
     * @return true if the membership has been successfully changed,
     *         false otherwise
     * @throws IDMException
     * @throws InvalidArgumentException    illegal input
     * @throws NoSuchTenantException        tenant does not exist
     * @throws NoSuchIdpException            System tenant is missing
     * @throws InValidPrincipleException    groupId is invalid
     */
    private boolean addGroupToGroup(String tenantName, PrincipalId groupId, String groupName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(groupId, "Group ID");
            ValidateUtil.validateNotEmpty(groupName, "Group name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            if (provider.findGroup(groupId) == null) {
                Group group = findGroup(tenantName, groupId);

                if (group != null) {
                    validateObjectIdNotNull(group);
                    PrincipalId newGroupId = getFspIdForSystemDomain(provider, group);
                    return provider.addGroupToGroup(newGroupId, groupName);
                }
            }

            return provider.addGroupToGroup(groupId, groupName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to add group [%s@%s] to group [%s] in tenant [%s]",
                    groupId != null ? groupId.getName() : "null", groupId != null ? groupId.getDomain() : "null",
                    groupName, tenantName));

            throw ex;
        }
    }

    private void deletePrincipal(String tenantName, String principalAccountName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(principalAccountName, "principal name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            provider.deletePrincipal(principalAccountName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to delete principalName [%s] in tenant [%s]", principalAccountName,
                    tenantName));

            throw ex;
        }
    }

    /**
     * Updates the details of a regular user in the system domain of the tenant
     *
     * @param tenantName  Name of tenant, required.non-null non-empty
     * @param userName   Name of the regular user
     * @param detail     Details to be updated
     * @return Principal id of the regular user
     * @throws InvalidPrincipalException    - empty username or user does not exist
     * @throws NoSuchTenantException
     * @throws IDMException all other unexpected errors
     */
    private PrincipalId updatePersonUserDetail(String tenantName, String accountName, PersonDetail detail)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ServerUtils.validateNotEmptyUsername(accountName);
            ValidateUtil.validateNotNull(detail, "user detail");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.updatePersonUserDetail(accountName, detail);
        } catch (Exception ex) {
            logger.error(String.format("Failed to update user [%s] in tenant [%s]", accountName, tenantName));

            throw ex;
        }
    }

    /**
     * Updates a service principal account in the tenant
     *
     * The service principal will be located in the system domain of the tenant
     *
     * @param tenantName Name of tenant
     * @param userName   Name of service principal
     * @param detail     Details of the service principal to be updated.
     * @return Principal Id of the service principal account
     * @throws InvalidPrincipalException    - empty username or user does not exist
     * @throws NoSuchTenantException
     * @throws IDMException all other unexpected errors
     */
    private PrincipalId updateSolutionUserDetail(String tenantName, String accountName, SolutionDetail detail)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ServerUtils.validateNotEmptyUsername(accountName);
            ValidateUtil.validateNotNull(detail, "solution user detail");
            // Validate solution user detail: make sure its certificate is valid
            ValidateUtil.validateSolutionDetail(detail, "Solution user detail", this.getClockTolerance(tenantName));

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            if (ServerUtils.isEquals(tenantName, this.getSystemTenant())) {
                return provider.updateServicePrincipalDetail(accountName, detail);
            } else {
                return provider.updateServicePrincipalDetailInExternalTenant(accountName, detail);
            }
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to update solution user [%s] in tenant [%s]", accountName, tenantName));

            throw ex;
        }
    }

    private PrincipalId updateGroupDetail(String tenantName, String groupName, GroupDetail detail)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(groupName, "group name");
            ValidateUtil.validateNotNull(detail, "group detail");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.updateGroupDetail(groupName, detail);
        } catch (Exception ex) {
            logger.error(String.format("Failed to update group [%s] in tenant [%s]", groupName, tenantName));

            throw ex;
        }
    }

    /**
     * Sets the password
     *
     * The new password is not subject to password policy requirements
     *
     * This routine must be invoked in the (login) context of a user with
     * administrative privileges in the domain of the Identity Provider
     * that governs the principal.
     *
     * @param tenantName  Name of tenant, required.non-null non-empty
        
     * @param userName    Principal for whom the password must be set. required.non-null non-empty
     * @param newPassword New password. required.non-null non-empty
     * @throws PasswordPolicyViolationException    Illegal password, such as empty pw, was used
     * @throws InvalidPrincipalException  Empty user name or user can not be found
     * @throws NoSuchTenantException
     * @throws IDMException
     * @throws Exception
     */
    private void setUserPassword(String tenantName, String accountName, char[] newPassword) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ServerUtils.validateNotEmptyUsername(accountName);
            ValidateUtil.validateNotNull(newPassword, "New password");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            provider.resetUserPassword(accountName, newPassword);

            updateSystemDomainStorePasswordIfNeeded(tenantName, accountName, newPassword);

            logger.info(String.format("Password for user [%s] successfully set for tenant [%s]", accountName,
                    tenantName));
        } catch (ConstraintViolationLdapException e) {
            logger.warn(String.format(
                    "provided password for user [%s] violates password policy constraint for tenant [%s]",
                    accountName, tenantName), e);
            throw new PasswordPolicyViolationException(e.getMessage(), e);
        } catch (Exception ex) {
            logger.error(String.format("Failed to reset password for user [%s] in tenant [%s]", accountName,
                    tenantName));

            throw ex;
        }
    }

    /**
     * Changes the user's password after verifying the current password
     *
     * The new password is subject to password policy requirements
     *
     * @param tenantName      Name of tenant
     * @param userName        Principal for which password must be changed
     * @param currentPassword Current password
     * @param newPassword     New password
     * @throws InvalidPrincipalException  Empty user name or user can not be found
     * @throws NoSuchTenantException
     * @throws IDMException
     * @throws Exception
     */
    private void changeUserPassword(String tenantName, String accountName, char[] currentPassword,
            char[] newPassword) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ServerUtils.validateNotEmptyUsername(accountName);
            ValidateUtil.validateNotNull(currentPassword, "Current password");
            ValidateUtil.validateNotNull(newPassword, "New password");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            provider.resetUserPassword(accountName, currentPassword, newPassword);

            updateSystemDomainStorePasswordIfNeeded(tenantName, accountName, newPassword);
        } catch (ConstraintViolationLdapException e) {
            logger.warn(String.format(
                    "provided password for user [%s] violates password policy constraint for tenant [%s]",
                    accountName, tenantName), e);
            throw new PasswordPolicyViolationException(e.getMessage(), e);
        } catch (Exception ex) {
            logger.error(String.format("Failed to reset password for user [%s] in tenant [%s]", accountName,
                    tenantName));

            throw ex;
        }
    }

    private void updateSystemDomainStorePasswordIfNeeded(String tenantName, String accountName, char[] newPassword)
            throws Exception {
        IdmServerConfig settings = IdmServerConfig.getInstance();

        ServerIdentityStoreData systemStoreData = this.getSystemDomainIdentityStoreData(tenantName);

        String adminAccountName = settings.getTenantAdminUserName(tenantName, accountName);

        if (adminAccountName.equalsIgnoreCase(systemStoreData.getUserName())) {
            systemStoreData.setPassword(new String(newPassword));
            _configStore.setProvider(tenantName, systemStoreData);
            _tenantCache.deleteTenant(tenantName);
        }
    }

    private String getDefaultTenant() throws Exception {
        String defaultTenant = _tenantCache.findDefaultTenant();

        if (defaultTenant == null || defaultTenant.isEmpty()) {
            try {
                defaultTenant = _tenantCache.setDefaultTenant(_configStore.getDefaultTenant());
            } catch (Exception ex) {
                logger.error("Failed to get default tenant");

                throw ex;
            }
        }

        return defaultTenant;
    }

    private String getSystemTenant() throws Exception {
        String systemTenant = _tenantCache.findSystemTenant();

        if (systemTenant == null || systemTenant.isEmpty()) {
            try {
                systemTenant = _tenantCache.setSystemTenant(_configStore.getSystemTenant());
            } catch (Exception ex) {
                logger.error("Failed to get system tenant");

                throw ex;
            }
        }

        return systemTenant;
    }

    private Collection<String> getAllTenants() throws Exception {
        Collection<String> allTenants = null;
        try {
            allTenants = _tenantCache.findAllTenants();
            // if do not find tenant list in the cache, hit the directory backend
            if (allTenants == null || allTenants.isEmpty()) {
                allTenants = _configStore.getAllTenants();
                if (allTenants == null || allTenants.isEmpty()) {
                    throw new IDMException("No tenants are found.");
                }
            }
        } catch (Exception ex) {
            logger.error("Failed to get a list of all tenants.");

            throw ex;
        }

        return allTenants;
    }

    private void setEntityID(String tenantName, String entityID) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(entityID, "Entity ID");

            _configStore.setEntityID(tenantName, entityID);

            _tenantCache.deleteTenant(tenantName);

            logger.info(String.format("EntityID [%s] successfully set for tenant [%s]", entityID, tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set entity Id [%s] in tenant [%s]", entityID, tenantName));

            throw ex;
        }
    }

    private String getEntityID(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            String serviceUri = tenantInfo.getEntityId();
            return expandEntityID(serviceUri);
        } catch (Exception ex) {
            logger.error(String.format("Failed to get entity Id for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private String getLocalIDPAlias(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            return tenantInfo.getEntityAlias();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get local idp alias for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setLocalIDPAlias(String tenantName, String alias) throws Exception {
        ValidateUtil.validateNotEmpty(tenantName, "tenantName");
        ValidateUtil.validateNotEmpty(alias, "alias");

        try {
            _configStore.setAlias(tenantName, alias);
            logger.info(String.format("Alias successfully set for tenant [%s]", tenantName));

            _tenantCache.deleteTenant(tenantName);
            logger.debug(String.format("tenant cache removed for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set local idp alias for tenant [%s]", tenantName));
            throw ex;
        }
    }

    private String expandEntityID(String serviceUri) throws UnknownHostException, SocketException, IOException {
        if (null != serviceUri) {
            String hostIPAddress = CommonUtil.getHostIPAddress(IdmUtils.getIdentityServicesConfigDir());
            String stsTomcatPort = IdmUtils.getStsTomcatPort();
            serviceUri = serviceUri.replace(HOSTNAME_MACRO, hostIPAddress);
            if (stsTomcatPort.equals(DEFAULT_HTTPS_PORT)) {
                //not emitting port if it is default https port. Spring framework work portmatching seem removes it
                // if the port is default causing SAML request validation failure. -- Note from Schai 09/13/2013
                serviceUri = serviceUri.replace(String.format(":%s", PORT_MACRO), "");
            } else {
                serviceUri = serviceUri.replace(PORT_MACRO, stsTomcatPort);
            }
        }

        return serviceUri;
    }

    private String getOIDCEntityID(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            // reuse the existing entity id to create OIDC entity id.
            // if the entity id has websso suffix, replace it with oidc suffix,
            // otherwise, create a new OIDC entity id.
            // in the long term, we want to change the entity id to be service neutral.
            // see bug# 1410188
            String serviceUri = getEntityID(tenantName);
            String webssoSuffix = String.format("/websso/SAML2/Metadata/%s", tenantName);
            String oidcSuffix = String.format("/openidconnect/%s", tenantName);
            if (serviceUri != null && serviceUri.endsWith(webssoSuffix)) {
                serviceUri = serviceUri.replace(webssoSuffix, oidcSuffix);
            } else {
                serviceUri = String.format("https://%s:%s/openidconnect/%s", HOSTNAME_MACRO, PORT_MACRO,
                        tenantName);
                serviceUri = expandEntityID(serviceUri);
            }

            return serviceUri;
        } catch (Exception ex) {
            logger.error(String.format("Failed to get OIDC Entity ID for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private void setDefaultTenant(String name) throws Exception {
        try {
            ValidateUtil.validateNotNull(name, "Tenant name");

            _configStore.setDefaultTenant(name);

            _tenantCache.setDefaultTenant(name);

            logger.info(String.format("Default tenant [%s] successfully set", name));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set default tenant [%s]", name));

            throw ex;
        }
    }

    /**
     * Retrieves the password policy for the specified tenant.
     *
     * @param tenantName Name of tenant, required non-null, non-empty
     * @return policy     pw Policy for the tenant.
     * @throws IDMException
     * @throws NoSuchTenantException        - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     */
    private PasswordPolicy getPasswordPolicy(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.getPasswordPolicy();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get password policy for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Sets the password policy for the specified tenant.
     *
     * @param tenantName Name of tenant, required non-null, non-empty
     * @param policy     password Policy for the tenant, required non-null
     * @throws IDMException
     * @throws NoSuchTenantException        - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     */
    private void setPasswordPolicy(String tenantName, PasswordPolicy policy) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(policy, "Password policy");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            provider.setPasswordPolicy(policy);

            logger.info(String.format("Password policy successfully set for tenant [%s]", tenantName));
        } catch (ConstraintViolationLdapException e) {
            logger.warn(String.format("Invalid password policy for tenant [%s]", tenantName), e);
            throw new InvalidPasswordPolicyException(e.getMessage(), e);
        } catch (Exception ex) {
            logger.error(String.format("Failed to set password policy for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private LockoutPolicy getLockoutPolicy(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.getLockoutPolicy();
        } catch (Exception ex) {
            logger.error(String.format("Failed to get lockout policy for tenant [%s]", tenantName));

            throw ex;
        }
    }

    /**
     * Sets the lockout policy for the specified tenant.
     *
     * @param tenantName Name of tenant, required non-null, non-empty
     * @param policy     Lockout Policy for the tenant, required non-null
     * @throws IDMException
     * @throws NoSuchTenantException        - tenant does not exist
     * @throws InvalidArguementException  - invalid input
     */
    private void setLockoutPolicy(String tenantName, LockoutPolicy policy) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(policy, "Lockout policy");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            provider.setLockoutPolicy(policy);

            logger.info(String.format("Lockout policy successfully set for tenant [%s]", tenantName));
        } catch (Exception ex) {
            logger.error(String.format("Failed to set lockout policy for tenant [%s]", tenantName));

            throw ex;
        }
    }

    private Group findGroup(String tenantName, String group) throws Exception {
        Group foundGroup = null;
        PrincipalId normalizedPrincipal = null;
        IIdentityProvider provider = null;

        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name should not be null/empty.");
            ValidateUtil.validateNotEmpty(group, "Group should not be null/empty.");

            TenantInformation tenantInfo = findTenant(tenantName);

            if (tenantInfo == null) {
                throw new NoSuchTenantException(String.format("Tenant [%s] does not exist.", tenantName));
            }

            normalizedPrincipal = getUserPrincipal(tenantName, group);
            if (normalizedPrincipal == null) {
                throw new InvalidPrincipalException(String.format("Invalid group [%s].", group), group //used as invalid principal
                );
            }

            provider = tenantInfo.findProviderADAsFallBack(normalizedPrincipal.getDomain());

            if (provider == null) {
                throw new NoSuchIdpException(
                        String.format("Unknown domain [%s].", normalizedPrincipal.getDomain()));
            }

            foundGroup = provider.findGroup(normalizedPrincipal);
            // Provider fulfills the contract return null in case group does not exist
            if (foundGroup == null) {
                logger.info(String.format("Failed to find group [%s@%s] in tenant [%s]",
                        normalizedPrincipal.getName(), normalizedPrincipal.getDomain(), tenantName));

                throw new InvalidPrincipalException(
                        String.format("Group [%s] could not be found for tenant [%s]", group, tenantName),
                        ServerUtils.getUpn(normalizedPrincipal));
            }

            return foundGroup;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find group [%s] for tenant [%s]", group, tenantName));

            throw ex;
        }
    }

    private Principal findUser(String tenantName, String user) throws Exception {
        Principal foundUser = null;
        PrincipalId normalizedPrincipal = null;
        IIdentityProvider provider = null;

        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name should not be null/empty.");
            ValidateUtil.validateNotEmpty(user, "User should not be null/empty.");

            TenantInformation tenantInfo = findTenant(tenantName);

            if (tenantInfo == null) {
                throw new NoSuchTenantException(String.format("Tenant [%s] does not exist.", tenantName));
            }

            normalizedPrincipal = getUserPrincipal(tenantName, user);
            if (normalizedPrincipal == null) {
                throw new InvalidPrincipalException(String.format("Invalid user [%s].", user), user);
            }

            provider = tenantInfo.findProviderADAsFallBack(normalizedPrincipal.getDomain());

            if (provider == null) {
                throw new NoSuchIdpException(
                        String.format("Unknown domain [%s].", normalizedPrincipal.getDomain()));
            }

            foundUser = provider.findUser(normalizedPrincipal);

            if ((foundUser == null) && (provider instanceof ISystemDomainIdentityProvider)) {
                ISystemDomainIdentityProvider systemProvider = (ISystemDomainIdentityProvider) provider;
                if (ServerUtils.isEquals(tenantName, this.getSystemTenant())) {
                    foundUser = systemProvider.findServicePrincipal(normalizedPrincipal.getName());
                } else {
                    foundUser = systemProvider.findServicePrincipalInExternalTenant(normalizedPrincipal.getName());
                }
            }

            if (foundUser == null) {
                throw new InvalidPrincipalException(
                        String.format("User [%s] could not be found for tenant [%s]", user, tenantName), user);
            }

            return foundUser;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find user [%s] for tenant [%s]", user, tenantName));

            throw ex;
        }
    }

    private ProvidersInfo loadProvidersInfo(String tenantName) throws Exception {
        Collection<IIdentityProvider> providers = new ArrayList<IIdentityProvider>();
        Collection<IIdentityStoreData> stores = _configStore.getProviders(tenantName,
                EnumSet.of(DomainType.EXTERNAL_DOMAIN, DomainType.SYSTEM_DOMAIN, DomainType.LOCAL_OS_DOMAIN), true);
        Collection<String> defaultProviders = _configStore.getDefaultProviders(tenantName);
        IIdentityProvider adProvider = null;

        Collection<Certificate> trustedCertificates = getAllCertificates(tenantName,
                CertificateType.LDAP_TRUSTED_CERT);
        Set<X509Certificate> trustedCertificatesSet = new HashSet<X509Certificate>();
        if (trustedCertificates != null) {
            for (Certificate x509Certificate : trustedCertificates) {
                trustedCertificatesSet.add((X509Certificate) x509Certificate);
            }
        }

        if (stores != null && !stores.isEmpty()) {
            for (IIdentityStoreData store : stores) {
                IIdentityProvider provider = providerFactory.buildProvider(tenantName, store,
                        trustedCertificatesSet);

                if ((adProvider == null) && (store.getExtendedIdentityStoreData() != null)
                        && (store.getExtendedIdentityStoreData()
                                .getProviderType() == IdentityStoreType.IDENTITY_STORE_TYPE_ACTIVE_DIRECTORY)) {
                    adProvider = provider;
                }
                providers.add(provider);
            }
        }

        return new ProvidersInfo(providers, stores, adProvider, defaultProviders);
    }

    private AuthnPolicy getAuthnPolicy(String tenantName, int[] authnTypes) throws Exception {
        boolean password = false;
        boolean windows = false;
        boolean certificate = false;
        boolean rsaSecureID = false;

        if (authnTypes == null) {
            // default values if authnTypes attribute is not set to any value
            password = true;
            windows = true;
        } else {
            for (int authnType : authnTypes) {
                if (authnType == DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_PASSWORD)
                    password = true;
                else if (authnType == DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_WINDOWS)
                    windows = true;
                else if (authnType == DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_TLS_CERTIFICATE)
                    certificate = true;
                else if (authnType == DirectoryConfigStore.FLAG_AUTHN_TYPE_ALLOW_RSA_SECUREID)
                    rsaSecureID = true;
            }
        }

        ClientCertPolicy certPolicy = _configStore.getClientCertPolicy(tenantName);
        if (certPolicy == null) { // set default values
            certPolicy = new ClientCertPolicy();
        }

        RSAAgentConfig rsaConfig = _configStore.getRSAAgentConfig(tenantName);
        return new AuthnPolicy(password, windows, certificate, rsaSecureID, certPolicy, rsaConfig);
    }

    protected TenantInformation buildTenantInformation(String tenantName) throws Exception {
        TenantInformation tenantInfo = null;

        Tenant tenant = _configStore.getTenant(tenantName);

        if (tenant != null) {

            TenantAttributes attrs = _configStore.getTokenPolicyExt(tenantName);
            TokenPolicy tokenPolicyInfo = attrs.getTokenPolicy();
            int delegationCount = tokenPolicyInfo.getMaxTokenDelegationCount();
            int renewCount = tokenPolicyInfo.getMaxTokenRenewCount();
            long clockTolerance = tokenPolicyInfo.getMaxTokenClockTolerance();
            long maxHOKLifetime = tokenPolicyInfo.getMaxHOKLifetime();
            long maxBearerTokenLifetime = tokenPolicyInfo.getMaxBearerTokenLifetime();
            long maxBearerRefreshTokenLifetime = tokenPolicyInfo.getMaxBearerRefreshTokenLifetime();
            long maxHoKRefreshTokenLifetime = tokenPolicyInfo.getMaxHoKRefreshTokenLifetime();
            String signatureAlgorithm = attrs.getSignatureAlgorithm();
            String brandName = attrs.getBrandName();
            String logonBannerTitle = attrs.getLogonBannerTitle();
            String logonBannerContent = attrs.getLogonBannerContent();
            boolean logonBannerEnableCheckbox = attrs.getLogonBannerCheckboxFlag();
            Collection<Attribute> attrDefinitions = _configStore.getTenantAttributes(tenantName);
            String entityId = attrs.getEntityId();
            String alias = attrs.getAlias();
            int[] authnTypes = attrs.getAuthnTypes();
            AuthnPolicy authnPolicy = getAuthnPolicy(tenantName, authnTypes);
            boolean idpSelectionFlag = attrs.isIDPSelectionEnabled();

            List<Certificate> certificate = _configStore.getTenantCertificate(tenantName);
            Collection<List<Certificate>> certChains = _configStore.getTenantCertChains(tenantName);
            PrivateKey key = _configStore.getTenantPrivateKey(tenantName);

            ProvidersInfo providersInfo = loadProvidersInfo(tenantName);
            Collection<IDPConfig> idpConfigs = _configStore.getExternalIDPConfigs(tenantName);

            tenantInfo = new TenantInformation(tenant, providersInfo != null ? providersInfo._providers : null,
                    providersInfo != null ? providersInfo._idsStores : null,
                    providersInfo != null ? providersInfo._adProvider : null, delegationCount, renewCount,
                    clockTolerance, maxHOKLifetime, maxBearerTokenLifetime, maxBearerRefreshTokenLifetime,
                    maxHoKRefreshTokenLifetime, signatureAlgorithm, brandName, logonBannerTitle, logonBannerContent,
                    logonBannerEnableCheckbox, certificate, certChains, key, idpConfigs, attrDefinitions, entityId,
                    alias, providersInfo != null ? providersInfo._defaultProviders : null, authnPolicy,
                    idpSelectionFlag);
        }

        return tenantInfo;
    }

    protected Collection<IIdentityStoreData> getProviders(String tenantName, EnumSet<DomainType> domainTypes,
            boolean bGetInternalInfo) throws Exception {
        TenantInformation tenantInfo = findTenant(tenantName);
        ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

        Collection<IIdentityStoreData> allIdsStores = tenantInfo.getIdsStores();
        ArrayList<IIdentityStoreData> idsStores = new ArrayList<IIdentityStoreData>();

        for (IIdentityStoreData store : allIdsStores) {
            if (domainTypes.contains(store.getDomainType())) {
                if (store instanceof ServerIdentityStoreData && !bGetInternalInfo) {
                    idsStores.add(((ServerIdentityStoreData) store).getExternalIdentityStoreData());
                } else {
                    idsStores.add(store);
                }
            }
        }

        return idsStores;
    }

    protected ILdapConnectionEx getSystemDomainConnection() throws Exception {
        IdmServerConfig settings = IdmServerConfig.getInstance();

        Collection<URI> uris = settings.getSystemDomainConnectionInfo();

        return ServerUtils.getLdapConnectionByURIs(uris, settings.getSystemDomainUserName(),
                settings.getSystemDomainPassword(), settings.getSystemDomainAuthenticationType(), false);
    }

    private String registerServiceProviderAsTenant() throws Exception {
        IdmServerConfig settings = IdmServerConfig.getInstance();

        String spDomain = settings.getDirectoryConfigStoreDomain();

        Tenant spTenant = _configStore.getTenant(spDomain);

        if (spTenant == null) {
            spTenant = new Tenant(spDomain, spDomain);

            spTenant._issuerName = String.format("%s-STS", spDomain);

            boolean keepTrying = false;

            do {
                keepTrying = false;

                try {
                    // Create a tenant with credentials
                    String adminUsername = settings.getDirectoryConfigStoreUserName();
                    String adminPassword = settings.getDirectoryConfigStorePassword();
                    registerTenant(spTenant, adminUsername, adminPassword.toCharArray());
                    // Change default password to administrator's password

                    ServerIdentityStoreData systemStoreData = getSystemDomainIdentityStoreData(spDomain);

                    systemStoreData.setUserName(settings.getDirectoryConfigStoreUserName());
                    systemStoreData.setPassword(settings.getDirectoryConfigStorePassword());
                    systemStoreData.setAuthenticationType(settings.getDirectoryConfigStoreAuthType());

                    _configStore.setProvider(spDomain, systemStoreData);

                    _configStore.setSystemTenant(spDomain);

                    _tenantCache.setSystemTenant(spDomain);
                } catch (ServerDownLdapException ex) {
                    logger.error("LDAP Server down, unable to register service provider as tenant. Retrying.", ex);
                    keepTrying = true; // allow system domain back-end to start
                }

            } while (keepTrying);

            if (settings.getIsSingletenantConfig()) {
                setDefaultTenant(spDomain);
            }

            String[] defaultProviders = { getComputerName() };
            logger.info(String.format("Determined localos providers's default name as [%s].", defaultProviders[0]));

            ServerIdentityStoreData localOSIdp = new ServerIdentityStoreData(DomainType.LOCAL_OS_DOMAIN,
                    defaultProviders[0]);
            localOSIdp.setProviderType(IdentityStoreType.IDENTITY_STORE_TYPE_LOCAL_OS);
            // for now we don't want to set an alias for local OS provider
            // if ( defaultProviders[0].equalsIgnoreCase(LOCAL_OS_STATIC_ALIAS) == false )
            //{
            //    localOSIdp.setAlias(LOCAL_OS_STATIC_ALIAS);
            //}

            addProvider(spDomain, localOSIdp);

            setDefaultProviders(spDomain, Arrays.asList(defaultProviders));
        }

        return spTenant.getName();
    }

    private ServerIdentityStoreData getSystemDomainIdentityStoreData(String tenantName) throws Exception {
        Collection<IIdentityStoreData> systemStores = getProviders(tenantName, EnumSet.of(DomainType.SYSTEM_DOMAIN),
                true);
        if (systemStores.size() != 1) {
            throw new IllegalStateException("Error : Unexpected number of system stores found");
        }

        IIdentityStoreData cfgStore = systemStores.iterator().next();

        if (!(cfgStore instanceof ServerIdentityStoreData)) {
            throw new IllegalStateException("Error : Unexpected system store found");
        }

        ServerIdentityStoreData systemStoreData = (ServerIdentityStoreData) cfgStore;

        return systemStoreData;
    }

    private static String getComputerName() {
        String computerName = LOCAL_OS_STATIC_ALIAS; // this is "localos"
        if (SystemUtils.IS_OS_WINDOWS) {
            IIdmClientLibrary client = IdmClientLibraryFactory.getInstance().getLibrary();
            computerName = client.getComputerName();
        }
        return computerName;
    }

    private void initializeTenantCache() throws Exception {
        Collection<String> allTenantNames = this.getAllTenants();
        assert (allTenantNames != null && allTenantNames.size() > 0);

        for (String tenantName : allTenantNames) {
            TenantInformation tenantInfo = loadTenant(tenantName);
            assert (tenantInfo != null);
        }
    }

    private void refreshTenantCache() throws Exception {
        Collection<String> allTenantNames = this.getAllTenants();
        assert (allTenantNames != null && allTenantNames.size() > 0);

        for (String tenantName : allTenantNames) {
            try {
                refreshTenant(tenantName);
            } catch (Exception ex) {
                // continue to refresh other tenant (do not fail refresh)
                logger.error(String.format("Failed to refreshTenantCredentialCache for tenant %s", tenantName));
                continue;
            }
        }
    }

    /**
     * Check update for cached CRL of all tenants
     *
     * @throws Exception
     */
    private void refreshTenantCrlCache() throws Exception {

        Collection<String> allTenantNames = this.getAllTenants();
        assert (allTenantNames != null && allTenantNames.size() > 0);

        for (String tenantName : allTenantNames) {

            //First, download custom CRL if defined
            TenantInformation info = this.getTenantInfo(tenantName);

            AuthnPolicy authnPolicy = info.getAuthnPolicy();
            Validate.notNull(authnPolicy, "AuthnPolicy can not be null.");

            ClientCertPolicy certPolicy = authnPolicy.getClientCertPolicy();
            Validate.notNull(certPolicy, "CertPolicy can not be null.");

            URL crlUrl = certPolicy.getCRLUrl();

            if (crlUrl != null) {
                String crlUriString = crlUrl.toString();
                IdmCrlCache crlCache = TenantCrlCache.get().get(tenantName);

                if (crlCache == null) {
                    crlCache = TenantCrlCache.get().put(tenantName, new IdmCrlCache());
                }
                if (null == crlCache.get(crlUriString) && !crlUriString.isEmpty()) {
                    try {
                        X509CRL crl = IdmCrlCache.downloadCrl(crlUriString);
                        if (null != crl) {
                            crlCache.put(crlUriString, crl);
                        } else {
                            throw new Exception("No CRL was download at " + crlUriString);
                        }
                    } catch (Exception e) {
                        //don't throw because of communication problem. This allow refreshing at other URI's.
                        logger.error("Failed to download custom CRL at CRL refresh. " + e.getMessage());
                    }
                }
            }
            _tenantCrlCache.refreshCrl(tenantName);
        }
    }

    /**
     * Update rsa_api.properties, sdconf.rec and sdopts.rec if there is change in tenant configuration.
     *
     * @param tenantInfo
     * @throws Exception
     */
    private void updateRSAConfigFiles(TenantInformation tenantInfo) throws Exception {
        Validate.notNull(tenantInfo, "tenantInfo");

        AuthnPolicy authnPolicy = tenantInfo.getAuthnPolicy();
        if (authnPolicy == null || authnPolicy.get_rsaAgentConfig() == null) {
            return;
        }

        RSAAgentConfig rsaConfig = authnPolicy.get_rsaAgentConfig();

        try {
            if (rsaConfig == null) {
                return;
            }

            String tenantName = tenantInfo.getTenant().getName();
            Validate.notEmpty(tenantName, "tenantName");

            //detect changes
            RSAAgentConfig existingConfig = _tenantCache.findExtRsaConfig(tenantName);
            if (null != existingConfig && existingConfig.equals(rsaConfig)) {
                return;
            }

            //update disk files
            RsaAgentConfFilesUpdater updater = new RsaAgentConfFilesUpdater(this.getClusterId());
            updater.updateRSAConfigFiles(tenantInfo, rsaConfig);

            //update existing config cache.
            _tenantCache.deleteExtRsaConfig(tenantName);
            _tenantCache.addExtRsaConfig(tenantName, rsaConfig);

            //remove cached AuthSessionFactory and cached session for the tenant .
            _rsaSessionCache.removeSessionCache(tenantName);
            _rsaSessionFactoryCache.removeFactory(tenantName);
        } catch (Exception e) {
            logger.error("Failed updating RSA config files", e);
            throw e;
        }
    }

    /**
     * install or remove jsafe provider only if secure ID authentication is enabled for
     * at least one tenant.
     *
     * @param authnPolicy
     * @throws Exception
     */
    private synchronized void installOrRemoveRSAProvider() throws Exception {

        try {
            Collection<String> allTenantNames = this.getAllTenants();
            assert (allTenantNames != null && allTenantNames.size() > 0);

            boolean serverNeedSecurID = false;

            for (String tenantName : allTenantNames) {
                AuthnPolicy authnPolicy = this.findTenant(tenantName).getAuthnPolicy();

                if (authnPolicy == null) {
                    continue;
                }

                if (authnPolicy.IsRsaSecureIDAuthnEnabled()) {
                    serverNeedSecurID = true;
                    break;
                }
            }

        } catch (Exception e) {
            logger.error("Failed in trying to add or removing TLRSAJsafeProvider provider.");
            throw e;
        }
    }

    private void registerTenant(Tenant tenant, String adminAccountName, char[] adminPwd) throws Exception {
        _configStore.addTenant(tenant, adminAccountName, adminPwd);

        _configStore.setTenantAttributes(tenant.getName(), _defaultAttributes);

        _configStore.setEntityID(tenant.getName(), String.format("https://%s:%s/websso/SAML2/Metadata/%s",
                HOSTNAME_MACRO, PORT_MACRO, tenant.getName()));
    }

    private void unregisterTenant(String name) throws Exception {
        _configStore.deleteTenant(name);
    }

    private void createTenantDomain(String tenantName) throws Exception {
        Collection<IIdentityStoreData> stores = getProviders(tenantName, EnumSet.of(DomainType.SYSTEM_DOMAIN),
                true /* get internal info */);

        if (stores.size() != 1) {
            throw new IllegalStateException("Failed to find tenant's system domain");
        }

        IIdentityStoreDataEx details = stores.iterator().next().getExtendedIdentityStoreData();

        String upn = details.getUserName();
        if (upn == null || upn.isEmpty()) {
            throw new IllegalStateException("Invalid system domain administrator name");
        }

        PrincipalId id = ServerUtils.getPrincipalId(upn);
        Directory.createInstance(tenantName, id.getName(), details.getPassword());
    }

    private void deleteTenantDomain(String tenantName) throws Exception {
        ILdapConnectionEx connection = getSystemDomainConnection();

        try {
            IdmServerConfig settings = IdmServerConfig.getInstance();
            connection.deleteObjectTree(ServerUtils.getDomainDN(settings.getTenantsSystemDomainName(tenantName)));
        } finally {
            connection.close();
        }
    }

    private TenantInformation findTenant(String tenantName) throws Exception {
        TenantInformation tenantInfo = _tenantCache.findTenant(tenantName);
        if (tenantInfo == null) {
            tenantInfo = loadTenant(tenantName);
        }

        return tenantInfo;
    }

    private TenantInformation getTenantInfo(String tenantName) throws Exception {
        TenantInformation tenantInfo = null;
        try {
            tenantInfo = findTenant(tenantName);
        } catch (Exception e) {
            logger.error(String.format("Failed to get all external IDP config for tenant [%s]", tenantName));
            throw e;
        }
        return tenantInfo;
    }

    // refresh tenant currently only refreshes tenant certificates and token policy if tenant already exists
    private TenantInformation refreshTenant(String tenantName) throws Exception {
        TenantInformation tenantInfo = loadTenant(tenantName);

        if (tenantInfo == null) {

            _tenantCache.deleteTenant(tenantName);

            ssoHealthStatistics.removeTenantStats(tenantName);

        }
        return tenantInfo;
    }

    private TenantInformation loadTenant(String tenantName) throws Exception {
        TenantInformation tenantInfo = buildTenantInformation(tenantName);

        if (tenantInfo != null) {
            _tenantCache.addTenant(tenantInfo);
            updateRSAConfigFiles(tenantInfo);
            installOrRemoveRSAProvider();
        }

        return tenantInfo;
    }

    private PrincipalId getUserPrincipal(String tenantName, String userPrincipal) throws Exception {
        PrincipalId principal = null;

        String userName = userPrincipal;
        String domain = null;

        // if multiple '@','\' or leading '@','\' exception is thrown
        int idxSep = -1;
        try {
            idxSep = ValidateUtil.getValidIdxAccountNameSeparator(userPrincipal, VALID_ACCOUNT_NAME_SEPARATORS);
        } catch (Exception e) {
            throw new InvalidPrincipalException(String.format(
                    "Invalid user name format [%s]: multiple/leading UPN or NetBIOS separators are not allowed.",
                    userName), userPrincipal);
        }

        // Found separator
        if (idxSep != -1) {
            if (userPrincipal.charAt(idxSep) == UPN_SEPARATOR) {
                userName = userPrincipal.substring(0, idxSep);
                domain = userPrincipal.substring(idxSep + 1);
            } else if (userPrincipal.charAt(idxSep) == NETBIOS_SEPARATOR) {
                domain = userPrincipal.substring(0, idxSep);
                userName = userPrincipal.substring(idxSep + 1);
            }
        }

        if ((ServerUtils.isNullOrEmpty(userName) == false) && (ServerUtils.isNullOrEmpty(domain) == true)) {
            Collection<String> defaultProviders = getDefaultProviders(tenantName);
            if ((defaultProviders != null) && (defaultProviders.size() > 0)) {
                domain = defaultProviders.iterator().next();
            }
        }

        if ((ServerUtils.isNullOrEmpty(userName) == false) && (ServerUtils.isNullOrEmpty(domain) == false)) {
            principal = new PrincipalId(userName, domain);
        }

        return principal;
    }

    /**
     * check the optional data against the set of invalid chars and throw
     * exception if detected
     *
     * @param data
     *           could be null or empty, in which case will return normally.
     * @param invalidChars
     *           array of invalid chars
     * @throws InvalidPrincipalException if any of illegal chars are found
     */
    private void checkInvalidCharForUserData(String data, final char[] invalidChars)
            throws InvalidArgumentException {
        if (null == data)
            return;
        for (char invalidChar : invalidChars) {
            if (-1 != data.indexOf(invalidChar)) {
                throw new InvalidArgumentException(String.format(
                        "Invalid character [%c] detected! please check PrincipalManagement service API",
                        invalidChar));
            }
        }
    }

    private void ensureWellKnownConfigurationUsersGroupExist(String tenantName) throws Exception {

        ensureWellKnownGroupExist(tenantName, WELLKNOWN_CONFIGURATIONUSERS_GROUP_NAME,
                WELLKNOWN_CONFIGURATIONUSERS_GROUP_DESCRIPTION);
    }

    private void EnsureWellKnownSolutionUsersGroupExist(String tenantName) throws Exception {
        ensureWellKnownGroupExist(tenantName, WELLKNOWN_SOLUTIONUSERS_GROUP_NAME,
                WELLKNOWN_SOLUTIONUSERS_GROUP_DESCRIPTION);
    }

    private void ensureWellKnownExternalIDPUsersGroupExist(String tenantName) throws Exception {
        ensureWellKnownGroupExist(tenantName, WELLKNOWN_EXTERNALIDP_USERS_GROUP_NAME,
                WELLKNOWN_EXTERNALIDP_USERS_GROUP_DESCRIPTION);
    }

    private void ensureWellKnownGroupExist(String tenantName, String wellknownGroupName, String description)
            throws Exception {
        try {
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider systemProvider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(systemProvider, tenantName);

            Group solutionUsersGroup = systemProvider.findGroup(new PrincipalId(wellknownGroupName, tenantName));
            if (solutionUsersGroup == null) {
                PrincipalId group = systemProvider.addGroup(wellknownGroupName, new GroupDetail(description));
                if (group == null) {
                    throw new InvalidPrincipalException(
                            String.format("Failed to create solutinUsers group %s for tenant %s",
                                    wellknownGroupName, tenantName),
                            new PrincipalId(wellknownGroupName, tenantName).getUPN());
                }
            }
        } catch (Exception ex) {
            logger.error(String.format("Failed to create solutinUsers group %s for tenant %s", wellknownGroupName,
                    tenantName));

            throw ex;
        }
    }

    /**
     * get external IDP user's object id.
     *
     * we use Upn at the moment.
     *
     * EG: external PersonUser
     * with PrincipalId [_name: joe, _domain: <guid>.vsphere.local] ==>
     * joe@<guid>.vsphere.local
     *
     * @param id PrincipalId of the external user
     * @return external IDP user's object id
        
        
     */
    private static String getExternalIdpUserObjectId(PrincipalId id)

    {
        return id.getUPN();
    }

    // TODO: we should look into whether this logic of building fspid can be eliminated or hidden within system provider
    private static PrincipalId getFspIdForSystemDomain(ISystemDomainIdentityProvider systemProvider,
            Principal fspPrincipal) {
        return getFspIdForSystemDomain(systemProvider, fspPrincipal.getObjectId(), fspPrincipal.getId());
    }

    // TODO: we should look into whether this logic of building fspid can be eliminated or hidden within system provider
    private static PrincipalId getFspIdForSystemDomain(ISystemDomainIdentityProvider systemProvider,
            String objectId, PrincipalId principalId) {
        return new PrincipalId(systemProvider.getObjectIdName(objectId), principalId.getDomain());
    }

    private IIdentityStoreData getADIdsToStore(IIdentityStoreData store) {
        IIdentityStoreDataEx extData = store.getExtendedIdentityStoreData();
        if (store == null || extData == null) {
            return null;
        }

        // native provider stores using domain forest information as provider name
        // in case forestDcInfo is null, use the generic name
        // to support a replicated multi-sites scenario
        // domainName is used internal and before server returns to client
        ActiveDirectoryJoinInfo joinInfo = IdmDomainState.getInstance().getDomainJoinInfo();
        DomainControllerInfo forestDcInfo = IdmDomainState.getInstance().getForestDcInfo();
        if (joinInfo == null) {
            throw new IllegalStateException(
                    "Trying to store native AD information, however machine is not properly joined.");
        }

        ArrayList<String> connStrs = new ArrayList<String>();
        connStrs.add(
                String.format("ldap://%s", forestDcInfo != null ? forestDcInfo.domainName : joinInfo.getName()));
        return IdentityStoreData.CreateExternalIdentityStoreData(
                forestDcInfo != null ? forestDcInfo.domainName : joinInfo.getName(),
                forestDcInfo != null ? forestDcInfo.domainNetBiosName : joinInfo.getAlias(),
                extData.getProviderType(), extData.getAuthenticationType(),
                forestDcInfo != null ? forestDcInfo.domainNetBiosName : joinInfo.getAlias(),
                extData.getSearchTimeoutSeconds(), extData.getUserName(), extData.useMachineAccount(),
                extData.getServicePrincipalName(), extData.getPassword(), null, //userBaseDN
                null, //groupBaseDN
                connStrs, /* connection strings */
                extData.getAttributeMap(), extData.getIdentityStoreSchemaMapping(), extData.getUpnSuffixes(),
                extData.getFlags(), extData.getAuthnTypes());
    }

    private void setExternalIdpForTenant(String tenantName, IDPConfig idpConfig) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "[tenantname]");
            ValidateUtil.validateNotNull(idpConfig, "[idpConfig]");

            // validate IDP config has all the required meta data before registering..
            ValidateUtil.validateNotEmpty(idpConfig.getSigningCertificateChain(),
                    "[idpConfig.signingCertificates]");

            // validate groups exist in system domain before setting claim group mappings
            Map<TokenClaimAttribute, List<String>> claimGroupMappings = idpConfig.getTokenClaimGroupMappings();
            if (claimGroupMappings != null && !claimGroupMappings.isEmpty()) {
                for (Entry<TokenClaimAttribute, List<String>> map : claimGroupMappings.entrySet()) {
                    if (map.getValue() == null || map.getValue().isEmpty()) {
                        continue;
                    }
                    for (String groupSid : map.getValue()) {
                        try {
                            findGroupByObjectId(tenantName, groupSid);
                        } catch (Exception e) {
                            throw new IDMException(String.format(
                                    "Failed to set claim group mapping. " + "Group %s is not found in tenant %s.",
                                    groupSid, tenantName), e);
                        }
                    }
                }
            }

            _configStore.registerExternalIdpConfig(tenantName, idpConfig);

            // delete cached data so that it will be reloaded next time
            _tenantCache.deleteTenant(tenantName);
        } catch (Exception e) {
            logger.error(String.format("Failed to set external IDP for tenant [%s]", tenantName));

            throw e;
        }
    }

    private void removeExternalIdpForTenant(String tenantName, String configEntityId, boolean removeJitUsers)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(configEntityId, "configEntityId");

            _configStore.removeExternalIdpConfig(tenantName, configEntityId);

            //delete JIT users
            if (removeJitUsers) {
                TenantInformation tenantInfo = findTenant(tenantName);
                ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

                ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
                ServerUtils.validateNotNullSystemIdp(provider, tenantName);
                provider.deleteJitUsers(configEntityId);
            }

            _tenantCache.deleteTenant(tenantName);
        } catch (Exception e) {
            logger.error(String.format("Failed to delete external IDP for tenant [%s]", tenantName));
            throw e;
        }
    }

    private Collection<IDPConfig> getAllExternalIdpsForTenant(String tenantName) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");

            TenantInformation tenantInfo = getTenantInfo(tenantName);
            if (tenantInfo == null) {
                throw new NoSuchTenantException(String.format("Tenant [%s] not found", tenantName));
            } else {
                return tenantInfo.getExternalIdpConfigs();
            }
        } catch (Exception e) {
            logger.error(String.format("Failed to get all external IDP for tenant [%s]", tenantName));
            throw e;
        }
    }

    private IDPConfig getExternalIdpForTenant(String tenantName, String configEntityId) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");

            TenantInformation tenantInfo = getTenantInfo(tenantName);
            if (tenantInfo == null) {
                throw new NoSuchTenantException(String.format("Tenant [%s] not found", tenantName));
            } else {
                for (IDPConfig config : tenantInfo.getExternalIdpConfigs()) {
                    if (config.getEntityID().equals(configEntityId))
                        return config;
                }
                return null; // not found
            }
        } catch (Exception e) {
            logger.error(
                    String.format("Failed to get external IDP [%s] for tenant [%s]", configEntityId, tenantName));
            throw e;
        }
    }

    private Collection<IDPConfig> getExternalIdpForTenantByUrl(String tenantName, String urlStr) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "urlStr");
            ValidateUtil.validateNotEmpty(urlStr, "urlStr");
            Collection<IDPConfig> result = new ArrayList<IDPConfig>();

            for (IDPConfig config : getAllExternalIdpsForTenant(tenantName)) {
                if (config.isMatchingUrl(urlStr)) {
                    result.add(config);
                }
            }
            return result;
        } catch (Exception e) {
            logger.error(String.format("Failed to get external IDP by URL for tenant [%s]", tenantName));
            throw e;
        }
    }

    private boolean registerThirdPartyIDPUser(String tenantName, PrincipalId userId) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(userId, "userId");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider systemProvider = tenantInfo.findSystemProvider();
            assert (systemProvider != null);

            //construct the fspUser's DN and use it to register with system provider
            PrincipalId fspId = getFspIdForSystemDomain(systemProvider, getExternalIdpUserObjectId(userId), userId);
            return systemProvider.registerExternalIDPUser(fspId.getUPN());
        } catch (Exception ex) {
            logger.error(String.format("Failed to register third party IDP user [%s] in tenant [%s]", userId,
                    tenantName));
            throw ex;
        }
    }

    private boolean removeThirdPartyIDPUser(String tenantName, PrincipalId userId) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotNull(userId, "userId");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider systemProvider = tenantInfo.findSystemProvider();
            assert (systemProvider != null);

            //construct the fsp objectId used to register with system provider
            PrincipalId fspId = getFspIdForSystemDomain(systemProvider, getExternalIdpUserObjectId(userId), userId);
            systemProvider.removeExternalIDPUser(fspId.getUPN());
            return true;
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to remove third party IDP user [%s] in tenant [%s]", userId, tenantName));
            throw ex;
        }
    }

    private PersonUser findRegisteredExternalIDPUser(String tenantName, PrincipalId userId) throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotNull(userId, "User principal Id");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();

            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            PrincipalId pid = provider.findExternalIDPUserRegistration(userId);

            if (pid != null) {
                return new PersonUser(pid, null/*alias*/, getExternalIdpUserObjectId(pid)/*objectId*/,
                        EXTERNAL_USER_SAMPLE_PERSON_DETAIL, false, false);
            }
            return null;
        } catch (Exception ex) {
            logger.error(String.format("Failed to find registered external IDP user [%s@%s] in tenant [%s]",
                    userId != null ? userId.getName() : "null", userId != null ? userId.getDomain() : "null",
                    tenantName));

            throw ex;
        }
    }

    private String getExternalIDPRegistrationGroupName() {
        return WELLKNOWN_EXTERNALIDP_USERS_GROUP_NAME;
    }

    private PrincipalId findActiveUserInSystemDomain(String tenantName, String attributeName, String attributeValue)
            throws Exception {
        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNotEmpty(attributeName, "attributeName");
            ValidateUtil.validateNotEmpty(attributeValue, "attributeValue");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();

            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            PrincipalId pid = provider.findActiveUser(attributeName, attributeValue);

            return pid;
        } catch (Exception ex) {
            logger.error(String.format(
                    "Failed to find active user by attribute=[%s], attribute_value=[%s], in tenant [%s]",
                    (attributeName != null ? attributeName : "(null)"),
                    (attributeValue != null ? attributeValue : "(null)"),
                    (tenantName != null ? tenantName : "(null)")));

            throw ex;
        }
    }

    private boolean registerUpnSuffix(String tenantName, String domainName, String upnSuffix) throws Exception {

        boolean updateStarted = false;
        boolean added = false;
        try {

            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(domainName, "domainName");
            ValidateUtil.validateNotEmpty(upnSuffix, "upnSuffix");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            for (IIdentityStoreData store : tenantInfo.getIdsStores()) {
                if (store.getName().equalsIgnoreCase(domainName)) {
                    IdentityStoreType type = store.getExtendedIdentityStoreData().getProviderType();
                    if (type.equals(IdentityStoreType.IDENTITY_STORE_TYPE_LDAP)
                            || type.equals(IdentityStoreType.IDENTITY_STORE_TYPE_LDAP_WITH_AD_MAPPING)
                            || type.equals(IdentityStoreType.IDENTITY_STORE_TYPE_ACTIVE_DIRECTORY)) {
                        return false; // for OpenLdap/AdOverLdap/NativeAD providers, registerUpn suffix is not allowed.
                    } else {
                        break;
                    }
                }
            }

            updateStarted = true;
            added = _configStore.registerUpnSuffixForDomain(tenantName, domainName, upnSuffix);

        } catch (Exception ex) {

            logger.error(String.format("Failed to register UPN suffix [%s] in domain [%s] for tenant [%s]",
                    upnSuffix, domainName, tenantName));
            throw ex;
        } finally {
            if (updateStarted) {
                _tenantCache.deleteTenant(tenantName);
            }
        }
        return added;
    }

    private boolean unregisterUpnSuffix(String tenantName, String domainName, String upnSuffix) throws Exception {

        boolean updateStarted = false;
        boolean removed = false;
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(domainName, "domainName");
            ValidateUtil.validateNotEmpty(upnSuffix, "upnSuffix");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            updateStarted = true;
            removed = _configStore.unregisterUpnSuffixForDomain(tenantName, domainName, upnSuffix);

        } catch (Exception ex) {
            logger.error(String.format("Failed to unregister UPN suffix [%s] in domain [%s] for tenant [%s]",
                    upnSuffix, domainName, tenantName));
            throw ex;
        } finally {
            if (updateStarted) {
                _tenantCache.deleteTenant(tenantName);
            }
        }
        return removed;
    }

    private Set<String> getUpnSuffixes(String tenantName, String domainName) throws Exception {

        Set<String> result = null;
        try {
            ValidateUtil.validateNotEmpty(tenantName, "tenantName");
            ValidateUtil.validateNotEmpty(domainName, "domainName");

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            Collection<IIdentityProvider> providers = tenantInfo.getProviders();

            boolean providerFound = false;
            for (IIdentityProvider provider : providers) {
                if (0 != provider.getDomain().compareToIgnoreCase(domainName)) {
                    continue;
                }

                providerFound = true;
                if (provider instanceof BaseLdapProvider) {
                    Set<String> data = provider.getRegisteredUpnSuffixes();
                    result = (data != null ? Collections.unmodifiableSet(data) : null);
                } else if (provider instanceof LocalOsIdentityProvider) {
                    throw new IllegalStateException("UPN suffixes for LocalOS provider is not supported");
                } else {
                    throw new IllegalStateException(
                            String.format("Unsupport IdentityProvider type: [%s]", provider.getClass().getName()));
                }
                break;
            }
            if (!providerFound) {
                String msg = String.format("Provider [%s] is not found in tenant [%s]", domainName, tenantName);
                throw new NoSuchIdpException(msg);
            }
        } catch (Exception e) {
            logger.error(String.format("Failed to get UPN suffixes in domain [%s] for tenant [%s]", domainName,
                    tenantName));
            throw e;
        }
        return result;
    }

    private String getExternalIDPAlias(String tenantName, String entityId) throws IDMException, Exception {
        ValidateUtil.validateNotEmpty(tenantName, "tenantName");
        ValidateUtil.validateNotEmpty(entityId, "entityId");

        try {
            IDPConfig idpConfig = this.getExternalIdpForTenant(tenantName, entityId);
            return idpConfig.getAlias();
        } catch (Exception e) {
            logger.error(
                    String.format("Failed to get alias for entityID [%s] for tenant [%s]", entityId, tenantName),
                    e);
            throw e;
        }
    }

    private void setExternalIDPAlias(String tenantName, String entityId, String alias)
            throws IDMException, Exception {
        ValidateUtil.validateNotEmpty(tenantName, "tenantName");
        ValidateUtil.validateNotEmpty(entityId, "entityId");

        try {
            IDPConfig idpConfig = this.getExternalIdpForTenant(tenantName, entityId);
            idpConfig.setAlias(alias);
            this.setExternalIdpForTenant(tenantName, idpConfig);
        } catch (Exception ex) {
            logger.error(
                    String.format("Failed to set alias for entityId [%s] for tenant [%s]", entityId, tenantName));
            throw ex;
        }
    }

    private ActiveDirectoryJoinInfo getActiveDirectoryJoinStatus() throws IDMException, Exception {
        try {
            ActiveDirectoryJoinInfo adJoinInfo = IdmDomainState.getInstance().getDomainJoinInfo();
            if (adJoinInfo == null) {
                adJoinInfo = new ActiveDirectoryJoinInfo("WORKGROUP", "WORKGROUP", null);
            }

            return adJoinInfo;
        } catch (Exception e) {
            logger.error("Failed to get active directory join status");
            throw e;
        }
    }

    private Collection<DomainTrustsInfo> getDomainTrustInfo() throws IDMException, Exception {
        try {
            ActiveDirectoryJoinInfo adJoinInfo = IdmDomainState.getInstance().getDomainJoinInfo();
            if (adJoinInfo != null && adJoinInfo
                    .getJoinStatus() == ActiveDirectoryJoinInfo.JoinStatus.ACTIVE_DIRECTORY_JOIN_STATUS_DOMAIN) {
                DomainTrustInfo[] domainTrustsInfo = IdmDomainState.getInstance().getDomainTrustInfo();
                //clone the DomainTrustInfo to another object, as it is not exposed to the client.
                Collection<DomainTrustsInfo> trustedDomains = new ArrayList<DomainTrustsInfo>();
                if (domainTrustsInfo != null && domainTrustsInfo.length > 0) {
                    for (DomainTrustInfo trust : domainTrustsInfo) {
                        if (trust != null && trust.dcInfo != null) {
                            DomainTrustsInfo domain = new DomainTrustsInfo.DomainTrustInfoBuilder(trust.IsInforest,
                                    trust.IsOutBound, trust.IsInBound).isExternal(trust.isExternal)
                                            .IsNativeMode(trust.IsNativeMode).IsPrimary(trust.IsPrimary)
                                            .IsRoot(trust.IsRoot)
                                            .dcInfo(trust.dcInfo.domainName, trust.dcInfo.domainNetBiosName,
                                                    trust.dcInfo.domainIpAddress, trust.dcInfo.domainFQDN,
                                                    trust.dcInfo.domainDnsForestName)
                                            .build();
                            trustedDomains.add(domain);
                        }
                    }
                }
                return trustedDomains;
            } else {
                return null;
            }
        } catch (Exception e) {
            logger.error("Failed to get domain trust info");
            throw e;
        }
    }

    private String getClusterId() throws Exception {
        try {
            String tenantName = getSystemTenant();

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.getSiteId();
        } catch (Exception e) {
            logger.error("Failed to get the cluster identifier");
            throw e;
        }
    }

    private String getDeploymentId() throws Exception {
        try {
            String tenantName = getSystemTenant();

            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();
            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            return provider.getDomainId();
        } catch (Exception e) {
            logger.error("Failed to get the deployment identifier");
            throw e;
        }
    }

    private String getSsoMachineHostName() throws Exception {
        try {
            return CommonUtil.getHostIPAddress(IdmUtils.getIdentityServicesConfigDir());
        } catch (Exception ex) {
            logger.error("Failed to get getSsoMachineHostName", ex);
            throw ex;
        }
    }

    private static IDiagnosticsContextScope getDiagnosticsContext(Tenant tenant, IIdmServiceContext serviceContext,
            String operationName) {
        return getDiagnosticsContext((tenant != null) ? tenant.getName() : "(NULL)", serviceContext, operationName);
    }

    private static IDiagnosticsContextScope getDiagnosticsContext(String tenantName,
            IIdmServiceContext serviceContext, String operationName) {
        String correlationId = null;
        if (serviceContext != null) {
            correlationId = serviceContext.getCorrelationId();
        }
        if (ServerUtils.isNullOrEmpty(correlationId)) {
            correlationId = UUID.randomUUID().toString();
        }
        return getDiagnosticsContext(tenantName, correlationId, operationName);
    }

    private static IDiagnosticsContextScope getDiagnosticsContext(String tenantName, String correlationId,
            String operationName) {
        // for now we don't use operationName, but can add support in the future
        return DiagnosticsContextFactory.createContext(correlationId, tenantName);
    }

    // ---------------------------------------------
    // IIdentityManager interface implementation
    // ---------------------------------------------

    /**
     * {@inheritDoc}
     */
    @Override
    public void addTenant(Tenant tenant, String adminAccountName, char[] adminPwd,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenant, serviceContext, "addTenant")) {
            try {
                this.addTenant(tenant, adminAccountName, adminPwd);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteTenant(String name, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(name, serviceContext, "deleteTenant")) {
            try {
                this.deleteTenant(name);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Tenant getTenant(String name, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(name, serviceContext, "getTenant")) {
            try {
                return this.getTenant(name);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getDefaultTenant(IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext, "getDefaultTenant")) {
            try {
                return this.getDefaultTenant();
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getSystemTenant(IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext, "getSystemTenant")) {
            try {
                return this.getSystemTenant();
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<String> getAllTenants(IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext, "getAllTenants")) {
            try {
                return this.getAllTenants();
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setDefaultTenant(String name, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(name, serviceContext, "setDefaultTenant")) {
            try {
                this.setDefaultTenant(name);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setTenant(Tenant tenant, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenant, serviceContext, "setTenant")) {
            try {
                this.setTenant(tenant);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getTenantSignatureAlgorithm(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getTenantSignatureAlgorithm")) {
            try {
                return this.getTenantSignatureAlgorithm(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setTenantSignatureAlgorithm(String tenantName, String signatureAlgorithm,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setTenantSignatureAlgorithm")) {
            try {
                this.setTenantSignatureAlgorithm(tenantName, signatureAlgorithm);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Certificate> getTenantCertificate(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getTenantCertificate")) {
            try {
                return this.getTenantCertificate(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<List<Certificate>> getTenantCertificates(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getTenantCertificates")) {
            try {
                return this.getTenantCertificates(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setTenantTrustedCertificateChain(String tenantName, Collection<Certificate> tenantCertificates,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setTenantTrustedCertificateChain")) {
            try {
                this.setTenantTrustedCertificateChain(tenantName, tenantCertificates);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setTenantCredentials(String tenantName, Collection<Certificate> tenantCertificate,
            PrivateKey tenantPrivateKey, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setTenantCredentials")) {
            try {
                this.setTenantCredentials(tenantName, tenantCertificate, tenantPrivateKey);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrivateKey getTenantPrivateKey(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getTenantPrivateKey")) {
            try {
                return this.getTenantPrivateKey(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public boolean isTenantIDPSelectionEnabled(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getTenantIDPSelectionFlag")) {
            try {
                return this.isTenantIDPSelectionEnabled(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public void setTenantIDPSelectionEnabled(String tenantName, boolean enableIDPSelection,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setTenantIDPSelectionFlag")) {
            try {
                this.setTenantIDPSelectionEnabled(tenantName, enableIDPSelection);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getClockTolerance(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getClockTolerance")) {
            try {
                return this.getClockTolerance(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setClockTolerance(String tenantName, long milliseconds, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setClockTolerance")) {
            try {
                this.setClockTolerance(tenantName, milliseconds);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getDelegationCount(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getDelegationCount")) {
            try {
                return this.getDelegationCount(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setDelegationCount(String tenantName, int delegationCount, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setDelegationCount")) {
            try {
                this.setDelegationCount(tenantName, delegationCount);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getRenewCount(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getRenewCount")) {
            try {
                return this.getRenewCount(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setRenewCount(String tenantName, int renewCount, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "setRenewCount")) {
            try {
                this.setRenewCount(tenantName, renewCount);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public SsoHealthStatsData getSsoStatistics(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {

        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getSsoStatistics")) {
            try {
                return this.getSsoStatistics(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * It calls SSOHealthStatistcs service to increment generated tokens count
     * for a given tenant.
     */
    @Override
    public void incrementGeneratedTokens(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {

        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "incrementGeneratedTokens")) {
            try {
                this.incrementGeneratedTokens(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * It calls SSOHealthStatistcs service to increment renewed tokens count for
     * a given tenant.
     */
    @Override
    public void incrementRenewedTokens(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "incrementRenewedTokens")) {
            try {
                this.incrementRenewedTokens(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getMaximumBearerTokenLifetime(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getMaximumBearerTokenLifetime")) {
            try {
                return this.getMaximumBearerTokenLifetime(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setMaximumBearerTokenLifetime(String tenantName, long maxLifetime,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setMaximumBearerTokenLifetime")) {
            try {
                this.setMaximumBearerTokenLifetime(tenantName, maxLifetime);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getMaximumHoKTokenLifetime(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getMaximumHoKTokenLifetime")) {
            try {
                return this.getMaximumHoKTokenLifetime(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setMaximumHoKTokenLifetime(String tenantName, long maxLifetime, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setMaximumHoKTokenLifetime")) {
            try {
                this.setMaximumHoKTokenLifetime(tenantName, maxLifetime);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getMaximumBearerRefreshTokenLifetime(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getMaximumBearerRefreshTokenLifetime")) {
            try {
                return this.getMaximumBearerRefreshTokenLifetime(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setMaximumBearerRefreshTokenLifetime(String tenantName, long maxLifetime,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setMaximumBearerRefreshTokenLifetime")) {
            try {
                this.setMaximumBearerRefreshTokenLifetime(tenantName, maxLifetime);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getMaximumHoKRefreshTokenLifetime(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getMaximumHoKRefreshTokenLifetime")) {
            try {
                return this.getMaximumHoKRefreshTokenLifetime(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setMaximumHoKRefreshTokenLifetime(String tenantName, long maxLifetime,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setMaximumHoKRefreshTokenLifetime")) {
            try {
                this.setMaximumHoKRefreshTokenLifetime(tenantName, maxLifetime);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setEntityID(String tenantName, String entityID, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "setEntityID")) {
            try {
                this.setEntityID(tenantName, entityID);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getEntityID(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getEntityID")) {
            try {
                return this.getEntityID(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public String getLocalIDPAlias(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getLocalIDPAlias")) {
            try {
                return this.getLocalIDPAlias(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public void setLocalIDPAlias(String tenantName, String alias, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setIdentityProviderAlias")) {
            try {
                this.setLocalIDPAlias(tenantName, alias);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public String getExternalIDPAlias(String tenantName, String entityId, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getLocalIDPAlias")) {
            try {
                return this.getExternalIDPAlias(tenantName, entityId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public void setExternalIDPAlias(String tenantName, String entityId, String alias,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setIdentityProviderAlias")) {
            try {
                this.setExternalIDPAlias(tenantName, entityId, alias);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getOIDCEntityID(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getOIDCEntityID")) {
            try {
                return this.getOIDCEntityID(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PasswordExpiration getPasswordExpirationConfiguration(String tenantName,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getPasswordExpirationConfiguration")) {
            try {
                return this.getPasswordExpirationConfiguration(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void updatePasswordExpirationConfiguration(String tenantName, PasswordExpiration config,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "updatePasswordExpirationConfiguration")) {
            try {
                this.updatePasswordExpirationConfiguration(tenantName, config);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addCertificate(String tenantName, Certificate idmCert, CertificateType certificateType,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addCertificate")) {
            try {
                this.addCertificate(tenantName, idmCert, certificateType);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<Certificate> getAllCertificates(String tenantName, CertificateType certificateType,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getAllCertificates")) {
            try {
                return this.getAllCertificates(tenantName, certificateType);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteCertificate(String tenantName, String fingerprint, CertificateType certificateType,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "deleteCertificate")) {
            try {
                this.deleteCertificate(tenantName, fingerprint, certificateType);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addRelyingParty(String tenantName, RelyingParty rp, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addRelyingParty")) {
            try {
                this.addRelyingParty(tenantName, rp);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteRelyingParty(String tenantName, String rpName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "deleteRelyingParty")) {
            try {
                this.deleteRelyingParty(tenantName, rpName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public RelyingParty getRelyingParty(String tenantName, String rpName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getRelyingParty")) {
            try {
                return this.getRelyingParty(tenantName, rpName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public RelyingParty getRelyingPartyByUrl(String tenantName, String url, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getRelyingPartyByUrl")) {
            try {
                return this.getRelyingPartyByUrl(tenantName, url);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setRelyingParty(String tenantName, RelyingParty rp, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "setRelyingParty")) {
            try {
                this.setRelyingParty(tenantName, rp);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<RelyingParty> getRelyingParties(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getRelyingParties")) {
            try {
                return this.getRelyingParties(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addOIDCClient(String tenantName, OIDCClient oidcClient, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addOIDCClient")) {
            try {
                this.addOIDCClient(tenantName, oidcClient);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteOIDCClient(String tenantName, String clientID, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "deleteOIDCClient")) {
            try {
                this.deleteOIDCClient(tenantName, clientID);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public OIDCClient getOIDCClient(String tenantName, String clientID, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getOIDCClient")) {
            try {
                return this.getOIDCClient(tenantName, clientID);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setOIDCClient(String tenantName, OIDCClient oidcClient, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "setOIDCClient")) {
            try {
                this.setOIDCClient(tenantName, oidcClient);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<OIDCClient> getOIDCClients(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getOIDCClients")) {
            try {
                return this.getOIDCClients(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public void addResourceServer(String tenantName, ResourceServer resourceServer,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "addResourceServer")) {
            try {
                this.addResourceServer(tenantName, resourceServer);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public void deleteResourceServer(String tenantName, String resourceServerName,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "deleteResourceServer")) {
            try {
                this.deleteResourceServer(tenantName, resourceServerName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public ResourceServer getResourceServer(String tenantName, String resourceServerName,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getResourceServer")) {
            try {
                return this.getResourceServer(tenantName, resourceServerName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public void setResourceServer(String tenantName, ResourceServer resourceServer,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setResourceServer")) {
            try {
                this.setResourceServer(tenantName, resourceServer);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public Collection<ResourceServer> getResourceServers(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getResourceServers")) {
            try {
                return this.getResourceServers(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addProvider(String tenantName, IIdentityStoreData idpData, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addProvider")) {
            try {
                this.addProvider(tenantName, idpData);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteProvider(String tenantName, String providerName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "deleteProvider")) {
            try {
                this.deleteProvider(tenantName, providerName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IIdentityStoreData getProvider(String tenantName, String ProviderName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getProvider")) {
            try {
                return this.getProvider(tenantName, ProviderName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IIdentityStoreData getProviderWithInternalInfo(String tenantName, String ProviderName,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getProvider")) {
            try {
                return this.getProviderWithInternalInfo(tenantName, ProviderName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setProvider(String tenantName, IIdentityStoreData idpData, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "setProvider")) {
            try {
                this.setProvider(tenantName, idpData);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setNativeADProvider(String tenantName, IIdentityStoreData idpData,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "setProvider")) {
            try {
                this.setNativeADProvider(tenantName, idpData);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<IIdentityStoreData> getProviders(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getProviders")) {
            try {
                return this.getProviders(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<SecurityDomain> getSecurityDomains(String tenantName, String providerName,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getSecurityDomains")) {
            try {
                return this.getSecurityDomains(tenantName, providerName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<IIdentityStoreData> getProviders(String tenantName, EnumSet<DomainType> domainTypes,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getProviders")) {
            try {
                return this.getProviders(tenantName, domainTypes);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public void probeProviderConnectivity(String tenantName, IIdentityStoreData idsData,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "probeProviderConnectivity")) {
            probeProviderConnectivity(tenantName, idsData);
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void probeProviderConnectivity(String tenantName, String providerUri, AuthenticationType authType,
            String userName, String pwd, Collection<X509Certificate> certificates,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "probeProviderConnectivity")) {
            try {
                LdapCertificateValidationSettings certValidationSettings = new LdapCertificateValidationSettings(
                        certificates);
                this.probeProviderConnectivity(tenantName, providerUri, authType, userName, pwd,
                        certValidationSettings);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void probeProviderConnectivityWithCertValidation(String tenantName, String providerUri,
            AuthenticationType authType, String userName, String pwd, Collection<X509Certificate> certificates,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "probeProviderConnectivity")) {
            try {
                LdapCertificateValidationSettings certValidationSettings = new LdapCertificateValidationSettings(
                        certificates, null, true);
                this.probeProviderConnectivity(tenantName, providerUri, authType, userName, pwd,
                        certValidationSettings);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<String> getDefaultProviders(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getDefaultProviders")) {
            try {
                return this.getDefaultProviders(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setDefaultProviders(String tenantName, Collection<String> defaultProviders,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setDefaultProviders")) {
            try {
                this.setDefaultProviders(tenantName, defaultProviders);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId authenticate(String tenantName, String principal, String password,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "authenticate")) {
            try {
                return this.authenticate(tenantName, principal, password);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public GSSResult authenticate(String tenantName, String contextId, byte[] gssTicket,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "authenticate")) {
            try {
                return this.authenticate(tenantName, contextId, gssTicket);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId authenticate(String tenantName, X509Certificate[] tlsCertChain,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "authenticate")) {
            try {
                return this.authenticate(tenantName, tlsCertChain);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * authenticate with secure ID
     *
     * @param tenantName
     * @param principal
     *            userID (if userID is UPN) or userID@domainName (if userID is not UPN)
     * @param passcode
     * @return
     * @throws RemoteException
     */

    @Override
    public RSAAMResult authenticateRsaSecurId(String tenantName, String sessionId, String principal,
            String passcode, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "authenticate")) {
            IIdmAuthStatRecorder idmAuthStatRecorder = this.createIdmAuthStatRecorderInstance(tenantName,
                    IdentityManager.PROVIDER_TYPE_RSA_SECURID, IdentityManager.PROVIDER_TYPE_RSA_SECURID, 0,
                    ActivityKind.AUTHENTICATE, EventLevel.INFO, principal);
            idmAuthStatRecorder.start();

            try {

                RSAAMResult result = this.authenticateRsaSecurId(tenantName, sessionId, principal, passcode);
                idmAuthStatRecorder.end();
                return result;
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    protected IIdmAuthStatRecorder createIdmAuthStatRecorderInstance(String tenantName, String providerName,
            String providerType, int providerFlag, ActivityKind opType, EventLevel eventLevel, String id) {
        if (PerformanceMonitorFactory.getPerformanceMonitor().getCache(tenantName).isEnabled()) {
            return new IdmAuthStatRecorder(tenantName, providerName, providerType, providerFlag, opType, eventLevel,
                    id != null ? id : "", PerformanceMonitorFactory.getPerformanceMonitor().summarizeLdapQueries(),
                    DiagnosticsContextFactory.getCurrentDiagnosticsContext().getCorrelationId());
        } else {
            return NoopIdmAuthStatRecorder.getInstance();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean IsActive(String tenantName, PrincipalId principal, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "IsActive")) {
            try {
                return this.IsActive(tenantName, principal);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<Attribute> getAttributeDefinitions(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getAttributeDefinitions")) {
            try {
                return this.getAttributeDefinitions(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<AttributeValuePair> getAttributeValues(String tenantName, PrincipalId principal,
            Collection<Attribute> attributes, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getAttributeValues")) {
            try {
                return this.getAttributeValues(tenantName, principal, attributes);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public byte[] getUserHashedPassword(String tenantName, PrincipalId principal, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getUserHashedPassword")) {
            try {
                return this.getUserHashedPassword(tenantName, principal);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId addSolutionUser(String tenantName, String userName, SolutionDetail detail,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addSolutionUser")) {
            try {
                return this.addSolutionUser(tenantName, userName, detail);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SolutionUser findSolutionUser(String tenantName, String userName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findSolutionUser")) {
            try {
                return this.findSolutionUser(tenantName, userName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SolutionUser findSolutionUserByCertDn(String tenantName, String subjectDN,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findSolutionUserByCertDn")) {
            try {
                return this.findSolutionUserByCertDn(tenantName, subjectDN);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PersonUser findPersonUser(String tenantName, PrincipalId id, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "findPersonUser")) {
            try {
                return this.findPersonUser(tenantName, id);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PersonUser findPersonUserByObjectId(String tenantName, String userObjectId,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findPersonUserByObjectId")) {
            try {
                return this.findPersonUserByObjectId(tenantName, userObjectId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Group findGroupByObjectId(String tenantName, String groupObjectId, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findGroupByObjectId")) {
            try {
                return this.findGroupByObjectId(tenantName, groupObjectId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<PersonUser> findPersonUsers(String tenantName, SearchCriteria criteria, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "findPersonUsers")) {
            try {
                return this.findPersonUsers(tenantName, criteria, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<PersonUser> findPersonUsersByName(String tenantName, SearchCriteria criteria, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findPersonUsersByName")) {
            try {
                return this.findPersonUsersByName(tenantName, criteria, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<SolutionUser> findSolutionUsers(String tenantName, String searchString, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findSolutionUsers")) {
            try {
                return this.findSolutionUsers(tenantName, searchString, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<Group> findGroups(String tenantName, SearchCriteria criteria, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "findGroups")) {
            try {
                return this.findGroups(tenantName, criteria, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<Group> findGroupsByName(String tenantName, SearchCriteria criteria, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findGroupsByName")) {
            try {
                return this.findGroupsByName(tenantName, criteria, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<PersonUser> findPersonUsersInGroup(String tenantName, PrincipalId groupId, String searchString,
            int limit, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findPersonUsersInGroup")) {
            try {
                return this.findPersonUsersInGroup(tenantName, groupId, searchString, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<PersonUser> findPersonUsersByNameInGroup(String tenantName, PrincipalId groupId, String searchString,
            int limit, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findPersonUsersByNameInGroup")) {
            try {
                return this.findPersonUsersByNameInGroup(tenantName, groupId, searchString, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<SolutionUser> findSolutionUsersInGroup(String tenantName, String groupName, String searchString,
            int limit, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findSolutionUsersInGroup")) {
            try {
                return this.findSolutionUsersInGroup(tenantName, groupName, searchString, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<Group> findGroupsInGroup(String tenantName, PrincipalId groupId, String searchString, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findGroupsInGroup")) {
            try {
                return this.findGroupsInGroup(tenantName, groupId, searchString, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<Group> findGroupsByNameInGroup(String tenantName, PrincipalId groupId, String searchString,
            int limit, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findGroupsByNameInGroup")) {
            try {
                return this.findGroupsByNameInGroup(tenantName, groupId, searchString, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isMemberOfSystemGroup(String tenantName, PrincipalId principalId, String groupName,
            IIdmServiceContext serviceContext) throws NoSuchTenantException, NoSuchIdpException,
            InvalidPrincipalException, RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "isMemberOfSystemGroup")) {
            try {
                return this.isMemberOfSystemGroup(tenantName, principalId, groupName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<Group> findDirectParentGroups(String tenantName, PrincipalId principalId,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findDirectParentGroups")) {
            try {
                return this.findDirectParentGroups(tenantName, principalId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<Group> findNestedParentGroups(String tenantName, PrincipalId userId,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findNestedParentGroups")) {
            try {
                return this.findNestedParentGroups(tenantName, userId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<PersonUser> findLockedUsers(String tenantName, String searchString, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "findLockedUsers")) {
            try {
                return this.findLockedUsers(tenantName, searchString, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<PersonUser> findDisabledPersonUsers(String tenantName, String searchString, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findDisabledPersonUsers")) {
            try {
                return this.findDisabledPersonUsers(tenantName, searchString, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId findActiveUserInSystemDomain(String tenantName, String attributeName, String attributeValue,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findActiveUserInSystemDomain")) {
            try {
                return this.findActiveUserInSystemDomain(tenantName, attributeName, attributeValue);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<SolutionUser> findDisabledSolutionUsers(String tenantName, String searchString,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findDisabledSolutionUsers")) {
            try {
                return this.findDisabledSolutionUsers(tenantName, searchString);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SearchResult find(String tenantName, SearchCriteria criteria, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "find")) {
            try {
                return this.find(tenantName, criteria, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SearchResult findByName(String tenantName, SearchCriteria criteria, int limit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "findByName")) {
            try {
                return this.findByName(tenantName, criteria, limit);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId addUser(String tenantName, String userName, PersonDetail detail, char[] password,
            IIdmServiceContext serviceContext) throws Exception {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addUser")) {
            try {
                return this.addUser(tenantName, userName, detail, null, null, password);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId addUser(String tenantName, String userName, PersonDetail detail, byte[] hashedPassword,
            String hashingAlgorithm, IIdmServiceContext serviceContext) throws Exception {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addUser")) {
            try {
                return this.addUser(tenantName, userName, detail, hashedPassword, hashingAlgorithm);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId addJitUser(String tenantName, String userName, PersonDetail detail, String extIdpEntityId,
            String extUserId, IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addUser")) {
            try {
                return this.addUser(tenantName, userName, detail, extIdpEntityId, extUserId, null);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId addGroup(String tenantName, String groupName, GroupDetail groupDetail,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addGroup")) {
            try {
                return this.addGroup(tenantName, groupName, groupDetail);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addUserToGroup(String tenantName, PrincipalId userId, String groupName,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addUserToGroup")) {
            try {
                return this.addUserToGroup(tenantName, userId, groupName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean removeFromLocalGroup(String tenantName, PrincipalId principalId, String groupName,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "removeFromLocalGroup")) {
            try {
                return this.removeFromLocalGroup(tenantName, principalId, groupName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addGroupToGroup(String tenantName, PrincipalId groupId, String groupName,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "addGroupToGroup")) {
            try {
                return this.addGroupToGroup(tenantName, groupId, groupName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deletePrincipal(String tenantName, String principalName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "deletePrincipal")) {
            try {
                this.deletePrincipal(tenantName, principalName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean enableUserAccount(String tenantName, PrincipalId userId, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "enableUserAccount")) {
            try {
                return this.enableUserAccount(tenantName, userId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean unlockUserAccount(String tenantName, PrincipalId userId, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "unlockUserAccount")) {
            try {
                return this.unlockUserAccount(tenantName, userId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean disableUserAccount(String tenantName, PrincipalId userId, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "disableUserAccount")) {
            try {
                return this.disableUserAccount(tenantName, userId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId updatePersonUserDetail(String tenantName, String userName, PersonDetail detail,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "updatePersonUserDetail")) {
            try {
                return this.updatePersonUserDetail(tenantName, userName, detail);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId updateGroupDetail(String tenantName, String groupName, GroupDetail detail,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "updateGroupDetail")) {
            try {
                return this.updateGroupDetail(tenantName, groupName, detail);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrincipalId updateSolutionUserDetail(String tenantName, String userName, SolutionDetail detail,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "updateSolutionUserDetail")) {
            try {
                return this.updateSolutionUserDetail(tenantName, userName, detail);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setUserPassword(String tenantName, String userName, char[] newPassword,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "setUserPassword")) {
            try {
                this.setUserPassword(tenantName, userName, newPassword);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void changeUserPassword(String tenantName, String userName, char[] currentPassword, char[] newPassword,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "changeUserPassword")) {
            try {
                this.changeUserPassword(tenantName, userName, currentPassword, newPassword);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void updateSystemDomainStorePassword(String tenantName, char[] newPassword,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
        ValidateUtil.validateNotNull(newPassword, "New password");

        ILdapConnectionEx connection = null;
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "updateSystemDomainStorePassword")) {
            ServerIdentityStoreData systemStoreData = this.getSystemDomainIdentityStoreData(tenantName);

            // validate the new password
            Collection<String> connectionStrings = systemStoreData.getConnectionStrings();
            connection = ServerUtils.getLdapConnectionByURIs(ServerUtils.toURIObjects(connectionStrings),
                    systemStoreData.getUserName(), new String(newPassword), systemStoreData.getAuthenticationType(),
                    false);

            // update password after validation
            systemStoreData.setPassword(new String(newPassword));
            _configStore.setProvider(tenantName, systemStoreData);
            _tenantCache.deleteTenant(tenantName);
        } catch (Exception ex) {
            logger.error("Encountered an error while updating system domain " + "identity store password.", ex);
            throw ServerUtils.getRemoteException(ex);
        } finally {
            if (connection != null)
                connection.close();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PasswordPolicy getPasswordPolicy(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getPasswordPolicy")) {
            try {
                return this.getPasswordPolicy(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setPasswordPolicy(String tenantName, PasswordPolicy policy, IIdmServiceContext serviceContext)
            throws Exception {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setPasswordPolicy")) {
            try {
                this.setPasswordPolicy(tenantName, policy);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public LockoutPolicy getLockoutPolicy(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getLockoutPolicy")) {
            try {
                return this.getLockoutPolicy(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setLockoutPolicy(String tenantName, LockoutPolicy policy, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setLockoutPolicy")) {
            try {
                this.setLockoutPolicy(tenantName, policy);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Group findGroup(String tenantName, PrincipalId groupId, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "findGroup")) {
            try {
                return this.findGroup(tenantName, groupId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Group findGroup(String tenantName, String group, IIdmServiceContext serviceContext)
            throws RemoteException, NoSuchTenantException, NoSuchIdpException, InvalidPrincipalException,
            IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "findGroup")) {
            try {
                return this.findGroup(tenantName, group);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Principal findUser(String tenantName, String user, IIdmServiceContext serviceContext)
            throws RemoteException, NoSuchTenantException, NoSuchIdpException, InvalidPrincipalException,
            IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "findUser")) {
            try {
                return this.findUser(tenantName, user);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setExternalIdpForTenant(String tenantName, IDPConfig idpConfig, IIdmServiceContext serviceContext)
            throws RemoteException, NoSuchTenantException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setExternalIdpForTenant")) {
            try {
                this.setExternalIdpForTenant(tenantName, idpConfig);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeExternalIdpForTenant(String tenantName, String configEntityId,
            IIdmServiceContext serviceContext)
            throws RemoteException, NoSuchTenantException, NoSuchExternalIdpConfigException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "removeExternalIdpForTenant")) {
            try {
                this.removeExternalIdpForTenant(tenantName, configEntityId, false);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeExternalIdpForTenant(String tenantName, String configEntityId, boolean removeJitUsers,
            IIdmServiceContext serviceContext)
            throws RemoteException, NoSuchTenantException, NoSuchExternalIdpConfigException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "removeExternalIdpForTenant")) {
            try {
                this.removeExternalIdpForTenant(tenantName, configEntityId, removeJitUsers);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<IDPConfig> getAllExternalIdpsForTenant(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, NoSuchTenantException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getAllExternalIdpsForTenant")) {
            try {
                return this.getAllExternalIdpsForTenant(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IDPConfig getExternalIdpForTenant(String tenantName, String configEntityId,
            IIdmServiceContext serviceContext) throws RemoteException, NoSuchTenantException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getExternalIdpForTenant")) {
            try {
                return this.getExternalIdpForTenant(tenantName, configEntityId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<IDPConfig> getExternalIdpForTenantByUrl(String tenantName, String urlStr,
            IIdmServiceContext serviceContext) throws RemoteException, NoSuchTenantException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getExternalIdpForTenantByUrl")) {
            try {
                return this.getExternalIdpForTenantByUrl(tenantName, urlStr);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean registerThirdPartyIDPUser(String tenantName, PrincipalId userId,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException, NoSuchTenantException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "registerThirdPartyIDPUser")) {
            try {
                return this.registerThirdPartyIDPUser(tenantName, userId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean removeThirdPartyIDPUser(String tenantName, PrincipalId userId, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException, NoSuchTenantException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "removeThirdPartyIDPUser")) {
            try {
                return this.removeThirdPartyIDPUser(tenantName, userId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PersonUser findRegisteredExternalIDPUser(String tenantName, PrincipalId userId,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException, NoSuchTenantException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "findRegisteredExternalIDPUser")) {
            try {
                return this.findRegisteredExternalIDPUser(tenantName, userId);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getExternalIDPRegistrationGroupName(IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext,
                "getExternalIDPRegistrationGroupName")) {
            try {
                return this.getExternalIDPRegistrationGroupName();
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean registerUpnSuffix(String tenantName, String domainName, String upnSuffix,
            IIdmServiceContext serviceContext)
            throws RemoteException, IDMException, NoSuchTenantException, NoSuchIdpException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "registerUpnSuffix")) {
            try {
                return this.registerUpnSuffix(tenantName, domainName, upnSuffix);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean unregisterUpnSuffix(String tenantName, String domainName, String upnSuffix,
            IIdmServiceContext serviceContext)
            throws RemoteException, IDMException, NoSuchTenantException, NoSuchIdpException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "unregisterUpnSuffix")) {
            try {
                return this.unregisterUpnSuffix(tenantName, domainName, upnSuffix);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<String> getUpnSuffixes(String tenantName, String domainName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException, NoSuchTenantException, NoSuchIdpException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getUpnSuffixes")) {
            try {
                return this.getUpnSuffixes(tenantName, domainName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getBrandName(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getBrandName")) {
            try {
                return this.getBrandName(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setBrandName(String tenantName, String brandName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "setBrandName")) {
            try {
                this.setBrandName(tenantName, brandName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getLogonBannerContent(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getLogonBannerContent")) {
            try {
                return this.getLogonBannerContent(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setLogonBannerContent(String tenantName, String logonBannerContent,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setLogonBannerContent")) {
            try {
                this.setLogonBannerContent(tenantName, logonBannerContent);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getLogonBannerTitle(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getLogonBannerTitle")) {
            try {
                return this.getLogonBannerTitle(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public boolean getLogonBannerCheckboxFlag(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getLogonBannerCheckboxFlag")) {
            try {
                return this.getLogonBannerCheckboxFlag(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setLogonBannerTitle(String tenantName, String logonBannerTitle, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setLogonBannerTitle")) {
            try {
                this.setLogonBannerTitle(tenantName, logonBannerTitle);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public void setLogonBannerCheckboxFlag(String tenantName, boolean enableLogonBannerCheckbox,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setLogonBannerCheckbox")) {
            try {
                this.setLogonBannerCheckbox(tenantName, enableLogonBannerCheckbox);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<Certificate> getTrustedCertificates(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getTrustedCertificates")) {
            try {
                return this.getTrustedCertificates(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<Certificate> getStsIssuersCertificates(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getStsIssuersCertificates")) {
            try {
                return this.getStsIssuersCertificates(tenantName);
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /*
     * {@inheritDoc}
     */
    @Override
    public ActiveDirectoryJoinInfo getActiveDirectoryJoinStatus(IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext,
                "getActiveDirectoryJoinStatus")) {
            try {
                return this.getActiveDirectoryJoinStatus();
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /*
     * {@inheritDoc}
     */
    @Override
    public Collection<DomainTrustsInfo> getDomainTrustInfo(IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext, "getDomainTrustInfo")) {
            try {
                return this.getDomainTrustInfo();
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /*
     * {@inheritDoc}
     */
    @Override
    public String getClusterId(IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext, "getClusterId")) {
            try {
                return this.getClusterId();
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /*
     * {@inheritDoc}
     */
    @Override
    public String getDeploymentId(IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext, "getDeploymentId")) {
            try {
                return this.getDeploymentId();
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    /*
     * {@inheritDoc}
     */
    @Override
    public String getSsoMachineHostName(IIdmServiceContext serviceContext) throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext, "getSsoMachineHostName")) {
            try {
                return this.getSsoMachineHostName();
            } catch (Exception ex) {
                throw ServerUtils.getRemoteException(ex);
            }
        }
    }

    @Override
    public Collection<VmHostData> getComputers(String tenantName, boolean getDCOnly,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException {

        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getComputers")) {
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);

            ISystemDomainIdentityProvider provider = tenantInfo.findSystemProvider();

            ServerUtils.validateNotNullSystemIdp(provider, tenantName);

            Collection<VmHostData> hosts = provider.getComputers(getDCOnly);

            return hosts;
        } catch (Exception ex) {
            logger.error(String.format("Failed to get joined systems from tenant [%s]", tenantName), ex);
            throw ServerUtils.getRemoteException(ex);
        }
    }

    @Override
    public void joinActiveDirectory(String user, String password, String domain, String orgUnit,
            IIdmServiceContext serviceContext) throws RemoteException, IDMException, IdmADDomainException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext, "joinActiveDirectory")) {
            VmafClientUtil.joinActiveDirectory(user, password, domain, orgUnit);
        } catch (VmAfAccessDeniedException e) {
            logger.error("VmafAccessDeniedException occurred", e);
            throw new IdmADDomainException(String.format("user [%s] cannot access domain [%s]", user, domain),
                    e.getErrorCode());
        } catch (VmAfUnknownServerException | VmAfNoSuchDomainException e) {
            logger.error(e.getClass().getSimpleName() + " occurred", e);
            throw new IdmADDomainException(String.format("cannot contact domain [%s]", domain), e.getErrorCode());
        } catch (VmAfAlreadyJoinedException e) {
            logger.error("VmafAlreadyJoinedException occurred", e);
            throw new IdmADDomainException("SSO server is already joined to AD domain", e.getErrorCode());
        } catch (VmAfInvalidComputerNameException e) {
            logger.error("VmAfInvalidComputerNameException occurred", e);
            throw new IdmADDomainException(
                    String.format("The format of the specified computer name is invalid [%s]", domain),
                    e.getErrorCode());
        } catch (VmAfBadPacketException e) {
            logger.error("VmAfBadPacketException occurred", e);
            throw new IdmADDomainException(String.format(
                    "A bad packet was received from a DNS server. Potentially the requested address [%s] does not exist.",
                    domain), e.getErrorCode());
        } catch (VmAfInvalidParameterException e) {
            logger.error("VmAfInvalidParameterException occurred", e);
            throw new IdmADDomainException("Invalid parameter passed", e.getErrorCode());
        } catch (VmAfNoSuchLogonSessionException e) {
            logger.error("VmAfNoSuchLogonSessionException occurred", e);
            throw new IdmADDomainException(String
                    .format("The specified logon session does not exist: domain [%s], username [%s]", domain, user),
                    e.getErrorCode());
        } catch (VmAfWrongPasswordException e) {
            logger.error("VmAfWrongPasswordException occurred", e);
            throw new IdmADDomainException("The value provided as the current password is incorrect",
                    e.getErrorCode());
        } catch (VmAfNotSupportedException e) {
            logger.error("VmAfNotSupportedException occurred", e);
            throw new IdmADDomainException("The request is not supported", e.getErrorCode());
        } catch (VmAfLdapNoSuchObjectException e) {
            logger.error("VmAfLdapNoSuchObjectException occurred", e);
            throw new IdmADDomainException(
                    String.format("The specified entry does not exist in the directory, domain [%s], orgUnit [%s]",
                            domain, orgUnit),
                    e.getErrorCode());
        } catch (VmAfClientNativeException e) {
            logger.error("VmAfClientNativeException occurred", e);
            throw new IDMException(
                    String.format("Error trying to join AD, error code [%s], user [%s], domain [%s], orgUnit [%s]",
                            e.getErrorCode(), user, domain, orgUnit));
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    @Override
    public void leaveActiveDirectory(String user, String password, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException, ADIDSAlreadyExistException, IdmADDomainException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext("", serviceContext, "leaveActiveDirectory")) {
            String adIDP = findIdpTypeRegistered(getSystemTenant(),
                    IdentityStoreType.IDENTITY_STORE_TYPE_ACTIVE_DIRECTORY);
            if (adIDP != null) {
                throw new ADIDSAlreadyExistException(adIDP);
            }
            VmafClientUtil.leaveActiveDirectory(user, password);
        } catch (VmAfAccessDeniedException e) {
            logger.error("VmafAccessDeniedException occurred", e);
            throw new IdmADDomainException(String.format("user [%s] cannot access domain [%s]", user, null),
                    e.getErrorCode());
        } catch (VmAfUnknownServerException | VmAfNoSuchDomainException e) {
            logger.error(e.getClass().getSimpleName() + " occurred", e);
            throw new IdmADDomainException(String.format("cannot contact domain [%s]", null), e.getErrorCode());
        } catch (VmAfAlreadyJoinedException e) {
            logger.error("VmafAlreadyJoinedException occurred", e);
            throw new IdmADDomainException("SSO server is already joined to AD domain", e.getErrorCode());
        } catch (VmAfInvalidComputerNameException e) {
            logger.error("VmAfInvalidComputerNameException occurred", e);
            throw new IdmADDomainException(
                    String.format("The format of the specified computer name is invalid [%s]", null),
                    e.getErrorCode());
        } catch (VmAfBadPacketException e) {
            logger.error("VmAfBadPacketException occurred", e);
            throw new IdmADDomainException(String.format(
                    "A bad packet was received from a DNS server. Potentially the requested address [%s] does not exist.",
                    null), e.getErrorCode());
        } catch (VmAfInvalidParameterException e) {
            logger.error("VmAfInvalidParameterException occurred", e);
            throw new IdmADDomainException("Invalid parameter passed", e.getErrorCode());
        } catch (VmAfNoSuchLogonSessionException e) {
            logger.error("VmAfNoSuchLogonSessionException occurred", e);
            throw new IdmADDomainException(String
                    .format("The specified logon session does not exist: domain [%s], username [%s]", null, user),
                    e.getErrorCode());
        } catch (VmAfWrongPasswordException e) {
            logger.error("VmAfWrongPasswordException occurred", e);
            throw new IdmADDomainException("The value provided as the current password is incorrect",
                    e.getErrorCode());
        } catch (VmAfNotSupportedException e) {
            logger.error("VmAfNotSupportedException occurred", e);
            throw new IdmADDomainException("The request is not supported", e.getErrorCode());
        } catch (VmAfLdapNoSuchObjectException e) {
            logger.error("VmAfLdapNoSuchObjectException occurred", e);
            throw new IdmADDomainException("The specified entry does not exist in the directory", e.getErrorCode());
        } catch (VmAfClientNativeException e) {
            logger.error("VmAfClientNativeException occurred", e);
            throw new IDMException(
                    String.format("Error trying to leave AD, error code [%s], user [%s]", e.getErrorCode(), user));
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    @Override
    public List<IIdmAuthStat> getIdmAuthStats(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getIdmAuthStats")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            return PerformanceMonitor.getInstance().getCache(tenantName).getIdmAuthStats();
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    @Override
    public IIdmAuthStatus getIdmAuthStatus(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "getIdmAuthStatus")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            IdmAuthStatCache cache = PerformanceMonitor.getInstance().getCache(tenantName);
            return new IdmAuthStatus(cache.isEnabled(), cache.getDepth());
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    @Override
    public void clearIdmAuthStats(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "clearIdmAuthStats")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            PerformanceMonitor.getInstance().getCache(tenantName).clear();
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    @Override
    public void setIdmAuthStatsSize(String tenantName, int size, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "setIdmAuthStatsSize")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            ValidateUtil.validateNonNegativeNumber(size, "size");
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            PerformanceMonitor.getInstance().getCache(tenantName).setDepth(size);
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    @Override
    public void enableIdmAuthStats(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "enableIdmAuthStats")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            PerformanceMonitor.getInstance().getCache(tenantName).enable();
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    @Override
    public void disableIdmAuthStats(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "disableIdmAuthStats")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            PerformanceMonitor.getInstance().getCache(tenantName).disable();
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    /**
     * Retrieves SPN in the form of HTTP/computername.domainname in case is
     * AD-joined, or the HTTP/computer name if not joined.
     *
     * Use case: WEBSSO server pass this information to logon page script for
     * windows session authentication.
     *
     * @throws Exception
     *
     *@return
     *
     * null : If the machine is not joined AD or left AD domain <br/>
     * HTTP/computername.domainname : If machine is domain joined.
     */
    @Override
    public String getServerSPN() throws Exception {

        IIdmClientLibrary idmAdapter = IdmClientLibraryFactory.getInstance().getLibrary();

        String spn = null;
        try {
            ActiveDirectoryJoinInfo joinInfo = getActiveDirectoryJoinStatus();
            ActiveDirectoryJoinInfo.JoinStatus joinStatus = joinInfo.getJoinStatus();
            if (logger.isDebugEnabled()) {
                logger.debug("AD-joined status is",
                        (joinStatus == ActiveDirectoryJoinInfo.JoinStatus.ACTIVE_DIRECTORY_JOIN_STATUS_DOMAIN)
                                ? "joined"
                                : "not joined");
            }
            String domainName = (joinInfo
                    .getJoinStatus() == ActiveDirectoryJoinInfo.JoinStatus.ACTIVE_DIRECTORY_JOIN_STATUS_DOMAIN)
                            ? joinInfo.getName()
                            : null;

            if (domainName != null && !domainName.isEmpty()) {
                if (SystemUtils.IS_OS_LINUX) {
                    final String FQDN_CONFIG_KEY = "Services\\lsass\\Parameters\\Providers\\ActiveDirectory\\DomainJoin\\"
                            + domainName.toUpperCase() + "\\Pstore";

                    IRegistryAdapter registryAdapter = RegistryAdapterFactory.getInstance().getRegistryAdapter();
                    IRegistryKey rootRegistryKey = registryAdapter.openRootKey((int) RegKeyAccess.KEY_READ);
                    String fqdn = null;

                    try {
                        fqdn = registryAdapter.getStringValue(rootRegistryKey, FQDN_CONFIG_KEY, "Fqdn", true);
                    } finally {
                        rootRegistryKey.close();
                    }

                    if (fqdn != null && !fqdn.isEmpty()) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Fqdn read from likewise regisry is: " + fqdn);
                        }

                        spn = "HTTP/" + fqdn;
                    }
                } else {
                    //Windows
                    String computerName = idmAdapter.getComputerName();
                    Validate.notEmpty(computerName);
                    spn = "HTTP/" + computerName;
                    spn += "." + domainName;
                }
            }
            return spn;
        } catch (Exception e) {
            logger.error("Failed to get service principal name");
            throw new IdmADDomainJoinStatusException("Failed to get service principal name in creating SPN", e);
        }

    }

    @Override
    public AuthnPolicy getAuthNPolicy(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getAuthPolicy")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            return tenantInfo.getAuthnPolicy();
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    @Override
    public void setAuthNPolicy(String tenantName, AuthnPolicy policy, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "getAuthPolicy")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            TenantInformation tenantInfo = findTenant(tenantName);
            ServerUtils.validateNotNullTenant(tenantInfo, tenantName);
            if (isAllAuthnTypesDisabled(policy)) {
                throw new IllegalArgumentException("Disabling all authentication types is not allowed.");
            }
            _configStore.setAuthnTypes(tenantName, policy.IsPasswordAuthEnabled(), policy.IsWindowsAuthEnabled(),
                    policy.IsTLSClientCertAuthnEnabled(), policy.IsRsaSecureIDAuthnEnabled());
            _configStore.setClientCertPolicy(tenantName, policy.getClientCertPolicy());

            RSAAgentConfig rsaConfig = policy.get_rsaAgentConfig();
            if (policy.IsRsaSecureIDAuthnEnabled()) {

                checkPasscodeAuthProviderConfigured();

                if (rsaConfig == null) {
                    //create a default tenant-wide settings
                    rsaConfig = new RSAAgentConfig();
                }
            }
            _configStore.setRsaAgentConfig(tenantName, rsaConfig);
            _tenantCache.deleteTenant(tenantName);

            ManageCrlCacheChecker();
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }
    }

    /**
     * Turn on/off crl checker thread based on current setting of authentication policy of all tenants.
     * Making sure the thread is on only if at least one tenant uses smartcard authentication.
     *
     * @throws Exception
     */
    private void ManageCrlCacheChecker() throws Exception {
        if (smartCardAuthnEnabled()) {
            //start the thread
            if (null == this._crlCacheChecker) {
                this._crlCacheChecker = new IdmCrlCachePeriodicChecker();
                this._crlCacheChecker.start();
                logger.info("Started CrlCacheChecker thread");
            }
        } else {
            //stop the thread
            if (null != this._crlCacheChecker) {
                this._crlCacheChecker = null;
                logger.info("Stopped CrlCacheChecker thread");
            }
        }
    }

    private boolean isAllAuthnTypesDisabled(AuthnPolicy policy) {
        return (!policy.IsPasswordAuthEnabled() && !policy.IsWindowsAuthEnabled()
                && !policy.IsTLSClientCertAuthnEnabled() && !policy.IsRsaSecureIDAuthnEnabled());
    }

    @Override
    public void setRSAConfig(String tenantName, RSAAgentConfig rsaAgentConfig, IIdmServiceContext serviceContext)
            throws Exception {

        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext, "setRSAConfig")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            Validate.notNull(rsaAgentConfig, "rsaAgentConfig");

            checkPasscodeAuthProviderConfigured();

            _configStore.setRsaAgentConfig(tenantName, rsaAgentConfig);
            _tenantCache.deleteTenant(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to set RSA agent configin tenant [%s]", tenantName));

            throw ServerUtils.getRemoteException(ex);
        }

    }

    @Override
    public RSAAgentConfig getRSAConfig(String tenantName, IIdmServiceContext serviceContext)
            throws RemoteException, IDMException {

        try {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");

            AuthnPolicy authnPolicy = getAuthNPolicy(tenantName, serviceContext);

            RSAAgentConfig rsaConfig = authnPolicy.get_rsaAgentConfig();

            // TODO for testing only . remove this and enable above two lines as well as in getAutynPolicy

            return rsaConfig;
        } catch (Exception ex) {
            throw ServerUtils.getRemoteException(ex);
        }

    }

    @Override
    public void addRSAInstanceInfo(String tenantName, RSAAMInstanceInfo instInfo, IIdmServiceContext serviceContext)
            throws Exception {

        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "addRSAInstanceInfo")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            Validate.notNull(instInfo, "instInfo");

            checkPasscodeAuthProviderConfigured();

            _configStore.addRSAInstanceInfo(tenantName, instInfo);
            _tenantCache.deleteTenant(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to set RSA agent configin tenant [%s]", tenantName));

            throw ServerUtils.getRemoteException(ex);
        }

    }

    private void checkPasscodeAuthProviderConfigured() {
        List<SessionFactoryProvider> providers = PasscodeAuthenticationServiceProvider.getInstance()
                .getProviders(PASSCODE_PROVIDER_TYPE);
        if (providers.size() == 0)
            throw new IllegalStateException("Passcode authentication provider implementation not found");
    }

    @Override
    public void deleteRSAInstanceInfo(String tenantName, String siteID, IIdmServiceContext serviceContext)
            throws Exception {
        try (IDiagnosticsContextScope ctxt = getDiagnosticsContext(tenantName, serviceContext,
                "deleteRSAInstanceInfo")) {
            ValidateUtil.validateNotEmpty(tenantName, "Tenant name");
            Validate.notNull(siteID, "siteID");

            _configStore.deleteRSAInstanceInfo(tenantName, siteID);
            _tenantCache.deleteTenant(tenantName);
        } catch (Exception ex) {
            logger.error(String.format("Failed to delete RSAAM instance config for tenant [%s]", tenantName));

            throw ServerUtils.getRemoteException(ex);
        }

    }
}