"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const configCompiler = require("./config-compiler");
const configValidator = require("./config-validator");
const rule_application_1 = require("./rule-application");
const rule_cache_1 = require("./rule-cache");
const rulesMap = require("./rules-map");
const types_1 = require("@deepstream/types");
const UNDEFINED = 'undefined';
class ConfigPermission extends types_1.DeepstreamPlugin {
    /**
     * A permission handler that reads a rules config YAML or JSON, validates
     * its contents, compiles it and executes the permissions that it contains
     * against every incoming message.
     *
     * This is the standard permission handler that deepstream exposes, in conjunction
     * with the default permission.yml it allows everything, but at the same time provides
     * a convenient starting point for permission declarations.
     */
    constructor(permissionOptions, services, config) {
        super();
        this.permissionOptions = permissionOptions;
        this.services = services;
        this.config = config;
        this.description = 'Valve Permissions';
        this.recordHandler = null;
        this.logger = this.services.logger.getNameSpace('PERMISSION');
        this.ruleCache = new rule_cache_1.default(this.permissionOptions);
        const maxRuleIterations = permissionOptions.maxRuleIterations;
        if (maxRuleIterations !== undefined && maxRuleIterations < 1) {
            this.logger.fatal(types_1.EVENT.PLUGIN_INITIALIZATION_ERROR, 'Maximum rule iteration has to be at least one');
        }
        this.useConfig(permissionOptions.permissions);
    }
    async whenReady() {
    }
    async close() {
        this.ruleCache.close();
    }
    /**
     * Will be invoked with the initialized recordHandler instance by deepstream.io
     */
    setRecordHandler(recordHandler) {
        this.recordHandler = recordHandler;
    }
    /**
     * Validates and compiles a loaded config. This can be called as the result
     * of a config being passed to the permission service upon initialization,
     * as a result of loadConfig or at runtime
     *
     * CLI useConfig <config>
     */
    useConfig(permissions) {
        const validationResult = configValidator.validate(permissions);
        if (validationResult !== true) {
            this.logger.fatal(types_1.EVENT.PLUGIN_INITIALIZATION_ERROR, `invalid permission config - ${validationResult}`);
            return;
        }
        this.permissions = configCompiler.compile(permissions);
        this.ruleCache.reset();
    }
    /**
     * Implements the permission service's canPerformAction interface
     * method
     *
     * This is the main entry point for permissionOperations and will
     * be called for every incoming message. This method executes four steps
     *
     * - Check if the incoming message conforms to basic specs
     * - Check if the incoming message requires permissions
     * - Load the applicable permissions
     * - Apply them
     */
    canPerformAction(socketWrapper, message, callback, passItOn) {
        const ruleSpecification = rulesMap.getRulesForMessage(message);
        if (ruleSpecification === null) {
            callback(socketWrapper, message, passItOn, null, true);
            return;
        }
        const ruleData = this.getCompiledRulesForName(message.name, ruleSpecification);
        if (!ruleData) {
            callback(socketWrapper, message, passItOn, null, false);
            return;
        }
        // tslint:disable-next-line
        new rule_application_1.default({
            recordHandler: this.recordHandler,
            socketWrapper,
            userId: socketWrapper.userId,
            serverData: socketWrapper.serverData,
            path: ruleData,
            ruleSpecification,
            message,
            action: ruleSpecification.action,
            regexp: ruleData.regexp,
            rule: ruleData.rule,
            name: message.name,
            callback,
            passItOn,
            logger: this.logger,
            permissionOptions: this.permissionOptions,
            config: this.config,
            services: this.services,
        });
    }
    /**
     * Evaluates the rules within a section and returns the matching rule for a path.
     * Takes basic specificity (as deduced from the path length) into account and
     * caches frequently used rules for faster access
     */
    getCompiledRulesForName(name, ruleSpecification) {
        const compiledRules = this.ruleCache.get(ruleSpecification.section, name, ruleSpecification.type);
        if (compiledRules) {
            return compiledRules;
        }
        const sections = this.permissions[ruleSpecification.section];
        let pathLength = 0;
        let result = null;
        for (let i = 0; i < sections.length; i++) {
            const { rules, path, regexp } = sections[i];
            if (typeof rules[ruleSpecification.type] !== UNDEFINED && path.length >= pathLength && regexp.test(name)) {
                pathLength = path.length;
                result = {
                    path,
                    regexp,
                    rule: rules[ruleSpecification.type],
                };
            }
        }
        if (result) {
            this.ruleCache.set(ruleSpecification.section, name, ruleSpecification.type, result);
        }
        return result;
    }
}
exports.ConfigPermission = ConfigPermission;
//# sourceMappingURL=config-permission.js.map