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}