001package com.identityworksllc.iiq.common; 002 003import com.identityworksllc.iiq.common.annotation.InProgress; 004import sailpoint.api.Formicator; 005import sailpoint.api.SailPointContext; 006import sailpoint.object.Application; 007import sailpoint.object.AttributeDefinition; 008import sailpoint.object.Attributes; 009import sailpoint.object.Field; 010import sailpoint.object.Form; 011import sailpoint.object.ManagedAttribute; 012import sailpoint.object.Schema; 013import sailpoint.tools.GeneralException; 014import sailpoint.tools.MapUtil; 015import sailpoint.tools.Util; 016 017import java.util.*; 018 019/** 020 * Utilities for ManagedAttributes / entitlement objects 021 */ 022@InProgress 023public class ManagedAttributeUtilities extends AbstractBaseUtility { 024 025 /** 026 * Namespace for arguments that can be passed to {@link #buildForm(Map, Option...)}. 027 */ 028 public static final class BuildForm { 029 /** 030 * The interface used to cluster options that can be passed to {@link #buildForm(Map, Option...)} 031 */ 032 public interface Option {} 033 034 /** 035 * Yes/no flags as form options 036 */ 037 public enum Flags implements BuildForm.Option { 038 RequireComments, 039 AllowExit, 040 AllowCancel 041 } 042 043 /** 044 * A button option for forms 045 */ 046 public static class Button implements BuildForm.Option { 047 /** 048 * Builds an {@link Option} indicating that a next Button will be 049 * added to the form with the given Label. 050 * 051 * @param label The label 052 * @return The button option 053 */ 054 public static BuildForm.Option next(String label) { 055 return new Button(Form.ACTION_NEXT, label); 056 } 057 058 /** 059 * Builds an {@link Option} indicating that a next Button will be 060 * added to the form with the given Label. 061 * 062 * @param label The label 063 * @param argumentName The variable to set when this button is clicked 064 * @param argumentValue The value to set in the variable when this button is clicked 065 * @return The button option 066 */ 067 public static BuildForm.Option next(String label, String argumentName, String argumentValue) { 068 return new Button(Form.ACTION_NEXT, label, argumentName, argumentValue); 069 } 070 071 /** 072 * The action for this button 073 */ 074 final String action; 075 076 /** 077 * The label for this button 078 */ 079 final String label; 080 081 /** 082 * The argument set by this button 083 */ 084 final String argumentName; 085 086 /** 087 * The value set to the argument set by this button 088 */ 089 final String argumentValue; 090 091 private Button(String action, String label) { 092 this(action, label, null, null); 093 } 094 095 private Button(String action, String label, String argumentName, String argumentValue) { 096 this.action = action; 097 this.label = label; 098 this.argumentName = argumentName; 099 this.argumentValue = argumentValue; 100 } 101 } 102 } 103 104 /** 105 * Constructs a new utility object 106 * @param context The IIQ context 107 */ 108 public ManagedAttributeUtilities(SailPointContext context) { 109 super(context); 110 } 111 112 /** 113 * Builds a form for the given ManagedAttribute based on the input Map. It 114 * will be derived from the existing Update or Create forms on the relevant 115 * Application. 116 * 117 * The input must be a {@link Map} containing metadata under the key `sys`, 118 * similar to the Maps produced for Form Models by {@link sailpoint.transformer.IdentityTransformer}. 119 * The `sys` map should contain these keys: 120 * 121 * - `nativeIdentity`: The native ID of the entitlement, if we are updating and not creating it 122 * - `application`: The application name 123 * - `type`: The schema name (e.g., _group_); will be used to look up the existing form 124 * 125 * You can build these maps with {@link #getCreateManagedAttributeMap(String, String)} and 126 * {@link #getUpdateManagedAttributeMap(ManagedAttribute)}. 127 * 128 * The resulting Form may be used in a workflow. 129 * 130 * @param map 131 * @param options 132 * @return 133 * @throws GeneralException 134 */ 135 public Form buildForm(Map<String, Object> map, BuildForm.Option... options) throws GeneralException { 136 Form form = new Form(); 137 138 boolean edit = Util.isNullOrEmpty((String) MapUtil.get(map, "sys.nativeIdentity")); 139 140 String appName = Util.otoa(MapUtil.get(map, "sys.application")); 141 String type = Util.otoa(MapUtil.get(map, "sys.type")); 142 143 Application application = context.getObject(Application.class, appName); 144 145 form.setPageTitle(edit ? "Update entitlement" : "Create entitlement"); 146 form.setBasePath("formModel"); 147 148 Form existingForm = application.getProvisioningForm(edit ? Form.Type.Update : Form.Type.Create, type); 149 Formicator formicator = new Formicator(context); 150 151 form.setType(existingForm.getType()); 152 form.setTitle(existingForm.getTitle()); 153 form.setObjectType(existingForm.getObjectType()); 154 form.setOwnerDefinition(existingForm.getOwnerDefinition()); 155 form.setMergeProfiles(existingForm.isMergeProfiles()); 156 form.setIncludeHiddenFields(existingForm.isIncludeHiddenFields()); 157 form.setApplication(application); 158 159 List<Form.Section> sections = existingForm.getSections(); 160 161 for(Form.Section section : Util.safeIterable(sections)) { 162 Form.Section copied = (Form.Section) section.deepCopy(context); 163 form.add(copied); 164 } 165 166 for(BuildForm.Option option : options) { 167 if (option == BuildForm.Flags.RequireComments) { 168 Field commentsField = new Field(); 169 commentsField.setName("sys.comments"); 170 commentsField.setType("string"); 171 commentsField.setRequired(true); 172 commentsField.setSection("__comments"); 173 174 formicator.assemble(form, Collections.singletonList(commentsField)); 175 } else if (option == BuildForm.Flags.AllowExit) { 176 Form.Button exitButton = new Form.Button(); 177 exitButton.setAction(Form.ACTION_NEXT); 178 exitButton.setLabel("Exit Workflow"); 179 exitButton.setParameter("exitWorkflow"); 180 exitButton.setValue("true"); 181 exitButton.setSkipValidation(true); 182 183 form.add(exitButton); 184 } else if (option == BuildForm.Flags.AllowCancel) { 185 Form.Button cancelButton = new Form.Button(); 186 cancelButton.setAction(Form.ACTION_CANCEL); 187 cancelButton.setLabel("Cancel"); 188 cancelButton.setSkipValidation(true); 189 190 form.add(cancelButton); 191 } else if (option instanceof BuildForm.Button) { 192 BuildForm.Button config = (BuildForm.Button) option; 193 Form.Button newButton = new Form.Button(); 194 newButton.setAction(config.action); 195 newButton.setLabel(config.label); 196 if (Util.isNotNullOrEmpty(config.argumentName)) { 197 newButton.setParameter(config.argumentName); 198 } 199 if (Util.isNotNullOrEmpty(config.argumentValue)) { 200 newButton.setValue(config.argumentValue); 201 } 202 203 form.add(newButton); 204 } 205 } 206 207 Form.Button nextButton = new Form.Button(); 208 nextButton.setAction("next"); 209 nextButton.setLabel(edit ? "Update" : "Create"); 210 211 return form; 212 } 213 214 /** 215 * Creates a new Map to pass into {@link #buildForm(Map, BuildForm.Option...)} 216 * 217 * @param applicationName The application name 218 * @param attribute The attribute on that application's account schema 219 * @return A map representation of a new entitlement 220 * @throws GeneralException if anything goes wrong 221 * @throws IllegalArgumentException if the inputs are bad 222 */ 223 public Map<String, Object> getCreateManagedAttributeMap(String applicationName, String attribute) throws GeneralException { 224 Application application = context.getObject(Application.class, applicationName); 225 if (application == null) { 226 throw new IllegalArgumentException("No such application: " + applicationName); 227 } 228 229 Schema accountSchema = application.getAccountSchema(); 230 if (accountSchema == null) { 231 throw new IllegalArgumentException("Application " + applicationName + " has no account schema??"); 232 } 233 234 AttributeDefinition attrDef = accountSchema.getAttributeDefinition(attribute); 235 236 String attrSchemaName = attrDef.getSchemaObjectType(); 237 238 Schema schema = application.getSchema(attrSchemaName); 239 if (schema == null) { 240 throw new IllegalArgumentException("Attribute " + attribute + " specifies non-existent schema: " + attrSchemaName); 241 } 242 243 Map<String, Object> result = new HashMap<>(); 244 result.put("attribute", attrDef.getName()); 245 result.put("value", ""); 246 result.put("displayName", ""); 247 248 MapUtil.put(result, "sys.nativeIdentity", null); 249 MapUtil.put(result, "sys.application", application.getId()); 250 MapUtil.put(result, "sys.type", schema.getName()); 251 MapUtil.put(result, "sys.attribute", attrDef.getName()); 252 253 return result; 254 } 255 256 /** 257 * Creates a new Map to pass into {@link #buildForm(Map, BuildForm.Option...)} 258 * 259 * @param ma The {@link ManagedAttribute} being modified 260 * @return A map representation of a new entitlement 261 * @throws GeneralException if anything goes wrong 262 * @throws IllegalArgumentException if the inputs are bad 263 */ 264 public Map<String, Object> getUpdateManagedAttributeMap(ManagedAttribute ma) throws GeneralException { 265 if (ma == null) { 266 throw new IllegalArgumentException("Cannot provide a null managed attribute"); 267 } 268 269 Application application = ma.getApplication(); 270 Schema schema = application.getSchema(ma.getType()); 271 272 Map<String, Object> result = new HashMap<>(); 273 274 result.put("attribute", ma.getAttribute()); 275 result.put("value", ma.getValue()); 276 result.put("displayName", ma.getDisplayName()); 277 278 for(String key : schema.getAttributeNames()) { 279 Object existing = ma.getAttribute(key); 280 if (Utilities.isSomething(existing)) { 281 result.put(key, existing); 282 } 283 } 284 285 MapUtil.put(result, "sys.id", ma.getId()); 286 MapUtil.put(result, "sys.nativeIdentity", null); 287 MapUtil.put(result, "sys.application", application.getId()); 288 MapUtil.put(result, "sys.type", schema.getName()); 289 MapUtil.put(result, "sys.attribute", ma.getAttribute()); 290 291 return result; 292 } 293}