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}