Ver código fonte

Merge pull request #1039 from weseek/imprv/abolish-old-config-api-2

Imprv/abolish old config api 2
Sou Mizobuchi 6 anos atrás
pai
commit
54f70c291c

+ 6 - 6
src/server/crowi/express-init.js

@@ -64,12 +64,12 @@ module.exports = function(crowi, app) {
     const Config = crowi.model('Config');
     const Config = crowi.model('Config');
     app.set('tzoffset', tzoffset);
     app.set('tzoffset', tzoffset);
 
 
-    req.config = config;
+    // req.config = config;
     req.csrfToken = null;
     req.csrfToken = null;
 
 
     res.locals.req = req;
     res.locals.req = req;
-    res.locals.baseUrl = configManager.getSiteUrl();
-    res.locals.config = config;
+    res.locals.baseUrl = crowi.appService.getSiteUrl();
+    // res.locals.config = config;
     res.locals.env = env;
     res.locals.env = env;
     res.locals.now = now;
     res.locals.now = now;
     res.locals.tzoffset = tzoffset;
     res.locals.tzoffset = tzoffset;
@@ -80,7 +80,7 @@ module.exports = function(crowi, app) {
       restrictGuestMode: Config.getRestrictGuestModeLabels(),
       restrictGuestMode: Config.getRestrictGuestModeLabels(),
       registrationMode: Config.getRegistrationModeLabels(),
       registrationMode: Config.getRegistrationModeLabels(),
     };
     };
-    res.locals.local_config = Config.getLocalconfig(config); // config for browser context
+    res.locals.local_config = Config.getLocalconfig(); // config for browser context
 
 
     next();
     next();
   });
   });
@@ -117,8 +117,8 @@ module.exports = function(crowi, app) {
       return next();
       return next();
     }
     }
 
 
-    const basicName = configManager.getConfig('crowi', 'security:basicName');
-    const basicSecret = configManager.getConfig('crowi', 'security:basicSecret');
+    const basicName = getConfig('crowi', 'security:basicName');
+    const basicSecret = getConfig('crowi', 'security:basicSecret');
     if (basicName && basicSecret) {
     if (basicName && basicSecret) {
       return basicAuth(basicName, basicSecret)(req, res, next);
       return basicAuth(basicName, basicSecret)(req, res, next);
     }
     }

+ 48 - 0
src/server/crowi/index.js

@@ -38,6 +38,10 @@ function Crowi(rootdir) {
   this.mailer = {};
   this.mailer = {};
   this.passportService = null;
   this.passportService = null;
   this.globalNotificationService = null;
   this.globalNotificationService = null;
+  this.slackNotificationService = null;
+  this.xssService = null;
+  this.aclService = null;
+  this.appService = null;
   this.restQiitaAPIService = null;
   this.restQiitaAPIService = null;
   this.cdnResourcesService = new CdnResourcesService();
   this.cdnResourcesService = new CdnResourcesService();
   this.interceptorManager = new InterceptorManager();
   this.interceptorManager = new InterceptorManager();
@@ -83,6 +87,10 @@ Crowi.prototype.init = async function() {
     this.setupSlack(),
     this.setupSlack(),
     this.setupCsrf(),
     this.setupCsrf(),
     this.setUpGlobalNotification(),
     this.setUpGlobalNotification(),
+    this.setUpSlacklNotification(),
+    this.setUpXss(),
+    this.setUpAcl(),
+    this.setUpApp(),
     this.setUpRestQiitaAPI(),
     this.setUpRestQiitaAPI(),
   ]);
   ]);
 };
 };
@@ -438,6 +446,46 @@ Crowi.prototype.setUpGlobalNotification = function() {
   }
   }
 };
 };
 
 
+/**
+ * setup SlackNotificationService
+ */
+Crowi.prototype.setUpSlacklNotification = function() {
+  const SlackNotificationService = require('../service/slack-notification');
+  if (this.slackNotificationService == null) {
+    this.slackNotificationService = new SlackNotificationService(this.configManager);
+  }
+};
+
+/**
+ * setup XssService
+ */
+Crowi.prototype.setUpXss = function() {
+  const XssService = require('../service/xss');
+  if (this.xssService == null) {
+    this.xssService = new XssService(this.configManager);
+  }
+};
+
+/**
+ * setup AclService
+ */
+Crowi.prototype.setUpAcl = function() {
+  const AclService = require('../service/acl');
+  if (this.aclService == null) {
+    this.aclService = new AclService(this.configManager);
+  }
+};
+
+/**
+ * setup AppService
+ */
+Crowi.prototype.setUpApp = function() {
+  const AppService = require('../service/app');
+  if (this.appService == null) {
+    this.appService = new AppService(this.configManager);
+  }
+};
+
 /**
 /**
  * setup RestQiitaAPIService
  * setup RestQiitaAPIService
  */
  */

+ 33 - 41
src/server/models/config.js

@@ -138,6 +138,14 @@ module.exports = function(crowi) {
     };
     };
   }
   }
 
 
+  function getDefaultNotificationConfigs() {
+    return {
+      'slack:isIncomingWebhookPrioritized': false,
+      'slack:incomingWebhookUrl': '',
+      'slack:token': '',
+    };
+  }
+
   function getValueForCrowiNS(config, key) {
   function getValueForCrowiNS(config, key) {
     crowi.configManager.getConfig('crowi', key);
     crowi.configManager.getConfig('crowi', key);
     // // return the default value if undefined
     // // return the default value if undefined
@@ -179,6 +187,13 @@ module.exports = function(crowi) {
     return getDefaultMarkdownConfigs();
     return getDefaultMarkdownConfigs();
   };
   };
 
 
+  /**
+   * It is deprecated to use this for anything other than ConfigLoader#load.
+   */
+  configSchema.statics.getDefaultNotificationConfigsObject = function() {
+    return getDefaultNotificationConfigs();
+  };
+
   configSchema.statics.getRestrictGuestModeLabels = function() {
   configSchema.statics.getRestrictGuestModeLabels = function() {
     const labels = {};
     const labels = {};
     labels[SECURITY_RESTRICT_GUEST_MODE_DENY] = 'security_setting.guest_mode.deny';
     labels[SECURITY_RESTRICT_GUEST_MODE_DENY] = 'security_setting.guest_mode.deny';
@@ -229,29 +244,6 @@ module.exports = function(crowi) {
   //   });
   //   });
   // };
   // };
 
 
-  configSchema.statics.setupConfigFormData = function(ns, config) {
-    let defaultConfig = {};
-
-    // set Default Settings
-    if (ns === 'crowi') {
-      defaultConfig = getDefaultCrowiConfigs();
-    }
-    else if (ns === 'markdown') {
-      defaultConfig = getDefaultMarkdownConfigs();
-    }
-
-    if (!defaultConfig[ns]) {
-      defaultConfig[ns] = {};
-    }
-    Object.keys(config[ns] || {}).forEach((key) => {
-      if (config[ns][key] !== undefined) {
-        defaultConfig[key] = config[ns][key];
-      }
-    });
-    return defaultConfig;
-  };
-
-
   configSchema.statics.updateNamespaceByArray = function(ns, configs, callback) {
   configSchema.statics.updateNamespaceByArray = function(ns, configs, callback) {
     const Config = this;
     const Config = this;
     if (configs.length < 0) {
     if (configs.length < 0) {
@@ -611,30 +603,30 @@ module.exports = function(crowi) {
     return (!!config.notification['slack:token']);
     return (!!config.notification['slack:token']);
   };
   };
 
 
-  configSchema.statics.getLocalconfig = function(config) {
+  configSchema.statics.getLocalconfig = function() { // CONF.RF: これも別のメソッドにする
     const Config = this;
     const Config = this;
     const env = process.env;
     const env = process.env;
 
 
     const localConfig = {
     const localConfig = {
       crowi: {
       crowi: {
         title: Config.appTitle(crowi),
         title: Config.appTitle(crowi),
-        url: crowi.configManager.getSiteUrl(),
+        url: crowi.appService.getSiteUrl(),
       },
       },
       upload: {
       upload: {
-        image: Config.isUploadable(config),
-        file: Config.fileUploadEnabled(config),
+        image: crowi.configManager.getIsUploadable(),
+        file: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
       },
       },
-      behaviorType: Config.behaviorType(config),
-      layoutType: Config.layoutType(config),
-      isEnabledLinebreaks: Config.isEnabledLinebreaks(config),
-      isEnabledLinebreaksInComments: Config.isEnabledLinebreaksInComments(config),
-      isEnabledXssPrevention: Config.isEnabledXssPrevention(config),
-      xssOption: Config.xssOption(config),
-      tagWhiteList: Config.tagWhiteList(config),
-      attrWhiteList: Config.attrWhiteList(config),
-      highlightJsStyleBorder: Config.highlightJsStyleBorder(config),
-      isSavedStatesOfTabChanges: Config.isSavedStatesOfTabChanges(config),
-      hasSlackConfig: Config.hasSlackConfig(config),
+      behaviorType: crowi.configManager.getConfig('crowi', 'customize:behavior'),
+      layoutType: crowi.configManager.getConfig('crowi', 'customize:layout'),
+      isEnabledLinebreaks: crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
+      isEnabledLinebreaksInComments: crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
+      isEnabledXssPrevention: crowi.configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
+      xssOption: crowi.configManager.getConfig('markdown', 'markdown:xss:option'),
+      tagWhiteList: crowi.xssService.getTagWhiteList(),
+      attrWhiteList: crowi.xssService.getAttrWhiteList(),
+      highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
+      isSavedStatesOfTabChanges: crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
+      hasSlackConfig: crowi.configManager.getConfig('crowi', 'customize:behavior'),
       env: {
       env: {
         PLANTUML_URI: env.PLANTUML_URI || null,
         PLANTUML_URI: env.PLANTUML_URI || null,
         BLOCKDIAG_URI: env.BLOCKDIAG_URI || null,
         BLOCKDIAG_URI: env.BLOCKDIAG_URI || null,
@@ -642,9 +634,9 @@ module.exports = function(crowi) {
         MATHJAX: env.MATHJAX || null,
         MATHJAX: env.MATHJAX || null,
         NO_CDN: env.NO_CDN || null,
         NO_CDN: env.NO_CDN || null,
       },
       },
-      recentCreatedLimit: Config.showRecentCreatedNumber(config),
-      isAclEnabled: !Config.isPublicWikiOnly(config),
-      globalLang: Config.globalLang(config),
+      recentCreatedLimit: crowi.configManager.getConfig('crowi', 'customize:showRecentCreatedNumber'),
+      isAclEnabled: !crowi.aclService.getIsPublicWikiOnly(),
+      globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
     };
     };
 
 
     return localConfig;
     return localConfig;

+ 1 - 1
src/server/models/user.js

@@ -713,7 +713,7 @@ module.exports = function(crowi) {
                 vars: {
                 vars: {
                   email: user.email,
                   email: user.email,
                   password: user.password,
                   password: user.password,
-                  url: crowi.configManager.getSiteUrl(),
+                  url: crowi.appService.getSiteUrl(),
                   appTitle: Config.appTitle(config),
                   appTitle: Config.appTitle(config),
                 },
                 },
               },
               },

+ 10 - 8
src/server/routes/admin.js

@@ -14,6 +14,8 @@ module.exports = function(crowi, app) {
   const GlobalNotificationMailSetting = models.GlobalNotificationMailSetting;
   const GlobalNotificationMailSetting = models.GlobalNotificationMailSetting;
   const GlobalNotificationSlackSetting = models.GlobalNotificationSlackSetting; // eslint-disable-line no-unused-vars
   const GlobalNotificationSlackSetting = models.GlobalNotificationSlackSetting; // eslint-disable-line no-unused-vars
 
 
+  const { configManager } = crowi;
+
   const recommendedWhitelist = require('@commons/service/xss/recommended-whitelist');
   const recommendedWhitelist = require('@commons/service/xss/recommended-whitelist');
   const PluginUtils = require('../plugins/plugin-utils');
   const PluginUtils = require('../plugins/plugin-utils');
   const ApiResponse = require('../util/apiResponse');
   const ApiResponse = require('../util/apiResponse');
@@ -90,7 +92,7 @@ module.exports = function(crowi, app) {
   // app.get('/admin/app'                  , admin.app.index);
   // app.get('/admin/app'                  , admin.app.index);
   actions.app = {};
   actions.app = {};
   actions.app.index = function(req, res) {
   actions.app.index = function(req, res) {
-    const settingForm = Config.setupConfigFormData('crowi', req.config);
+    const settingForm = configManager.getConfigByPrefix('crowi', 'app:');
 
 
     return res.render('admin/app', {
     return res.render('admin/app', {
       settingForm,
       settingForm,
@@ -103,16 +105,16 @@ module.exports = function(crowi, app) {
   // app.get('/admin/security'                  , admin.security.index);
   // app.get('/admin/security'                  , admin.security.index);
   actions.security = {};
   actions.security = {};
   actions.security.index = function(req, res) {
   actions.security.index = function(req, res) {
-    const settingForm = Config.setupConfigFormData('crowi', req.config);
-    const isAclEnabled = !Config.isPublicWikiOnly(req.config);
+    const settingForm = configManager.getConfigByPrefix('crowi', 'security:');
+    const isAclEnabled = crowi.aclService.getIsPublicWikiOnly();
+
     return res.render('admin/security', { settingForm, isAclEnabled });
     return res.render('admin/security', { settingForm, isAclEnabled });
   };
   };
 
 
   // app.get('/admin/markdown'                  , admin.markdown.index);
   // app.get('/admin/markdown'                  , admin.markdown.index);
   actions.markdown = {};
   actions.markdown = {};
   actions.markdown.index = function(req, res) {
   actions.markdown.index = function(req, res) {
-    const config = crowi.getConfig();
-    const markdownSetting = Config.setupConfigFormData('markdown', config);
+    const markdownSetting = configManager.getConfigByPrefix('crowi', 'markdown:');
 
 
     return res.render('admin/markdown', {
     return res.render('admin/markdown', {
       markdownSetting,
       markdownSetting,
@@ -188,7 +190,7 @@ module.exports = function(crowi, app) {
   // app.get('/admin/customize' , admin.customize.index);
   // app.get('/admin/customize' , admin.customize.index);
   actions.customize = {};
   actions.customize = {};
   actions.customize.index = function(req, res) {
   actions.customize.index = function(req, res) {
-    const settingForm = Config.setupConfigFormData('crowi', req.config);
+    const settingForm = configManager.getConfigByPrefix('crowi', 'customize:');
 
 
     /* eslint-disable quote-props, no-multi-spaces */
     /* eslint-disable quote-props, no-multi-spaces */
     const highlightJsCssSelectorOptions = {
     const highlightJsCssSelectorOptions = {
@@ -216,7 +218,7 @@ module.exports = function(crowi, app) {
   actions.notification.index = async(req, res) => {
   actions.notification.index = async(req, res) => {
     const config = crowi.getConfig();
     const config = crowi.getConfig();
     const UpdatePost = crowi.model('UpdatePost');
     const UpdatePost = crowi.model('UpdatePost');
-    let slackSetting = Config.setupConfigFormData('notification', config);
+    let slackSetting = configManager.getConfigByPrefix('notification', 'slack:');
     const hasSlackIwhUrl = Config.hasSlackIwhUrl(config);
     const hasSlackIwhUrl = Config.hasSlackIwhUrl(config);
     const hasSlackToken = Config.hasSlackToken(config);
     const hasSlackToken = Config.hasSlackToken(config);
 
 
@@ -851,7 +853,7 @@ module.exports = function(crowi, app) {
   // Importer management
   // Importer management
   actions.importer = {};
   actions.importer = {};
   actions.importer.index = function(req, res) {
   actions.importer.index = function(req, res) {
-    const settingForm = Config.setupConfigFormData('crowi', req.config);
+    const settingForm = configManager.getConfigByPrefix('crowi', 'importer:');
 
 
     return res.render('admin/importer', {
     return res.render('admin/importer', {
       settingForm,
       settingForm,

+ 1 - 1
src/server/routes/hackmd.js

@@ -40,7 +40,7 @@ module.exports = function(crowi, app) {
       agentScriptContentTpl = swig.compileFile(agentScriptPath);
       agentScriptContentTpl = swig.compileFile(agentScriptPath);
     }
     }
 
 
-    const origin = crowi.configManager.getSiteUrl();
+    const origin = crowi.appService.getSiteUrl();
 
 
     // generate definitions to replace
     // generate definitions to replace
     const definitions = {
     const definitions = {

+ 1 - 1
src/server/routes/login.js

@@ -222,7 +222,7 @@ module.exports = function(crowi, app) {
                     vars: {
                     vars: {
                       createdUser: userData,
                       createdUser: userData,
                       adminUser,
                       adminUser,
-                      url: crowi.configManager.getSiteUrl(),
+                      url: crowi.appService.getSiteUrl(),
                       appTitle,
                       appTitle,
                     },
                     },
                   },
                   },

+ 44 - 0
src/server/service/acl.js

@@ -0,0 +1,44 @@
+const logger = require('@alias/logger')('growi:service:AclService'); // eslint-disable-line no-unused-vars
+
+// const SECURITY_RESTRICT_GUEST_MODE_DENY = 'Deny';
+const SECURITY_RESTRICT_GUEST_MODE_READONLY = 'Readonly';
+// const SECURITY_REGISTRATION_MODE_OPEN = 'Open';
+// const SECURITY_REGISTRATION_MODE_RESTRICTED = 'Resricted';
+// const SECURITY_REGISTRATION_MODE_CLOSED = 'Closed';
+
+/**
+ * the service class of AclService
+ */
+class AclService {
+
+  constructor(configManager) {
+    this.configManager = configManager;
+  }
+
+  getIsPublicWikiOnly() {
+    // CONF.RF save PUBLIC_WIKI_ONLY in mongodb?
+    const publicWikiOnly = process.env.PUBLIC_WIKI_ONLY;
+    if (publicWikiOnly === 'true' || publicWikiOnly === 1) {
+      return true;
+    }
+    return false;
+  }
+
+  getIsGuestAllowedToRead() {
+    // return true if puclic wiki mode
+    if (this.getIsPublicWikiOnly()) {
+      return true;
+    }
+
+    // return false if undefined
+    const isRestrictGuestMode = this.configManager.getConfig('crowi', 'security:restrictGuestMode');
+    if (isRestrictGuestMode) {
+      return false;
+    }
+
+    return SECURITY_RESTRICT_GUEST_MODE_READONLY === isRestrictGuestMode;
+  }
+
+}
+
+module.exports = AclService;

+ 36 - 0
src/server/service/app.js

@@ -0,0 +1,36 @@
+const logger = require('@alias/logger')('growi:service:SiteUrl'); // eslint-disable-line no-unused-vars
+const { pathUtils } = require('growi-commons');
+
+/**
+ * the service class of GlobalNotificationSetting
+ */
+class AppService {
+
+  constructor(configManager) {
+    this.configManager = configManager;
+  }
+
+  /**
+   * get the site url
+   *
+   * If the config for the site url is not set, this returns a message "[The site URL is not set. Please set it!]".
+   *
+   * With version 3.2.3 and below, there is no config for the site URL, so the system always uses auto-generated site URL.
+   * With version 3.2.4 to 3.3.4, the system uses the auto-generated site URL only if the config is not set.
+   * With version 3.3.5 and above, the system use only a value from the config.
+   */
+  /* eslint-disable no-else-return */
+  getSiteUrl() {
+    const siteUrl = this.configManager.getConfig('crowi', 'app:siteUrl');
+    if (siteUrl != null) {
+      return pathUtils.removeTrailingSlash(siteUrl);
+    }
+    else {
+      return '[The site URL is not set. Please set it!]';
+    }
+  }
+  /* eslint-enable no-else-return */
+
+}
+
+module.exports = AppService;

+ 1 - 1
src/server/service/config-loader.js

@@ -218,7 +218,7 @@ class ConfigLoader {
     // merge defaults
     // merge defaults
     let mergedConfigFromDB = Object.assign({ crowi: this.configModel.getDefaultCrowiConfigsObject() }, configFromDB);
     let mergedConfigFromDB = Object.assign({ crowi: this.configModel.getDefaultCrowiConfigsObject() }, configFromDB);
     mergedConfigFromDB = Object.assign({ markdown: this.configModel.getDefaultMarkdownConfigsObject() }, mergedConfigFromDB);
     mergedConfigFromDB = Object.assign({ markdown: this.configModel.getDefaultMarkdownConfigsObject() }, mergedConfigFromDB);
-
+    mergedConfigFromDB = Object.assign({ notification: this.configModel.getDefaultNotificationConfigsObject() }, mergedConfigFromDB);
 
 
     // In getConfig API, only null is used as a value to indicate that a config is not set.
     // In getConfig API, only null is used as a value to indicate that a config is not set.
     // So, if a value loaded from the database is emtpy,
     // So, if a value loaded from the database is emtpy,

+ 79 - 22
src/server/service/config-manager.js

@@ -1,5 +1,4 @@
 const debug = require('debug')('growi:service:ConfigManager');
 const debug = require('debug')('growi:service:ConfigManager');
-const pathUtils = require('growi-commons').pathUtils;
 const ConfigLoader = require('../service/config-loader');
 const ConfigLoader = require('../service/config-loader');
 
 
 const KEYS_FOR_SAML_USE_ONLY_ENV_OPTION = [
 const KEYS_FOR_SAML_USE_ONLY_ENV_OPTION = [
@@ -20,6 +19,7 @@ class ConfigManager {
     this.configModel = configModel;
     this.configModel = configModel;
     this.configLoader = new ConfigLoader(this.configModel);
     this.configLoader = new ConfigLoader(this.configModel);
     this.configObject = null;
     this.configObject = null;
+    this.configKeys = [];
 
 
     this.getConfig = this.getConfig.bind(this);
     this.getConfig = this.getConfig.bind(this);
   }
   }
@@ -29,8 +29,10 @@ class ConfigManager {
    */
    */
   async loadConfigs() {
   async loadConfigs() {
     this.configObject = await this.configLoader.load();
     this.configObject = await this.configLoader.load();
-
     debug('ConfigManager#loadConfigs', this.configObject);
     debug('ConfigManager#loadConfigs', this.configObject);
+
+    // cache all config keys
+    this.configKeys = this.getAllConfigKeys();
   }
   }
 
 
   /**
   /**
@@ -53,6 +55,63 @@ class ConfigManager {
     return this.defaultSearch(namespace, key);
     return this.defaultSearch(namespace, key);
   }
   }
 
 
+  /**
+   * get a config specified by namespace and regular expresssion
+   */
+  getConfigByRegExp(namespace, regexp) {
+    const result = {};
+
+    for (const key of this.configKeys) {
+      if (regexp.test(key)) {
+        result[key] = this.getConfig(namespace, key);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * get a config specified by namespace and prefix
+   */
+  getConfigByPrefix(namespace, prefix) {
+    const regexp = new RegExp(`^${prefix}`);
+
+    return this.getConfigByRegExp(namespace, regexp);
+  }
+
+  /**
+   * generate an array of config keys from this.configObject
+   */
+  getAllConfigKeys() {
+    // type: fromDB, fromEnvVars
+    const types = Object.keys(this.configObject);
+    let namespaces = [];
+    let keys = [];
+
+    for (const type of types) {
+      if (this.configObject[type] != null) {
+        // ns: crowi, markdown, notification
+        namespaces = [...namespaces, ...Object.keys(this.configObject[type])];
+      }
+    }
+
+    // remove duplicates
+    namespaces = [...new Set(namespaces)];
+
+    for (const type of types) {
+      for (const ns of namespaces) {
+        if (this.configObject[type][ns] != null) {
+          keys = [...keys, ...Object.keys(this.configObject[type][ns])];
+        }
+      }
+    }
+
+    // remove duplicates
+    keys = [...new Set(keys)];
+
+    return keys;
+  }
+
   /**
   /**
    * get a config specified by namespace & key from configs loaded from the database
    * get a config specified by namespace & key from configs loaded from the database
    *
    *
@@ -71,26 +130,21 @@ class ConfigManager {
     return this.searchOnlyFromEnvVarConfigs(namespace, key);
     return this.searchOnlyFromEnvVarConfigs(namespace, key);
   }
   }
 
 
-  /**
-   * get the site url
-   *
-   * If the config for the site url is not set, this returns a message "[The site URL is not set. Please set it!]".
-   *
-   * With version 3.2.3 and below, there is no config for the site URL, so the system always uses auto-generated site URL.
-   * With version 3.2.4 to 3.3.4, the system uses the auto-generated site URL only if the config is not set.
-   * With version 3.3.5 and above, the system use only a value from the config.
-   */
-  /* eslint-disable no-else-return */
-  getSiteUrl() {
-    const siteUrl = this.getConfig('crowi', 'app:siteUrl');
-    if (siteUrl != null) {
-      return pathUtils.removeTrailingSlash(siteUrl);
-    }
-    else {
-      return '[The site URL is not set. Please set it!]';
+  // CONF.RF refactor file-uploader
+  // create parent class and each uploader inherits from it.
+  getIsUploadable() {
+    const method = process.env.FILE_UPLOAD || 'aws';
+
+    if (method === 'aws' && (
+      !this.getConfig('crowi', 'aws:accessKeyId')
+        || !this.getConfig('crowi', 'aws:secretAccessKey')
+        || !this.getConfig('crowi', 'aws:region')
+        || !this.getConfig('crowi', 'aws:bucket'))) {
+      return false;
     }
     }
+
+    return method !== 'none';
   }
   }
-  /* eslint-enable no-else-return */
 
 
   /**
   /**
    * update configs in the same namespace
    * update configs in the same namespace
@@ -149,27 +203,30 @@ class ConfigManager {
    * and then from configs loaded from the environment variables
    * and then from configs loaded from the environment variables
    */
    */
   defaultSearch(namespace, key) {
   defaultSearch(namespace, key) {
+    // does not exist neither in db nor in env vars
     if (!this.configExistsInDB(namespace, key) && !this.configExistsInEnvVars(namespace, key)) {
     if (!this.configExistsInDB(namespace, key) && !this.configExistsInEnvVars(namespace, key)) {
       return undefined;
       return undefined;
     }
     }
 
 
+    // only exists in db
     if (this.configExistsInDB(namespace, key) && !this.configExistsInEnvVars(namespace, key)) {
     if (this.configExistsInDB(namespace, key) && !this.configExistsInEnvVars(namespace, key)) {
       return this.configObject.fromDB[namespace][key];
       return this.configObject.fromDB[namespace][key];
     }
     }
 
 
+    // only exists env vars
     if (!this.configExistsInDB(namespace, key) && this.configExistsInEnvVars(namespace, key)) {
     if (!this.configExistsInDB(namespace, key) && this.configExistsInEnvVars(namespace, key)) {
       return this.configObject.fromEnvVars[namespace][key];
       return this.configObject.fromEnvVars[namespace][key];
     }
     }
 
 
+    // exists both in db and in env vars [db > env var]
     if (this.configExistsInDB(namespace, key) && this.configExistsInEnvVars(namespace, key)) {
     if (this.configExistsInDB(namespace, key) && this.configExistsInEnvVars(namespace, key)) {
-      /* eslint-disable no-else-return */
       if (this.configObject.fromDB[namespace][key] !== null) {
       if (this.configObject.fromDB[namespace][key] !== null) {
         return this.configObject.fromDB[namespace][key];
         return this.configObject.fromDB[namespace][key];
       }
       }
+      /* eslint-disable-next-line no-else-return */
       else {
       else {
         return this.configObject.fromEnvVars[namespace][key];
         return this.configObject.fromEnvVars[namespace][key];
       }
       }
-      /* eslint-enable no-else-return */
     }
     }
   }
   }
 
 

+ 5 - 5
src/server/service/passport.js

@@ -330,7 +330,7 @@ class PassportService {
           clientId: config.crowi['security:passport-google:clientId'] || process.env.OAUTH_GOOGLE_CLIENT_ID,
           clientId: config.crowi['security:passport-google:clientId'] || process.env.OAUTH_GOOGLE_CLIENT_ID,
           clientSecret: config.crowi['security:passport-google:clientSecret'] || process.env.OAUTH_GOOGLE_CLIENT_SECRET,
           clientSecret: config.crowi['security:passport-google:clientSecret'] || process.env.OAUTH_GOOGLE_CLIENT_SECRET,
           callbackURL: (this.crowi.configManager.getConfig('crowi', 'app:siteUrl') != null)
           callbackURL: (this.crowi.configManager.getConfig('crowi', 'app:siteUrl') != null)
-            ? urljoin(this.crowi.configManager.getSiteUrl(), '/passport/google/callback') // auto-generated with v3.2.4 and above
+            ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/google/callback') // auto-generated with v3.2.4 and above
             : config.crowi['security:passport-google:callbackUrl'] || process.env.OAUTH_GOOGLE_CALLBACK_URI, // DEPRECATED: backward compatible with v3.2.3 and below
             : config.crowi['security:passport-google:callbackUrl'] || process.env.OAUTH_GOOGLE_CALLBACK_URI, // DEPRECATED: backward compatible with v3.2.3 and below
           skipUserProfile: false,
           skipUserProfile: false,
         },
         },
@@ -381,7 +381,7 @@ class PassportService {
           clientID: config.crowi['security:passport-github:clientId'] || process.env.OAUTH_GITHUB_CLIENT_ID,
           clientID: config.crowi['security:passport-github:clientId'] || process.env.OAUTH_GITHUB_CLIENT_ID,
           clientSecret: config.crowi['security:passport-github:clientSecret'] || process.env.OAUTH_GITHUB_CLIENT_SECRET,
           clientSecret: config.crowi['security:passport-github:clientSecret'] || process.env.OAUTH_GITHUB_CLIENT_SECRET,
           callbackURL: (this.crowi.configManager.getConfig('crowi', 'app:siteUrl') != null)
           callbackURL: (this.crowi.configManager.getConfig('crowi', 'app:siteUrl') != null)
-            ? urljoin(this.crowi.configManager.getSiteUrl(), '/passport/github/callback') // auto-generated with v3.2.4 and above
+            ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/github/callback') // auto-generated with v3.2.4 and above
             : config.crowi['security:passport-github:callbackUrl'] || process.env.OAUTH_GITHUB_CALLBACK_URI, // DEPRECATED: backward compatible with v3.2.3 and below
             : config.crowi['security:passport-github:callbackUrl'] || process.env.OAUTH_GITHUB_CALLBACK_URI, // DEPRECATED: backward compatible with v3.2.3 and below
           skipUserProfile: false,
           skipUserProfile: false,
         },
         },
@@ -432,7 +432,7 @@ class PassportService {
           consumerKey: config.crowi['security:passport-twitter:consumerKey'] || process.env.OAUTH_TWITTER_CONSUMER_KEY,
           consumerKey: config.crowi['security:passport-twitter:consumerKey'] || process.env.OAUTH_TWITTER_CONSUMER_KEY,
           consumerSecret: config.crowi['security:passport-twitter:consumerSecret'] || process.env.OAUTH_TWITTER_CONSUMER_SECRET,
           consumerSecret: config.crowi['security:passport-twitter:consumerSecret'] || process.env.OAUTH_TWITTER_CONSUMER_SECRET,
           callbackURL: (this.crowi.configManager.getConfig('crowi', 'app:siteUrl') != null)
           callbackURL: (this.crowi.configManager.getConfig('crowi', 'app:siteUrl') != null)
-            ? urljoin(this.crowi.configManager.getSiteUrl(), '/passport/twitter/callback') // auto-generated with v3.2.4 and above
+            ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/twitter/callback') // auto-generated with v3.2.4 and above
             : config.crowi['security:passport-twitter:callbackUrl'] || process.env.OAUTH_TWITTER_CALLBACK_URI, // DEPRECATED: backward compatible with v3.2.3 and below
             : config.crowi['security:passport-twitter:callbackUrl'] || process.env.OAUTH_TWITTER_CALLBACK_URI, // DEPRECATED: backward compatible with v3.2.3 and below
           skipUserProfile: false,
           skipUserProfile: false,
         },
         },
@@ -485,7 +485,7 @@ class PassportService {
     const clientId = configManager.getConfig('crowi', 'security:passport-oidc:clientId') || process.env.OAUTH_OIDC_CLIENT_ID;
     const clientId = configManager.getConfig('crowi', 'security:passport-oidc:clientId') || process.env.OAUTH_OIDC_CLIENT_ID;
     const clientSecret = configManager.getConfig('crowi', 'security:passport-oidc:clientSecret') || process.env.OAUTH_OIDC_CLIENT_SECRET;
     const clientSecret = configManager.getConfig('crowi', 'security:passport-oidc:clientSecret') || process.env.OAUTH_OIDC_CLIENT_SECRET;
     const redirectUri = (configManager.getConfig('crowi', 'app:siteUrl') != null)
     const redirectUri = (configManager.getConfig('crowi', 'app:siteUrl') != null)
-      ? urljoin(this.crowi.configManager.getSiteUrl(), '/passport/oidc/callback')
+      ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/oidc/callback')
       : config.crowi['security:passport-oidc:callbackUrl'] || process.env.OAUTH_OIDC_CALLBACK_URI; // DEPRECATED: backward compatible with v3.2.3 and below
       : config.crowi['security:passport-oidc:callbackUrl'] || process.env.OAUTH_OIDC_CALLBACK_URI; // DEPRECATED: backward compatible with v3.2.3 and below
     const oidcIssuer = await OIDCIssuer.discover(issuerHost);
     const oidcIssuer = await OIDCIssuer.discover(issuerHost);
     debug('Discovered issuer %s %O', oidcIssuer.issuer, oidcIssuer.metadata);
     debug('Discovered issuer %s %O', oidcIssuer.issuer, oidcIssuer.metadata);
@@ -545,7 +545,7 @@ class PassportService {
         {
         {
           entryPoint: configManager.getConfig('crowi', 'security:passport-saml:entryPoint'),
           entryPoint: configManager.getConfig('crowi', 'security:passport-saml:entryPoint'),
           callbackUrl: (this.crowi.configManager.getConfig('crowi', 'app:siteUrl') != null)
           callbackUrl: (this.crowi.configManager.getConfig('crowi', 'app:siteUrl') != null)
-            ? urljoin(this.crowi.configManager.getSiteUrl(), '/passport/saml/callback') // auto-generated with v3.2.4 and above
+            ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/saml/callback') // auto-generated with v3.2.4 and above
             : configManager.getConfig('crowi', 'security:passport-saml:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
             : configManager.getConfig('crowi', 'security:passport-saml:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
           issuer: configManager.getConfig('crowi', 'security:passport-saml:issuer'),
           issuer: configManager.getConfig('crowi', 'security:passport-saml:issuer'),
           cert: configManager.getConfig('crowi', 'security:passport-saml:cert'),
           cert: configManager.getConfig('crowi', 'security:passport-saml:cert'),

+ 25 - 0
src/server/service/slack-notification.js

@@ -0,0 +1,25 @@
+const logger = require('@alias/logger')('growi:service:SlackNotification'); // eslint-disable-line no-unused-vars
+/**
+ * the service class of SlackNotificationService
+ */
+class SlackNotificationService {
+
+  constructor(configManager) {
+    this.configManager = configManager;
+  }
+
+  hasSlackConfig() {
+    let hasSlackToken = false;
+    let hasSlackIwhUrl = false;
+
+    if (this.configObject.notification) {
+      hasSlackToken = !!this.configManager.getConfig('notification', 'slack:token');
+      hasSlackIwhUrl = !!this.configManager.getConfig('notification', 'slack:incomingWebhookUrl');
+    }
+
+    return hasSlackToken || hasSlackIwhUrl;
+  }
+
+}
+
+module.exports = SlackNotificationService;

+ 63 - 0
src/server/service/xss.js

@@ -0,0 +1,63 @@
+const logger = require('@alias/logger')('growi:service:XssSerivce'); // eslint-disable-line no-unused-vars
+const { tags, attrs } = require('@commons/service/xss/recommended-whitelist');
+
+/**
+ * the service class of XssSerivce
+ */
+class XssSerivce {
+
+  constructor(configManager) {
+    this.configManager = configManager;
+  }
+
+  getTagWhiteList() {
+    const isEnabledXssPrevention = this.configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention');
+    const xssOpiton = this.configManager.getConfig('markdown', 'markdown:xss:option');
+
+    if (isEnabledXssPrevention) {
+      switch (xssOpiton) {
+        case 1: // ignore all: use default option
+          return [];
+
+        case 2: // recommended
+          return tags;
+
+        case 3: // custom white list
+          return this.configManager.getConfig('markdown', 'markdown:xss:tagWhiteList');
+
+        default:
+          return [];
+      }
+    }
+    else {
+      return [];
+    }
+  }
+
+  getAttrWhiteList() {
+    const isEnabledXssPrevention = this.configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention');
+    const xssOpiton = this.configManager.getConfig('markdown', 'markdown:xss:option');
+
+    if (isEnabledXssPrevention) {
+      switch (xssOpiton) {
+        case 1: // ignore all: use default option
+          return [];
+
+        case 2: // recommended
+          return attrs;
+
+        case 3: // custom white list
+          return this.configManager.getConfig('markdown', 'markdown:xss:attrWhiteList');
+
+        default:
+          return [];
+      }
+    }
+    else {
+      return [];
+    }
+  }
+
+}
+
+module.exports = XssSerivce;

+ 2 - 2
src/server/util/googleAuth.js

@@ -20,7 +20,7 @@ module.exports = function(crowi) {
   }
   }
 
 
   lib.createAuthUrl = function(req, callback) {
   lib.createAuthUrl = function(req, callback) {
-    const callbackUrl = urljoin(crowi.configManager.getSiteUrl(), '/google/callback');
+    const callbackUrl = urljoin(crowi.appService.getSiteUrl(), '/google/callback');
     const oauth2Client = createOauth2Client(callbackUrl);
     const oauth2Client = createOauth2Client(callbackUrl);
     google.options({ auth: oauth2Client });
     google.options({ auth: oauth2Client });
 
 
@@ -33,7 +33,7 @@ module.exports = function(crowi) {
   };
   };
 
 
   lib.handleCallback = function(req, callback) {
   lib.handleCallback = function(req, callback) {
-    const callbackUrl = urljoin(crowi.configManager.getSiteUrl(), '/google/callback');
+    const callbackUrl = urljoin(crowi.appService.getSiteUrl(), '/google/callback');
     const oauth2Client = createOauth2Client(callbackUrl);
     const oauth2Client = createOauth2Client(callbackUrl);
     google.options({ auth: oauth2Client });
     google.options({ auth: oauth2Client });
 
 

+ 3 - 4
src/server/util/middlewares.js

@@ -207,16 +207,15 @@ module.exports = (crowi, app) => {
    * @param {boolean} isStrictly whethere strictly restricted (default true)
    * @param {boolean} isStrictly whethere strictly restricted (default true)
    */
    */
   middlewares.loginRequired = function(isStrictly = true) {
   middlewares.loginRequired = function(isStrictly = true) {
+    const isGuestAllowedToRead = crowi.aclService.getIsGuestAllowedToRead();
+
     return function(req, res, next) {
     return function(req, res, next) {
       const User = crowi.model('User');
       const User = crowi.model('User');
 
 
       // when the route is not strictly restricted
       // when the route is not strictly restricted
       if (!isStrictly) {
       if (!isStrictly) {
-        const config = req.config;
-        const Config = crowi.model('Config');
-
         // when allowed to read
         // when allowed to read
-        if (Config.isGuestAllowedToRead(config)) {
+        if (isGuestAllowedToRead) {
           return next();
           return next();
         }
         }
       }
       }

+ 5 - 5
src/server/util/slack.js

@@ -48,7 +48,7 @@ module.exports = function(crowi) {
   };
   };
 
 
   const convertMarkdownToMarkdown = function(body) {
   const convertMarkdownToMarkdown = function(body) {
-    const url = crowi.configManager.getSiteUrl();
+    const url = crowi.appService.getSiteUrl();
 
 
     return body
     return body
       .replace(/\n\*\s(.+)/g, '\n• $1')
       .replace(/\n\*\s(.+)/g, '\n• $1')
@@ -107,7 +107,7 @@ module.exports = function(crowi) {
   };
   };
 
 
   const prepareSlackMessageForPage = function(page, user, channel, updateType, previousRevision) {
   const prepareSlackMessageForPage = function(page, user, channel, updateType, previousRevision) {
-    const url = crowi.configManager.getSiteUrl();
+    const url = crowi.appService.getSiteUrl();
     let body = page.revision.body;
     let body = page.revision.body;
 
 
     if (updateType === 'create') {
     if (updateType === 'create') {
@@ -142,7 +142,7 @@ module.exports = function(crowi) {
   };
   };
 
 
   const prepareSlackMessageForComment = function(comment, user, channel, path) {
   const prepareSlackMessageForComment = function(comment, user, channel, path) {
-    const url = crowi.configManager.getSiteUrl();
+    const url = crowi.appService.getSiteUrl();
     const body = prepareAttachmentTextForComment(comment);
     const body = prepareAttachmentTextForComment(comment);
 
 
     const attachment = {
     const attachment = {
@@ -169,7 +169,7 @@ module.exports = function(crowi) {
 
 
   const getSlackMessageTextForPage = function(path, pageId, user, updateType) {
   const getSlackMessageTextForPage = function(path, pageId, user, updateType) {
     let text;
     let text;
-    const url = crowi.configManager.getSiteUrl();
+    const url = crowi.appService.getSiteUrl();
 
 
     const pageUrl = `<${urljoin(url, pageId)}|${path}>`;
     const pageUrl = `<${urljoin(url, pageId)}|${path}>`;
     if (updateType === 'create') {
     if (updateType === 'create') {
@@ -183,7 +183,7 @@ module.exports = function(crowi) {
   };
   };
 
 
   const getSlackMessageTextForComment = function(path, pageId, user) {
   const getSlackMessageTextForComment = function(path, pageId, user) {
-    const url = crowi.configManager.getSiteUrl();
+    const url = crowi.appService.getSiteUrl();
     const pageUrl = `<${urljoin(url, pageId)}|${path}>`;
     const pageUrl = `<${urljoin(url, pageId)}|${path}>`;
     const text = `:speech_balloon: ${user.username} commented on ${pageUrl}`;
     const text = `:speech_balloon: ${user.username} commented on ${pageUrl}`;