import sailpoint.object.Policy;
import sailpoint.object.PolicyViolation;
import sailpoint.object.Identity;
import sailpoint.object.Bundle;
import sailpoint.object.Application;
import sailpoint.object.Custom;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Set; // Imported but not used, can be removed if not needed elsewhere
/**
* Helper function to convert a list of objects (expected to be Bundles) to a list of their names (Strings).
* Handles cases where assigned roles might already be Strings.
*/
public List<String> getRoleNames(List<?> roles) {
List<String> roleNames = new ArrayList<>();
if (roles != null) {
for (Object roleObj : roles) {
if (roleObj instanceof Bundle) {
Bundle role = (Bundle) roleObj;
if (role != null && role.getName() != null) {
roleNames.add(role.getName());
}
} else if (roleObj instanceof String) { // Handle if roles are already Strings
roleNames.add((String) roleObj);
} else {
// Log if an unexpected object type is found
log.debug("getRoleNames: Encountered unexpected object type in roles list: " + (roleObj != null ? roleObj.getClass().getName() : "null"));
}
}
}
return roleNames;
}
log.debug("Inside Policy Violation rule: SLF_Rule_Policy_Violation_Generic_OneRoleAtATime_AppName.");
PolicyViolation policyViolation = null;
StringBuilder violationMessage = new StringBuilder();
// Get the optimistic identity (what the user *will* have)
Identity expectedIdentity = identity;
// Get the current identity from the database for comparison
Identity oldIdentity = context.getObjectByName(Identity.class, identity.getName());
if (oldIdentity == null) {
log.error("Policy Rule: Could not retrieve old identity for " + identity.getName() + ". Aborting policy check.");
return null; // Cannot proceed without the old identity
}
// Convert Bundle lists to List<String> of role names
List<String> allRoles = getRoleNames(expectedIdentity.getAssignedRoles());
List<String> existingRoles = getRoleNames(oldIdentity.getAssignedRoles());
// Determine newly requested roles by removing existing roles from the optimistic roles
List<String> newlyRequestedRoles = new ArrayList<>(allRoles);
// Remove existing roles from the optimistic list to get only the NEW ones
if (existingRoles != null) {
newlyRequestedRoles.removeAll(existingRoles);
}
log.debug("Newly requested roles: " + newlyRequestedRoles);
log.debug("Existing roles: " + existingRoles);
Map<String, List<String>> roleMap = new HashMap<>(); // Use generics for better type safety
Custom OneRoleAtATimeCustomObject = context.getObjectByName(Custom.class, "SLF - Custom - OneRoleAtATime");
if (OneRoleAtATimeCustomObject != null) {
Object configObj = OneRoleAtATimeCustomObject.get("OneRoleAtATime_Applications_Config");
if (configObj instanceof Map) {
// Safe cast to Map<String, List<String>>
roleMap = (Map<String, List<String>>) configObj;
log.debug("Custom object 'SLF - Custom - OneRoleAtATime' config loaded. Entries: " + roleMap.size());
} else {
log.error("Policy Rule: Custom object 'SLF - Custom - OneRoleAtATime' attribute 'OneRoleAtATime_Applications_Config' is not a Map. Actual type: " + (configObj != null ? configObj.getClass().getName() : "null"));
return null; // Exit if configuration is malformed
}
} else {
log.error("Policy Rule: Custom object 'SLF - Custom - OneRoleAtATime' not found. Policy cannot be evaluated.");
return null; // Exit if configuration object is missing
}
boolean violationDetected = false; // Flag to indicate if any violation was found
// Iterate through the application configurations from the Custom object
for (Map.Entry<String, List<String>> entry : roleMap.entrySet()) {
String applicationName = entry.getKey();
List<String> configuredRolesForApp = entry.getValue(); // Roles configured for this specific application
List<String> matchingNewlyRequestedRoles = new ArrayList<>();
List<String> matchingExistingRoles = new ArrayList<>();
// Find NEWLY REQUESTED roles that match this app's configuration
for (String singleConfiguredRole : configuredRolesForApp) {
if (newlyRequestedRoles.contains(singleConfiguredRole)) {
matchingNewlyRequestedRoles.add(singleConfiguredRole);
}
}
// Find EXISTING roles that match this app's configuration
for (String singleConfiguredRole : configuredRolesForApp) {
if (existingRoles.contains(singleConfiguredRole)) {
matchingExistingRoles.add(singleConfiguredRole);
}
}
// --- VIOLATION TYPE 1: Multiple NEW roles requested for the same application ---
if (matchingNewlyRequestedRoles.size() > 1) {
violationDetected = true;
if (violationMessage.length() > 0) {
violationMessage.append("\n"); // Add a newline for multiple violation messages
}
violationMessage.append("Policy Violation for application: '").append(applicationName).append("'. ");
violationMessage.append("You have requested multiple NEW roles (");
violationMessage.append(String.join(", ", matchingNewlyRequestedRoles));
violationMessage.append(") for this application. Please request only one role at a time.");
log.debug("Violation: Multiple new roles requested for app '" + applicationName + "': " + matchingNewlyRequestedRoles);
}
// --- VIOLATION TYPE 2: Requesting any NEW role when existing configured roles are present ---
// This applies if any NEW role was requested for this app
// AND if the user already has any configured role for this app.
if (!matchingNewlyRequestedRoles.isEmpty() && !matchingExistingRoles.isEmpty()) {
violationDetected = true;
if (violationMessage.length() > 0) {
violationMessage.append("\n");
}
violationMessage.append("Policy Violation for application: '").append(applicationName).append("'. ");
violationMessage.append("You have requested new roles (");
violationMessage.append(String.join(", ", matchingNewlyRequestedRoles));
violationMessage.append(") while already having existing roles (");
violationMessage.append(String.join(", ", matchingExistingRoles));
violationMessage.append(") for this application. Please remove existing roles before requesting new ones, or request a modification if allowed.");
log.debug("Violation: New roles requested while existing roles present for app '" + applicationName + "'. New: " + matchingNewlyRequestedRoles + ", Existing: " + matchingExistingRoles);
}
// You might also want a policy that says: "You can NEVER have more than one configured role for this app AT ALL"
// If so, you'd combine newlyRequestedRoles + existingRoles for the app and check if size > 1.
// That's a different policy logic. This implements the "don't request new if existing" specifically.
}
// Create the PolicyViolation object ONLY if a violation was detected
if (violationDetected) {
policyViolation = new PolicyViolation();
policyViolation.setActive(true);
policyViolation.setIdentity(expectedIdentity);
policyViolation.setPolicy(policy);
// Set the constraint if you want the violation to be linked to a specific constraint within the policy
// policyViolation.setConstraint(constraint);
policyViolation.setDescription(violationMessage.toString()); // Set the accumulated dynamic description
policyViolation.setStatus(sailpoint.object.PolicyViolation.Status.Open);
log.debug("Final Policy Violation description: " + policyViolation.getDescription());
} else {
log.debug("No policy violations detected for identity " + identity.getName());
}
return policyViolation;