var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var _a, _b;
import { IsBillingEnabled, getAllEnvVars, } from "../../../Server/EnvironmentConfig";
import DatabaseRequestType from "../BaseDatabase/DatabaseRequestType";
import QueryHelper from "../Database/QueryHelper";
import DatabaseCommonInteractionPropsUtil, { PermissionType, } from "../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
import Includes from "../../../Types/BaseDatabase/Includes";
import SubscriptionPlan from "../../../Types/Billing/SubscriptionPlan";
import PermissionScope from "../../../Types/Database/AccessControl/PermissionScope";
import Columns from "../../../Types/Database/Columns";
import LIMIT_MAX from "../../../Types/Database/LimitMax";
import BadDataException from "../../../Types/Exception/BadDataException";
import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
import PaymentRequiredException from "../../../Types/Exception/PaymentRequiredException";
import ObjectID from "../../../Types/ObjectID";
import Permission, { PermissionHelper, } from "../../../Types/Permission";
import UserType from "../../../Types/UserType";
import CaptureSpan from "../../Utils/Telemetry/CaptureSpan";
const scopeResolveCache = new WeakMap();
function getScopeCacheBucket(props) {
    let bucket = scopeResolveCache.get(props);
    if (!bucket) {
        bucket = { labeledIds: new Map() };
        scopeResolveCache.set(props, bucket);
    }
    return bucket;
}
export default class ModelPermission {
    static async checkDeletePermission(modelType, query, props) {
        if (props.isRoot || props.isMasterAdmin) {
            query = await this.addTenantScopeToQueryAsRoot(modelType, query, props);
        }
        if (!props.isRoot && !props.isMasterAdmin) {
            this.checkModelLevelPermissions(modelType, props, DatabaseRequestType.Delete);
            query = await this.addTenantScopeToQuery(modelType, query, null, props);
            // Owned scope: restrict deletes to telemetry from accessible services.
            query = await this.addOwnedScopeToQuery(modelType, query, props, DatabaseRequestType.Delete);
        }
        return query;
    }
    static async checkUpdatePermissions(modelType, query, data, props) {
        if (props.isRoot || props.isMasterAdmin) {
            return query;
        }
        this.checkModelLevelPermissions(modelType, props, DatabaseRequestType.Update);
        const checkReadPermissionType = await this.checkReadPermission(modelType, query, null, props);
        query = checkReadPermissionType.query;
        this.checkDataColumnPermissions(modelType, data, props, DatabaseRequestType.Update);
        return query;
    }
    static checkCreatePermissions(modelType, data, props) {
        // If system is making this query then let the query run!
        if (props.isRoot || props.isMasterAdmin) {
            return;
        }
        this.checkModelLevelPermissions(modelType, props, DatabaseRequestType.Create);
        this.checkDataColumnPermissions(modelType, data, props, DatabaseRequestType.Create);
    }
    static checkDataColumnPermissions(modelType, data, props, requestType) {
        const model = new modelType();
        const userPermissions = DatabaseCommonInteractionPropsUtil.getUserPermissions(props, PermissionType.Allow);
        const permissionColumns = this.getModelColumnsByPermissions(modelType, userPermissions, requestType);
        const excludedColumnNames = ModelPermission.getExcludedColumnNames();
        const tableColumns = model.getTableColumns();
        for (const column of tableColumns) {
            const key = column.key;
            if (data[key] === undefined) {
                continue;
            }
            if (excludedColumnNames.includes(key)) {
                continue;
            }
            if (!permissionColumns.columns.includes(key)) {
                if (requestType === DatabaseRequestType.Create &&
                    column.forceGetDefaultValueOnCreate) {
                    continue; // this is a special case where we want to force the default value on create.
                }
                throw new BadDataException(`User is not allowed to ${requestType} on ${key} column of ${model.singularName}`);
            }
            const billingAccessControl = model.getColumnBillingAccessControl(key);
            if (IsBillingEnabled && props.currentPlan && billingAccessControl) {
                if (requestType === DatabaseRequestType.Create &&
                    billingAccessControl.create) {
                    if (!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(billingAccessControl.create, props.currentPlan, getAllEnvVars())) {
                        throw new PaymentRequiredException("Please upgrade your plan to " +
                            billingAccessControl.create +
                            " to access this feature");
                    }
                }
                if (requestType === DatabaseRequestType.Read &&
                    billingAccessControl.read) {
                    if (!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(billingAccessControl.read, props.currentPlan, getAllEnvVars())) {
                        throw new PaymentRequiredException("Please upgrade your plan to " +
                            billingAccessControl.read +
                            " to access this feature");
                    }
                }
                if (requestType === DatabaseRequestType.Update &&
                    billingAccessControl.update) {
                    if (!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(billingAccessControl.update, props.currentPlan, getAllEnvVars())) {
                        throw new PaymentRequiredException("Please upgrade your plan to " +
                            billingAccessControl.update +
                            " to access this feature");
                    }
                }
            }
        }
    }
    static async checkReadPermission(modelType, query, select, props) {
        if (props.isRoot || props.isMasterAdmin) {
            query = await this.addTenantScopeToQueryAsRoot(modelType, query, props);
        }
        if (!props.isRoot && !props.isMasterAdmin) {
            //check if the user is logged in.
            this.checkIfUserIsLoggedIn(modelType, props, DatabaseRequestType.Read);
            // add tenant scope.
            query = await this.addTenantScopeToQuery(modelType, query, select, props);
            if (!props.isMultiTenantRequest) {
                // We will check for this permission in recursive function.
                // check model level permissions.
                this.checkModelLevelPermissions(modelType, props, DatabaseRequestType.Read);
                /*
                 * Apply the `Owned` permission scope filter for telemetry models
                 * (Log/Span/Metric). Resolves the user's accessible Service IDs
                 * once and constrains the ClickHouse query with `serviceId IN (...)`.
                 * See Internal/Docs/PermissionsSimplification.md.
                 */
                query = await this.addOwnedScopeToQuery(modelType, query, props, DatabaseRequestType.Read);
                /*
                 * We will check for this permission in recursive function.
                 * check query permissions.
                 */
                this.checkQueryPermission(modelType, query, props);
                if (select) {
                    // check query permission.
                    this.checkSelectPermission(modelType, select, props);
                }
            }
        }
        query = this.serializeQuery(query);
        if (select) {
            const result = this.sanitizeSelect(select);
            select = result.select;
        }
        return { query, select };
    }
    static serializeQuery(query) {
        query = query;
        return query;
    }
    static sanitizeSelect(select) {
        return { select };
    }
    static getExcludedColumnNames() {
        const returnArr = [
            "_id",
            "createdAt",
            "deletedAt",
            "updatedAt",
            "version",
            /*
             * Synthetic query key compiled by StatementGenerator to
             * (hasAny(entityKeys, ...) OR attributes[...] = ...) — not a real
             * column; it only narrows rows on models that have entityKeys and
             * never widens access (authorization stays on primaryEntityId).
             */
            "entityScope",
        ];
        return returnArr;
    }
    static checkQueryPermission(modelType, query, props) {
        var _a;
        const model = new modelType();
        const userPermissions = DatabaseCommonInteractionPropsUtil.getUserPermissions(props, PermissionType.Allow);
        const canReadOnTheseColumns = this.getModelColumnsByPermissions(modelType, userPermissions || [], DatabaseRequestType.Read);
        const tableColumns = model.getTableColumns();
        const excludedColumnNames = ModelPermission.getExcludedColumnNames();
        // Now we need to check all columns.
        for (const key in query) {
            if (excludedColumnNames.includes(key)) {
                continue;
            }
            if (!canReadOnTheseColumns.columns.includes(key)) {
                const column = tableColumns.find((item) => {
                    return item.key === key;
                });
                if (!column) {
                    throw new BadDataException(`Invalid column on ${model.singularName} - ${key}. Column does not exist.`);
                }
                throw new NotAuthorizedException(`You do not have permissions to query on - ${key}. You need any one of these permissions: ${PermissionHelper.getPermissionTitles(((_a = column.accessControl) === null || _a === void 0 ? void 0 : _a.read) || []).join(", ")}`);
            }
        }
    }
    static async addTenantScopeToQueryAsRoot(modelType, query, props) {
        var _a;
        const model = new modelType();
        const tenantColumn = ((_a = model.getTenantColumn()) === null || _a === void 0 ? void 0 : _a.key) || null;
        // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query.
        if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) {
            query[tenantColumn] = props.tenantId;
        }
        return query;
    }
    static async addTenantScopeToQuery(modelType, query, select, props) {
        var _a, _b;
        const model = new modelType();
        const tenantColumn = ((_a = model.getTenantColumn()) === null || _a === void 0 ? void 0 : _a.key) || null;
        // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query.
        if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) {
            query[tenantColumn] = props.tenantId;
        }
        else if (tenantColumn &&
            props.userGlobalAccessPermission &&
            (!props.tenantId || props.isMultiTenantRequest)) {
            /*
             * for each of these projectIds,
             * check if they have valid permissions for these projects
             * and if they do, include them in the query.
             */
            const queries = [];
            let projectIDs = [];
            if (props.userGlobalAccessPermission &&
                props.userGlobalAccessPermission.projectIds) {
                projectIDs = (_b = props.userGlobalAccessPermission) === null || _b === void 0 ? void 0 : _b.projectIds;
            }
            let lastException = null;
            for (const projectId of projectIDs) {
                if (!props.userId) {
                    continue;
                }
                try {
                    const checkReadPermissionType = await this.checkReadPermission(modelType, query, select, Object.assign(Object.assign({}, props), { isMultiTenantRequest: false, tenantId: projectId, userTenantAccessPermission: props.userTenantAccessPermission }));
                    queries.push(Object.assign({}, checkReadPermissionType.query));
                }
                catch (e) {
                    // do nothing here. Ignore.
                    lastException = e;
                }
            }
            if (queries.length === 0) {
                throw new NotAuthorizedException((lastException === null || lastException === void 0 ? void 0 : lastException.message) ||
                    "Does not have permission to read " + model.singularName);
            }
            return queries;
        }
        return query;
    }
    static getModelColumnsByPermissions(modelType, userPermissions, requestType) {
        var _a, _b, _c;
        const model = new modelType();
        const tableColumns = model.getTableColumns();
        const columns = [];
        const permissions = userPermissions.map((item) => {
            return item.permission;
        });
        for (const column of tableColumns) {
            let columnPermissions = [];
            if (requestType === DatabaseRequestType.Read) {
                columnPermissions = ((_a = column.accessControl) === null || _a === void 0 ? void 0 : _a.read) || [];
            }
            if (requestType === DatabaseRequestType.Create) {
                columnPermissions = ((_b = column.accessControl) === null || _b === void 0 ? void 0 : _b.create) || [];
            }
            if (requestType === DatabaseRequestType.Update) {
                columnPermissions = ((_c = column.accessControl) === null || _c === void 0 ? void 0 : _c.update) || [];
            }
            if (requestType === DatabaseRequestType.Delete) {
                throw new BadDataException("Invalid request type delete");
            }
            if (columnPermissions &&
                PermissionHelper.doesPermissionsIntersect(permissions, columnPermissions)) {
                columns.push(column.key);
            }
        }
        return new Columns(columns);
    }
    static checkSelectPermission(modelType, select, props) {
        var _a;
        const model = new modelType();
        const userPermissions = DatabaseCommonInteractionPropsUtil.getUserPermissions(props, PermissionType.Allow);
        const canReadOnTheseColumns = this.getModelColumnsByPermissions(modelType, userPermissions || [], DatabaseRequestType.Read);
        const tableColumns = model.getTableColumns();
        const excludedColumnNames = ModelPermission.getExcludedColumnNames();
        for (const key in select) {
            if (excludedColumnNames.includes(key)) {
                continue;
            }
            if (!canReadOnTheseColumns.columns.includes(key)) {
                const column = tableColumns.find((column) => {
                    return column.key === key;
                });
                if (!column) {
                    throw new BadDataException(`Invalid select clause. Cannot select on "${key}". This column does not exist on ${model.singularName}. Here are the columns you can select on instead: ${tableColumns.join(", ")}`);
                }
                throw new NotAuthorizedException(`You do not have permissions to select on - ${key}.
                    You need any one of these permissions: ${PermissionHelper.getPermissionTitles(((_a = column.accessControl) === null || _a === void 0 ? void 0 : _a.read) || []).join(", ")}`);
            }
        }
    }
    static getModelPermissions(modelType, type) {
        var _a, _b, _c, _d;
        let modelPermissions = [];
        const model = new modelType();
        if (type === DatabaseRequestType.Create) {
            modelPermissions = ((_a = model.accessControl) === null || _a === void 0 ? void 0 : _a.create) || [];
        }
        if (type === DatabaseRequestType.Update) {
            modelPermissions = ((_b = model.accessControl) === null || _b === void 0 ? void 0 : _b.update) || [];
        }
        if (type === DatabaseRequestType.Delete) {
            modelPermissions = ((_c = model.accessControl) === null || _c === void 0 ? void 0 : _c.delete) || [];
        }
        if (type === DatabaseRequestType.Read) {
            modelPermissions = ((_d = model.accessControl) === null || _d === void 0 ? void 0 : _d.read) || [];
        }
        return modelPermissions;
    }
    /*
     * Mirror of TablePermission.getEffectiveModelPermissions. Adds the
     * *AllOperationalResources wildcard for @OperationalResource analytics models. See
     * Internal/Docs/PermissionsSimplification.md.
     */
    static getEffectiveModelPermissions(modelType, modelPermissions, type) {
        const effective = [...modelPermissions];
        const model = new modelType();
        if (model.isOperationalResource) {
            const wildcard = this.getWildcardPermissionForOperation(type);
            if (wildcard && !effective.includes(wildcard)) {
                effective.push(wildcard);
            }
        }
        return effective;
    }
    static getWildcardPermissionForOperation(type) {
        switch (type) {
            case DatabaseRequestType.Read:
                return Permission.ReadAllOperationalResources;
            case DatabaseRequestType.Update:
                return Permission.EditAllOperationalResources;
            case DatabaseRequestType.Delete:
                return Permission.DeleteAllOperationalResources;
            case DatabaseRequestType.Create:
                return Permission.CreateAllOperationalResources;
            default:
                return null;
        }
    }
    /*
     * Scope filter for analytics models (Log, Span, Metric, ...). For each
     * applicable user permission row we resolve allowed parent resource IDs
     * based on its scope (`Owned` → ServiceOwner* tables; `Labels` → parent
     * resources matching the user's labels), union them, and inject
     * `serviceId IN (...)` into the ClickHouse query.
     *
     * Telemetry's serviceId is polymorphic — comment in Metric.ts says it
     * "can be the monitor id or the telemetry service id" — so Labels-scope
     * resolution walks both Service.labels (ServiceLabel join) and
     * Monitor.labels (MonitorLabel join). Owned-scope keeps the original
     * single-source ServiceOwner* lookup since that's what's wired today.
     *
     * The operational per-row owner-join from the Postgres path doesn't
     * scale to telemetry volume; one Postgres roundtrip + one indexed
     * predicate does. See Internal/Docs/PermissionsSimplification.md.
     */
    static async addOwnedScopeToQuery(modelType, query, props, type) {
        if (props.isRoot || props.isMasterAdmin) {
            return query;
        }
        if (type === DatabaseRequestType.Create) {
            return query;
        }
        /*
         * Only applies to analytics models that declare @OwnedThrough — today
         * Log, Span, Metric all do (to Service via serviceId). Anything without
         * it can't have ownership filtering applied, so we leave the query alone.
         */
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const model = new modelType();
        if (!model.ownedThrough) {
            return query;
        }
        const modelPermissions = this.getModelPermissions(modelType, type);
        const effectivePermissions = this.getEffectiveModelPermissions(modelType, modelPermissions, type);
        const userPermissions = DatabaseCommonInteractionPropsUtil.getUserPermissions(props, PermissionType.Allow);
        const applicableRows = userPermissions.filter((p) => {
            return effectivePermissions.includes(p.permission);
        });
        if (applicableRows.length === 0) {
            return query;
        }
        /*
         * If any applicable row grants unrestricted access (scope=All, or the
         * legacy Labels/unset scope with no labelIds), the broader grant wins
         * and no filter is applied. Owned and label-restricted rows narrow
         * access; an unrestricted row coexisting with them widens it back to
         * full access.
         */
        const hasUnrestrictedGrant = applicableRows.some((p) => {
            if (p.scope === PermissionScope.All) {
                return true;
            }
            if (p.scope === PermissionScope.Owned) {
                return false;
            }
            // scope === Labels or undefined (legacy default)
            return !p.labelIds || p.labelIds.length === 0;
        });
        if (hasUnrestrictedGrant) {
            return query;
        }
        const allowedResourceIds = new Set();
        const hasOwnedGrant = applicableRows.some((p) => {
            return p.scope === PermissionScope.Owned;
        });
        if (hasOwnedGrant) {
            const ownedIds = await this.resolveOwnedParentIds(props);
            for (const id of ownedIds) {
                allowedResourceIds.add(id);
            }
        }
        const labelScopedRows = applicableRows.filter((p) => {
            const isLabelsScope = p.scope === PermissionScope.Labels || p.scope === undefined;
            return isLabelsScope && Boolean(p.labelIds && p.labelIds.length > 0);
        });
        if (labelScopedRows.length > 0) {
            const labelIdSet = new Set();
            for (const row of labelScopedRows) {
                for (const labelId of row.labelIds) {
                    labelIdSet.add(labelId.toString());
                }
            }
            const labelIds = Array.from(labelIdSet).map((id) => {
                return new ObjectID(id);
            });
            const labeledIds = await this.resolveLabeledParentIds(labelIds, props);
            for (const id of labeledIds) {
                allowedResourceIds.add(id);
            }
        }
        /*
         * Telemetry with no owning resource (the unattributed "Unknown"
         * bucket) is tagged with the projectId in place of a resource id. It
         * belongs to the project, not any owner, so an Owned-scoped user
         * (project-level catch-all access) sees it. Gated on hasOwnedGrant:
         * a purely Labels-scoped user asked for label-matching telemetry, and
         * the unattributed bucket carries no labels, so it stays excluded for
         * them.
         */
        if (hasOwnedGrant &&
            model.ownedThrough.includeProjectScope &&
            props.tenantId) {
            allowedResourceIds.add(props.tenantId.toString());
        }
        const fkColumn = model.ownedThrough.fkColumn;
        const sentinelNoMatch = [
            ObjectID.getZeroObjectID().toString(),
        ];
        let idList = allowedResourceIds.size > 0
            ? Array.from(allowedResourceIds)
            : sentinelNoMatch;
        /*
         * Intersect with any caller-supplied FK filter (e.g. a per-service
         * telemetry page querying primaryEntityId) instead of overwriting it —
         * overwriting would widen the query to every allowed resource. The FK
         * is a scalar column, so set intersection is exact.
         */
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const existingFkFilter = query[fkColumn];
        if (existingFkFilter instanceof Includes) {
            const requestedIds = new Set(existingFkFilter.values.map((value) => {
                return value.toString();
            }));
            idList = idList.filter((id) => {
                return requestedIds.has(id);
            });
        }
        else if (typeof existingFkFilter === "string" ||
            existingFkFilter instanceof ObjectID) {
            const requestedId = existingFkFilter.toString();
            idList = idList.filter((id) => {
                return id === requestedId;
            });
        }
        if (idList.length === 0) {
            idList = sentinelNoMatch;
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        query[fkColumn] = new Includes(idList);
        return query;
    }
    /*
     * Resolves Service IDs the user owns via ServiceOwnerUser /
     * ServiceOwnerTeam in Postgres. Lazy-required to avoid circular deps
     * with services that extend DatabaseService. Returns string IDs to
     * make set-union with other resolvers straightforward.
     *
     * Cached per request via the WeakMap on `props` — the inputs (userId,
     * teamIds, tenantId) are stable for the lifetime of one props object,
     * so repeated calls within the same request reuse the first result.
     */
    static async resolveOwnedParentIds(props) {
        const cache = getScopeCacheBucket(props);
        if (cache.ownedIds) {
            return cache.ownedIds;
        }
        const result = new Set();
        const ownerTableRegistry = 
        // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
        require("../Database/Permissions/OwnerTableRegistry").default;
        /*
         * Telemetry serviceId is polymorphic — it can reference any resource
         * type flagged `canOwnTelemetry` in the registry (Service, Monitor,
         * Host, DockerHost, KubernetesCluster). Resolve ownership across all of
         * them so a user who owns any such resource sees its telemetry, not
         * just owned Services. The polymorphic set lives only in the registry
         * (single source of truth); the resolved union is the same for every
         * telemetry analytics model, so the single per-request `ownedIds`
         * cache slot still holds it.
         */
        for (const entry of ownerTableRegistry.values()) {
            if (!entry.canOwnTelemetry) {
                continue;
            }
            const fkColumn = entry.fkColumn;
            if (props.userId) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const userOwnedRows = await entry.ownerUserService.findBy({
                    query: Object.assign({ userId: props.userId }, (props.tenantId ? { projectId: props.tenantId } : {})),
                    select: { [fkColumn]: true },
                    props: { isRoot: true },
                    skip: 0,
                    limit: LIMIT_MAX,
                });
                for (const row of userOwnedRows) {
                    const id = row[fkColumn];
                    if (id) {
                        result.add(id.toString());
                    }
                }
            }
            if (props.userTeamIds && props.userTeamIds.length > 0) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const teamOwnedRows = await entry.ownerTeamService.findBy({
                    query: Object.assign({ teamId: QueryHelper.any(props.userTeamIds) }, (props.tenantId ? { projectId: props.tenantId } : {})),
                    select: { [fkColumn]: true },
                    props: { isRoot: true },
                    skip: 0,
                    limit: LIMIT_MAX,
                });
                for (const row of teamOwnedRows) {
                    const id = row[fkColumn];
                    if (id) {
                        result.add(id.toString());
                    }
                }
            }
        }
        cache.ownedIds = result;
        return result;
    }
    /*
     * Resolves parent IDs whose labels intersect the given labelIds. Walks
     * both TelemetryService (`ServiceLabel`) and Monitor (`MonitorLabel`)
     * because telemetry's serviceId is polymorphic between the two. The
     * Postgres findBy passes labels through QueryUtil, which turns the
     * EntityArray filter into a many-to-many subquery against the join
     * table — see QueryUtil.ts ~line 528.
     *
     * Cached per request via the WeakMap on `props`, keyed by the sorted
     * label IDs joined into a string. Different model permission rows can
     * carry different label sets, so we key by the actual labelIds rather
     * than a single per-request slot.
     */
    static async resolveLabeledParentIds(labelIds, props) {
        const result = new Set();
        if (labelIds.length === 0) {
            return result;
        }
        const cacheKey = labelIds
            .map((id) => {
            return id.toString();
        })
            .sort()
            .join(",");
        const cache = getScopeCacheBucket(props);
        const cached = cache.labeledIds.get(cacheKey);
        if (cached) {
            return cached;
        }
        const tenantFilter = props.tenantId
            ? { projectId: props.tenantId }
            : {};
        const ownerTableRegistry = 
        // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
        require("../Database/Permissions/OwnerTableRegistry").default;
        /*
         * Telemetry serviceId is polymorphic across every resource type
         * flagged `canOwnTelemetry` in the registry (Service, Monitor, Host,
         * DockerHost, KubernetesCluster), each of which carries labels. Find
         * rows of each whose labels intersect the user's. Keeping this set in
         * the registry (single source of truth) means a new telemetry-owning
         * resource is picked up here automatically — no edits needed.
         */
        for (const entry of ownerTableRegistry.values()) {
            if (!entry.canOwnTelemetry || !entry.modelService) {
                continue;
            }
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const rows = await entry.modelService.findBy({
                query: Object.assign({ labels: labelIds }, tenantFilter),
                select: { _id: true },
                props: { isRoot: true },
                skip: 0,
                limit: LIMIT_MAX,
            });
            for (const row of rows) {
                const id = row._id;
                if (id) {
                    result.add(id.toString());
                }
            }
        }
        cache.labeledIds.set(cacheKey, result);
        return result;
    }
    static isPublicPermissionAllowed(modelType, type) {
        let isPublicAllowed = false;
        isPublicAllowed = this.getModelPermissions(modelType, type).includes(Permission.Public);
        return isPublicAllowed;
    }
    static checkIfUserIsLoggedIn(modelType, props, type) {
        // 1 CHECK: PUBLIC check -- Check if this is a public request and if public is allowed.
        if (!this.isPublicPermissionAllowed(modelType, type) && !props.userId) {
            if (props.userType === UserType.API) {
                // if its an API request then continue.
                return;
            }
            // this means the record is not publicly createable and the user is not logged in.
            throw new NotAuthenticatedException(`Authenticated user or a valid API key is needed to ${type} record of ${new modelType().singularName}.`);
        }
    }
    static checkModelLevelPermissions(modelType, props, type) {
        var _a, _b, _c, _d, _e, _f;
        this.checkIfUserIsLoggedIn(modelType, props, type);
        // 2nd CHECK: Does user have access to CRUD data on this model.
        const userPermissions = DatabaseCommonInteractionPropsUtil.getUserPermissions(props, PermissionType.Allow);
        const modelPermissions = this.getModelPermissions(modelType, type);
        /*
         * Mirror of TablePermission's wildcard short-circuit so the analytics
         * path (ClickHouse-backed Log/Span/Metric) honors the same *AllOperationalResources
         * permissions. See Internal/Docs/PermissionsSimplification.md.
         */
        const effectiveModelPermissions = this.getEffectiveModelPermissions(modelType, modelPermissions, type);
        if (!PermissionHelper.doesPermissionsIntersect(userPermissions.map((userPermission) => {
            return userPermission.permission;
        }) || [], effectiveModelPermissions)) {
            const permissions = PermissionHelper.getPermissionTitles(modelPermissions);
            if (permissions.length === 0) {
                throw new NotAuthorizedException(`${type} on ${new modelType().singularName} is not allowed.`);
            }
            throw new NotAuthorizedException(`You do not have permissions to ${type} ${new modelType().singularName}. You need one of these permissions: ${permissions.join(", ")}`);
        }
        /// Check billing permissions.
        if (IsBillingEnabled && props.currentPlan) {
            const model = new modelType();
            if (props.isSubscriptionUnpaid &&
                !model.allowAccessIfSubscriptionIsUnpaid) {
                throw new PaymentRequiredException("Your current subscription is in an unpaid state. Looks like your payment method failed. Please add a new payment method in Project Settings > Invoices to pay unpaid invoices.");
            }
            if (type === DatabaseRequestType.Create &&
                ((_a = model.tableBillingAccessControl) === null || _a === void 0 ? void 0 : _a.create)) {
                if (!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(model.tableBillingAccessControl.create, props.currentPlan, getAllEnvVars())) {
                    throw new PaymentRequiredException("Please upgrade your plan to " +
                        model.tableBillingAccessControl.create +
                        " to access this feature");
                }
            }
            if (type === DatabaseRequestType.Update &&
                ((_b = model.tableBillingAccessControl) === null || _b === void 0 ? void 0 : _b.update)) {
                if (!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(model.tableBillingAccessControl.update, props.currentPlan, getAllEnvVars())) {
                    throw new PaymentRequiredException("Please upgrade your plan to " +
                        model.tableBillingAccessControl.create +
                        " to access this feature");
                }
            }
            if (type === DatabaseRequestType.Delete &&
                ((_c = model.tableBillingAccessControl) === null || _c === void 0 ? void 0 : _c.delete)) {
                if (!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(model.tableBillingAccessControl.delete, props.currentPlan, getAllEnvVars())) {
                    throw new PaymentRequiredException("Please upgrade your plan to " +
                        model.tableBillingAccessControl.create +
                        " to access this feature");
                }
            }
            if (type === DatabaseRequestType.Read &&
                ((_d = model.tableBillingAccessControl) === null || _d === void 0 ? void 0 : _d.read)) {
                if (!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan((_e = model.tableBillingAccessControl) === null || _e === void 0 ? void 0 : _e.read, props.currentPlan, getAllEnvVars())) {
                    throw new PaymentRequiredException("Please upgrade your plan to " +
                        ((_f = model.tableBillingAccessControl) === null || _f === void 0 ? void 0 : _f.read) +
                        " to access this feature");
                }
            }
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, Object]),
    __metadata("design:returntype", Promise)
], ModelPermission, "checkDeletePermission", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, typeof (_a = typeof TBaseModel !== "undefined" && TBaseModel) === "function" ? _a : Object, Object]),
    __metadata("design:returntype", Promise)
], ModelPermission, "checkUpdatePermissions", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, typeof (_b = typeof TBaseModel !== "undefined" && TBaseModel) === "function" ? _b : Object, Object]),
    __metadata("design:returntype", void 0)
], ModelPermission, "checkCreatePermissions", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, Object, Object]),
    __metadata("design:returntype", Promise)
], ModelPermission, "checkReadPermission", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, String]),
    __metadata("design:returntype", void 0)
], ModelPermission, "checkIfUserIsLoggedIn", null);
//# sourceMappingURL=ModelPermission.js.map