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}