"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccessRightsService = void 0;
const tslib_1 = require("tslib");
/* eslint-disable @typescript-eslint/no-unused-vars */
const authorization_1 = require("@loopback/authorization");
const core_1 = require("@loopback/core");
const lodash_1 = require("lodash");
const object_node_service_1 = require("../object-node/object-node.service");
const application_error_1 = require("./../../helper/application-error");
const extension_description_1 = require("./../../integration/extension-description");
const action_entity_service_1 = require("./../action-entity/action-entity.service");
const application_service_1 = require("./../application.service");
const object_tree_service_1 = require("./../object-tree/object-tree.service");
const object_type_service_1 = require("./../object-type.service");
const access_rights_const_1 = require("./access-rights.const");
let AccessRightsService = class AccessRightsService {
    constructor(appCtx, objectTypeService, objectNodeService, objectTreeService, actionEntityService) {
        this.appCtx = appCtx;
        this.objectTypeService = objectTypeService;
        this.objectNodeService = objectNodeService;
        this.objectTreeService = objectTreeService;
        this.actionEntityService = actionEntityService;
        this.accessRights = {};
        this.forcedAccessRightsByObjectType = {};
        /*
        this.init = new AccessRightsInit(
          appCtx,
          objectTypeService,
          objectNodeService,
        );*/
    }
    getPreTreatmentDescription() {
        const treatments = [];
        for (const resource in this.accessRights) {
            treatments.push(new extension_description_1.TreatmentDescription(this.accessRights[resource].providerId, this.accessRights[resource].serviceId, resource + ': Access check'));
        }
        return treatments;
    }
    getPostTreatmentDescription() {
        const treatments = [];
        for (const resource in this.accessRights) {
            treatments.push(new extension_description_1.TreatmentDescription(this.accessRights[resource].providerId, this.accessRights[resource].serviceId, resource + ': Clean'));
        }
        return treatments;
    }
    registerAccessRightsService(resource, accessRights) {
        this.accessRights[resource] = accessRights;
    }
    get ready() {
        return this.appCtx.getExtensionContext(access_rights_const_1.ACCESS_RIGHT_PROVIDER).ready;
    }
    async authorize(ctx, context, metadata) {
        try {
            await this.ready;
            ctx.accessRightsContext.user.value =
                0 < context.principals.length
                    ? context.principals[0]
                    : null;
            await ctx.accessRightsContext.rootRights.getOrSetValue(async () => {
                return this.calculateRootPermissions(ctx);
            });
            if ((metadata === null || metadata === void 0 ? void 0 : metadata.resource) && (metadata === null || metadata === void 0 ? void 0 : metadata.resource) in this.accessRights) {
                const accessRights = this.accessRights[metadata.resource];
                if (metadata.scopes) {
                    if (context.scopes) {
                        context.scopes = context.scopes.concat(metadata.scopes);
                    }
                    else {
                        context.scopes = metadata.scopes;
                    }
                }
                if (context.scopes && 'method' === context.scopes[0]) {
                    context.scopes[0] = await this.actionEntityService.getMethodAuthorizationContext(metadata.resource, context.invocationContext.args, ctx);
                }
                if (context.scopes && 'view' === context.scopes[0]) {
                    context.scopes[0] = await this.actionEntityService.getViewAuthorizationContext(metadata.resource, context.invocationContext.args, ctx);
                }
                return await accessRights.authorize(ctx, context);
            }
        }
        catch (error) {
            return authorization_1.AuthorizationDecision.DENY;
        }
        return authorization_1.AuthorizationDecision.ALLOW;
    }
    async calculateRootPermissions(ctx) {
        var _a, _b, _c;
        const actCtx = this.appCtx.getExtensionContext(access_rights_const_1.ACCESS_RIGHT_PROVIDER);
        if (!((_c = (_b = (_a = ctx.accessRightsContext) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.value) === null || _c === void 0 ? void 0 : _c.id)) {
            return new access_rights_const_1.AccessRightsSet();
        }
        const rootRights = (await this.computeACLTree(await this.objectTreeService.loadTree(actCtx.nodes.rootACL.value.id, application_service_1.CurrentContext.get(ctx, {
            treeContext: { treeNode: actCtx.nodes.rootACL },
        })), ctx.accessRightsContext.user.value)).rights;
        return rootRights;
    }
    async cleanReturnedEntity(entityName, entity, ctx) {
        if (entityName in this.accessRights) {
            const accessRights = this.accessRights[entityName];
            await accessRights.cleanReturnedEntity(entity, ctx);
        }
        else {
            throw application_error_1.ApplicationError.forbidden();
        }
    }
    async cleanReturnedEntities(entityName, entities, ctx) {
        var _a;
        if (entityName in this.accessRights) {
            for (let childIndex = entities.length - 1; childIndex >= 0; childIndex--) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const entity = entities[childIndex];
                await this.cleanReturnedEntity(entityName, entity, ctx);
                if (((_a = entity.entityCtx) === null || _a === void 0 ? void 0 : _a.aclCtx) && !entity.entityCtx.aclCtx.rights.read) {
                    entities.splice(childIndex, 1);
                }
            }
        }
    }
    async getForcedAccessRightsForObjectType(objectTypeId) {
        let forcedAccessRights = {};
        const forcedAccessRightsTypes = lodash_1.intersection(Object.keys(this.forcedAccessRightsByObjectType), await this.objectTypeService.getImplementedTypes(objectTypeId));
        for (const type of forcedAccessRightsTypes) {
            forcedAccessRights = lodash_1.merge(forcedAccessRights, this.forcedAccessRightsByObjectType[type]);
        }
        return forcedAccessRights;
    }
    async getNodeAccessRights(node, ctx) {
        // TODO: disable update for object created during boot ?
        //check if the rights if forced for this Node
        const forcedAccessRights = await this.getForcedAccessRightsForObjectType(node.objectTypeId);
        try {
            const rights = await ctx.accessRightsContext.rights.waitForValue;
            // if node is just the root of the acl tree
            if (node.id === ctx.accessRightsContext.treeRootNodeId.value) {
                return rights.treeRootNode.toAccessRightsCRUD();
            }
            // if owner => true
            if (rights.treeChildrenNodes.owner) {
                return new access_rights_const_1.AccessRightsCRUD(true, true, true, true, forcedAccessRights);
            }
            //Check if node is not in last def tree ACLDef
            //  => rights.treeChildrenNodes[scope]
            const aclTrees = await ctx.accessRightsContext.aclTrees.waitForValue;
            if (!(node.parentACLId in aclTrees) ||
                !this.objectTreeService.isInTree(aclTrees[node.parentACLId], node)) {
                return rights.treeChildrenNodes.toAccessRightsCRUD();
            }
            // if not acl => false (as the node is part of ACL)
            if (!rights.treeChildrenNodes.access) {
                return new access_rights_const_1.AccessRightsCRUD(false, false, false, false, forcedAccessRights);
            }
            // If Node = aclDef => read, write, create
            if (node.objectTypeId ===
                (await this.appCtx.accessRightsDefinitionType.waitForValue).id) {
                return new access_rights_const_1.AccessRightsCRUD(true, true, false, true, forcedAccessRights);
            }
            // If Node = Owners or children  => false
            const owners = this.objectTreeService.getChildOfType(aclTrees[node.parentACLId], (await this.appCtx.accessRightsOwnersType.waitForValue).id);
            if (owners && this.objectTreeService.isInTree(owners, node)) {
                return new access_rights_const_1.AccessRightsCRUD(false, false, false, false, forcedAccessRights);
            }
            // If Node = Managers or children => just read
            const managers = this.objectTreeService.getChildOfType(aclTrees[node.parentACLId], (await this.appCtx.accessRightsAccessManagersType.waitForValue)
                .id);
            if (managers && this.objectTreeService.isInTree(managers, node)) {
                return new access_rights_const_1.AccessRightsCRUD(false, true, false, false, forcedAccessRights);
            }
            // Else (acl and access right groups) => true
            return new access_rights_const_1.AccessRightsCRUD(true, true, true, true, forcedAccessRights);
        }
        catch (error) {
            return new access_rights_const_1.AccessRightsCRUD(false, false, false, false, forcedAccessRights);
        }
    }
    addForcedAccessRightsForObjectType(objectTypeId, forcedAccessRights) {
        this.forcedAccessRightsByObjectType[objectTypeId] = forcedAccessRights;
    }
    async hasNodeAccessRights(scope, node, ctx) {
        //check if the rights if forced for this Node
        const forceAccessRightsTypes = await this.getForcedAccessRightsForObjectType(node.objectTypeId);
        if (scope in forceAccessRightsTypes) {
            return forceAccessRightsTypes[scope];
        }
        await this.loadRights(node, ctx);
        const rights = await this.getNodeAccessRights(node, ctx);
        return !!rights[scope];
    }
    async loadACLTrees(acl, ctx) {
        return ctx.accessRightsContext.aclTrees.getOrSetValue(async () => {
            const aclType = await this.appCtx.accessRightsDefinitionType
                .waitForValue;
            const aclRoots = await this.objectNodeService.searchByParentIdsAndObjectTypeId(acl, aclType.id);
            const aclNodes = await this.objectNodeService.searchByTreeIds(aclRoots.map((aclRoot) => aclRoot.id));
            const aclTrees = {};
            for (const aclRoot of aclRoots) {
                aclTrees[aclRoot.parentNodeId] = await this.objectTreeService.buildTreeFromNodes(aclRoot, aclNodes, ctx);
            }
            return aclTrees;
        });
    }
    async loadRights(node, ctx) {
        return ctx.accessRightsContext.rights.getOrSetValue(async () => {
            const rights = new access_rights_const_1.AccessRightsPermissions();
            try {
                const acl = node.aclList ? lodash_1.cloneDeep(node.aclList) : [];
                if (node.acl) {
                    acl.push(node.id);
                }
                if (0 === acl.length) {
                    ctx.accessRightsContext.treeRootNodeId.value = null;
                    return rights;
                }
                ctx.accessRightsContext.treeRootNodeId.value = acl[acl.length - 1];
                const aclTrees = await this.loadACLTrees(acl, ctx);
                const reset = new access_rights_const_1.AccessRightsPermissions();
                if (node.id === acl[acl.length - 1] && node.id in aclTrees) {
                    const nodeRightSet = await this.computeACLTree(aclTrees[acl.pop()], ctx.accessRightsContext.user.value);
                    reset.treeChildrenNodes = nodeRightSet.reset;
                    rights.treeChildrenNodes = nodeRightSet.rights;
                    rights.treeRootNode.read =
                        rights.treeRootNode.read || rights.treeChildrenNodes.read;
                    rights.treeRootNode.create =
                        rights.treeRootNode.create || rights.treeChildrenNodes.create;
                }
                let aclId = acl.pop();
                while (aclId &&
                    lodash_1.some(Object.keys(reset.treeChildrenNodes), (key) => false === reset.treeRootNode[key]) &&
                    lodash_1.some(Object.keys(rights.treeChildrenNodes), (key) => false === reset.treeRootNode[key])) {
                    if (aclId in aclTrees) {
                        const nodeRightSet = await this.computeACLTree(aclTrees[aclId], ctx.accessRightsContext.user.value);
                        this.mergeRight(reset.treeChildrenNodes, rights.treeChildrenNodes, nodeRightSet.rights);
                        this.mergeReset(reset.treeChildrenNodes, nodeRightSet.reset);
                        this.mergeRight(reset.treeRootNode, rights.treeRootNode, nodeRightSet.rights);
                        this.mergeReset(reset.treeRootNode, nodeRightSet.reset);
                    }
                    aclId = acl.pop();
                }
                // eslint-disable-next-line no-empty
            }
            catch (error) { }
            return rights;
        });
    }
    mergeReset(actualResets, addedResets) {
        for (const permission in actualResets) {
            actualResets[permission] =
                actualResets[permission] ||
                    addedResets[permission];
        }
    }
    mergeRight(resetRights, actualRights, addedRights) {
        for (const right in resetRights) {
            for (const permission in resetRights) {
                if (!resetRights[permission]) {
                    actualRights[permission] =
                        actualRights[permission] ||
                            addedRights[permission];
                }
            }
        }
    }
    async computeACLTree(aclTree, principal) {
        const computedTree = {
            reset: new access_rights_const_1.AccessRightsSet(),
            rights: new access_rights_const_1.AccessRightsSet(),
        };
        computedTree.reset.create = !!aclTree.treeNode.resetCreate;
        computedTree.reset.read = !!aclTree.treeNode.resetRead;
        computedTree.reset.update = !!aclTree.treeNode.resetUpdate;
        computedTree.reset.delete = !!aclTree.treeNode.resetDelete;
        const aclTreeSubTypes = aclTree.childrenByObjectTypeId;
        const ownersTypeId = this.appCtx.accessRightsOwnersType.value
            .id;
        if (ownersTypeId in aclTreeSubTypes) {
            const owners = aclTreeSubTypes[ownersTypeId][0];
            computedTree.reset.owner = !!owners.treeNode.resetOwner;
            if (this.findMatchingUser(owners.children, principal)) {
                computedTree.rights.owner = true;
                computedTree.rights.access = true;
                computedTree.rights.create = true;
                computedTree.rights.read = true;
                computedTree.rights.update = true;
                computedTree.rights.delete = true;
            }
        }
        const accessRightsAccessManagersId = this.appCtx
            .accessRightsAccessManagersType.value.id;
        if (accessRightsAccessManagersId in aclTreeSubTypes) {
            const accessRightsAccessManagers = aclTreeSubTypes[accessRightsAccessManagersId][0];
            computedTree.reset.access = !!accessRightsAccessManagers.treeNode
                .resetAccess;
            if (!computedTree.rights.access &&
                this.findMatchingUser(accessRightsAccessManagers.children, principal)) {
                computedTree.rights.access = true;
                computedTree.rights.create = true;
                computedTree.rights.read = true;
                computedTree.rights.update = true;
                computedTree.rights.delete = true;
            }
        }
        if (!(computedTree.rights.create &&
            computedTree.rights.read &&
            computedTree.rights.update &&
            computedTree.rights.delete)) {
            const groupTypeId = this.appCtx.accessRightsGroupType.value
                .id;
            if (groupTypeId in aclTreeSubTypes) {
                for (const group of aclTreeSubTypes[groupTypeId]) {
                    if (computedTree.rights.create &&
                        computedTree.rights.read &&
                        computedTree.rights.update &&
                        computedTree.rights.delete) {
                        break;
                    }
                    if ((!group.treeNode.grantCreate || computedTree.rights.create) &&
                        (!group.treeNode.grantRead || computedTree.rights.read) &&
                        (!group.treeNode.grantUpdate || computedTree.rights.update) &&
                        (!group.treeNode.grantDelete || computedTree.rights.delete)) {
                        continue;
                    }
                    if (this.findMatchingUser(group.children, principal)) {
                        computedTree.rights.create =
                            computedTree.rights.create || group.treeNode.grantCreate;
                        computedTree.rights.read =
                            computedTree.rights.read || group.treeNode.grantRead;
                        computedTree.rights.update =
                            computedTree.rights.update || group.treeNode.grantUpdate;
                        computedTree.rights.delete =
                            computedTree.rights.delete || group.treeNode.grantDelete;
                    }
                }
            }
            computedTree.rights.read =
                computedTree.rights.read ||
                    computedTree.rights.create ||
                    computedTree.rights.update ||
                    computedTree.rights.delete;
        }
        return computedTree;
    }
    findMatchingUser(users, principal) {
        return lodash_1.some(users, (user) => {
            switch (user.treeNode.objectTypeId) {
                case this.appCtx.userType.value.id:
                    return principal && user.treeNode.contentUserId === principal.id;
                case this.appCtx.anonymousUserType.value.id:
                    return true;
            }
        });
    }
};
AccessRightsService = tslib_1.__decorate([
    core_1.injectable({ scope: core_1.BindingScope.SINGLETON }),
    tslib_1.__param(0, core_1.service(application_service_1.ApplicationService)),
    tslib_1.__param(1, core_1.service(object_type_service_1.ObjectTypeService)),
    tslib_1.__param(2, core_1.service(object_node_service_1.ObjectNodeService)),
    tslib_1.__param(3, core_1.service(object_tree_service_1.ObjectTreeService)),
    tslib_1.__param(4, core_1.service(action_entity_service_1.ActionEntityService)),
    tslib_1.__metadata("design:paramtypes", [application_service_1.ApplicationService,
        object_type_service_1.ObjectTypeService,
        object_node_service_1.ObjectNodeService,
        object_tree_service_1.ObjectTreeService,
        action_entity_service_1.ActionEntityService])
], AccessRightsService);
exports.AccessRightsService = AccessRightsService;
//# sourceMappingURL=access-rights.service.js.map