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);
};
import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType";
import QueryHelper from "../QueryHelper";
import TablePermission from "./TablePermission";
import LIMIT_MAX from "../../../../Types/Database/LimitMax";
import DatabaseCommonInteractionPropsUtil, { PermissionType, } from "../../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
import PermissionScope from "../../../../Types/Database/AccessControl/PermissionScope";
import ObjectID from "../../../../Types/ObjectID";
import Permission, { PermissionHelper, } from "../../../../Types/Permission";
import { combineWithPrivacyClause } from "../../../Utils/PrivacyFilterUtil";
import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan";
/*
 * Implements the `Owned` permission scope (see
 * Internal/Docs/PermissionsSimplification.md). When the requesting user's
 * applicable permission rows are exclusively `Owned`-scoped, this restricts
 * the query to resources where the user is in *OwnerUser or any of the
 * user's teams is in *OwnerTeam.
 *
 * `All` and `Labels` scoped rows are evaluated elsewhere; if any non-Owned
 * row also grants the operation, that broader grant wins and this filter
 * is skipped.
 */
export default class OwnedScopePermission {
    static async addOwnedScopeToQuery(modelType, query, props, type) {
        if (props.isRoot || props.isMasterAdmin) {
            return query;
        }
        /*
         * Create has no resource to scope to; auto-owner-on-create lives in the
         * create path itself, not here.
         */
        if (type === DatabaseRequestType.Create) {
            return query;
        }
        const model = new modelType();
        /*
         * Owned scope only restricts resources that can carry ownership:
         * operational resources (own *OwnerUser / *OwnerTeam tables) or
         * nested resources that inherit ownership via @OwnedThrough. For
         * everything else (settings / config tables like IncidentState,
         * Label, Team, etc.) the table-level permission check is the only
         * gate — Owned has no resources to scope to, so it's a no-op.
         */
        if (!model.isOperationalResource && !model.ownedThrough) {
            return query;
        }
        const effectivePermissions = OwnedScopePermission.getEffectivePermissionsForModel(modelType, type);
        const userPermissions = DatabaseCommonInteractionPropsUtil.getUserPermissions(props, PermissionType.Allow);
        const applicableRows = userPermissions.filter((p) => {
            return effectivePermissions.includes(p.permission);
        });
        if (applicableRows.length === 0) {
            /*
             * No grant applies — the existing table-level check will reject this
             * request. Leave the query untouched.
             */
            return query;
        }
        /*
         * If any applicable row is non-Owned (All / Labels / undefined), it
         * grants broader access than Owned and the Owned constraint is moot.
         * Rows for scope-exempt permissions (e.g. ProjectOwner) are also
         * treated as broader grants regardless of their stored scope, since
         * scoping doesn't apply to them.
         */
        const hasNonOwnedGrant = applicableRows.some((p) => {
            if (!PermissionHelper.isScopeApplicable(p.permission)) {
                return true;
            }
            return p.scope !== PermissionScope.Owned;
        });
        if (hasNonOwnedGrant) {
            return query;
        }
        // All applicable rows are Owned-scoped. Resolve allowed resource IDs.
        const allowedIds = await OwnedScopePermission.getAllowedResourceIds(modelType, props);
        if (model.ownedThrough) {
            /*
             * Nested resource: ownership inherits via the parent FK. The allowedIds
             * we computed are the parent's IDs, so filter on the FK column.
             */
            const fkColumn = model.ownedThrough.fkColumn;
            if (allowedIds.length === 0) {
                // No accessible parents -> match nothing.
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                query._id = QueryHelper.equalTo(ObjectID.getZeroObjectID().toString());
            }
            else {
                /*
                 * AND-combine with any caller-supplied FK filter (e.g. the
                 * dashboard's `incidentId: <this incident>`) — overwriting it would
                 * widen the query to every owned parent.
                 */
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                query[fkColumn] = combineWithPrivacyClause(
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                query[fkColumn], QueryHelper.any(allowedIds));
            }
        }
        else if (allowedIds.length === 0) {
            // Top-level operational resource: no accessible IDs -> match nothing.
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            query._id = QueryHelper.equalTo(ObjectID.getZeroObjectID().toString());
        }
        else {
            /*
             * Top-level operational resource: filter on _id, AND-combined with any
             * caller-supplied _id (get/update of a specific record) — overwriting
             * it would resolve the request against a different owned record.
             */
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            query._id = combineWithPrivacyClause(
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            query._id, QueryHelper.any(allowedIds));
        }
        return query;
    }
    /*
     * Returns the permissions that should be considered to grant access for
     * this op/model — model-enumerated plus operational wildcards. Mirrors the
     * logic in TablePermission.getEffectiveModelPermissions but exposed here
     * because we need to filter user-permission rows by this set.
     */
    static getEffectivePermissionsForModel(modelType, type) {
        const modelPermissions = TablePermission.getTablePermission(modelType, type);
        const effective = [...modelPermissions];
        const model = new modelType();
        if (model.isOperationalResource) {
            const wildcard = OwnedScopePermission.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;
        }
    }
    /*
     * Computes the set of resource IDs the requesting user can access via
     * ownership: those where they personally sit in *OwnerUser OR where any of
     * their teams sits in *OwnerTeam.
     *
     * For nested models the lookup uses the parent's owner tables and returns
     * parent IDs (the caller filters the nested query by the parent FK).
     */
    static async getAllowedResourceIds(modelType, props) {
        var _a;
        const model = new modelType();
        /*
         * Which model(s) owner tables to consult. A nested model can inherit
         * ownership from several parent resource types when its FK is
         * polymorphic (e.g. a telemetry serviceId that may point at a Service,
         * Host, DockerHost or KubernetesCluster) — resolve and union the owned
         * ids across all of them. Top-level operational resources consult
         * their own owner tables.
         */
        const resolverNames = model.ownedThrough
            ? model.ownedThrough.parentModels.map(
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            (parentModel) => {
                return parentModel.name;
            })
            : // eslint-disable-next-line @typescript-eslint/no-explicit-any
                [modelType.name];
        /*
         * Lazy require to avoid the circular dep cycle: this file is reachable
         * from DatabaseService at module-load time, and the registry imports
         * services that extend DatabaseService.
         */
        const ownerTableRegistry = 
        // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
        require("./OwnerTableRegistry").default;
        const seen = new Set();
        for (const resolverName of resolverNames) {
            const registryEntry = ownerTableRegistry.get(resolverName);
            if (!registryEntry) {
                /*
                 * No registered owner tables for this parent — skip it. Other
                 * parents (or includeProjectScope below) may still resolve.
                 */
                continue;
            }
            const fkColumn = registryEntry.fkColumn;
            /*
             * User-ownership lookup. Skipped for non-user callers (API keys,
             * Probes with no userId); those evaluate `Owned` as `All` elsewhere.
             */
            if (props.userId) {
                const userOwnedRows = await registryEntry.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) {
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    const value = row[fkColumn];
                    if (value) {
                        seen.add(value.toString());
                    }
                }
            }
            // Team-ownership lookup.
            if (props.userTeamIds && props.userTeamIds.length > 0) {
                const teamOwnedRows = await registryEntry.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) {
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    const value = row[fkColumn];
                    if (value) {
                        seen.add(value.toString());
                    }
                }
            }
        }
        /*
         * Polymorphic FK rows with no owning resource (the unattributed
         * "Unknown" telemetry bucket) carry the projectId in the FK column.
         * They belong to the project, not any single owner, so include the
         * tenant id when the model opts in via includeProjectScope.
         */
        if (((_a = model.ownedThrough) === null || _a === void 0 ? void 0 : _a.includeProjectScope) && props.tenantId) {
            seen.add(props.tenantId.toString());
        }
        const result = [];
        for (const id of seen) {
            result.push(new ObjectID(id));
        }
        return result;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, Object, String]),
    __metadata("design:returntype", Promise)
], OwnedScopePermission, "addOwnedScopeToQuery", null);
//# sourceMappingURL=OwnedScopePermission.js.map