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 BasePermission from "./BasePermission";
import TablePermission from "./TablePermission";
import Includes from "../../../../Types/BaseDatabase/Includes";
import DatabaseCommonInteractionPropsUtil, { PermissionType, } from "../../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
import BadDataException from "../../../../Types/Exception/BadDataException";
import NotAuthorizedException from "../../../../Types/Exception/NotAuthorizedException";
import Permission from "../../../../Types/Permission";
import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan";
/*
 * Permissions auto-granted to every logged-in tenant user. Holding only these
 * (without an actual role permission) does not signal admin authority and so
 * should not unlock cross-row access on models that scope by user.
 */
const AUTO_GRANTED_TENANT_PERMISSIONS = [
    Permission.CurrentUser,
    Permission.Public,
    Permission.UnAuthorizedSsoUser,
];
export default class TenantPermission {
    static async addTenantScopeToQuery(modelType, query, select, props, type) {
        var _a;
        const model = new modelType();
        const tenantColumn = model.getTenantColumn();
        if (props.isMultiTenantRequest && !model.canQueryMultiTenant()) {
            throw new BadDataException(`isMultiTenantRequest not allowed on ${model.singularName}`);
        }
        // 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;
            /*
             * If Permission.CurrentUser is the only thing letting the user through
             * for this model+operation, also restrict the query to records they own.
             * Otherwise the tenant filter alone leaves the user able to act on any
             * row in the project (CVE-class issue when CurrentUser appears in a
             * model's delete/update list alongside admin permissions).
             */
            if (TenantPermission.shouldScopeQueryByCurrentUser(modelType, props, type)) {
                const userColumn = model.getUserColumn();
                if (userColumn) {
                    query[userColumn] = props.userId;
                }
            }
        }
        // if model allows user query without tenant, and user column is present, and userId is present, then add userId to query.
        else if (model.isUserQueryWithoutTenantAllowed() &&
            model.getUserColumn() &&
            props.userId) {
            query[model.getUserColumn()] = props.userId;
        }
        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 = (_a = props.userGlobalAccessPermission) === null || _a === void 0 ? void 0 : _a.projectIds;
            }
            /*
             * Check if the query already has a filter on the tenant column (e.g., projectId filter)
             * If so, only iterate through projects that match both the filter AND user's permissions
             */
            const existingTenantFilter = query[tenantColumn];
            if (existingTenantFilter && existingTenantFilter instanceof Includes) {
                const filterValues = existingTenantFilter.values.map((v) => {
                    return v.toString();
                });
                // Filter projectIDs to only include those that are in the filter
                projectIDs = projectIDs.filter((pid) => {
                    return filterValues.includes(pid.toString());
                });
                // Remove the tenant filter from query since we're handling it via projectIDs iteration
                delete query[tenantColumn];
            }
            let lastException = null;
            for (const projectId of projectIDs) {
                if (!props.userId) {
                    continue;
                }
                try {
                    const checkBasePermissions = await BasePermission.checkPermissions(modelType, query, select, Object.assign(Object.assign({}, props), { isMultiTenantRequest: false, tenantId: projectId, userTenantAccessPermission: props.userTenantAccessPermission }), type);
                    queries.push(Object.assign({}, checkBasePermissions.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;
    }
    /**
     * True if the only permission letting this user through the table-level
     * check for this op is Permission.CurrentUser. In that case the query must
     * be restricted to rows the user owns (via the model's user column).
     */
    static shouldScopeQueryByCurrentUser(modelType, props, type) {
        const model = new modelType();
        if (!model.getUserColumn() || !props.userId) {
            return false;
        }
        const modelPermissions = TablePermission.getTablePermission(modelType, type);
        if (!modelPermissions.includes(Permission.CurrentUser)) {
            return false;
        }
        const userPermissions = DatabaseCommonInteractionPropsUtil.getUserPermissions(props, PermissionType.Allow).map((up) => {
            return up.permission;
        });
        const intersection = userPermissions.filter((p) => {
            return modelPermissions.includes(p);
        });
        if (!intersection.includes(Permission.CurrentUser)) {
            return false;
        }
        const adminMatch = intersection.filter((p) => {
            return !AUTO_GRANTED_TENANT_PERMISSIONS.includes(p);
        });
        return adminMatch.length === 0;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, Object, Object, String]),
    __metadata("design:returntype", Promise)
], TenantPermission, "addTenantScopeToQuery", null);
//# sourceMappingURL=TenantPermission.js.map