Viewing File: /usr/local/cpanel/whostmgr/docroot/templates/mod_security/config.cmb.js

/*
# templates/mod_security/views/commonController.js Copyright(c) 2020 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define */

/* ------------------------------------------------------------------------------
* DEVELOPER NOTES:
*  1) Put all common application functionality here, maybe
*-----------------------------------------------------------------------------*/

define(
    'app/views/commonController',[
        "angular",
        "cjt/filters/wrapFilter",
        "cjt/services/alertService",
        "cjt/directives/alertList",
        "uiBootstrap"
    ],
    function(angular) {

        var app;
        try {
            app = angular.module("App");
        } catch (e) {
            app = angular.module("App", ["ui.bootstrap", "ngSanitize"]);
        }

        var controller = app.controller(
            "commonController",
            ["$scope", "$location", "$rootScope", "alertService", "PAGE",
                function($scope, $location, $rootScope, alertService, PAGE) {

                // Setup the installed bit...
                    $scope.isInstalled = PAGE.installed;

                    // Bind the alerts service to the local scope
                    $scope.alerts = alertService.getAlerts();

                    $scope.route = null;

                    /**
                 * Closes an alert and removes it from the alerts service
                 *
                 * @method closeAlert
                 * @param {String} index The array index of the alert to remove
                 */
                    $scope.closeAlert = function(id) {
                        alertService.remove(id);
                    };

                    /**
                 * Determines if the current view matches the supplied pattern
                 *
                 * @method isCurrentView
                 * @param {String} view The path to the view to match
                 */
                    $scope.isCurrentView = function(view) {
                        if ( $scope.route && $scope.route.$$route ) {
                            return $scope.route.$$route.originalPath === view;
                        }
                        return false;
                    };

                    // register listener to watch route changes
                    $rootScope.$on( "$routeChangeStart", function(event, next, current) {
                        $scope.route = next;
                    });
                }
            ]);


        return controller;
    }
);

/*
# mod_security/services/configService.js          Copyright(c) 2020 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define: false */

define(
    'app/services/configService',[

        // Libraries
        "angular",

        // CJT
        "cjt/util/locale",
        "cjt/util/parse",
        "cjt/io/api",
        "cjt/io/whm-v1-request",
        "cjt/io/whm-v1", // IMPORTANT: Load the driver so its ready

        // Angular components
        "cjt/services/APIService"
    ],
    function(angular, LOCALE, PARSE, API, APIREQUEST, APIDRIVER) {

        // Constants
        var NO_MODULE = "";

        // Fetch the current application
        var app = angular.module("App");

        /**
         * Apply the defaults to the config if needed.
         * @param  {Array} configs
         */
        function applyDefaults(config) {
            if (config.default && config.missing) {
                if (config.type === "number") {
                    config.state = parseInt(config.default, 10);
                } else {
                    config.state = config.default;
                }
            }
        }

        /**
         * Converts the response to our application data structure
         * @method convertResponseToList
         * @private
         * @param  {Object} response
         * @return {Object} Sanitized data structure.
         */
        function convertResponseToList(response) {
            var items = [];
            if (response.status) {
                var data = response.data;
                for (var i = 0, length = data.length; i < length; i++) {
                    var config = data[i];

                    // Clean up the boolean data
                    if (typeof (config.engine) !== "undefined") {
                        config.engine = PARSE.parsePerlBoolean(config.engine);
                    }

                    // Apply the default if the config is missing
                    applyDefaults(config);

                    // Mark the record as unchanged
                    config.changed = false;

                    items.push(
                        config
                    );
                }

                return items;
            } else {
                return [];
            }
        }

        /**
         * Setup the configuration models API service
         */
        app.factory("configService", ["$q", "APIService", function($q, APIService) {

            // Set up the service's constructor and parent
            var ConfigService = function() {};
            ConfigService.prototype = new APIService();

            // Extend the prototype with any class-specific functionality
            angular.extend(ConfigService.prototype, {

                /**
                 * Get a list of mod_security rule hits that match the selection criteria passed in meta parameter
                 * @return {Promise} Promise that will fulfill the request.
                 */
                fetchList: function() {
                    var apiCall = new APIREQUEST.Class();
                    apiCall.initialize(NO_MODULE, "modsec_get_settings");
                    var deferred = this.deferred(apiCall, { transformAPISuccess: convertResponseToList });
                    return deferred.promise;
                },

                /**
                 * Save the changed configurations.
                 * @param  {Array} configs
                 * @return {Promise} Promise that will fulfill the request.
                 */
                save: function(configs) {
                    if (!configs) {
                        return;
                    }

                    var toSave = [];
                    for (var i = 0, l = configs.length; i < l; i++) {
                        if (configs[i].changed) {
                            toSave.push(configs[i]);
                        }
                    }

                    if (toSave.length > 0 ) {

                        var apiCall = new APIREQUEST.Class();
                        apiCall.initialize(NO_MODULE, "modsec_batch_settings");
                        for (var j = 0, jl = toSave.length; j < jl; j++) {
                            var item = toSave[j];
                            if (
                                (!item.engine && item.default && (                                                        // Not an engine and has a default
                                    ((item.type === "text" || item.type === "radio") && (item.state === item.default)) || // Text or radio field with a default set to default, but not missing from file
                                    (item.type === "number" && (parseInt(item.state, 10) === item.default))               // Number field with a default set to default, but not missing from file
                                )) ||
                                (item.state === "") // Text or number that has been cleared, but isn't missing from file
                            ) {
                                if (!item.missing) {
                                    apiCall.addArgument("setting_id", toSave[j].setting_id, true);
                                    apiCall.addArgument("remove", 1, true);

                                    // Otherwise, nothing to do here.
                                }
                            } else {
                                apiCall.addArgument("setting_id", toSave[j].setting_id, true);
                                apiCall.addArgument("state", item.state, true);
                            }
                            apiCall.incrementAuto();
                        }
                        apiCall.addArgument("commit", 1);

                        var deferred = this.deferred(apiCall, {
                            apiSuccess: function(response, deferred) {
                                for (var i = 0, l = configs.length; i < l; i++) {
                                    if (configs[i].changed) {
                                        configs[i].changed = false;
                                    }
                                }

                                deferred.resolve(response.data);
                            },
                            apiFailure: function(response) {

                                // TODO: Get the list from the data
                                deferred.reject(response.error);
                            }
                        });

                        // pass the promise back to the controller
                        return deferred.promise;
                    }
                },

                /**
                *  Helper method that calls convertResponseToList to prepare the data structure
                * @param  {Object} response
                * @return {Object} Sanitized data structure.
                */
                prepareList: function(response) {

                    // Since this is coming from the backend, but not through the api.js layer,
                    // we need to parse it to the frontend format.
                    response = APIDRIVER.parse_response(response).parsedResponse;
                    return convertResponseToList(response);
                },

                /**
                 * Apply the defaults to the config if needed.
                 * @param  {Array} configs
                 */
                applyDefaults: applyDefaults
            });

            return new ConfigService();
        }]);
    }
);

/*
# templates/mod_security/views/configController.js Copyright(c) 2020 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define: false */

define(
    'app/views/configController',[
        "angular",
        "lodash",
        "cjt/validator/datatype-validators",
        "cjt/validator/ascii-data-validators",
        "cjt/util/locale",
        "uiBootstrap",
        "cjt/directives/autoFocus",
        "cjt/filters/wrapFilter",
        "cjt/directives/spinnerDirective",
        "app/services/configService",
        "cjt/directives/validationContainerDirective",
        "cjt/validator/validateDirectiveFactory",
        "cjt/directives/dynamicValidatorDirective",
        "cjt/decorators/dynamicName"
    ],
    function(angular, _, DATA_TYPE_VALIDATORS, ASCII_DATA_VALIDATORS, LOCALE) {

        // Retrieve the current application
        var app = angular.module("App");

        var controller = app.controller(
            "configController",
            ["$scope", "$location", "$anchorScroll", "$routeParams", "$q", "configService", "spinnerAPI", "PAGE",
                function($scope, $location, $anchorScroll, $routeParams, $q, configService, spinnerAPI, PAGE) {

                // Setup some scope variables to defaults
                    $scope.saveSuccess = false;
                    $scope.saveError = false;

                    // setup data structures for the view
                    $scope.configs = [];

                    // Setup the installed bit...
                    $scope.isInstalled = PAGE.installed;

                    /**
                     * SecAuditEngine directive's 'Log All' option is
                     * not recommended security wise. For this we are
                     * re-ordered the log options in UI to make sure it shows up in the end.
                     * @param  {Array} configs
                     * @return {Array}
                     */
                    var reorderSecAuditEngineOptions = function(configs) {
                        _.each(configs, function(cfg) {
                            if (cfg.directive !== "SecAuditEngine") {
                                return;
                            }
                            _.reverse(cfg.radio_options);
                            return false;
                        });
                        return configs;
                    };

                    /**
                 * Validate rules for the dynamic validation
                 * @param  {Any} value
                 * @param  {String} name
                 * @param  {Any} arg
                 * @param  {Result} result
                 * @return {Boolean}
                 */
                    $scope.validateField = function(value, name, arg, result) {
                        var regex;
                        if (!value) {
                            result.isValid = true;
                            return true;
                        }

                        var ret;
                        switch (name) {
                            case "path":
                                break;

                            case "startsWith":
                                ret = ASCII_DATA_VALIDATORS.methods.startsWith(value, arg);
                                if (!ret.isValid) {
                                    result.isValid = false;
                                    result.add(name, LOCALE.maketext("The value must start with the “[_1]” character.", "|"));
                                }
                                break;

                            case "honeypotAccessKey":

                                // http://www.projecthoneypot.org/httpbl_api.php
                                // All Access Keys are 12-characters in length, lower case, and contain only alpha characters
                                regex = /^[a-z]{12}$/;
                                if (!regex.test(value)) {
                                    result.isValid = false;
                                    result.add(name, LOCALE.maketext("The value that you provided is not a valid [asis,honeypot] access key. This value must be a sequence of 12 lower-case alphabetic characters."));
                                }
                                break;

                            case "positiveInteger":
                                ret = DATA_TYPE_VALIDATORS.methods.positiveInteger(value);
                                if (!ret.isValid) {
                                    result.isValid = false;
                                    result.add(name, ret.get("positiveInteger").message);
                                }

                                break;

                            default:
                                if (window.console) {
                                    window.console.log("Unknown validation type.");
                                }
                                break;
                        }
                        return result.isValid;
                    };

                    /**
                 * Toggles the clear button and conditionally performs a search.
                 * The expected behavior is if the user clicks the button or focuses the button and hits enter the button state rules.
                 * If the user hits <enter> in the field, its a submit action with just request the data.
                 * @param {Boolean} inSearch Toggle button clicked.
                 */
                    $scope.toggleSearch = function(inSearch) {
                        if ( !inSearch ) {
                            $scope.searchPattern = "";
                        }
                    };

                    /**
                 * Clears the search field when the user
                 * presses the Esc key
                 * @param {Event} event - The event object
                 */
                    $scope.clearSearch = function(event) {
                        if (event.keyCode === 27) {
                            $scope.searchPattern = "";
                        }
                    };

                    /**
                 * Fetch the list of hits from the server
                 * @return {Promise} Promise that when fulfilled will result in the list being loaded with the new criteria.
                 */
                    $scope.fetch = function() {
                        $scope.saveSuccess = false;
                        $scope.saveError = false;
                        spinnerAPI.startGroup("loadingSpinner");
                        return configService
                            .fetchList()
                            .then(function(results) {
                                $scope.configs = reorderSecAuditEngineOptions(results);
                                spinnerAPI.stopGroup("loadingSpinner");
                            }, function(error) {
                                $scope.saveError = error;
                                spinnerAPI.stopGroup("loadingSpinner");
                            });
                    };

                    /**
                 * Disable the save button based on form state
                 * @param  {FormController} form
                 * @return {Boolean}
                 */
                    $scope.disableSave = function(form) {
                        return form.$pristine || (form.$dirty && form.$invalid);
                    };

                    /**
                 * Update the changed flag based on the event
                 * @param  {Object} setting Setting that changed.
                 */
                    $scope.changed = function(setting) {
                        setting.changed = true;
                    };

                    /**
                 * Get the field type for text fields. Either text or number.
                 * @param  {Object} setting
                 * @return {String}         text || number.
                 */
                    $scope.getFieldType = function(setting) {
                        return setting.field_type || "text";
                    };

                    /**
                 * Construct the model name from the parts
                 * @param  {String} prefix
                 * @param  {String} id
                 * @return {String}
                 */
                    $scope.makeModelName = function(prefix, id) {
                        return prefix + id;
                    };

                    /**
                 * Save the changes
                 * @param  {FormController} form
                 * @return {Promise}
                 */
                    $scope.save = function(form) {
                        $scope.saveSuccess = false;
                        $scope.saveError = false;

                        if (!form.$valid) {
                            return;
                        }

                        var promise = configService.save($scope.configs);

                        // Since the service may not return a promise,
                        // we check this first.
                        if (promise) {
                            promise.then(
                                function(data) {
                                    $scope.saveError = false;
                                    $scope.saveSuccess = true;
                                    form.$setPristine();

                                    var comparisonFactory = function(test) {
                                        return function(item) {
                                            return item.setting_id === test.setting_id;
                                        };
                                    };

                                    // Patch the defaults for items not in default state.
                                    for (var j = 0, ll = $scope.configs.length; j < ll; j++) {
                                        configService.applyDefaults($scope.configs[j]);
                                    }

                                    // Patch the state for returned items
                                    for (var i = 0, l = data.length; i < l; i++) {
                                        var config = _.find($scope.configs, comparisonFactory(data[i]));
                                        if (config) {
                                            config.state = data[i].state || data[i].default;
                                            config.missing = data[i].missing;
                                        }
                                    }

                                },
                                function(error) {
                                    $scope.saveError = error ? error : LOCALE.maketext("The system experienced an unknown error when it attempted to save the file.");
                                    $scope.saveSuccess = false;
                                }
                            ).then(function() {
                                $scope.scrollTo("top", true);
                            });

                            return promise;
                        }

                        return promise;
                    };

                    $scope.showLogAllWarning = function(radioOption, directive) {
                        return (radioOption === "On" && directive === "SecAuditEngine");
                    };

                    $scope.showLogNoteworthyLabel = function(radioOption, directive) {
                        return (radioOption === "RelevantOnly" && directive === "SecAuditEngine");
                    };

                    // check for page data in the template if this is a first load
                    if (app.firstLoad.configs && PAGE.configs) {
                        app.firstLoad.configs = false;
                        $scope.configs = configService.prepareList(PAGE.configs);
                        $scope.configs = reorderSecAuditEngineOptions($scope.configs);
                    } else {

                    // Otherwise, retrieve it via ajax
                        $scope.fetch();
                    }
                }
            ]);

        return controller;
    }
);

/*
# templates/mod_security/config.js                Copyright(c) 2020 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global require, define, PAGE */

define(
    'app/config',[
        "angular",
        "jquery",
        "lodash",
        "cjt/core",
        "cjt/modules",
        "ngRoute",
        "uiBootstrap"
    ],
    function(angular, $, _, CJT) {

        // First create the application
        angular.module("App", [
            "cjt2.config.whm.configProvider", // This needs to load first
            "ngRoute",
            "ui.bootstrap",
            "cjt2.whm"
        ]);

        // Then load the application dependencies
        var app = require(
            [
                "cjt/bootstrap",
                "cjt/util/locale",

                // Application Modules
                "cjt/views/applicationController",
                "app/views/commonController",
                "app/views/configController",
                "cjt/services/autoTopService",
                "cjt/services/whm/breadcrumbService",
                "cjt/services/whm/titleService"
            ], function(BOOTSTRAP, LOCALE) {

                var app = angular.module("App");
                app.value("PAGE", PAGE);

                app.firstLoad = {
                    configs: true,
                    vendors: true
                };

                // routing
                app.config(["$routeProvider",
                    function($routeProvider) {

                        // Configuration
                        $routeProvider.when("/config", {
                            controller: "configController",
                            templateUrl: CJT.buildFullPath("mod_security/views/configView.ptt"),
                            breadcrumb: LOCALE.maketext("Configure Global Directives"),
                            title: LOCALE.maketext("Configure Global Directives"),
                            reloadOnSearch: false,
                            group: "config",
                            name: "config"
                        });

                        $routeProvider.otherwise({
                            redirectTo: function(routeParams, path, search) {
                                return "/config?" + window.location.search;
                            }
                        });
                    }
                ]);

                app.run(["autoTopService", "breadcrumbService", "titleService", function(autoTopService, breadcrumbService, titleService) {

                    // Setup the automatic scroll to top for view changes
                    autoTopService.initialize();

                    // Setup the breadcrumbs service
                    breadcrumbService.initialize();

                    // Setup the title update service
                    titleService.initialize();
                }]);

                BOOTSTRAP(document);

            });

        return app;
    }
);

Back to Directory File Manager