Share all details related to your problem, including any error messages you may have received.
Hi Team,
We have a requirement to create a contact object in Active Directory for managers via SailPoint Identity IQ. We have a total of 4 Active Directories in SailPoint IIQ, resulting in 4 different AD domains. When we execute the joiner feature, if a manager belongs to a different domain from the user, the manager field remains empty in Active Directory during provisioning. Thus, we aim to address this by implementing a check: if the manager’s domain differs from the user’s domain, we will create a contact object for the manager within the user’s domain and link the manager’s details with the user’s profile in AD.
For example, considering the following 4 AD domains:
“OU=adtechnologies.com,DC=lab,DC=ad”
“OU=mrl.vlt.intra,DC=lab,DC=ad”
“OU=Network.Local,DC=lab,DC=ad”
“OU=Mexichem,DC=mexqas,DC=com”
If the user’s domain belongs to “OU=adtechnologies.com,DC=lab,DC=ad” and the manager’s domain belongs to “OU=mrl.vlt.intra,DC=lab,DC=ad,” then we will create a contact object for the manager as follows: “CN=Manager Firstname LastName, OU=Contacts,OU=adtechnologies.com,DC=lab,DC=ad.” Additionally, we will include the manager’s first name, last name, and email address in the contact object.
Now during the joiner check if the contact object is already present, directly update that as manager if not create a contact object using code in above link and set that as manager.
Hi @iamksatish Thank you for your prompt response. However, we aim to establish a custom rule that runs daily via a custom task. This rule should verify whether the manager’s domain differs from the user’s domain and if the manager field is empty in the user’s Active Directory profile. If these conditions are met, the rule will create a manager contact object in the user’s domain. If a manager contact object already exists, it will link that manager to the user’s profile in AD. If not, it will create a new manager contact object. Thank you!!
Your approach seems good( if you don’t bother about a immediate manager update during joiner, but I still would prefer doing this during joiner ) and anyhow with the approach you mentioned you can utilise the above code for creating the contact objects and adding the object type contact in your schema and aggregating the same to Sailpoint will provide the contact details within Sailpoint itself for validating the presence or you can use JNDI code to check against AD for presence and create the contact object if not present
Can I know what is the challenge or where exactly need the help in this approach to assist further
Hi @iamksatish if i will doing this during joiner then where I need to write that rule in Before Provisioning or After Provisioning or some where else? Thank you!!
You can do it in either of the place , it doesn’t matter , but preferably during before provisioning rule so that during account creation itself
You will have the right manager in place
If it’s during create you just have to update the existing attribute request of plan
If you want to do during after provisioning rule you have to call a workflow or another process and provisioning plan to modify the account
Hi @iamksatish For POC, I have wrote below code to run via custom task but the code is failing and error is “Failed to submit contact object creation request for Active Directory: Missing identity”
Can you please check and suggest what I am missing?
import sailpoint.object.Identity;
import sailpoint.object.QueryOptions;
import sailpoint.object.Application;
import sailpoint.object.Attributes;
import sailpoint.api.SailPointContext;
import sailpoint.api.SailPointFactory;
import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
import sailpoint.tools.GeneralException;
import sailpoint.api.Provisioner;
import sailpoint.api.IdentityService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
// Define the log
//Log log = LogFactory.getLog("rule.Log");
// Get the SailPoint context
SailPointContext context = SailPointFactory.getCurrentContext();
// Replace with actual identity name to be provisioned
String identityName = "ct252525";
Identity identity = context.getObjectByName(Identity.class, identityName);
if (identity == null) {
log.error("Identity '" + identityName + "' not found.");
} else {
// Replace with actual manager ID
String managerId = "gj256565";
Identity manager = context.getObjectByName(Identity.class, managerId);
if (manager == null) {
log.error("Manager identity '" + managerId + "' not found.");
} else {
String userDN = identity.getAttribute("adDn");
String managerDN = manager.getAttribute("adDn");
String managerFN = manager.getAttribute("firstname");
String managerLN = manager.getAttribute("lastname");
String managerEmail = manager.getAttribute("emailAzure");
log.error("userDN: " + userDN);
log.error("managerDN: " + managerDN);
log.error("managerFN: " + managerFN);
log.error("managerLN: " + managerLN);
log.error("managerEmail: " + managerEmail);
// Check if manager's DN does not contain "OU=Network.Local"
if (managerDN != null && !managerDN.contains("OU=Network.Local")) {
// Construct DN for contact object
String contactDN = "cn=" + managerFN + " " + managerLN + ",OU=Contacts,OU=_Central_services,OU=Network.Local,DC=lab,DC=ad";
log.error("contactDN: " + contactDN);
// Create attributes for contact object
Attributes contactAttributes = new Attributes();
contactAttributes.put("objectClass", "contact");
contactAttributes.put("cn", managerFN + " " + managerLN);
contactAttributes.put("sn", managerLN);
contactAttributes.put("givenName", managerFN);
contactAttributes.put("mail", managerEmail);
log.error("contactAttributes: " + contactAttributes);
// Fetch the AD application
Application adApp = context.getObjectByName(Application.class, "Wavin-Application-ActiveDirectory");
log.error("adApp: " + adApp);
if (adApp != null) {
try {
// Create a new AccountRequest
AccountRequest accountRequest = new AccountRequest();
accountRequest.setOperation(AccountRequest.Operation.Create);
accountRequest.setApplication(adApp.getName());
accountRequest.setNativeIdentity(contactDN);
// Create and add AttributeRequests to the AccountRequest
accountRequest.add(new AttributeRequest("objectClass", ProvisioningPlan.Operation.Set, "contact"));
accountRequest.add(new AttributeRequest("cn", ProvisioningPlan.Operation.Set, managerFN + " " + managerLN));
accountRequest.add(new AttributeRequest("sn", ProvisioningPlan.Operation.Set, managerLN));
accountRequest.add(new AttributeRequest("givenName", ProvisioningPlan.Operation.Set, managerFN));
accountRequest.add(new AttributeRequest("mail", ProvisioningPlan.Operation.Set, managerEmail));
log.error("Added attributes to accountRequest");
// Create a new ProvisioningPlan
ProvisioningPlan provisioningPlan = new ProvisioningPlan();
provisioningPlan.add(accountRequest);
log.error("provisioningPlan: " + provisioningPlan);
// Use Provisioner to execute the provisioning plan
Provisioner provisioner = new Provisioner(context);
provisioner.execute(provisioningPlan);
context.commitTransaction();
log.info("Contact object creation request submitted successfully for Active Directory.");
} catch (GeneralException e) {
context.rollbackTransaction();
log.error("Failed to submit contact object creation request for Active Directory: " + e.getMessage());
}
} else {
log.error("Active Directory application 'Wavin-Application-ActiveDirectory' not found.");
}
} else {
log.warn("Manager's DN contains 'OU=Network.Local'.");
}
}
}
Hi @iamksatish I tried the above line and now it’s not throwing the error. But getting provisioning failed error. Also, it’s creating manager identity as user objectClass instead of contact. Additionally it’s not linking manager with identity as well.
Hi @iamksatish Please find code below. Thank you!!
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Rule created="" id="" language="beanshell" modified="" name="POC2-Manager-Contact" type="FieldValue">
<Description>This rule can be used to generate a field value (eg - an account name) using data from the given Identity. If this rule is run in the context of a workflow step then the arguments passed into the step will also be available. Also, any field values that have been processed so far from the policy related to the Application/Role will be available.</Description>
<Signature returnType="String">
<Inputs>
<Argument name="log" type="org.apache.commons.logging.Log">
<Description>
The log object associated with the SailPointContext.
</Description>
</Argument>
<Argument name="context" type="sailpoint.api.SailPointContext">
<Description>
A sailpoint.api.SailPointContext object that can be used to query the database if necessary.
</Description>
</Argument>
<Argument name="identity" type="Identity">
<Description>
The Identity object that represents the user needing the field value.
</Description>
</Argument>
<Argument name="link" type="Link">
<Description>
The sailpoint.object.Link that is being acted upon. If the link is not applicable,
this value will be null.
</Description>
</Argument>
<Argument name="group" type="AccountGroupDTO">
<Description>
The sailpoint.web.group.AccountGroupDTO that is being acted upon. If the AccountGroupDTO
is not applicable, the value will be null.
</Description>
</Argument>
<Argument name="project" type="ProvisioningProject">
<Description>
The provisioning project being acted upon. If a provisioning project is not applicable,
the value will be null.
</Description>
</Argument>
<Argument name="accountRequest" type="ProvisioningPlan.AccountRequest">
<Description>
The account request. If an account request is not applicable, the value will be null.
</Description>
</Argument>
<Argument name="objectRequest" type="ProvisioningPlan.ObjectRequest">
<Description>
The object request. If an object request is not applicable, the value will be null.
</Description>
</Argument>
<Argument name="role" type="Bundle">
<Description>
The role with the template we are compiling. If the role is
not applicable, the value will be null.
</Description>
</Argument>
<Argument name="application" type="Application">
<Description>
The sailpont.object.Application with the template we are compiling. If the application
is not applicable, the value will be null.
</Description>
</Argument>
<Argument name="template" type="Template">
<Description>
The Template that contains this field.
</Description>
</Argument>
<Argument name="field" type="Field">
<Description>
The current field being computed.
</Description>
</Argument>
<Argument name="current" type="Object">
<Description>
The current value corresponding to the identity or account attribute that the field represents.
If no current value is set, this value will be null.
</Description>
</Argument>
<Argument name="operation" type="ProvisioningPlan.Operation">
<Description>
The operation being performed.
</Description>
</Argument>
</Inputs>
<Returns>
<Argument name="value">
<Description>
The string value created.
</Description>
</Argument>
</Returns>
</Signature>
<Source>import sailpoint.object.Identity;
import sailpoint.object.Application;
import sailpoint.object.Attributes;
import sailpoint.api.SailPointContext;
import sailpoint.api.SailPointFactory;
import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
import sailpoint.tools.GeneralException;
import sailpoint.api.Provisioner;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
try {
// Get the SailPoint context
SailPointContext context = SailPointFactory.getCurrentContext();
// Replace with actual identity name to be provisioned
String identityName = "22222224";
Identity identity = context.getObjectByName(Identity.class, identityName);
if (identity == null) {
log.error("Identity '" + identityName + "' not found.");
} else {
// Replace with actual manager ID
String managerId = "11111119";
Identity manager = context.getObjectByName(Identity.class, managerId);
if (manager == null) {
log.error("Manager identity '" + managerId + "' not found.");
} else {
String userDN = (String) identity.getAttribute("adDn");
String managerDN = (String) manager.getAttribute("adDn");
String managerFN = (String) manager.getAttribute("firstname");
String managerLN = (String) manager.getAttribute("lastname");
String managerEmail = (String) manager.getAttribute("emailAzure");
log.error("userDN: " + userDN);
log.error("managerDN: " + managerDN);
log.error("managerFN: " + managerFN);
log.error("managerLN: " + managerLN);
log.error("managerEmail: " + managerEmail);
// Check if manager's DN does not contain "DC=mexqas"
if (managerDN != null && !managerDN.contains("DC=mexqas")) {
// Construct DN for contact object
String contactDN = "cn=" + managerFN + " " + managerLN + ",OU=Contacts,DC=mexqas,DC=com";
log.error("contactDN: " + contactDN);
// Fetch the AD application
Application adApp = context.getObjectByName(Application.class, "Mexichem-Application-ActiveDirectory");
log.error("adApp: " + adApp);
if (adApp != null) {
try {
// Create a new AccountRequest
ProvisioningPlan provisioningPlan = new ProvisioningPlan();
AccountRequest accountRequest = new AccountRequest();
accountRequest.setOperation(AccountRequest.Operation.Create);
accountRequest.setApplication(adApp.getName());
accountRequest.setNativeIdentity(contactDN);
// Create and add AttributeRequests to the AccountRequest
//accountRequest.add(new AttributeRequest("objectClass","contact"));
accountRequest.add(new AttributeRequest("objectType", "contact"));
accountRequest.add(new AttributeRequest("cn", managerFN + " " + managerLN));
accountRequest.add(new AttributeRequest("sn", managerLN));
accountRequest.add(new AttributeRequest("givenName", managerFN));
accountRequest.add(new AttributeRequest("mail", managerEmail));
log.error("Added attributes to accountRequest: " + accountRequest.toXml());
// Create a new ProvisioningPlan
provisioningPlan.add(accountRequest);
provisioningPlan.setIdentity(manager);
log.error("provisioningPlan: " + provisioningPlan.toXml());
//return provisioningPlan;
// Use Provisioner to execute the provisioning plan
Provisioner provisioner = new Provisioner(context);
provisioner.execute(provisioningPlan);
log.error("provisioner: " + provisioner);
context.commitTransaction();
log.info("Contact object creation request submitted successfully for Active Directory.");
} catch (GeneralException e) {
context.rollbackTransaction();
log.error("Failed to submit contact object creation request for Active Directory: " + e.getMessage());
}
} else {
log.error("Active Directory application 'Mexichem-Application-ActiveDirectory' not found.");
}
} else {
log.warn("Manager's DN contains 'OU=Network.Local'.");
}
}
}
} catch (GeneralException e) {
log.error("GeneralException: " + e.getMessage());
}
</Source>
</Rule>
Looks like a single value allowed at AD is passed as a List or multi valued, can you paste the plan from logs and also logs of IQService side along with all details
Code wise I don’t see any Multi value being passed
Also share the provisioning transaction xml as well