IdRepoUtils.java revision a82d04bc19fee9d5e34b41ccd7641da1f62ab634
/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2009 Sun Microsystems, Inc. All Rights Reserved.
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://opensso.dev.java.net/public/CDDLv1.0.html or
* opensso/legal/CDDLv1.0.txt
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: IdRepoUtils.java,v 1.3 2010/01/06 22:31:55 veiming Exp $
*
* Portions Copyrighted 2011-2015 ForgeRock AS.
*/
package com.sun.identity.idm.common;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import com.iplanet.am.sdk.AMHashMap;
import com.iplanet.services.naming.ServerEntryNotFoundException;
import com.iplanet.services.naming.WebtopNaming;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.common.CaseInsensitiveHashMap;
import com.sun.identity.idm.IdConstants;
import com.sun.identity.idm.IdOperation;
import com.sun.identity.idm.IdRepoBundle;
import com.sun.identity.idm.IdRepoErrorCode;
import com.sun.identity.idm.IdRepoException;
import com.sun.identity.idm.IdType;
import com.sun.identity.setup.ServicesDefaultValues;
import com.sun.identity.shared.StringUtils;
import com.sun.identity.shared.datastruct.CollectionHelper;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.sm.SMSException;
import com.sun.identity.sm.ServiceConfig;
import com.sun.identity.sm.ServiceConfigManager;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContext;
import org.forgerock.openam.ldap.LDAPRequests;
import org.forgerock.openam.ldap.LDAPURL;
import org.forgerock.openam.ldap.LDAPUtils;
import org.forgerock.openam.ldap.LdifUtils;
import org.forgerock.openam.utils.CollectionUtils;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldif.ConnectionEntryReader;
import org.forgerock.util.Options;
import org.forgerock.util.time.Duration;
/**
* This class provides common utility functions for IdRepo.
*/
public class IdRepoUtils {
private static final String LDAPv3ForODSEE = "LDAPv3ForAMDS";
private static final String LDAPv3ForOpenDS = "LDAPv3ForOpenDS";
private static final String LDAPv3ForAD = "LDAPv3ForAD";
private static final String LDAPv3ForADAM = "LDAPv3ForADAM";
private static final String LDAPv3ForTivoli = "LDAPv3ForTivoli";
private static final String ODSEE_LDIF = "odseeSchema";
private static final String OpenDS_LDIF = "opendsUserSchema";
private static final String AD_LDIF = "adUserSchema";
private static final String ADAM_LDIF = "adamUserSchema";
private static final String TIVOLI_LDIF = "tivoliUserSchema";
private static final String SCHEMA_PROPERTY_FILENAME = "schemaNames";
private static final String LDAPv3_LDAP_SERVER = "sun-idrepo-ldapv3-config-ldap-server";
private static final Set<String> defaultPwdAttrs = new HashSet<String>(2);
private static Debug DEBUG = Debug.getInstance("IdRepoUtils");
static {
defaultPwdAttrs.add("userpassword");
defaultPwdAttrs.add("unicodepwd");
}
/**
* Returns an attribute map with all the password attributes being masked.
*
* @param attrMap an attribute map
* @param pwdAttrs a set of password attribute names
*
* @return an attribute map with all the password attributes being masked.
*/
public static Map<String, ?> getAttrMapWithoutPasswordAttrs(Map<String, ?> attrMap, Set<String> pwdAttrs) {
if (attrMap == null || attrMap.isEmpty()) {
return attrMap;
}
//the attrmap needs to be case-insensitive in order to detect password attributes correctly
attrMap = new CaseInsensitiveHashMap(attrMap);
Set<String> allPwdAttrs = new HashSet<String>(defaultPwdAttrs);
if (pwdAttrs != null) {
allPwdAttrs.addAll(pwdAttrs);
}
AMHashMap returnAttrMap = null;
for (String pwdAttr : allPwdAttrs) {
if (attrMap.containsKey(pwdAttr)) {
if (returnAttrMap == null) {
returnAttrMap = new AMHashMap();
returnAttrMap.copy(attrMap);
}
returnAttrMap.put(pwdAttr, "xxx...");
}
}
return (returnAttrMap == null ? attrMap : returnAttrMap);
}
private static String getSchemaFiles(String idRepoType) {
ResourceBundle rb = ResourceBundle.getBundle(
SCHEMA_PROPERTY_FILENAME);
String schemaFiles = null;
if (idRepoType.equals(LDAPv3ForODSEE)) {
schemaFiles = rb.getString(ODSEE_LDIF);
} else if (idRepoType.equals(LDAPv3ForOpenDS)) {
schemaFiles = rb.getString(OpenDS_LDIF);
} else if (idRepoType.equals(LDAPv3ForAD)) {
schemaFiles = rb.getString(AD_LDIF);
} else if (idRepoType.equals(LDAPv3ForADAM)) {
schemaFiles = rb.getString(ADAM_LDIF);
} else if (idRepoType.equals(LDAPv3ForTivoli)) {
schemaFiles = rb.getString(TIVOLI_LDIF);
}
return schemaFiles;
}
/**
* Return true if specified IdRepo type has schemas.
*
* @param idRepoType IdRepo type
* @return true if specified IdRepo type has schemas
*/
public static boolean hasIdRepoSchema(String idRepoType) {
if (idRepoType == null) {
return false;
}
String schemaFiles = getSchemaFiles(idRepoType);
return ((schemaFiles != null) && (schemaFiles.trim().length() > 0));
}
/**
* Loads schema to specified IdRepo.
*
* @param ssoToken single sign on token of authenticated user identity
* @param idRepoName IdRepo name
* @param realm the realm
* @param servletCtx the servlet context
*
* @throws IdRepoException If schema can't be loaded or there are
* repository related error conditions.
*/
public static void loadIdRepoSchema(SSOToken ssoToken, String idRepoName,
String realm, ServletContext servletCtx) throws IdRepoException {
if (servletCtx == null) {
return;
}
try {
ServiceConfigManager svcCfgMgr = new ServiceConfigManager(
IdConstants.REPO_SERVICE, ssoToken);
ServiceConfig cfg = svcCfgMgr.getOrganizationConfig(realm, null);
ServiceConfig ss = cfg.getSubConfig(idRepoName);
if (ss == null) {
if (DEBUG.messageEnabled()) {
DEBUG.message("IdRepoUtils.loadIdRepoSchema: data store " +
idRepoName + " for realm " + realm + " doesn't exist.");
}
Object args[] = { idRepoName, realm };
throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.PLUGIN_DOESNT_EXIST_FOR_REALM,
args);
}
String idRepoType = ss.getSchemaID();
Map attrValues = ss.getAttributes();
String schemaFiles = getSchemaFiles(idRepoType);
if ((schemaFiles == null) || (schemaFiles.trim().length() == 0)) {
if (DEBUG.messageEnabled()) {
DEBUG.message("IdRepoUtils.loadIdRepoSchema: data store " +
idRepoName + " for realm " + realm + " doesn't have " +
"schema files.");
}
return;
}
StringTokenizer st = new StringTokenizer(schemaFiles);
while (st.hasMoreTokens()) {
String schemaFile = st.nextToken();
tagSwapAndImportSchema(schemaFile, attrValues, servletCtx,
idRepoType);
}
} catch (SMSException smsex) {
if (DEBUG.messageEnabled()) {
DEBUG.message("IdRepoUtils.loadIdRepoSchema:", smsex);
}
Object args[] = { idRepoName, realm };
throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_READ_PLUGIN_FOR_REALM, args);
} catch (SSOException ssoex) {
if (DEBUG.messageEnabled()) {
DEBUG.message("IdRepoUtils.loadIdRepoSchema:", ssoex);
}
Object args[] = { idRepoName, realm };
throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
IdRepoErrorCode.UNABLE_READ_PLUGING_FOR_REALM_SSOTOKEN_NOT_VALID, args);
} catch (Exception ex) {
if (DEBUG.messageEnabled()) {
DEBUG.message("IdRepoUtils.loadIdRepoSchema:", ex);
}
Object args[] = { idRepoName, realm, ex.getMessage() };
throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
IdRepoErrorCode.UNABLE_LOAD_SCHEMA_FOR_PLUGIN_FOR_REALM, args);
}
}
private static void tagSwapAndImportSchema(String schemaFile,
Map attrValues, ServletContext servletCtx, String idRepoType)
throws Exception {
DataInputStream dis = null;
try (ConnectionFactory factory = getLDAPConnection(attrValues);
Connection ld = factory.getConnection();
InputStreamReader fin = new InputStreamReader(servletCtx.getResourceAsStream(schemaFile))) {
StringBuilder sbuf = new StringBuilder();
char[] cbuf = new char[1024];
int len;
while ((len = fin.read(cbuf)) > 0) {
sbuf.append(cbuf, 0, len);
}
String schemaStr = sbuf.toString();
String suffix = CollectionHelper.getMapAttr(attrValues,
"sun-idrepo-ldapv3-config-organization_name");
if (suffix != null) {
schemaStr = StringUtils.strReplaceAll(schemaStr,
"@userStoreRootSuffix@", suffix);
String dbName = LDAPUtils.getDBName(suffix, ld);
schemaStr = StringUtils.strReplaceAll(schemaStr, "@DB_NAME@",
dbName);
}
if (idRepoType.equals(LDAPv3ForADAM)) {
String adamInstanceGUID = getADAMInstanceGUID(attrValues);
if (adamInstanceGUID != null) {
schemaStr = StringUtils.strReplaceAll(schemaStr,
"@INSTANCE_GUID@", adamInstanceGUID);
}
}
schemaStr = ServicesDefaultValues.tagSwap(schemaStr);
dis = new DataInputStream(new ByteArrayInputStream(schemaStr.getBytes()));
LdifUtils.createSchemaFromLDIF(dis, ld);
} finally {
if (dis != null) {
try {
dis.close();
} catch (Exception ex) {
//No handling requried
}
}
}
}
private static String getADAMInstanceGUID(Map attrValues) throws Exception {
try (ConnectionFactory factory = getLDAPConnection(attrValues);
Connection ld = factory.getConnection()){
String attrName = "schemaNamingContext";
String[] attrs = { attrName };
ConnectionEntryReader res = ld.search(LDAPRequests.newSearchRequest("", SearchScope.BASE_OBJECT,
"(objectclass=*)"));
if (res.hasNext()) {
SearchResultEntry entry = res.readEntry();
Attribute ldapAttr = entry.getAttribute(attrName);
if (ldapAttr != null) {
String value = ldapAttr.firstValueAsString();
int index = value.lastIndexOf("=");
if (index != -1) {
return value.substring(index + 1).trim();
}
}
}
}
return null;
}
private static ConnectionFactory getLDAPConnection(Map attrValues)
throws Exception {
Options options = Options.defaultOptions()
.set(CONNECT_TIMEOUT, new Duration((long) 300, TimeUnit.MILLISECONDS));
if (CollectionHelper.getBooleanMapAttr(attrValues, "sun-idrepo-ldapv3-config-ssl-enabled", false)) {
options = options.set(SSL_CONTEXT, new SSLContextBuilder().getSSLContext());
}
Set<LDAPURL> ldapUrls = getLDAPUrls(attrValues);
if (CollectionUtils.isEmpty(ldapUrls)) {
if (DEBUG.warningEnabled()) {
DEBUG.warning("IdRepoUtils.getLDAPConnection: No LDAPURLs found");
}
throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_AUTHENTICATE_LDAP_SERVER, null);
}
LDAPURL ldapUrl = ldapUrls.iterator().next();
if (org.forgerock.openam.utils.StringUtils.isEmpty(ldapUrl.getHost())) {
if (DEBUG.warningEnabled()) {
DEBUG.warning("IdRepoUtils.getLDAPConnection: No LDAP host found");
}
throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_AUTHENTICATE_LDAP_SERVER, null);
}
// All connections will use authentication
String bindDn = CollectionHelper.getMapAttr(attrValues, "sun-idrepo-ldapv3-config-authid");
if (org.forgerock.openam.utils.StringUtils.isBlank(bindDn)) {
if (DEBUG.warningEnabled()) {
DEBUG.warning("IdRepoUtils.getLDAPConnection: No LDAP bindDN found");
}
throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_AUTHENTICATE_LDAP_SERVER, null);
}
String bindPwd = CollectionHelper.getMapAttr(attrValues, "sun-idrepo-ldapv3-config-authpw");
if (org.forgerock.openam.utils.StringUtils.isBlank(bindPwd)) {
if (DEBUG.warningEnabled()) {
DEBUG.warning("IdRepoUtils.getLDAPConnection: No LDAP bindPW found");
}
throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_AUTHENTICATE_LDAP_SERVER, null);
}
options = options.set(AUTHN_BIND_REQUEST, LDAPRequests.newSimpleBindRequest(bindDn, bindPwd.toCharArray()));
return new LDAPConnectionFactory(ldapUrl.getHost(), ldapUrl.getPort(), options);
}
/**
* Parses the incoming types and operations in string format to their domain object equivalent. For example:
* <code>user=read,edit,service,delete</code>
* will be turned into USER identity type with READ, EDIT, SERVICE, DELETE operations.
*
* @param typesAndOperations The parsable type and operation lines (each line represents an identity type.
* @return The parsed identity types and operation mappings.
*/
public static Map<IdType, Set<IdOperation>> parseSupportedTypesAndOperations(Set<String> typesAndOperations) {
Map<IdType, Set<IdOperation>> supportedTypesAndOperations = new HashMap<IdType, Set<IdOperation>>(6);
if (DEBUG.messageEnabled()) {
DEBUG.message("Parsing supported types and operations:" + typesAndOperations);
}
for (String typeAndOperation : typesAndOperations) {
int idx = typeAndOperation.indexOf('=');
if (idx == -1) {
DEBUG.error("Invalid supported type/operation configuration: " + typeAndOperation);
continue;
}
String type = typeAndOperation.substring(0, idx).trim();
IdType idType = null;
boolean supportsService = false;
if (type.equalsIgnoreCase(IdType.USER.getName())) {
idType = IdType.USER;
supportsService = true;
} else if (type.equalsIgnoreCase(IdType.GROUP.getName())) {
idType = IdType.GROUP;
} else if (type.equalsIgnoreCase(IdType.ROLE.getName())) {
idType = IdType.ROLE;
} else if (type.equalsIgnoreCase(IdType.FILTEREDROLE.getName())) {
idType = IdType.FILTEREDROLE;
} else if (type.equalsIgnoreCase(IdType.AGENT.getName())) {
idType = IdType.AGENT;
} else if (type.equalsIgnoreCase(IdType.REALM.getName())) {
idType = IdType.REALM;
supportsService = true;
}
supportedTypesAndOperations.put(idType,
parseSupportedOperations(typeAndOperation.substring(idx + 1), supportsService));
}
//we need to make sure that realm=service is always present
Set<IdOperation> realmOps = supportedTypesAndOperations.get(IdType.REALM);
if (realmOps == null) {
realmOps = new HashSet<IdOperation>(1);
}
realmOps.add(IdOperation.SERVICE);
supportedTypesAndOperations.put(IdType.REALM, realmOps);
if (DEBUG.messageEnabled()) {
DEBUG.message("Parsed supported types and operations are: " + supportedTypesAndOperations);
}
return supportedTypesAndOperations;
}
private static Set<IdOperation> parseSupportedOperations(String operations, boolean supportsService) {
String[] ops = operations.split(",");
Set<IdOperation> idOperations = new HashSet<IdOperation>(5);
for (String op : ops) {
op = op.trim();
if (op.equalsIgnoreCase(IdOperation.CREATE.getName())) {
idOperations.add(IdOperation.CREATE);
} else if (op.equalsIgnoreCase(IdOperation.DELETE.getName())) {
idOperations.add(IdOperation.DELETE);
} else if (op.equalsIgnoreCase(IdOperation.EDIT.getName())) {
idOperations.add(IdOperation.EDIT);
} else if (op.equalsIgnoreCase(IdOperation.READ.getName())) {
idOperations.add(IdOperation.READ);
} else if (op.equalsIgnoreCase(IdOperation.SERVICE.getName())) {
if (supportsService) {
idOperations.add(IdOperation.SERVICE);
}
}
}
return idOperations;
}
/**
* Parses attribute mapping settings in the format of "from=to". If there is no mapping provided, then this will be
* a mapping to itself.
*
* @param mappings A set of mappings that needs to be parsed
* @return The processed mappings returned as a map.
*/
public static Map<String, String> parseAttributeMapping(Set<String> mappings) {
Map<String, String> map = new CaseInsensitiveHashMap();
if (mappings == null) {
return map;
}
for (String mapping : mappings) {
int idx = mapping.indexOf('=');
if (idx == -1) {
map.put(mapping, mapping);
} else {
String from = mapping.substring(0, idx);
String to = mapping.substring(idx + 1);
map.put(from, to);
}
}
return map;
}
private static Set<LDAPURL> getLDAPUrls( Map<String, Set<String>> attrValues) {
// Get the prioritised set of ldap servers
Set<String> ldapServers = attrValues.get(LDAPv3_LDAP_SERVER);
Set<LDAPURL> ldapUrls = null;
if (null != ldapServers) {
String hostServerId = null;
String hostSiteId = "";
try {
hostServerId = WebtopNaming.getAMServerID();
hostSiteId = WebtopNaming.getSiteID(hostServerId);
} catch (ServerEntryNotFoundException senfe) {
if (DEBUG.warningEnabled()) {
DEBUG.warning("ServerEntryNotFoundException, hostServerId=" + hostServerId + ", hostSiteId="
+ hostSiteId);
}
}
ldapUrls = org.forgerock.openam.ldap.LDAPUtils.prioritizeServers(ldapServers, hostServerId, hostSiteId);
}
return ldapUrls;
}
}