Services Standard Before Provisioning Rule

Hi, Does someone have the latest version of Services Standard Before Provisioning Rule 1.7.1 ? Can you please share the latest rule XML file ?

Hi Vijay!

The Standard Services Before Provisioning Rule is a cloud executed rule. As such, only SailPoint can update and push changes to this rule. You should already have the latest version available to your tenant which was last modified 07/22/2022, assuming you requested it to be deployed to your tenant at some point.

If you have never requested it to be deployed to your tenant, see this post. It basically says you can submit a request on support.sailpoint.com to have it deployed to your tenant.

Please let me know if this helps!

  • Zach
1 Like

@vijaylca Here’s the code from version 1.7.1 (available in the attached ZIP file in this article https://community.sailpoint.com/t5/Working-With-Services-Knowledge/IdentityNow-Mock-Project/ta-p/208216


	import sailpoint.object.Application;
	import sailpoint.object.Attributes;
	import sailpoint.object.Filter;
	import sailpoint.object.Identity;
	import sailpoint.object.ManagedAttribute.Type;
	import sailpoint.object.ProvisioningPlan;
	import sailpoint.object.ProvisioningPlan.AccountRequest;
	import sailpoint.object.ProvisioningPlan.AttributeRequest;
	import sailpoint.tools.GeneralException;
	import java.text.SimpleDateFormat;
	import sailpoint.rule.Account;
	import sailpoint.rule.ManagedAttributeDetails;

	/*
	 * Version 1.7.1
	 * Last Updated: 07/18/2022
	 */
	
	/*
	 * Main Application Method. This will go through the passed in event configurations. 
	 * If any event trigger/operation matches, it will apply the action for that configuration
	 * 
	 * The Before Provisioning Rule configurations allows for the generic BeforeProvisioningRule to process common events based on standard best practices. 
	 * The configuration itself is a list of JSON Maps that include 2 basic concepts
	 * 		Trigger: This configuration determines if this event should be applied based on
	 * 			Operation of the account request
	 * 			Identity Attribute values for the user being modified
	 * 			Attribute updates in the account request
	 * 			Entitlement updates in the account request
	 * 			Entitlement Name updates in the account request
	 *			Entitlement Cardinality Update Triggers: This checks if the account request contains 
	 *				updates on the entitlement to either add when previously empty or remove the last entitlement
	 *			Current Account Status Trigger: This checks the current status of the account
	 * 		Action: This configuration determines what to do if this event should be applied. Available options are
	 * 			Move AD Account: Adds AC_NewParent to the specified OU
	 * 			Change Operation: changes the AccountRequest Operation to the specified operation
	 * 			Remove Entitlements: removes all entitlements based on the account in IdentityNow
	 * 			Remove AD Entitlements: sets the memberOf attribute to a list containing the one group
	 * 			Scramble Password: sets the specified attribute to a string of characters
	 * 			Update Attribute: Adds the specified attribute with the specified value
	 * 			Add Argument: Adds the specified argument with the specified value
 	 * 			Add Argument If Not Null: Adds the specified argument with the specified value. If the value is null, it ignores.
	 * 			Throw Error: Throws an error specified in the value attribute or "Unspecified Exception"
	 * 			Stop Processing: Stops processing the BeforeProvisioningRule and proceeds to provisioning.
	 */

	public void applyEventConfigurations(ProvisioningPlan plan, List events) throws GeneralException{
		if(events == null) {
			log.info("Events are null");
			return;
		}
		if(plan == null) {
			log.error("Plan is null");
			return;
		}
		List accountRequests = plan.getAccountRequests();
		Identity identity = plan.getIdentity();
		for(AccountRequest accountRequest: accountRequests) {
			for(Map event: events) {
				// Check if this request matches the event configuration
				log.debug("Check AccountRequest " + accountRequest + " matches event configuration: " + event);
				if(!isAccountRequestMatch(identity, accountRequest, event)) {
					log.info("Match NOT Found");
					continue;
				}
				
				// Found a match. need to apply the configurations associated
				log.info("Match Found ... apply configurations");
				List actionConfigs = (List) event.get("eventActions");
				if(actionConfigs == null || actionConfigs.isEmpty()) {
					log.error("No actions for event");
					continue;
				}
				for(Map actionConfig: actionConfigs) {
					/*
					 * Each action should have
					 * 	Action: What to do
					 *  Attribute: Specific to the action
					 *  Value: Specific to the action
					 */
					String action = (String) actionConfig.get("Action");
					if(action == null) {
						log.error("Action Config does not have Action set");
						continue;
					}
					String attribute = (String) actionConfig.get("Attribute");
					Object value = (String) actionConfig.get("Value");
					switch(action) {
					case "ADMoveAccount":moveADAccount(identity, accountRequest, value);break;
					case "ChangeOperation":changeOperation(accountRequest, value);break;
					case "RemoveEntitlements":removeEntitlements(identity, accountRequest, attribute);break;
					case "RemoveADEntitlements":removeADEntitlements(identity, accountRequest, value);break;
					case "ScramblePassword":scramblePassword(accountRequest, attribute, value);break;
					case "UpdateAttribute":updateAttribute(identity, accountRequest, attribute, value);break;
					case "AddArgument":addArgument(identity, accountRequest, attribute, value);break;
					case "AddArgumentIfNotNull":addArgument(identity, accountRequest, attribute, value, true);break;
					case "ThrowError":throwError(identity, accountRequest, attribute, value);break;
					case "StopProcessing":return;
					default: log.error("Invalid Action: " + action);
					}
				}
			}
		}
	}
	
	/*
	 * Helper Action Methods 
	 */
	public void moveADAccount(Identity identity, AccountRequest accountRequest, Object value) {
		log.debug("Enter MoveADAccount: " + value);
		if(accountRequest == null) {
			log.error("MoveADAccount: Invalid Arguments: accountRequest is null");
			return;
		}
		if(value instanceof String) {
			log.debug("MoveADAccount: value is a string, send through replace");
			String newOU = replaceIdentityValue(identity, accountRequest, (String) value);
			log.info("MoveADAccount: replaced new value: " + newOU);
			String currentNativeIdentity = accountRequest.getNativeIdentity();
			if(currentNativeIdentity == null) {
				log.error("moveADAccount: Invalid State: Current Native Identity is null");
				return;
			}
			if(newOU == null || "".equals(newOU)) {
				log.error("moveADAccount: Invalid Arguments: newOU is null");
				return;
			}
			int indexOfCurrentOU = currentNativeIdentity.indexOf(newOU);
			if(indexOfCurrentOU > 0 && !currentNativeIdentity.substring(0, indexOfCurrentOU).contains("OU=")) {
				log.info("moveADAccount: Account already lives in OU");
				return;
			}
			//TODO: Uniqueness checks and serialization
			accountRequest.add(new AttributeRequest("AC_NewParent", ProvisioningPlan.Operation.Set, newOU));
		}
		else{
			log.error("MoveADAccount: value is not a string or is null: " + value);
		}
		log.debug("Exit MoveADAccount");
	}
	public void changeOperation(AccountRequest accountRequest, Object value) {
		log.debug("Enter changeOperation: " + value);
		AccountRequest.Operation newOp = null;
		try {
			newOp = AccountRequest.Operation.valueOf((String) value);
		}
		catch(IllegalArgumentException e) {
			log.error("ChangeOperation: Invalid Operation: " + value);
		}
		if(newOp != null) {
			log.info("ChangeOperation: update Operation: " + newOp);
			accountRequest.setOperation(newOp);
		}
		log.debug("Exit changeOperation");
	}
	public void removeEntitlements(Identity identity, AccountRequest accountRequest, String attribute) {
		log.debug("Enter RemoveEntitlements: " + attribute);
		if(accountRequest == null) {
			log.error("RemoveEntitlements: Invalid Arguments: accountRequest is null");
			return;
		}
		if(attribute == null) {
			log.error("RemoveEntitlements: Invalid Arguments: attribute is null");
			return;
		}
		if(identity == null) {
			log.error("RemoveEntitlements: Invalid Arguments: Identity is null");
			return;
		}

		Account account = getAccount(identity, accountRequest);
		if(null != idn.getRawAccountAttribute(account, attribute)) {
			List ents = asList(idn.getRawAccountAttribute(account, attribute));
			if(ents != null) {
				accountRequest.add(new AttributeRequest(attribute, ProvisioningPlan.Operation.Remove, ents));
			}
		}
		log.debug("Exit RemoveEntitlements");
	}
	public void removeADEntitlements(Identity identity, AccountRequest accountRequest, Object value) {
		log.debug("Enter removeADEntitlements: " + value);
		if(accountRequest == null) {
			log.error("removeADEntitlements: Invalid Arguments: accountRequest is null");
			return;
		}
		String adDomainUsers = null;
		if(value == null) {
			log.error("removeADEntitlements: Invalid Arguments: value is null");
			return;
		}
		else if(value instanceof String) {
			if(((String) value).contains("DC")) {
				adDomainUsers = (String)value;
			}
		}
		if(adDomainUsers == null) {
			log.error("removeADEntitlements: Invalid Arguments: value is not valid");
			return;
		}
		if(identity == null) {
			log.error("removeADEntitlements: Invalid Arguments: Identity is null");
			return;
		}
		List groupList = new ArrayList();
		groupList.add(adDomainUsers);
		accountRequest.add(new AttributeRequest("memberOf", ProvisioningPlan.Operation.Set, groupList));
		log.debug("Exit RemoveEntitlements");
	}
	public void scramblePassword(AccountRequest accountRequest, String attribute, Object value) {
		log.debug("Enter ScramblePassword");
		if(accountRequest == null) {
			log.error("UpdateAttribute: Invalid Arguments: accountRequest is null");
			return;
		}
		if(attribute == null) {
			log.error("UpdateAttribute: Invalid Arguments: attribute is null");
			return;
		}
		String newPassword = "";
		String charset = "ABNCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()_+-={}[]:;<>?,./";
		int count;
		int index;
		int len;
		Random rnd = new Random();
		len = charset.length();
		for (count = 0; count < 18; count++) {
			index = rnd.nextInt(len);
			newPassword += charset.charAt(index);
		}
		accountRequest.add(new AttributeRequest(attribute, ProvisioningPlan.Operation.Set, newPassword));
		//TODO: Update to use PasswordGenerator
		log.debug("Exit ScramblePassword");
	}
	public void updateAttribute(Identity identity, AccountRequest accountRequest, String attribute, Object value) {
		log.debug("Enter UpdateAttribute: " + value);
		if(accountRequest == null) {
			log.error("UpdateAttribute: Invalid Arguments: accountRequest is null");
			return;
		}
		if(attribute == null) {
			log.error("UpdateAttribute: Invalid Arguments: attribute is null");
			return;
		}
		Object newValue = null;
		if(value instanceof String) {
			log.debug("UpdateAttribute: value is a string, send through replace");
			newValue = replaceIdentityValue(identity, accountRequest, (String) value);
			log.info("UpdateAttribute: replaced new value: " + newValue );
		}
		else{
			log.debug("UpdateAttribute: value is not a string or is null: " + value);
			newValue = value;
		}
		accountRequest.add(new AttributeRequest(attribute, ProvisioningPlan.Operation.Set, newValue));
		log.debug("Exit UpdateAttribute");
	}
	public void addArgument(Identity identity, AccountRequest accountRequest, String attribute, Object value) {
		log.debug("Enter AddArgument without ignoreIfNotNull ... calling other method with a false");
		addArgument(identity, accountRequest, attribute, value, false);
	}
	public void addArgument(Identity identity, AccountRequest accountRequest, String attribute, Object value, boolean ignoreIfNotNull) {
		log.debug("Enter AddArgument: " + value);
		log.debug("Ignore If Not Null: " + ignoreIfNotNull);
		if(accountRequest == null) {
			log.error("AddArgument: Invalid Arguments: accountRequest is null");
			return;
		}
		if(attribute == null) {
			log.error("AddArgument: Invalid Arguments: attribute is null");
			return;
		}
		Object newValue = null;
		if(value instanceof String) {
			log.debug("AddArgument: value is a string, send through replace");
			newValue = replaceIdentityValue(identity, accountRequest, (String) value);
			log.info("AddArgument: replaced new value: " + newValue );
		}
		else{
			log.debug("AddArgument: value is not a string or is null: " + value);
			newValue = value;
		}
		Attributes args = accountRequest.getArguments();
		if(args == null) {
			args = new Attributes();
			accountRequest.setArguments(args);
		}
		if((newValue == null || newValue.toString().isEmpty()) && ignoreIfNotNull) {
			log.debug("AddArgument: value is null and ignoreIfNotNull is true ... ignoring");
		}
		else {
			args.put(attribute, newValue);
			log.debug("AddArgument: setting value");
		}
		log.debug("Exit AddArgument");
	}
	public void throwError(Identity identity, AccountRequest accountRequest, String attribute, Object value) throws GeneralException {
		log.debug("Enter throwError: " + value);
		if(value instanceof String)
			throw new GeneralException((String) value);
		else {
			throw new GeneralException("Unspecified Exception");
		}
	}
	
	/*
	 * Helper Matching Methods
	 */
	 
	/*
	 * This is the main matching method. It will check the operation of the account request first.
	 * Operation is the only required check. If there are not triggers, then every request of that operation matches.
	 * if the operation matches, it will then check to see if everything of the following matches. These are all AND matches 
	 * 		Identity Attribute Triggers: Validating against the identity
	 * 		Account Attribute Update Triggers: Validating against updates in the account request
	 * 		Entitlement Update Triggers: Similar to Account Attribute Update Triggers, but checking multivalued attributes Add or Remove
	 * 		Entitlement Name Update Triggers: Similar to Entitlement Update Triggers, but the entitlement's name is checked instead of value.
	 * 		
	 */
	public boolean isAccountRequestMatch(Identity identity, AccountRequest accountRequest, Map event) {
		log.debug("Enter isAccountRequestMatch: " + accountRequest + " on event " + event);
		String opCheck = (String) event.get("Operation");
		if(accountRequest.getOperation().toString().equals(opCheck)) {
			log.debug("Account Request matches op of event: " + opCheck);
			List idAttrTriggers = (List) event.get("Identity Attribute Triggers");
			if(idAttrTriggers != null && !idAttrTriggers.isEmpty()) {
				for(Map trigger: idAttrTriggers) {
					if(isIdentityAttributeConfigMatch(identity, trigger)) {
						log.debug("Identity " + identity + " matches " + trigger);
					}
					else {
						log.debug("Identity " + identity + " doesn't match " + trigger);
						return false;
					}
				}
			}
			List acctAttrUpdateTriggers = (List) event.get("Account Attribute Update Triggers");
			if(acctAttrUpdateTriggers != null && !acctAttrUpdateTriggers.isEmpty()) {
				for(Map trigger: acctAttrUpdateTriggers) {
					if(isAccountAttributeUpdateConfigMatch(accountRequest, trigger)) {
						log.debug("Request matches " + trigger);
					}
					else {
						log.debug("Request doesn't match " + trigger);
						return false;
					}
				}
			}
			List entUpdateTriggers = (List) event.get("Entitlement Update Triggers");
			if(entUpdateTriggers != null && !entUpdateTriggers.isEmpty()) {
				for(Map trigger: entUpdateTriggers) {
					if(isEntitlementUpdateConfigMatch(accountRequest, trigger)) {
						log.debug("Request matches " + trigger);
					}
					else {
						log.debug("Request doesn't match " + trigger);
						return false;
					}
				}
			}
			List entNameUpdateTriggers = (List) event.get("Entitlement Name Update Triggers");
			if(entNameUpdateTriggers != null && !entNameUpdateTriggers.isEmpty()) {
				for(Map trigger: entNameUpdateTriggers) {
					if(isEntitlementNameUpdateConfigMatch(accountRequest, trigger)) {
						log.debug("Request matches " + trigger);
					}
					else {
						log.debug("Request doesn't match " + trigger);
						return false;
					}
				}
			}
			List cardinalityChangeTriggers = (List) event.get("Entitlement Cardinality Update Triggers");
			if(cardinalityChangeTriggers != null && !cardinalityChangeTriggers.isEmpty()) {
				for(Map trigger: cardinalityChangeTriggers) {
					if(isCardinalityMatch(accountRequest, trigger)) {
						log.debug("Request matches " + trigger);
					}
					else {
						log.debug("Request doesn't match " + trigger);
						return false;
					}
				}
			}
			String accountStatusCheck = (String) event.get("Current Account Status Trigger");
			if(accountStatusCheck != null && !accountStatusCheck.equals("")) {
				if(isAccountStatusMatch(accountRequest, accountStatusCheck)) {
					log.debug("Request existing account status matches " + accountStatusCheck);
				}
				else {
					log.debug("Request existing account status doesn't match " + accountStatusCheck);
					return false;
				}
			}
		}
		else {
			log.debug("AccountRequest " + accountRequest + " doesn't match operation check: " + opCheck);
			return false;
		}
		return true;
	}
	public boolean isAccountAttributeUpdateConfigMatch(AccountRequest accountRequest, Map trigger) {
		if(trigger == null) {
			log.error("Error isIdentityAttributeConfigMatch Configuration: trigger is null");
			return false;
		}
		String attr = (String) trigger.get("Attribute");
		String op = (String) trigger.get("Operation");
		String val = (String) trigger.get("Value");
		if(attr == null) {
			log.error("Error Configuration: attribute is null");
			return false;
		}
		if(!"eq".equals(op) && !"ne".equals(op)) {
			log.error("Error Configuration: operation is invalid: " + op);
			return false;
		}
		List attrReqs = accountRequest.getAttributeRequests();
		if(attrReqs != null) {
			for(AttributeRequest attrReq: attrReqs) {
				if(attr.equals(attrReq.getName())) {
					Object attrVal = attrReq.getValue();
					boolean valsMatch = isValueMatch(attrVal, val);
					if("eq".equals(op) && !valsMatch) {
						log.debug("Check for eq did not match: " + attrVal + " vs " + val);
						return false;
					}
					if("ne".equals(op) && valsMatch) {
						log.debug("Check for ne matched: " + attrVal + " vs " + val);
						return false;
					}
					log.debug("Found match for attribute change " + op + " " + val);
					return true;
				}
			}
		}
		return false;
	}
	public boolean isIdentityAttributeConfigMatch(Identity identity, Map trigger) {
		log.debug("Enter isIdentityAttributeConfigMatch: " + identity + " " + trigger);
		if(trigger == null) {
			log.error("Error isIdentityAttributeConfigMatch Configuration: trigger is null");
			return false;
		}
		String attr = (String) trigger.get("Attribute");
		String op = (String) trigger.get("Operation");
		String val = (String) trigger.get("Value");
		if(attr == null) {
			log.error("Error Configuration: attribute is null");
			return false;
		}
		if(!"eq".equals(op) && !"ne".equals(op)) {
			log.error("Error Configuration: operation is invalid: " + op);
			return false;
		}
		log.debug("Replace value: " + val);
		Object idVal = identity.getAttribute(attr);
		boolean valsMatch = isValueMatch(idVal, val);
		if("eq".equals(op) && !valsMatch) {
			log.debug("Check for eq did not match: " + idVal + " vs " + val);
			return false;
		}
		if("ne".equals(op) && valsMatch) {
			log.debug("Check for ne matched: " + idVal + " vs " + val);
			return false;
		}
		return true;
	}
	public boolean isEntitlementUpdateConfigMatch(AccountRequest accountRequest, Map trigger) {
		log.debug("Enter isEntitlementUpdateConfigMatch: " + identity + " " + trigger);
		if(trigger == null) {
			log.error("Error isEntitlementUpdateConfigMatch Configuration: trigger is null");
			return false;
		}
		String attr = (String) trigger.get("Attribute");
		String op = (String) trigger.get("Operation");
		String val = (String) trigger.get("Value");
		if(attr == null) {
			log.error("Error Configuration: attribute is null");
			return false;
		}
		if(!"Add".equals(op) && !"Remove".equals(op) && !"Set".equals(op)) {
			log.error("Error Configuration: operation is invalid: " + op);
			return false;
		}
		log.debug("Check change in group: " + val);
		boolean foundMatch = false;
		List entAttrReqs = accountRequest.getAttributeRequests(attr);
		if(entAttrReqs != null) {
			for(AttributeRequest entAttrReq: entAttrReqs) {
				boolean doCompare = false;
				if("Add".equals(op) && entAttrReq.getOperation() == ProvisioningPlan.Operation.Add) {
					log.debug("Looking at an Add and operation in trigger is Add");
					doCompare = true;
				}
				else if("Remove".equals(op) && entAttrReq.getOperation() == ProvisioningPlan.Operation.Remove) {
					log.debug("Looking at an Remove and operation in trigger is Remove");
					doCompare = true;
				}
				else if("Set".equals(op) && entAttrReq.getOperation() == ProvisioningPlan.Operation.Set) {
					log.debug("Looking at an Set and operation in trigger is Set");
					doCompare = true;
				}
				if(doCompare) {
					List valuesUpdated = asList(entAttrReq.getValue());
					if(valuesUpdated != null) {
						for(String update: valuesUpdated){
							if(isValueMatch(update, val)) {
								foundMatch = true;
							}
						}
					}
				}
			}
		}
		return foundMatch;
	}

	public boolean isCardinalityMatch(AccountRequest accountRequest, Map trigger) {
		log.debug("Enter isCardinalityMatch: " + accountRequest + " " + trigger);
		if(trigger == null) {
			log.error("Error isCardinalityMatch Configuration: trigger is null");
			return false;
		}
		String attr = (String) trigger.get("Attribute");
		String op = (String) trigger.get("Operation");
		

		if(attr == null) {
			log.error("Error Configuration: attribute is null");
			return false;
		}

		if(op == null) {
			log.error("Error Configuration: operation is null");
			return false;
		}
		Identity identity = plan.getIdentity();
		log.debug("Have Identity: " + identity);
		Account acct = getAccount(identity, accountRequest);
		log.debug("Have acct: " + acct);
		if(acct == null) {
			log.error("Error Configuration: account doesn't exist");
			return false;
		}
		
		String scenario = "";
		if (op.toUpperCase().equals("LASTREMOVED")) {
			scenario = "LASTREMOVED";
		}
		else if (op.toUpperCase().equals("FIRSTADDED")) {
			scenario = "FIRSTADDED";
		}
		else {
			//Currently, we only care if we are adding the first or removing the last.
			//Therefore, only the configuration combinations above are valid.
			//Theorertically we can expand this to look at arbitrary
			//cardinalities and comparators
			log.error("Error Configuration: operation is invalid: " + op);
			return false;
		}

		log.debug("Check change in group cardinality for attribute " + attr);
		boolean foundMatch = false;
		Map pendingChangeMap = new HashMap();
		List entAttrReqs = accountRequest.getAttributeRequests(attr);
		log.debug("Have entAttrReqs: " + entAttrReqs);
		if(entAttrReqs != null && !entAttrReqs.isEmpty()) {
			for(Object entAttrReqObj: entAttrReqs) {
				AttributeRequest entAttrReq = (AttributeRequest) entAttrReqObj;
				boolean collectForAnalysis = false;
				if(entAttrReq.getOperation() == ProvisioningPlan.Operation.Add) {
					log.debug("Looking at an Add and operation in trigger has cardinality 1");
					collectForAnalysis = true;
				}
				else if(entAttrReq.getOperation() == ProvisioningPlan.Operation.Remove) {
					log.debug("Looking at a Remove and operation in trigger has cardinality 0");
					collectForAnalysis = true;
				}
				else {
					//Currently, we are ignoring set commands because 
					//in most instances this should not result in a 
					//change in cardinality. However, we may want to
					//add this feature one day to support potential
					//corner cases or plans that have not been compiled
					//against an accurate account model.
					log.debug("Not an add or remove. Ignoring attribute request");
					collectForAnalysis = false;
				}
				if(collectForAnalysis) {
					List valuesUpdated = asList(entAttrReq.getValue());
					List changesForOperation = (List) pendingChangeMap.get(entAttrReq.getOperation());
					if (changesForOperation != null) {
						changesForOperation.addAll(valuesUpdated);

					}
					else {
						changesForOperation = new ArrayList();
						changesForOperation.addAll(valuesUpdated);
					}
					pendingChangeMap.put(entAttrReq.getOperation(), changesForOperation);
					log.debug("Updated map after request: " + pendingChangeMap);
				}
			}
			List currentValues = asList(idn.getRawAccountAttribute(acct, attr));
			log.debug("currentValues: " + currentValues);

			int initialValuesSize = 0;
			if (currentValues != null && currentValues.size() > 0) {
				initialValuesSize = currentValues.size();
			}
			else {
				currentValues = new ArrayList();
			}
			log.debug("initialValuesSize: " + initialValuesSize);
			List valuesToAdd = (List) pendingChangeMap.get(ProvisioningPlan.Operation.Add);
			if (valuesToAdd != null) {
				currentValues.addAll(valuesToAdd);
			}
			List valuesToRemove = (List) pendingChangeMap.get(ProvisioningPlan.Operation.Remove);
			if (valuesToRemove != null) {
				currentValues.removeAll(valuesToRemove);
			}
			int targetValuesSize = currentValues.size();
			log.debug("targetValuesSize: " + targetValuesSize);
			if ("FIRSTADDED".equals(scenario) && initialValuesSize == 0 && targetValuesSize > 0) {
				foundMatch = true;
			}
			else if ("LASTREMOVED".equals(scenario) && initialValuesSize > 0 && targetValuesSize == 0) {
				foundMatch = true;
			}
		}
		return foundMatch;
	}

	public boolean isAccountStatusMatch(AccountRequest accountRequest, String status) {
		log.debug("Enter isAccountStatusMatch: " + accountRequest + " " + status);
		if(status == null) {
			log.error("Error Configuration: Status is null");
			return false;
		}

		Identity identity = plan.getIdentity();
		Account acct = getAccount(identity, accountRequest);

		if (acct == null) {
			//This is likely a create operation, so we can't match
			log.debug("Exit isAccountStatusMatch: no account");
			return false;
		}
		
		if (status.toUpperCase().equals("ACTIVE")) {
			log.debug("Exit isAccountStatusMatch: " + !acct.isDisabled());
			return !acct.isDisabled();
		}
		else if (status.toUpperCase().equals("DISABLED")) {
			log.debug("Exit isAccountStatusMatch: " + acct.isDisabled());
			return acct.isDisabled();
		}
		else {
			//Invalid Configuration
			log.error("Error Configuration: Status is invalid: " + status);
			return false;
		}
	}
	public boolean isEntitlementNameUpdateConfigMatch(AccountRequest accountRequest, Map trigger) {
		log.debug("Enter isEntitlementNameUpdateConfigMatch: " + identity + " " + trigger);
		if(trigger == null) {
			log.error("Error isEntitlementNameUpdateConfigMatch Configuration: trigger is null");
			return false;
		}
		String attr = (String) trigger.get("Attribute");
		String op = (String) trigger.get("Operation");
		String val = (String) trigger.get("Value");
		if(attr == null) {
			log.error("Error Configuration: attribute is null");
			return false;
		}
		if(!"Add".equals(op) && !"Remove".equals(op) && !"Set".equals(op)) {
			log.error("Error Configuration: operation is invalid: " + op);
			return false;
		}
		log.debug("Check change in group: " + val);
		boolean foundMatch = false;
		List entAttrReqs = accountRequest.getAttributeRequests(attr);
		if(entAttrReqs != null) {
			for(AttributeRequest entAttrReq: entAttrReqs) {
				boolean doCompare = false;
				if("Add".equals(op) && entAttrReq.getOperation() == ProvisioningPlan.Operation.Add) {
					log.debug("Looking at an Add and operation in trigger is Add");
					doCompare = true;
				}
				else if("Remove".equals(op) && entAttrReq.getOperation() == ProvisioningPlan.Operation.Remove) {
					log.debug("Looking at an Remove and operation in trigger is Remove");
					doCompare = true;
				}
				else if("Set".equals(op) && entAttrReq.getOperation() == ProvisioningPlan.Operation.Set) {
					log.debug("Looking at an Set and operation in trigger is Set");
					doCompare = true;
				}
				if(doCompare) {
					List valuesUpdated = asList(entAttrReq.getValue());
					if(valuesUpdated != null) {
						for(String update: valuesUpdated){
							String entitlementDisplayName = getEntitlementDisplayName(application.getId(), attr, update);
							if(entitlementDisplayName != null && isValueMatch(entitlementDisplayName, val)) {
								foundMatch = true;
							}
						}
					}
				}
			}
		}
		return foundMatch;
	}
	/*
	 * Helper Utility Methods
	 */
	 
	/*
	 * Is Value Match is used when determining a specific val1 (identity attribute, account attribute, request attribute) matches val2
	 * If val2 contains wild cards "*" or "?", then we need to check the wild card match method
	 */
	public boolean isValueMatch(Object val1, String val2) {
		log.debug("Enter isValueMatch: " + val1 + " " + val2);
		if(val1 instanceof String) {
			val1 = replaceNullValuesWithNull((String) val1);
		}
		if(val2 instanceof String) {
			val2 = replaceNullValuesWithNull(val2);
		}
		if(val1 == null && val2 == null) {
			log.debug("Exit isValueMatch: Both null");
			return true;
		}
		if(val1 == null && val2 != null) {
			log.debug("Exit isValueMatch: obj1 null, val2 not");
			return false;
		}
		if(val1 != null && val2 == null) {
			log.debug("Exit isValueMatch: obj1 not null, val2 null");
			return false;
		}
		
		if(val1 instanceof String) {
			log.debug("val1 is string, comparing against val2");
			if(val2.contains("?") || val2.contains("*")) {
				log.debug("val2 contains wild cards. checking wild card match");
				return wildCardMatch((String) val1, val2);
			}
			if(((String) val1).equals(val2)) {
				log.debug("val1 matches val2");
				return true;
			}
		}
		return false;
	}
	
	// The function that checks if
	// two given strings match. The first string
	// may contain wild card characters "?" and "*"
	public boolean wildCardMatch(String value, String pattern)
	{
		log.debug("Enter wildCardMatch: " + value + " against " + pattern);
		// If we reach at the end of both strings,
		// we are done
		if (pattern.length() == 0 && value.length() == 0) {
			log.debug("Exit wildCardMatch: each value is empty now");
				return true;
		}
 
		// Make sure that the characters after '*'
		// are present in second string.
		// This function assumes that the first
		// string will not contain two consecutive '*'
		if (pattern.length() > 1 && pattern.charAt(0) == '*' &&
				value.length() == 0) {
			log.debug("Exit wildCardMatch: pattern contains additional characters but value is empty");
			return false;
		}
 
		// If the first string contains '?',
		// or current characters of both strings match
		if ((pattern.length() > 1 && pattern.charAt(0) == '?') ||
				(pattern.length() != 0 && value.length() != 0 &&
						pattern.charAt(0) == value.charAt(0))) {
			log.debug("Recurse wildCardMatch: first character of " + value + " matches first character of " + pattern);
			return wildCardMatch(value.substring(1),
						pattern.substring(1));
		}
		// If there is *, then there are two possibilities
		// a - We consider current character of second string
		// b - We ignore current character of second string.
		if (pattern.length() > 0 && pattern.charAt(0) == '*') {
			log.debug("Recurse wildCardMatch: first character of " + pattern + " is a wild card need to check rest of pattern AND rest of string against " + value);
			return wildCardMatch(value, pattern.substring(1)) ||
					wildCardMatch(value.substring(1), pattern);
		}
		log.debug("Exit wildCardMatch: value " + value + " does not match pattern " + pattern);
		return false;
	}
	
	public String replaceNullValuesWithNull(String val) {
		if("#{null}".equals(val) || "".equals(val)){
			return null;
		}
		return val;
	}
	public String replaceIdentityValue(Identity identity, AccountRequest accountRequest, String val) {
		log.debug("Enter replaceIdentityValue: " + identity + ": " + val);
		if("#{null}".equals(val) || val == null){
			return null;
		}
		log.debug("Start with val: " + val);
		if(val.contains("#{now}")) {
			val = val.replace("#{now}", (new Date()).toString());
		}
		log.debug("Val after now replacement: " + val);
		int nextReplaceCount = val.split("#\\{now\\.").length - 1;  // Adding '}' to allow for validator to ignore mismatched counts of braces
		for (int i = 1; i <= nextReplaceCount && val.contains("#{now"); i++) { // Adding '}' to allow for validator to ignore mismatched counts of braces
			log.debug("Val still has now ... has a format change: " + val);
			String dateRegex = "(.*)(#\\{now\\.)([^\\}]*)(\\})(.*)";  // Adding '{' to allow for validator to ignore mismatched counts of braces
			String dateFormat = val.replaceAll(dateRegex, "$3");
			log.debug("Date Format: " + dateFormat);
			if(dateFormat == null) {
				dateFormat = "yyyy-MM-dd";
			}
			String formattedDate = "";
			switch(dateFormat){
			case "EPOCH_TIME_WIN32":
				Long win32TimeStamp = ((new Date()).getTime()+ 11644473600000L) * 10000L ;
				formattedDate = "" + win32TimeStamp;
				break;
			case "EPOCH_TIME_JAVA":
				formattedDate = "" + (new Date()).getTime();
				break;
			case "EPOCH_TIME":
				formattedDate = "" + ((new Date()).getTime() / 1000);
				break;
			default: 
				SimpleDateFormat sdf = null;
				log.debug("Use SimpleDateFormat for " + dateFormat);
				if("ISO8601".equals(dateFormat)) {
					sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
			}
			else {
					sdf = new SimpleDateFormat(dateFormat);
				}
				formattedDate = sdf.format(new Date());
			}
			log.debug("replace #{now." + dateFormat+ "} with " + formattedDate);
			val = val.replace("#{now." + dateFormat+ "}", formattedDate);
			
			log.debug("Val replaced with date formated change: " + val);
		}
		nextReplaceCount = val.split("#\\{identity\\.").length - 1;  // Adding '}' to allow for validator to ignore mismatched counts of braces
		for (int i = 1; i <= nextReplaceCount && val.contains("#{identity"); i++) {  // Adding '}' to allow for validator to ignore mismatched counts of braces
			String attributeRegex = "(.*)(#\\{identity\\.)([^\\}]*)(\\})(.*)";  // Adding '{' to allow for validator to ignore mismatched counts of braces
			String attributeName = val.replaceAll(attributeRegex, "$3");
			log.debug("Identity attribute to grab: " + attributeName);
			String identityVal = null; 
			if(identity == null) {
				log.warn("No Identity");
			}
			else {
				identityVal = (String) identity.getAttribute(attributeName);
			}
				if(identityVal == null) {
					identityVal = "";
				}
			val = val.replace("#{identity." + attributeName + "}", identityVal);
				log.debug("Val replaced with attribute: " + val);
			}
		nextReplaceCount = val.split("#\\{manager\\.").length - 1;  // Adding '}' to allow for validator to ignore mismatched counts of braces
		for (int i = 1; i <= nextReplaceCount && val.contains("#{manager"); i++) {  // Adding '}' to allow for validator to ignore mismatched counts of braces
			String attributeRegex = "(.*)(#\\{manager\\.)([^\\}]*)(\\})(.*)";  // Adding '{' to allow for validator to ignore mismatched counts of braces 
			String attributeName = val.replaceAll(attributeRegex, "$3");
			log.debug("Manager attribute to grab: " + attributeName);
			String identityVal = null; 
			if(identity == null || identity.getManager() == null) {
				log.warn("No Manager");
			}
			else {
				identityVal = (String) identity.getManager().getAttribute(attributeName);
			}
			if(identityVal == null) {
				identityVal = "";
		}
			val = val.replace("#{manager." + attributeName + "}", identityVal);
			log.debug("Val replaced with attribute: " + val);
		}
		nextReplaceCount = val.split("#\\{account\\.").length - 1;  // Adding '}' to allow for validator to ignore mismatched counts of braces
		for (int i = 1; i <= nextReplaceCount && val.contains("#{account"); i++) {  // Adding '}' to allow for validator to ignore mismatched counts of braces
			String attributeRegex = "(.*)(#\\{account\\.)([^\\}]*)(\\})(.*)";  // Adding '{' to allow for validator to ignore mismatched counts of braces
			String attributeName = val.replaceAll(attributeRegex, "$3");
			log.debug("Account attribute to grab: " + attributeName);
			Account account = getAccount(identity, accountRequest);
			log.debug("Got account");
			String accountVal = "";
			if(account != null) {
				accountVal = idn.getAccountAttribute(account, attributeName);
				log.debug("Got account attribute value: " + accountVal);
			}
			if(accountVal == null) {
				accountVal = "";
			}
			val = val.replace("#{account." + attributeName + "}", accountVal);
			log.debug("Val replaced with attribute: " + val);
		}
		log.debug("Exit replaceIdentityValue: " + val);
		return val;
	}
	
	/*
	 * Method takes an object and returns a list. If the object is already a list, it returns the object
	 * Otherwise it'll return a list containing the object if not null
	 */
	public List asList(Object val){
		if(val == null) {
			return null;
		}
		List retList = null;
		if(val instanceof List) {
			retList = (List) val;
		}
		else if(val instanceof String) {
			retList = new ArrayList();
			retList.add((String) val);
		}
		return retList;
	}
	
	public Account getAccount(Identity identity, AccountRequest accountRequest) {
		String appName = accountRequest.getApplicationName();
		String nativeId = accountRequest.getNativeIdentity();
		Account account =  idn.getAccountByNativeIdentity(appName,nativeId);
		return account;
	}
	
	public String getEntitlementDisplayName(String id, String attr, String value) {
		ManagedAttributeDetails entDetails = idn.getManagedAttributeDetails(id, attr, value, Type.Entitlement);
		Map entAttrs = entDetails != null ? entDetails.getAttributes() : null;
		return (entAttrs != null && entAttrs.containsKey("displayName")) ? (String) entAttrs.get("displayName") : null;
	}
	
	/*
		Main Running Code 
	*/
	Attributes appAtts = application.getAttributes();
	if(appAtts != null && appAtts.get("cloudServicesIDNSetup") != null) {
		
		Map idnSetup = (Map) appAtts.get("cloudServicesIDNSetup");
		log.debug("In Before Provisioning Rule :::: idnSetup: " + idnSetup);
		List eventConfigs = (List) idnSetup.get("eventConfigurations");
		log.debug("In Before Provisioning Rule :::: eventConfigs: " + eventConfigs);
		applyEventConfigurations(plan, eventConfigs);
	}
	```

Great!! thank you very much @edmarks and @zachm117

Once it is deployed in your tenant, you can export and import the rule to another with sp-config, without needing to go through expert services. Example: Moving from a Sandbox to Production.

1 Like

This is now available in the Mock Project so it can be deployed to Sandbox directly without Expert Services now. The file provided contains the hashes needed in sp_config so this is much easier to deploy now.

2 Likes

The Standard Services Before Provisioning Rule 1.7.1 has been deployed in our tenant for AD. I can see no changes in my rule when compared to SailPoint’s original rule. I’m curious about the necessity of having this rule in our system. Is this rule a mandatory?

Not mandatory, but highly useful for a number of AD specific use cases. 1.7.1 is the latest version that I’m aware of and by definition the code should match if both are 1.7.1.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.