001package com.identityworksllc.iiq.common;
002
003import com.fasterxml.jackson.annotation.JsonIgnore;
004import com.fasterxml.jackson.annotation.JsonInclude;
005import com.identityworksllc.iiq.common.vo.IIQObject;
006import sailpoint.object.IdentitySelector;
007import sailpoint.object.Rule;
008import sailpoint.object.Script;
009import sailpoint.tools.GeneralException;
010import sailpoint.tools.Util;
011
012import java.io.Serializable;
013import java.util.ArrayList;
014import java.util.Arrays;
015import java.util.Collections;
016import java.util.HashMap;
017import java.util.List;
018import java.util.Map;
019import java.util.StringJoiner;
020import java.util.function.BiConsumer;
021import java.util.stream.Collectors;
022
023/**
024 * This is the implementation of the Common Security configuration object, as expected
025 * by {@link ThingAccessUtils}. The intention is that {@link ObjectMapper} be used
026 * to decode an instance of this class.
027 */
028@JsonInclude(JsonInclude.Include.NON_NULL)
029@SuppressWarnings("unused")
030public class CommonSecurityConfig implements Serializable, MapDecodable {
031    /**
032     * The cached object mapper to use for this decoding. Note that due to classloader
033     * weirdness, plugins may each have their own, potentially obfuscated copy that is
034     * different than the "base" code version.
035     */
036    private final static ObjectMapper<CommonSecurityConfig> objectMapper = ObjectMapper.get(CommonSecurityConfig.class);
037
038    /**
039     * Decodes the given Map into an instance of this class using {@link ObjectMapper}.
040     *
041     * @param input The input map
042     * @return An instance of this class decoded from the Map
043     * @throws GeneralException if any decoding failures occur
044     */
045    public static CommonSecurityConfig decode(Map<String, Object> input) throws GeneralException {
046        try {
047            return objectMapper.decode(input, true);
048        } catch(ObjectMapper.ObjectMapperException e) {
049            throw SailpointObjectMapper.unwrap(e);
050        }
051    }
052
053    private static void handleNested(Map<String, Object> map, String name, List<? extends CommonSecurityConfig> list) {
054        if (Utilities.isNotEmpty(list)) {
055            map.put(name, list.stream().map(CommonSecurityConfig::toMap).collect(Collectors.toCollection(ArrayList::new)));
056        }
057    }
058
059    /**
060     * Creates a new {@link CommonSecurityConfig} that inverts the inputs. If any of
061     * the nested checks passes, the inverted check will fail.
062     *
063     * @param others The other security configs to invert
064     * @return The inverted security config
065     * @throws GeneralException if any failures occur
066     */
067    public static CommonSecurityConfig not(CommonSecurityConfig... others) throws GeneralException {
068        if (others == null || others.length == 0) {
069            throw new IllegalArgumentException("At least one 'not' security string must be provided");
070        }
071        List<CommonSecurityConfig> configs = new ArrayList<>(Arrays.asList(others));
072        CommonSecurityConfig masterConfig = new CommonSecurityConfig();
073        masterConfig.not = configs;
074
075        return masterConfig;
076    }
077
078    /**
079     * Mainly intended for the test scripts, constructs a common security config
080     * based on the single given field name and value. All other values in the
081     * configuration will be empty or defaulted.
082     *
083     * @param field The field name
084     * @param value The field value
085     * @return A common security config as though a map were passed with that field set only
086     * @throws GeneralException if any failures occur during parsing
087     */
088    public static CommonSecurityConfig simple(String field, Object value) throws GeneralException {
089        Map<String, Object> input = new HashMap<>();
090        input.put(field, value);
091        return decode(input);
092    }
093    /**
094     * The original map, or a reconstructed one, captured on decode() or
095     * constructed on the first call to toMap().
096     */
097    @ObjectMapper.Ignore
098    @JsonIgnore
099    private final Map<String, Object> _originalMap;
100    /**
101     * The check will pass if the subject Identity matches the given Filter
102     */
103    /*package*/ String accessCheckFilter;
104    /**
105     * The check will pass if this rule returns Boolean true. The subject and target
106     * will be passed to the script as 'subject' and 'target', respectively.
107     */
108    @IIQObject
109    /*package*/ Rule accessCheckRule;
110    /**
111     * The check will pass if this script returns Boolean true. The subject and target
112     * will be passed to the script as 'subject' and 'target', respectively.
113     */
114    @IIQObject
115    /*package*/ Script accessCheckScript;
116    /**
117     * The check will pass if the subject Identity matches this IdentitySelector
118     */
119    @IIQObject
120    /*package*/ IdentitySelector accessCheckSelector;
121    /**
122     * The check will pass if all of the configurations in this list pass
123     */
124    @ObjectMapper.Nested(CommonSecurityConfig.class)
125    /*package*/ List<CommonSecurityConfig> allOf;
126    /**
127     * The description of this security config, which can be output in debug messages
128     */
129    private String description;
130    /**
131     * The check will always fail because it is disabled
132     */
133    private boolean disabled;
134    /**
135     * The check will fail if the subject Identity has any of the listed capabilities
136     */
137    /*package*/ List<String> excludedCapabilities;
138    /**
139     * The check will fail if the subject Identity has any of the listed rights.
140     */
141    /*package*/ List<String> excludedRights;
142    /**
143     * The check will fail if the subject Identity is a member of any of the listed workgroups
144     */
145    /*package*/ List<String> excludedWorkgroups;
146    /**
147     * The check will fail if the target matches the given Filter
148     */
149    /*package*/ String invalidTargetFilter;
150    /**
151     * The check will pass if the given Quicklink Population would allow access for the subject and target
152     */
153    /*package*/ String mirrorQuicklinkPopulation;
154
155    /**
156     * The check will pass if the given Bundle's access criteria matches this user. The Bundle must
157     * have an IdentitySelector attached, which can be of any type.
158     */
159    /*package*/ String mirrorRole;
160    /**
161     * True if we should NOT cache the results of this check
162     */
163    private boolean noCache;
164    /**
165     * The check will pass if none of the configurations in this list pass
166     */
167    @ObjectMapper.Nested(CommonSecurityConfig.class)
168    /*package*/ List<CommonSecurityConfig> not;
169    /**
170     * The check will pass if any of the configurations in this list passes
171     */
172    @ObjectMapper.Nested(CommonSecurityConfig.class)
173    /*package*/ List<CommonSecurityConfig> oneOf;
174    /**
175     * The check will pass if the subject Identity has any of the listed capabilities
176     */
177    /*package*/ List<String> requiredCapabilities;
178    /**
179     * The check will pass if the subject Identity has any of the listed SPRights
180     */
181    /*package*/ List<String> requiredRights;
182    /**
183     * The check will pass if the subject Identity is a member of any of the listed workgroups
184     */
185    /*package*/ List<String> requiredWorkgroups;
186    /**
187     * If this Plugin setting is TRUE, the security check will always fail
188     */
189    /*package*/ String settingOffSwitch;
190    /**
191     * The check will pass if the target Identity has any of the listed capabilities
192     */
193    /*package*/ List<String> validTargetCapabilities;
194    /**
195     * The check will fail if the target Identity has any of the listed capabilities
196     */
197    /*package*/ List<String> validTargetExcludedCapabilities;
198    /**
199     * The check will fail if the target Identity has any of the listed SPRights
200     */
201    /*package*/ List<String> validTargetExcludedRights;
202    /**
203     * The check will pass if the target Identity matches the given Filter
204     */
205    /*package*/ String validTargetFilter;
206    /**
207     * The check will pass if the target Identity matches this IdentitySelector
208     */
209    @IIQObject
210    /*package*/ IdentitySelector validTargetSelector;
211    /**
212     * The check will pass if the target Identity is a member of any of the listed workgroups
213     */
214    /*package*/ List<String> validTargetWorkgroups;
215
216    /**
217     * Basic constructor, which will initialize the various lists (because Jackson)
218     */
219    public CommonSecurityConfig() {
220        this.not = new ArrayList<>();
221        this.oneOf = new ArrayList<>();
222        this.allOf = new ArrayList<>();
223        this.excludedCapabilities = new ArrayList<>();
224        this.excludedRights = new ArrayList<>();
225        this.excludedWorkgroups = new ArrayList<>();
226        this.validTargetCapabilities = new ArrayList<>();
227        this.validTargetExcludedCapabilities = new ArrayList<>();
228        this.validTargetExcludedRights = new ArrayList<>();
229        this.validTargetWorkgroups = new ArrayList<>();
230        this.requiredCapabilities = new ArrayList<>();
231        this.requiredRights = new ArrayList<>();
232        this.requiredWorkgroups = new ArrayList<>();
233        this._originalMap = new HashMap<>();
234    }
235
236    public String getAccessCheckFilter() {
237        return accessCheckFilter;
238    }
239
240    public Rule getAccessCheckRule() {
241        return accessCheckRule;
242    }
243
244    public Script getAccessCheckScript() {
245        return accessCheckScript;
246    }
247
248    public IdentitySelector getAccessCheckSelector() {
249        return accessCheckSelector;
250    }
251
252    public List<CommonSecurityConfig> getAllOf() {
253        return allOf;
254    }
255
256    public String getDescription() {
257        return description;
258    }
259
260    public List<String> getExcludedCapabilities() {
261        return excludedCapabilities;
262    }
263
264    public List<String> getExcludedRights() {
265        return excludedRights;
266    }
267
268    public List<String> getExcludedWorkgroups() {
269        return excludedWorkgroups;
270    }
271
272    public String getInvalidTargetFilter() {
273        return invalidTargetFilter;
274    }
275
276    public String getMirrorQuicklinkPopulation() {
277        return mirrorQuicklinkPopulation;
278    }
279
280    public String getMirrorRole() {
281        return mirrorRole;
282    }
283
284    public List<CommonSecurityConfig> getNot() {
285        return not;
286    }
287
288    public List<CommonSecurityConfig> getOneOf() {
289        return oneOf;
290    }
291
292    public List<String> getRequiredCapabilities() {
293        return requiredCapabilities;
294    }
295
296    public List<String> getRequiredRights() {
297        return requiredRights;
298    }
299
300    public List<String> getRequiredWorkgroups() {
301        return requiredWorkgroups;
302    }
303
304    public String getSettingOffSwitch() {
305        return settingOffSwitch;
306    }
307
308    public List<String> getValidTargetCapabilities() {
309        return validTargetCapabilities;
310    }
311
312    public List<String> getValidTargetExcludedCapabilities() {
313        return validTargetExcludedCapabilities;
314    }
315
316    public List<String> getValidTargetExcludedRights() {
317        return validTargetExcludedRights;
318    }
319
320    public String getValidTargetFilter() {
321        return validTargetFilter;
322    }
323
324    public IdentitySelector getValidTargetSelector() {
325        return validTargetSelector;
326    }
327
328    public List<String> getValidTargetWorkgroups() {
329        return validTargetWorkgroups;
330    }
331
332    /**
333     * Stores the original map from which this entry was decoded, if possible.
334     *
335     * @param input The input
336     */
337    @Override
338    public void initializeFromMap(Map<String, Object> input) {
339        if (this._originalMap.isEmpty()) {
340            this._originalMap.putAll(input);
341            handleNested(this._originalMap, "oneOf", this.oneOf);
342            handleNested(this._originalMap, "allOf", this.allOf);
343            handleNested(this._originalMap, "not", this.not);
344        }
345    }
346
347    public boolean isDisabled() {
348        return disabled;
349    }
350
351    public boolean isNoCache() {
352        return noCache;
353    }
354
355    public void setAccessCheckFilter(String accessCheckFilter) {
356        this.accessCheckFilter = accessCheckFilter;
357    }
358
359    public void setAccessCheckRule(Rule accessCheckRule) {
360        this.accessCheckRule = accessCheckRule;
361    }
362
363    public void setAccessCheckScript(Script accessCheckScript) {
364        this.accessCheckScript = accessCheckScript;
365    }
366
367    public void setAccessCheckSelector(IdentitySelector accessCheckSelector) {
368        this.accessCheckSelector = accessCheckSelector;
369    }
370
371    public void setAllOf(List<CommonSecurityConfig> allOf) {
372        this.allOf = allOf;
373    }
374
375    public void setDescription(String description) {
376        this.description = description;
377    }
378
379    public void setDisabled(boolean disabled) {
380        this.disabled = disabled;
381    }
382
383    public void setExcludedCapabilities(List<String> excludedCapabilities) {
384        this.excludedCapabilities = excludedCapabilities;
385    }
386
387    public void setExcludedRights(List<String> excludedRights) {
388        this.excludedRights = excludedRights;
389    }
390
391    public void setExcludedWorkgroups(List<String> excludedWorkgroups) {
392        this.excludedWorkgroups = excludedWorkgroups;
393    }
394
395    public void setInvalidTargetFilter(String invalidTargetFilter) {
396        this.invalidTargetFilter = invalidTargetFilter;
397    }
398
399    public void setMirrorQuicklinkPopulation(String mirrorQuicklinkPopulation) {
400        this.mirrorQuicklinkPopulation = mirrorQuicklinkPopulation;
401    }
402
403    public void setMirrorRole(String mirrorRole) {
404        this.mirrorRole = mirrorRole;
405    }
406
407    public void setNoCache(boolean noCache) {
408        this.noCache = noCache;
409    }
410
411    public void setNot(List<CommonSecurityConfig> not) {
412        this.not = not;
413    }
414
415    public void setOneOf(List<CommonSecurityConfig> oneOf) {
416        this.oneOf = oneOf;
417    }
418
419    public void setRequiredCapabilities(List<String> requiredCapabilities) {
420        this.requiredCapabilities = requiredCapabilities;
421    }
422
423    public void setRequiredRights(List<String> requiredRights) {
424        this.requiredRights = requiredRights;
425    }
426
427    public void setRequiredWorkgroups(List<String> requiredWorkgroups) {
428        this.requiredWorkgroups = requiredWorkgroups;
429    }
430
431    public void setSettingOffSwitch(String settingOffSwitch) {
432        this.settingOffSwitch = settingOffSwitch;
433    }
434
435    public void setValidTargetCapabilities(List<String> validTargetCapabilities) {
436        this.validTargetCapabilities = validTargetCapabilities;
437    }
438
439    public void setValidTargetExcludedCapabilities(List<String> validTargetExcludedCapabilities) {
440        this.validTargetExcludedCapabilities = validTargetExcludedCapabilities;
441    }
442
443    public void setValidTargetExcludedRights(List<String> validTargetExcludedRights) {
444        this.validTargetExcludedRights = validTargetExcludedRights;
445    }
446
447    public void setValidTargetFilter(String validTargetFilter) {
448        this.validTargetFilter = validTargetFilter;
449    }
450
451    public void setValidTargetSelector(IdentitySelector validTargetSelector) {
452        this.validTargetSelector = validTargetSelector;
453    }
454
455    public void setValidTargetWorkgroups(List<String> validTargetWorkgroups) {
456        this.validTargetWorkgroups = validTargetWorkgroups;
457    }
458
459    /**
460     * Returns a Map representation of this {@link CommonSecurityConfig}.
461     *
462     * If it was originally constructed via ObjectMapper, the returned Map will be
463     * a copy of the original input. Otherwise, we will do our best to reconstruct one.
464     *
465     * @return The Map representation
466     */
467    public Map<String, Object> toMap() {
468        if (this._originalMap.isEmpty()) {
469            final Map<String, Object> map = new HashMap<>();
470
471            // Consumer to simplify taking a list of strings and adding it to the Map if not empty
472            BiConsumer<String, List<String>> handleStringList = (name, list) -> {
473                if (Utilities.isNotEmpty(list)) {
474                    map.put(name, new ArrayList<>(list));
475                }
476            };
477
478            handleNested(map, "oneOf", this.oneOf);
479            handleNested(map, "allOf", this.allOf);
480            handleNested(map, "not", this.not);
481
482            handleStringList.accept("requiredRights", this.requiredRights);
483            handleStringList.accept("requiredCapabilities", this.requiredCapabilities);
484            handleStringList.accept("requiredWorkgroups", this.requiredWorkgroups);
485            handleStringList.accept("excludedRights", this.excludedRights);
486            handleStringList.accept("excludedCapabilities", this.excludedCapabilities);
487            handleStringList.accept("excludedWorkgroups", this.excludedWorkgroups);
488            handleStringList.accept("validTargetCapabilities", this.validTargetCapabilities);
489            handleStringList.accept("validTargetExcludedRights", this.validTargetExcludedRights);
490            handleStringList.accept("validTargetWorkgroups", this.validTargetWorkgroups);
491            handleStringList.accept("validTargetExcludedCapabilities", this.validTargetExcludedCapabilities);
492            handleStringList.accept("validTargetWorkgroups", this.validTargetWorkgroups);
493
494            if (Util.isNotNullOrEmpty(this.description)) {
495                map.put("description", this.description);
496            }
497
498            if (this.disabled) {
499                map.put("disabled", true);
500            }
501
502            if (this.noCache) {
503                map.put("noCache", true);
504            }
505
506            if (Util.isNotNullOrEmpty(this.settingOffSwitch)) {
507                map.put("settingOffSwitch", this.settingOffSwitch);
508            }
509
510            if (this.validTargetSelector != null) {
511                map.put("validTargetSelector", this.validTargetSelector);
512            }
513
514            if (Util.isNotNullOrEmpty(this.accessCheckFilter)) {
515                map.put("accessCheckFilter", this.accessCheckFilter);
516            }
517
518            if (this.accessCheckScript != null) {
519                map.put("accessCheckScript", this.accessCheckScript);
520            }
521
522            if (this.accessCheckRule != null) {
523                map.put("accessCheckRule", this.accessCheckRule.getName());
524            }
525
526            if (this.accessCheckSelector != null) {
527                map.put("accessCheckSelector", this.accessCheckSelector);
528            }
529
530            if (Util.isNotNullOrEmpty(this.invalidTargetFilter)) {
531                map.put("invalidTargetFilter", this.invalidTargetFilter);
532            }
533
534            if (Util.isNotNullOrEmpty(this.mirrorQuicklinkPopulation)) {
535                map.put("mirrorQuicklinkPopulation", this.mirrorQuicklinkPopulation);
536            }
537
538            this._originalMap.putAll(map);
539        }
540
541        return Collections.unmodifiableMap(this._originalMap);
542
543    }
544
545    @Override
546    public String toString() {
547        StringJoiner joiner = new StringJoiner(", ", CommonSecurityConfig.class.getSimpleName() + "[", "]");
548        if ((accessCheckFilter) != null) {
549            joiner.add("accessCheckFilter='" + accessCheckFilter + "'");
550        }
551        if ((accessCheckRule) != null) {
552            joiner.add("accessCheckRule=" + accessCheckRule);
553        }
554        if ((accessCheckScript) != null) {
555            joiner.add("accessCheckScript=" + accessCheckScript);
556        }
557        if ((accessCheckSelector) != null) {
558            joiner.add("accessCheckSelector=" + accessCheckSelector);
559        }
560        if ((allOf) != null) {
561            joiner.add("allOf=" + allOf);
562        }
563        if ((description) != null) {
564            joiner.add("description='" + description + "'");
565        }
566        joiner.add("disabled=" + disabled);
567        if ((excludedCapabilities) != null) {
568            joiner.add("excludedCapabilities=" + excludedCapabilities);
569        }
570        if ((excludedRights) != null) {
571            joiner.add("excludedRights=" + excludedRights);
572        }
573        if ((excludedWorkgroups) != null) {
574            joiner.add("excludedWorkgroups=" + excludedWorkgroups);
575        }
576        if ((invalidTargetFilter) != null) {
577            joiner.add("invalidTargetFilter='" + invalidTargetFilter + "'");
578        }
579        if (mirrorRole != null) {
580            joiner.add("mirrorRole='" + mirrorRole + "'");
581        }
582        if ((mirrorQuicklinkPopulation) != null) {
583            joiner.add("mirrorQuicklinkPopulation='" + mirrorQuicklinkPopulation + "'");
584        }
585        joiner.add("noCache=" + noCache);
586        if ((not) != null) {
587            joiner.add("not=" + not);
588        }
589        if ((oneOf) != null) {
590            joiner.add("oneOf=" + oneOf);
591        }
592        if ((requiredCapabilities) != null) {
593            joiner.add("requiredCapabilities=" + requiredCapabilities);
594        }
595        if ((requiredRights) != null) {
596            joiner.add("requiredRights=" + requiredRights);
597        }
598        if ((requiredWorkgroups) != null) {
599            joiner.add("requiredWorkgroups=" + requiredWorkgroups);
600        }
601        if ((settingOffSwitch) != null) {
602            joiner.add("settingOffSwitch='" + settingOffSwitch + "'");
603        }
604        if ((validTargetCapabilities) != null) {
605            joiner.add("validTargetCapabilities=" + validTargetCapabilities);
606        }
607        if ((validTargetExcludedCapabilities) != null) {
608            joiner.add("validTargetExcludedCapabilities=" + validTargetExcludedCapabilities);
609        }
610        if ((validTargetExcludedRights) != null) {
611            joiner.add("validTargetExcludedRights=" + validTargetExcludedRights);
612        }
613        if ((validTargetFilter) != null) {
614            joiner.add("validTargetFilter='" + validTargetFilter + "'");
615        }
616        if ((validTargetSelector) != null) {
617            joiner.add("validTargetSelector=" + validTargetSelector);
618        }
619        if ((validTargetWorkgroups) != null) {
620            joiner.add("validTargetWorkgroups=" + validTargetWorkgroups);
621        }
622        return joiner.toString();
623    }
624}