"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkPermission = checkPermission;
const checkLicense_1 = require("./_internal/checkLicense");
const HubPermissionPolicies_1 = require("./HubPermissionPolicies");
const isPermission_1 = require("./isPermission");
const getPolicyResponseCode_1 = require("./_internal/getPolicyResponseCode");
const checkAuthentication_1 = require("./_internal/checkAuthentication");
const checkOwner_1 = require("./_internal/checkOwner");
const checkEdit_1 = require("./_internal/checkEdit");
const checkDelete_1 = require("./_internal/checkDelete");
const checkPrivileges_1 = require("./_internal/checkPrivileges");
const checkEntityPolicy_1 = require("./_internal/checkEntityPolicy");
const checkAssertions_1 = require("./_internal/checkAssertions");
const checkEnvironment_1 = require("./_internal/checkEnvironment");
const checkAvailability_1 = require("./_internal/checkAvailability");
const checkServiceStatus_1 = require("./_internal/checkServiceStatus");
const get_prop_1 = require("../objects/get-prop");
const get_with_default_1 = require("../objects/get-with-default");
const logger_1 = require("../utils/logger");
const checkReleaseGating_1 = require("./_internal/checkReleaseGating");
const checkPlatformVersion_1 = require("./_internal/checkPlatformVersion");
/**
 * @internal
 * Check the parent policies for the given policy
 * @param policy
 * @param context
 * @param entity
 * @returns
 */
function checkParents(policy, context, entity) {
    var _a;
    let checks = [];
    if ((_a = policy.dependencies) === null || _a === void 0 ? void 0 : _a.length) {
        // map over the parents array of permissions and check each one
        checks = policy.dependencies.reduce((acc, parent) => {
            const result = checkPermission(parent, context, entity);
            acc = [...acc, ...result.checks];
            return acc;
        }, []);
    }
    return checks;
}
/**
 * Check a permission against the system policies, and possibly an entity policy
 * Note: Calls that fail will automatically be logged to the console. Additional
 * context for the call can be passed via `.label` on the `entityOrOptions` argument.
 * @param permission
 * @param context
 * @param entityOrOptions
 * @returns
 */
function checkPermission(permission, context, entityOrOptions) {
    var _a;
    const label = typeof (entityOrOptions === null || entityOrOptions === void 0 ? void 0 : entityOrOptions.label) === "string" ? entityOrOptions.label : "";
    const entity = typeof entityOrOptions === "object" &&
        entityOrOptions !== null &&
        "entity" in entityOrOptions
        ? entityOrOptions.entity
        : entityOrOptions;
    // Is this even a valid permission?
    if (!(0, isPermission_1.isPermission)(permission)) {
        const invalidPermissionResponse = {
            policy: permission,
            access: false,
            response: "invalid-permission",
            code: (0, getPolicyResponseCode_1.getPolicyResponseCode)("invalid-permission"),
            checks: [],
        };
        logResponse(invalidPermissionResponse, label);
        return invalidPermissionResponse;
    }
    // Get the system policy for this permission
    const systemPolicy = (0, HubPermissionPolicies_1.getPermissionPolicy)(permission);
    // handle null systemPolicy
    if (!systemPolicy) {
        const missingPolicyResponse = {
            policy: permission,
            access: false,
            response: "no-policy-exists",
            code: (0, getPolicyResponseCode_1.getPolicyResponseCode)("no-policy-exists"),
            checks: [],
        };
        logResponse(missingPolicyResponse, label);
        return missingPolicyResponse;
    }
    const flagging = {
        hasFlag: false,
        value: false,
        type: "none",
    };
    // Entity Feature Flags
    // aka how we disable "features" on a per-entity basis
    // Is this policy configurable by the entity?
    if (systemPolicy.entityConfigurable) {
        // Has the entity provided a flag value?
        if ((entity === null || entity === void 0 ? void 0 : entity.features) &&
            typeof entity.features === "object" &&
            entity.features !== null &&
            Object.prototype.hasOwnProperty.call(entity.features, permission)) {
            flagging.hasFlag = true;
            flagging.value = entity.features[permission];
            flagging.type = "entity";
        }
    }
    // Feature Flags
    // Passed in from the application when context is created,
    // these override entity flags, so they are checked after
    if (context.featureFlags &&
        Object.prototype.hasOwnProperty.call(context.featureFlags, permission)) {
        flagging.hasFlag = true;
        flagging.value = context.featureFlags[permission];
        flagging.type = "uri-flag";
    }
    // Preview was too limiting as it could only be true, so we
    // added a features object to userHubSettings that can be
    // used to opt-in/out of features
    if ((_a = context.userHubSettings) === null || _a === void 0 ? void 0 : _a.features) {
        const features = (0, get_with_default_1.getWithDefault)(context, "userHubSettings.features", {});
        Object.keys(features).forEach((key) => {
            if (permission === `hub:feature:${key}`) {
                flagging.hasFlag = true;
                flagging.value = (0, get_prop_1.getProp)(features, key);
                flagging.type = "feature";
            }
        });
    }
    // in all cases, if flag exists and false, access is denied
    if (flagging.hasFlag && !flagging.value) {
        const respValue = `disabled-by-${flagging.type}-flag`;
        const disabledByFlagResponse = {
            policy: permission,
            access: false,
            response: respValue,
            code: (0, getPolicyResponseCode_1.getPolicyResponseCode)(respValue),
            // checks are needed so the aggregations in checkParents function works
            checks: [
                {
                    response: respValue,
                    code: (0, getPolicyResponseCode_1.getPolicyResponseCode)(respValue),
                    name: `Feature Flag: ${permission}`,
                },
            ],
        };
        logResponse(disabledByFlagResponse, label);
        return disabledByFlagResponse;
    }
    // required checks - things feature flags can not override
    const requiredChecks = [
        checkServiceStatus_1.checkServiceStatus,
        checkAuthentication_1.checkAuthentication,
        checkLicense_1.checkLicense,
        checkPrivileges_1.checkPrivileges,
        checkOwner_1.checkOwner,
        checkEdit_1.checkEdit,
        checkDelete_1.checkDelete,
        checkAssertions_1.checkAssertions,
        checkPlatformVersion_1.checkPlatformVersion,
    ];
    // checks that feature flags can override
    const overridableChecks = [
        checkAvailability_1.checkAvailability,
        checkEnvironment_1.checkEnvironment,
        checkReleaseGating_1.checkReleaseGating,
    ];
    let checkFns = [];
    // If there is a "feature" flag, set to true, for the policy
    // we just run the required checks
    if (flagging.hasFlag && flagging.value) {
        if (flagging.type === "feature") {
            // feature flags are very limited so they can skip the parents check
            checkFns = [...requiredChecks];
        }
        else {
            // uri-flag or entity flag must run the parents check
            checkFns = [checkParents, ...requiredChecks];
        }
    }
    else {
        // otherwise we run all the checks
        checkFns = [...overridableChecks, checkParents, ...requiredChecks];
    }
    // execute the checks
    const checks = checkFns.reduce((acc, fn) => {
        acc = [...acc, ...fn(systemPolicy, context, entity)];
        return acc;
    }, []);
    // Default to granted
    const response = {
        policy: permission,
        access: true,
        response: "granted",
        code: (0, getPolicyResponseCode_1.getPolicyResponseCode)("granted"),
        checks: [],
    };
    // For system policies, all conditions must be met, so we can
    // iterate through the checks and set the response to the first failure
    // while still returning all the checks for observability
    checks.forEach((check) => {
        if (check.response !== "granted" && response.response === "granted") {
            response.response = check.response;
            response.code = check.code;
            response.access = false;
        }
    });
    response.checks = checks;
    // Entity policies are treated as "grants" so we only need to pass one
    if (entity) {
        const entityPolicies = (0, get_with_default_1.getWithDefault)(entity, "permissions", []);
        const entityPermissionPolicies = entityPolicies.filter((e) => e.permission === permission);
        // Entity Policies are "grants" in that only one needs to pass
        // but we still want each check returned so we can see why they
        // got access or got denied
        const entityChecks = entityPermissionPolicies.map((policy) => {
            return (0, checkEntityPolicy_1.checkEntityPolicy)(policy, context);
        });
        // Process them to see if any grant access
        const grantedCheck = entityChecks.find((e) => e.response === "granted");
        // If we did not find a check that grants access, AND we've passed
        // all the system checks, then we set the response to "not-granted"
        // and set the access to false
        if (entityChecks.length &&
            !grantedCheck &&
            response.response === "granted") {
            response.access = false;
            response.response = "not-granted";
        }
        // Merge in the entity checks...
        response.checks = [...response.checks, ...entityChecks];
    }
    // log response
    logResponse(response, label);
    return response;
}
function logResponse(response, label) {
    if (!response.access) {
        logger_1.Logger.warn(`checkPermission: ${label} ${response.policy} : ${response.response}`, response);
    }
}
//# sourceMappingURL=checkPermission.js.map