import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import sailpoint.api.PasswordGenerator; import sailpoint.object.Application; import sailpoint.object.Custom; import sailpoint.object.Identity; import sailpoint.object.Link; import sailpoint.object.ProvisioningPlan; import sailpoint.object.ProvisioningPlan.AccountRequest; import sailpoint.tools.Util; import pl.conceptdata.identityiq.helper.ActiveDirectory; import pl.conceptdata.identityiq.helper.CustomObject; import pl.conceptdata.identityiq.helper.Transliteration; //============================================================================================= Logger logger_libraryAD = LogManager.getLogger("pl.conceptdata.library.AD"); //============================================================================================= public String generateDN(Identity identity) { boolean enabled = isIdentityActive(identity); String dn = generateDN(identity, enabled, logger_libraryAD); return dn; } //============================================================================================= public String generateDN(Identity identity, boolean enabled, Logger logger) { if (logger == null) logger = logger_libraryAD; logger.trace("====================================="); logger.debug("generateDN::Calculate DN for " + (enabled ? "active" : "disabled") + " accounts for user: \"" + getName(identity) + "\"."); if (identity == null) { logger.error("generateDN::Failed because the identity is not defined."); return null; } String organizationalUnit = calculateOU(identity, enabled, logger); String cn = generateCN(identity, organizationalUnit, logger); String dn = null; if (Util.isNotNullOrEmpty(organizationalUnit) @and Util.isNotNullOrEmpty(cn)) { dn = "CN=" + cn + "," + organizationalUnit; logger.debug("generateDN::DN is calculated for user \"" + identity.getName() + "\": \"" + dn + "\"."); } else { logger.warn("generateDN::DN is not calculated for user \"" + identity.getName() + "\"."); } logger.trace("====================================="); return dn; } //============================================================================================= public String generateCN(Identity identity, String organizationalUnit, Logger logger) { if (logger == null) logger = logger_libraryAD; logger.debug("generateCN::Generate CN for user \"" + getName(identity) + "\" in \"" + organizationalUnit + "\"."); if (identity == null || Util.isNullOrEmpty(organizationalUnit) || Util.isNullOrEmpty(identity.getFirstname()) || Util.isNullOrEmpty(identity.getLastname())) { logger.error("generateCN::Failed because some of mandatory attributes is/are not defined (user: \"" + getName(identity) + "\")."); return null; } String cn = null; try { String genericCN = removeProhibitedChars(getDisplayName(identity.getFirstname(), identity.getLastname())); logger.debug("generateCN::Generic CN: \"" + genericCN + "\"."); if (Util.isNotNullOrEmpty(genericCN)) { // Looking for the existing AD account (in the case of renaming) String existing_sAMAccountName = getLinkAttribute(identity, APP_AD, "sAMAccountName"); String existing_cn = getLinkAttribute(identity, APP_AD, "cn"); if (Util.isNotNullOrEmpty(existing_sAMAccountName) @and Util.isNotNullOrEmpty(existing_cn)) { String existing_DN = "CN=" + existing_cn + "," + organizationalUnit; logger.debug("generateCN::AD account is detected: \"" + existing_sAMAccountName + "\"."); logger.debug("generateCN::Current DN: \"" + existing_DN + "\"."); // Remove numbers from the current CN and compare with the newly generated value - if both values are equal - do not rename String existing_genericCN = existing_cn.replaceAll("\\d", "").trim(); if (genericCN.equalsIgnoreCase(existing_genericCN) @and !isUserExists("distinguishedName", existing_DN, existing_sAMAccountName)) { logger.debug("generateCN::The new CN is equals to the current CN and is unique in the container - renaming is not required."); cn = existing_cn; } else { logger.debug("generateCN::The new CN is not equals to the current CN or the CN is not unique in the container - renaming may be required."); cn = generateCN(genericCN, organizationalUnit, existing_sAMAccountName, logger); } } else { logger.debug("generateCN::AD account is not detected - generate the unique CN."); cn = generateCN(genericCN, organizationalUnit, null, logger); } } if (Util.isNotNullOrEmpty(cn)) logger.debug("generateCN::CN generated for user \"" + identity.getName() + "\": \"" + cn + "\"."); else logger.warn("generateCN::CN is not generated for user \"" + identity.getName() + "\"."); } catch(Exception ex) { logger.error("generateCN::Failed for user \"" + identity.getName() + "\" due error: \"" + ex.toString() + "\"."); } return cn; } //============================================================================================= public String generateSAMAccountName(Identity identity, Logger logger) { if (logger == null) logger = logger_libraryAD; logger.trace("====================================="); logger.debug("generateSAMAccountName::Generate sAMAccountName for user \"" + getName(identity) + "\"."); if (identity == null || Util.isNullOrEmpty(identity.getFirstname()) || Util.isNullOrEmpty(identity.getLastname())) { logger.error("generateSAMAccountName::Failed because some of mandatory attributes is/are not defined (user: \"" + getName(identity) + "\")."); return null; } String sAMAccountName = null; try { String firstName = transliterate(identity.getFirstname()).toLowerCase(); String lastName = transliterate(identity.getLastname()).toLowerCase(); logger.debug("generateSAMAccountName::Transliterated escaped first name: \"" + firstName + "\"; transliterated escaped last name: \"" + lastName + "\"."); String genericSAMAccountName = (Util.isNotNullOrEmpty(firstName) @and Util.isNotNullOrEmpty(lastName)) ? (firstName + "." + lastName) : null; logger.debug("generateSAMAccountName::Generic sAMAccountName #1: \"" + genericSAMAccountName + "\"."); if (genericSAMAccountName.length() @gt 20) { logger.debug("generateSAMAccountName::The generated saMAaccountName name is too long (length: \"" + genericSAMAccountName.length() + "\"), truncate firstName to the 1st character only."); genericSAMAccountName = trimString(firstName, 1) + "." + lastName; logger.debug("generateSAMAccountName::Generic sAMAccountName #2: \"" + genericSAMAccountName + "\"."); } if (genericSAMAccountName.length() @gt 20) { if (lastName.contains("-")) { logger.debug("generateSAMAccountName::The generated saMAaccountName name is too long (length: \"" + genericSAMAccountName.length() + "\"), 'double' family name found (dash character detected), truncate the 1st part of lastName."); genericSAMAccountName = trimString(firstName, 1) + "." + lastName.substring(lastName.indexOf("-") + 1); logger.debug("generateSAMAccountName::Generic sAMAccountName #3: \"" + genericSAMAccountName + "\"."); } } if (Util.isNotNullOrEmpty(genericSAMAccountName)) { // Looking for the existing AD account (in the case of renaming) String existing_SAMAccountName = getLinkAttribute(identity, APP_AD, "sAMAccountName"); if (Util.isNotNullOrEmpty(existing_SAMAccountName)) { logger.debug("generateSAMAccountName::AD account is detected: \"" + existing_SAMAccountName + "\"."); // Remove numbers from the current sAMAccountName and compare with the newly generated value - if both values are equal - do not rename String existing_genericSAMAccountName = existing_SAMAccountName.replaceAll("\\d", "").trim(); if (genericSAMAccountName.equalsIgnoreCase(existing_genericSAMAccountName)) { logger.debug("generateSAMAccountName::The current sAMAccountName is equal to the newly generated - renaming is not required."); sAMAccountName = existing_SAMAccountName; } else { logger.debug("generateSAMAccountName::Renaming may be required."); sAMAccountName = generateSAMAccountName(genericSAMAccountName, existing_SAMAccountName, logger); } } else { logger.debug("generateSAMAccountName::AD account is not detected - generate the unique sAMAccountName."); sAMAccountName = generateSAMAccountName(genericSAMAccountName, null, logger); } } if (Util.isNotNullOrEmpty(sAMAccountName)) logger.debug("generateSAMAccountName::sAMAccountName generated for user \"" + identity.getName() + "\": \"" + sAMAccountName + "\"."); else logger.warn("generateSAMAccountName::sAMAccountName is not generated for user \"" + identity.getName() + "\"."); } catch(Exception ex) { logger.error("generateSAMAccountName::Failed for user \"" + identity.getName() + "\" due error: \"" + ex.toString() + "\"."); } logger.trace("====================================="); return sAMAccountName; } //============================================================================================= public String generateUserPrincipalName(Identity identity, Logger logger) { if (logger == null) logger = logger_libraryAD; logger.trace("====================================="); logger.debug("generateUserPrincipalName::Generate userPrincipalName for user \"" + getName(identity) + "\"."); if (identity == null || Util.isNullOrEmpty(identity.getFirstname()) || Util.isNullOrEmpty(identity.getLastname())) { logger.error("generateUserPrincipalName::Failed because some of mandatory attributes is/are not defined (user: \"" + getName(identity) + "\")."); return null; } String userPrincipalName = null; try { String firstName = transliterate(identity.getFirstname()).toLowerCase(); String lastName = transliterate(identity.getLastname()).toLowerCase(); logger.debug("generateUserPrincipalName::Transliterated escaped first name: \"" + firstName + "\"; transliterated escaped last name: \"" + lastName + "\"."); String genericUserPrincipalName = (Util.isNotNullOrEmpty(firstName) @and Util.isNotNullOrEmpty(lastName)) ? (firstName + "." + lastName) : null; logger.debug("generateUserPrincipalName::Generic userPrincipalName: \"" + genericUserPrincipalName + "\"."); String domainSuffix = calculateDomainSuffix(identity, logger); logger.debug("generateUserPrincipalName::Domain suffix: \"" + domainSuffix + "\"."); if (Util.isNotNullOrEmpty(genericUserPrincipalName) @and Util.isNotNullOrEmpty(domainSuffix)) { // Looking for the existing AD account (in the case of renaming) String existing_SAMAccountName = getLinkAttribute(identity, APP_AD, "sAMAccountName"); String existing_UserPrincipalName = getLinkAttribute(identity, APP_AD, "userPrincipalName"); if (Util.isNotNullOrEmpty(existing_SAMAccountName) @and Util.isNotNullOrEmpty(existing_UserPrincipalName)) { logger.debug("generateUserPrincipalName::AD account is detected: \"" + existing_SAMAccountName + "\" (userPrincipalName: \"" + existing_UserPrincipalName + "\")."); // Remove numbers from the current userPrincipalName and compare with the newly generated value - if both values are equal - do not rename String existing_genericUserPrincipalName = existing_UserPrincipalName.replaceAll("\\d", "").trim(); if (genericUserPrincipalName.equalsIgnoreCase(existing_genericUserPrincipalName)) { logger.debug("generateUserPrincipalName::The current userPrincipalName is equal to the newly generated - renaming is not required."); userPrincipalName = existing_UserPrincipalName; } else { logger.debug("generateUserPrincipalName::Renaming may be required."); userPrincipalName = generateUserPrincipalName(genericUserPrincipalName, domainSuffix, existing_SAMAccountName, logger); } } else { logger.debug("generateUserPrincipalName::AD account is not detected - generate the unique userPrincipalName."); userPrincipalName = generateUserPrincipalName(genericUserPrincipalName, domainSuffix, null, logger); } } if (Util.isNotNullOrEmpty(userPrincipalName)) logger.debug("generateUserPrincipalName::userPrincipalName generated for user \"" + identity.getName() + "\": \"" + userPrincipalName + "\"."); else logger.warn("generateUserPrincipalName::userPrincipalName is not generated for user \"" + identity.getName() + "\"."); } catch(Exception ex) { logger.error("generateUserPrincipalName::Failed for user \"" + identity.getName() + "\" due error: \"" + ex.toString() + "\"."); } logger.trace("====================================="); return userPrincipalName; } //============================================================================================= public String generateHomeMDB(Identity identity, Logger logger) { if (logger == null) logger = logger_libraryAD; logger.debug("generateHomeMDB::Calculate HomeMDB for user \"" + getName(identity) + "\"."); if (identity == null || Util.isNullOrEmpty(identity.getType())) { logger.error("generateHomeMDB::Failed because some of mandatory attributes is/are not defined (user: \"" + getName(identity) + "\")."); return null; } String homeMDB = null; try { List homeMDBs = CustomObject.getList(context, CUSTOM_OBJECT_APP_AD, "homeMDB"); if (!homeMDBs.isEmpty()) { Random random = new Random(); homeMDB = homeMDBs.get(random.nextInt(homeMDBs.size())); } } catch(Exception ex) { logger.error("generateHomeMDB::Failed for user \"" + identity.getName() + "\" due error: \"" + ex.toString() + "\"."); } return homeMDB; } //============================================================================================= public String calculateOU(Identity identity, boolean enabled, Logger logger) { if (logger == null) logger = logger_libraryAD; logger.debug("calculateOU::Calculate OU for user \"" + getName(identity) + "\"."); if (identity == null || Util.isNullOrEmpty(identity.getType())) { logger.error("calculateOU::Failed because some of mandatory attributes is/are not defined (user: \"" + getName(identity) + "\")."); return null; } String organizationalUnit = null; try { HashMap organizationalUnits = CustomObject.getMap(context, CUSTOM_OBJECT_APP_AD, "organizationalUnit-User"); if (enabled) { // Calculate by the user type and country String userType = identity.getType(); logger.debug("calculateOU::User type: \"" + userType + "\"; Enabled: \"" + enabled + "\"."); if (isIdentityTypeRecognized(userType)) { if (isIdentityInternal(identity)) { String countryCode = getCountryCodeByCountryName(identity.getAttribute("country")); logger.debug("calculateOU::User country code: \"" + countryCode + "\" (country: \"" + identity.getAttribute("country") + "\")."); if (Util.isNotNullOrEmpty(countryCode)) organizationalUnit = organizationalUnits.get("internal-" + countryCode.toLowerCase()); } else if (isIdentityExternal(identity)) { organizationalUnit = organizationalUnits.get("external"); } } else { logger.warn("calculateOU::OU can't be properly calculated for user \"" + identity.getName() + "\", because the user type \"" + userType + "\" is not recognized."); } } else { organizationalUnit = organizationalUnits.get("disabled"); } if (Util.isNotNullOrEmpty(organizationalUnit)) { logger.debug("calculateOU::OU is calculated: \"" + organizationalUnit + "\"."); } else { organizationalUnit = organizationalUnits.get("default"); logger.warn("calculateOU::OU is not calculated for user \"" + identity.getName() + "\", and the default OU will be used instead: \"" + organizationalUnit + "\"."); } } catch(Exception ex) { logger.error("calculateOU::Failed for user \"" + identity.getName() + "\" due error: \"" + ex.toString() + "\"."); } return organizationalUnit; } //============================================================================================= public String calculateDomainSuffix(Identity identity, Logger logger) { if (logger == null) logger = logger_libraryAD; logger.debug("calculateDomainSuffix::Calculate domain suffix for user \"" + getName(identity) + "\"."); if (identity == null || Util.isNullOrEmpty(identity.getType())) { logger.error("calculateDomainSuffix::Failed because some of mandatory attributes is/are not defined (user: \"" + getName(identity) + "\")."); return null; } String domainSuffix = null; try { HashMap domainSuffixes = CustomObject.getMap(context, CUSTOM_OBJECT_APP_AD, "domainSuffix-User"); String countryCode = getCountryCodeByCountryName(identity.getAttribute("country")); logger.debug("calculateDomainSuffix::Country code: \"" + countryCode + "\" (country: \"" + identity.getAttribute("country") + "\")."); if (Util.isNotNullOrEmpty(countryCode)) domainSuffix = domainSuffixes.get(countryCode.toLowerCase()); if (Util.isNotNullOrEmpty(domainSuffix)) { logger.debug("calculateDomainSuffix::Domain suffix for country code \"" + countryCode + "\" found: \"" + domainSuffix + "\"."); } else { domainSuffix = domainSuffixes.get("default"); logger.debug("calculateDomainSuffix::Domain suffix for country code \"" + countryCode + "\" is not found and the default will be used instead: \"" + domainSuffix + "\"."); } } catch(Exception ex) { logger.error("calculateDomainSuffix::Failed for user \"" + identity.getName() + "\" due error: \"" + ex.toString() + "\"."); } return domainSuffix; } //============================================================================================= public void refreshDN(Identity identity, Link link, AccountRequest accountRequest, boolean enabled, Logger logger) { if (logger == null) logger = logger_libraryAD; logger.debug("refreshDN::Refresh DN for user \"" + getName(identity) + "\" (link: \"" + (link != null ? link.getAttribute("sAMAccountName") : null) + "\"; enabled: \"" + enabled + "\")."); if (identity == null || link == null || accountRequest == null) { logger.error("refreshDN::Failed because some of mandatory attributes is/are not defined (user: \"" + getName(identity) + "\")."); return; } String dn = generateDN(identity, enabled, logger); if (Util.isNotNullOrEmpty(dn)) { String organizationalUnit = getOrganizationalUnit(dn); String cn = getCommonName(dn); String existing_organizationalUnit = getOrganizationalUnit(link.getAttribute("distinguishedName")); String existing_cn = getCommonName(link.getAttribute("distinguishedName")); if (!organizationalUnit.equalsIgnoreCase(existing_organizationalUnit)) { logger.debug("refreshDN::Create an attribute request to set the new OU: \"" + organizationalUnit + "\"."); accountRequest.add(new AttributeRequest("AC_NewParent", ProvisioningPlan.Operation.Set, organizationalUnit)); } else { logger.debug("refreshDN::OU change is not required."); } if (!cn.equalsIgnoreCase(existing_cn)) { logger.debug("refreshDN::Create an attribute request to set the new CN: \"" + cn + "\"."); accountRequest.add(new AttributeRequest("AC_NewName", ProvisioningPlan.Operation.Set, "CN=" + cn)); } else { logger.debug("refreshDN::CN change is not required."); } } } //============================================================================================= public String getDN(Identity identity) { return getLinkAttribute(identity, APP_AD, "distinguishedName"); } // ============================================================================================= public String getCommonName(String distinguishedName) { if (Util.isNullOrEmpty(distinguishedName) || !distinguishedName.toLowerCase().startsWith("cn=")) return null; String commonName = distinguishedName.substring(3); if (commonName.contains("=")) commonName = commonName.substring(0, commonName.indexOf("=")-3); return commonName; } // ============================================================================================= public String getOrganizationalUnit(String distinguishedName) { if (Util.isNullOrEmpty(distinguishedName) || !distinguishedName.toLowerCase().contains("ou=") || !distinguishedName.toLowerCase().contains("dc=")) return null; return distinguishedName.substring(distinguishedName.toLowerCase().indexOf("ou=")); } // ============================================================================================= public boolean isDNValid(String distinguishedName) { if (Util.isNullOrEmpty(distinguishedName) || !distinguishedName.toLowerCase().contains("cn=") || !distinguishedName.toLowerCase().contains("ou=") || !distinguishedName.toLowerCase().contains("dc=")) return false; return true; } //============================================================================================= public String getMail(String sAMAccountName, String preferredDC) { if (Util.isNullOrEmpty(sAMAccountName)) return null; ActiveDirectory activeDirectory = new ActiveDirectory(context, APP_AD); activeDirectory.setPreferredDC(preferredDC); activeDirectory.connect(); try { return activeDirectory.getUserAttribute(sAMAccountName, "mail"); } finally { if (Util.isNotNullOrEmpty(preferredDC)) activeDirectory.setPreferredDC(null); activeDirectory.disconnect(); } } //============================================================================================= // The attribute "accountExpires" should be defined as String public String convertDateToAccountExpires(Object value) { // Must be "never", otherwise, the provisioning will provision 0/9223372036854775807 every time, because in the Link it has value - "never" // "never" may cause errors like '... can't convert string to int ...' appeared in the sailpoint.log String accountExpires = "never"; if (value != null) { Date date = parseDate(value); if (date != null) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); if (calendar.get(Calendar.YEAR) @lt 9999) { // accountExpires - is the last working day date = addDays(date, 1); accountExpires = formatDateAsString(date, "MM/dd/yyyy hh:mm:ss a z"); } } } return accountExpires; } // ============================================================================================= private String generateCN(String genericCommonName, String organizationalUnit, String existingSAMAccountName, Logger logger) { ActiveDirectory activeDirectory = new ActiveDirectory(context, APP_AD); activeDirectory.connect(); try { // 1st step if (genericCommonName.length() > 64) genericCommonName = trimString(genericCommonName, 64); String distinguishedName = "CN=" + genericCommonName + "," + organizationalUnit; boolean accountExists = activeDirectory.isUserExists("distinguishedName", distinguishedName, existingSAMAccountName); logger.debug("generateCN::Generated generic distinguishedName " + (accountExists ? "is not unique: \"" : "is unique: \"") + distinguishedName + "\"."); if (!accountExists) return genericCommonName; // 2nd step if (genericCommonName.length() > 63) genericCommonName = trimString(genericCommonName, 63); int index = 1; while (accountExists @and index @lt 10) { index++; String generatedCommonName = genericCommonName + " " + String.format("%01d", index); distinguishedName = "CN=" + generatedCommonName + "," + organizationalUnit; accountExists = activeDirectory.isUserExists("distinguishedName", distinguishedName, existingSAMAccountName); logger.debug("generateCN::Step #" + index + ". Generated distinguishedName " + (accountExists ? "is not unique: \"" : "is unique: \"") + distinguishedName + "\"."); if (!accountExists) return generatedCommonName; } } finally { activeDirectory.disconnect(); } return null; } //============================================================================================= private String generateSAMAccountName(String genericSAMAccountName, String existingSAMAccountName, Logger logger) { ActiveDirectory activeDirectory = new ActiveDirectory(context, APP_AD); activeDirectory.connect(); try { // 1st step if (genericSAMAccountName.length() @gt 20) genericSAMAccountName = trimString(genericSAMAccountName, 20); boolean accountExists = activeDirectory.isUserExists("sAMAccountName", genericSAMAccountName, existingSAMAccountName); logger.debug("generateSAMAccountName::Generic sAMAccountName \"" + genericSAMAccountName + (accountExists ? "\" is not unique" : "\" is unique") + "."); if (!accountExists) return genericSAMAccountName; // 2nd step if (genericSAMAccountName.length() @gt 19) genericSAMAccountName = trimString(genericSAMAccountName, 19); int index = 1; while (accountExists @and index @lt 10) { index++; String generatedSAMAccountName = genericSAMAccountName + String.format("%01d", index); accountExists = activeDirectory.isUserExists("sAMAccountName", generatedSAMAccountName, existingSAMAccountName); logger.debug("generateSAMAccountName::Step #" + index + ". Generated sAMAccountName \"" + generatedSAMAccountName + (accountExists ? "\" is not unique" : "\" is unique") + "."); if (!accountExists) return generatedSAMAccountName; } } finally { activeDirectory.disconnect(); } return null; } //============================================================================================= private String generateUserPrincipalName(String genericUserPrincipalName, String domainSuffix, String existingSAMAccountName, Logger logger) { ActiveDirectory activeDirectory = new ActiveDirectory(context, APP_AD); activeDirectory.connect(); try { // 1st step int maxLength = 255 - domainSuffix.length(); if (genericUserPrincipalName.length() @gt maxLength) genericUserPrincipalName = trimString(genericUserPrincipalName, maxLength); boolean accountExists = activeDirectory.isUserExists("userPrincipalName", genericUserPrincipalName + domainSuffix, existingSAMAccountName); logger.debug("generateUserPrincipalName::Generic userPrincipalName \"" + genericUserPrincipalName + domainSuffix + (accountExists ? "\" is not unique" : "\" is unique") + "."); if (!accountExists) return (genericUserPrincipalName + domainSuffix); // 2nd step if (genericUserPrincipalName.length() @gt (maxLength - 1)) genericUserPrincipalName = trimString(genericUserPrincipalName, maxLength - 1); int index = 1; while (accountExists @and index @lt 10) { index++; String generatedUserPrincipalName = genericUserPrincipalName + String.format("%01d", index) + domainSuffix; accountExists = activeDirectory.isUserExists("userPrincipalName", generatedUserPrincipalName, existingSAMAccountName); logger.debug("generateUserPrincipalName::Step #" + index + ". Generated userPrincipalName \"" + generatedUserPrincipalName + (accountExists ? "\" is not unique" : "\" is unique") + "."); if (!accountExists) return generatedUserPrincipalName; } } finally { activeDirectory.disconnect(); } return null; } //============================================================================================= private boolean isUserExists(String attributeName, String attributeValue, String existingSAMAccountName) { ActiveDirectory activeDirectory = new ActiveDirectory(context, APP_AD); try { activeDirectory.connect(); return activeDirectory.isUserExists(attributeName, attributeValue, existingSAMAccountName); } finally { activeDirectory.disconnect(); } return false; } //=============================================================================================