001package com.identityworksllc.iiq.common.access; 002 003import com.identityworksllc.iiq.common.Metered; 004import com.identityworksllc.iiq.common.ThingAccessUtils; 005import org.apache.commons.logging.Log; 006import org.apache.commons.logging.LogFactory; 007import sailpoint.api.SailPointContext; 008import sailpoint.object.Configuration; 009import sailpoint.object.Identity; 010import sailpoint.rest.plugin.BasePluginResource; 011import sailpoint.tools.GeneralException; 012import sailpoint.tools.Util; 013 014import java.util.ArrayList; 015import java.util.List; 016import java.util.Map; 017import java.util.function.Function; 018 019import static com.identityworksllc.iiq.common.access.DelegatedAccessConstants.*; 020 021/** 022 * A default adapter for delegated access checks. 023 * 024 * This class implements the OOTB Java {@link Function} interface to allow it to be 025 * shared across plugins without relying on reflection. 026 * 027 * The input contains various arguments for the access check. In particular, it contains 028 * an 'action' and an optional 'name'. These will be concatenated with a ':' to form the 029 * purpose string. For example, 'view:field:myField', where 'view:field' is the action and 030 * 'myField' is the name. 031 * 032 * Certain actions can bypass the DA checks, deferring to a default ThingAccessUtils 033 * checks. This is particularly useful if the particular thing has good inline configuration 034 * that doesn't need to be shared or nested. 035 * 036 * The output is a {@link Boolean} indicating whether the access is DENIED - 037 * i.e., true = deny, false = allow. 038 */ 039public class DelegatedAccessAdapter implements Function<Map<String, Object>, Boolean> { 040 041 private static final Log logger = LogFactory.getLog(DelegatedAccessAdapter.class); 042 043 /** 044 * Applies the delegated access check to the input. The input is expected to contain 045 * the configuration arguments in {@link DelegatedAccessConstants}. The output 046 * indicates whether the access check DENIES access (true = deny). 047 * 048 * @param input the map containing configuration arguments 049 * @return true if the access check is DENIED, false if the access check is ALLOWED 050 */ 051 @Override 052 public Boolean apply(Map<String, Object> input) { 053 SailPointContext context = (SailPointContext) input.get(INPUT_CONTEXT); 054 BasePluginResource pluginContext = (BasePluginResource) input.get(INPUT_PLUGIN_RESOURCE); 055 Identity target = (Identity) input.get(INPUT_TARGET); 056 057 String action = (String) input.get(INPUT_ACTION); 058 String name = (String) input.get(INPUT_THING_NAME); 059 060 try { 061 return Metered.meter("DelegatedAccessAdapter.apply", () -> { 062 List<String> bypassActions = new ArrayList<>(); 063 064 Configuration delegatedAccessConfig = DelegatedAccessController.getDelegatedAccessConfig(context); 065 if (delegatedAccessConfig.get(CONFIG_BYPASS_ACTIONS) instanceof List) { 066 bypassActions.addAll(Util.otol(delegatedAccessConfig.get(CONFIG_BYPASS_ACTIONS))); 067 } 068 069 if (bypassActions.contains(action)) { 070 // We will just use the default ThingAccessUtils for this 071 Object restrictions = input.get(INPUT_CONFIG); 072 if (restrictions instanceof Map) { 073 boolean outcome = ThingAccessUtils.checkThingAccess(pluginContext, target, name, (Map<String, Object>) restrictions); 074 075 // Yeah I know, but the output is inverted and may change type later 076 return outcome ? OUTCOME_ALLOWED : OUTCOME_DENIED; 077 } 078 } 079 080 DelegatedAccessController da = new DelegatedAccessController(pluginContext); 081 082 String actionToken = action; 083 if (Util.isNotNullOrEmpty(name)) { 084 actionToken = actionToken + TOKEN_DIVIDER + name; 085 } 086 087 if (da.canSeeIdentity(target, actionToken)) { 088 return OUTCOME_ALLOWED; 089 } else { 090 return OUTCOME_DENIED; 091 } 092 }); 093 } catch(GeneralException e) { 094 logger.error("Caught an error verifying access to " + action + " " + name, e); 095 return OUTCOME_DENIED; 096 } 097 } 098}